scaffold some common services
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,3 @@
|
|||||||
|
|
||||||
*/build/*
|
build/*
|
||||||
*.log
|
*.log
|
||||||
|
|||||||
@@ -1,10 +1,23 @@
|
|||||||
|
|
||||||
cmake_minimum_required(VERSION 3.21)
|
cmake_minimum_required(VERSION 3.21)
|
||||||
|
|
||||||
project(accordion LANGUAGES CXX)
|
project(accordion-server LANGUAGES CXX)
|
||||||
|
project(accordion-client LANGUAGES CXX)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
# get dependencies
|
||||||
|
include(FetchContent)
|
||||||
|
FetchContent_Declare(
|
||||||
|
libconfig
|
||||||
|
GIT_REPOSITORY https://github.com/hyperrealm/libconfig.git
|
||||||
|
GIT_TAG v1.8.2
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(libconfig)
|
||||||
|
|
||||||
|
set(ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
|
add_subdirectory(common)
|
||||||
add_subdirectory(client)
|
add_subdirectory(client)
|
||||||
add_subdirectory(server)
|
add_subdirectory(server)
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
|
|
||||||
cmake_minimum_required(VERSION 3.21)
|
|
||||||
|
|
||||||
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
|
||||||
project(accordion-client LANGUAGES CXX)
|
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_executable(accordion-client
|
add_executable(accordion-client
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
|
# other client source files go here
|
||||||
|
)
|
||||||
|
|
||||||
|
message(STATUS ${ROOT_DIR})
|
||||||
|
|
||||||
|
target_include_directories(accordion-client PRIVATE
|
||||||
|
${ROOT_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(accordion-client PRIVATE
|
||||||
|
accordion-common
|
||||||
)
|
)
|
||||||
|
|||||||
21
client/config/main.cfg
Normal file
21
client/config/main.cfg
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
Logger = (
|
||||||
|
{
|
||||||
|
Id = "main";
|
||||||
|
|
||||||
|
FlagsEnabled = (
|
||||||
|
"Debug",
|
||||||
|
"Info",
|
||||||
|
"Warning",
|
||||||
|
"Error"
|
||||||
|
);
|
||||||
|
|
||||||
|
ShowTime = false;
|
||||||
|
ShowSourceTrace = false;
|
||||||
|
CoutEnabled = true;
|
||||||
|
|
||||||
|
FileEnabled = true;
|
||||||
|
FilePath = "build/client/logs";
|
||||||
|
FileName = "app.log";
|
||||||
|
}
|
||||||
|
);
|
||||||
0
client/src/App.cpp
Normal file
0
client/src/App.cpp
Normal file
0
client/src/App.hpp
Normal file
0
client/src/App.hpp
Normal file
@@ -1,8 +1,15 @@
|
|||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "common/config/ConfigService.hpp"
|
||||||
|
#include "common/config/LoggerConfig.hpp"
|
||||||
|
#include "common/LoggerService.hpp"
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
|
|
||||||
std::cout << "hi mom from client!" << std::endl;
|
ConfigService config {"client/config/main.cfg"}; // TODO: main config file should be the foremost cli argument
|
||||||
|
LoggerService logger {&config, "main"};
|
||||||
|
|
||||||
|
logger.log("main", LogFlag::Debug, "hello world from the client!");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
0
client/test/.gitkeep
Normal file
0
client/test/.gitkeep
Normal file
20
common/CMakeLists.txt
Normal file
20
common/CMakeLists.txt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
add_library(accordion-common STATIC
|
||||||
|
LoggerService.cpp
|
||||||
|
config/ConfigService.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(accordion-common PUBLIC
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../
|
||||||
|
${libconfig_SOURCE_DIR}/lib
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(accordion-common PUBLIC
|
||||||
|
config++ # note: differs on windows (ld automatically adds 'lib' to library searches and would look for 'liblibconfig++' if we didnt strip it)
|
||||||
|
# rare linux < windows right hereg
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(accordion-common PRIVATE
|
||||||
|
# pass in some compiler macros
|
||||||
|
BINARY_DIR="${CMAKE_BINARY_DIR}" # useful for runtime filepaths
|
||||||
|
)
|
||||||
103
common/LoggerService.cpp
Normal file
103
common/LoggerService.cpp
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
|
||||||
|
#include <chrono> // Tracking the time when the log function is called
|
||||||
|
#include <string>
|
||||||
|
#include <source_location>
|
||||||
|
#include "LoggerService.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <fstream>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
LoggerService::LoggerService(ConfigService* config, const std::string& loggerId) {
|
||||||
|
|
||||||
|
if(!(config->getConfig<LoggerConfig>("Logger", loggerId, &configuration_))) {
|
||||||
|
std::cout << "Failed to get logger configuration from config service" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(std::string& flag : configuration_.flagsEnabled) {
|
||||||
|
bool found = false;
|
||||||
|
for(const char* validFlag : LogFlagStrings) {
|
||||||
|
if(flag == std::string(validFlag)) {
|
||||||
|
found = true;
|
||||||
|
for(int i = 0; i < LogFlag::Count; i++) {
|
||||||
|
if(LogFlagStrings[i] == flag) {
|
||||||
|
activeFlags_.emplace_back(static_cast<LogFlag>(i));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!found) {
|
||||||
|
std::cout << "Log flag '" << flag << "' in configuration file is not a valid log flag" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto now = std::chrono::system_clock::now();
|
||||||
|
const std::time_t t_c = std::chrono::system_clock::to_time_t(now);
|
||||||
|
|
||||||
|
std::string finaltime = std::ctime(&t_c);
|
||||||
|
|
||||||
|
finaltime.pop_back(); // removing the newline
|
||||||
|
|
||||||
|
std::replace(finaltime.begin(),finaltime.end(), ':' , '-'); // filenames cant have dashes
|
||||||
|
std::replace(finaltime.begin(),finaltime.end(), ' ' , '_');
|
||||||
|
|
||||||
|
fs::create_directories(configuration_.filePath + "/" + finaltime);
|
||||||
|
std::string logPath = configuration_.filePath + "/" + finaltime + "/" + configuration_.fileName;
|
||||||
|
|
||||||
|
if(configuration_.fileEnabled) outfile_.open(logPath);
|
||||||
|
|
||||||
|
log("Logger", LogFlag::Info, "Logger initialized.");
|
||||||
|
}
|
||||||
|
|
||||||
|
LoggerService::~LoggerService() {
|
||||||
|
if(outfile_) outfile_.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoggerService::log(std::string component, LogFlag flag, std::string message, std::source_location Source) {
|
||||||
|
|
||||||
|
// check if flag is in the list of active flags
|
||||||
|
bool culled = true;
|
||||||
|
for(LogFlag& testFlag : activeFlags_) {
|
||||||
|
if(flag == testFlag) {
|
||||||
|
culled = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(culled) return;
|
||||||
|
|
||||||
|
std::string finalmessage = "";
|
||||||
|
|
||||||
|
if(configuration_.showTime) {
|
||||||
|
finalmessage = finalmessage + "[" + "Not Implemented" + "] ";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string componentTrace = "[" + configuration_.id + ": " + component + "] ";
|
||||||
|
finalmessage += componentTrace; // component is the section of the program (For example Mesh or Engine) that is calling the logger
|
||||||
|
|
||||||
|
std::string level = "";
|
||||||
|
|
||||||
|
level = LogFlagStrings[flag];
|
||||||
|
|
||||||
|
// level.append(7 - level.length(), ' ') pads out the level string with whitespace so every line is aligned the same
|
||||||
|
// it looked weird though
|
||||||
|
finalmessage = finalmessage + "[" + level + "] ";
|
||||||
|
finalmessage = finalmessage + message + " ";
|
||||||
|
|
||||||
|
if (configuration_.showSourceTrace) {
|
||||||
|
finalmessage = finalmessage + "[Function: " + Source.function_name() + "]" + " " + "[Line: " + std::to_string(Source.line()) + "]" + " " + "[File: " + Source.file_name() + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(configuration_.coutEnabled) {
|
||||||
|
std::cout << finalmessage << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(configuration_.fileEnabled) {
|
||||||
|
outfile_ << finalmessage << std::endl;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
41
common/LoggerService.hpp
Normal file
41
common/LoggerService.hpp
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <fstream>
|
||||||
|
#include <source_location>
|
||||||
|
|
||||||
|
#include "config/ConfigService.hpp"
|
||||||
|
#include "config/LoggerConfig.hpp"
|
||||||
|
|
||||||
|
enum LogFlag {
|
||||||
|
Debug,
|
||||||
|
Info,
|
||||||
|
Warning,
|
||||||
|
Error,
|
||||||
|
Count
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr const char* LogFlagStrings[] = {
|
||||||
|
"Debug",
|
||||||
|
"Info",
|
||||||
|
"Warning",
|
||||||
|
"Error"
|
||||||
|
};
|
||||||
|
|
||||||
|
class LoggerService {
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
LoggerService(ConfigService* config, const std::string& loggerId);
|
||||||
|
~LoggerService();
|
||||||
|
|
||||||
|
void log(std::string component, LogFlag flag, std::string message, std::source_location Source = std::source_location::current()); // Using the <source_location>
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::ofstream outfile_;
|
||||||
|
std::vector<LogFlag> activeFlags_;
|
||||||
|
|
||||||
|
LoggerParams configuration_;
|
||||||
|
};
|
||||||
8
common/Messages.hpp
Normal file
8
common/Messages.hpp
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// the common directory houses source code thats shared for both the server and the client:
|
||||||
|
// common data structures (message types)
|
||||||
|
// checksum operations
|
||||||
|
// encode/decode on the messages
|
||||||
|
// send and receive on those messages
|
||||||
35
common/config/ConfigService.cpp
Normal file
35
common/config/ConfigService.cpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
|
||||||
|
#include "ConfigService.hpp"
|
||||||
|
|
||||||
|
#include <exception>
|
||||||
|
|
||||||
|
ConfigService::ConfigService(const std::string& filePath) {
|
||||||
|
|
||||||
|
if(!loadFromFile(filePath)) {
|
||||||
|
std::cout << "Error loading file " << filePath << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConfigService::loadFromFile(const std::string& filePath) {
|
||||||
|
try {
|
||||||
|
config_.clear();
|
||||||
|
config_.readFile(filePath.c_str());
|
||||||
|
lastError_.clear();
|
||||||
|
return true;
|
||||||
|
} catch (const libconfig::FileIOException&) {
|
||||||
|
lastError_ = "Unable to read config file: " + filePath;
|
||||||
|
} catch (const libconfig::ParseException& error) {
|
||||||
|
lastError_ = std::string("Parse error in ") + error.getFile() + ":" +
|
||||||
|
std::to_string(error.getLine()) + " - " + error.getError();
|
||||||
|
} catch (const std::exception& error) {
|
||||||
|
lastError_ = error.what();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& ConfigService::lastError() const {
|
||||||
|
return lastError_;
|
||||||
|
}
|
||||||
|
|
||||||
56
common/config/ConfigService.hpp
Normal file
56
common/config/ConfigService.hpp
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <libconfig.h++>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <iostream>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "common/config/IConfig.hpp"
|
||||||
|
|
||||||
|
// TODO: would be cool for the config file to include other config files so theyre a bit more compartmentalized
|
||||||
|
// then the main file will be like a hub and would be more like each component having its own config file
|
||||||
|
class ConfigService {
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
ConfigService(const std::string& filePath);
|
||||||
|
~ConfigService() = default;
|
||||||
|
|
||||||
|
bool loadFromFile(const std::string& filePath);
|
||||||
|
|
||||||
|
template<typename Config>
|
||||||
|
bool getConfig(const std::string& type, const std::string& id, typename Config::Params* params) const {
|
||||||
|
|
||||||
|
Config config { params };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const libconfig::Setting& configs = config_.lookup(type);
|
||||||
|
for (int index = 0; index < configs.getLength(); ++index) {
|
||||||
|
const libconfig::Setting& configSetting = configs[index];
|
||||||
|
|
||||||
|
std::string configId;
|
||||||
|
if (!configSetting.lookupValue("Id", configId) || configId != id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return config.parseConfig(configSetting);
|
||||||
|
}
|
||||||
|
} catch (const libconfig::SettingException& ex) {
|
||||||
|
std::cout << "libconfig setting exception: " << ex.what() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& lastError() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
libconfig::Config config_;
|
||||||
|
std::string lastError_;
|
||||||
|
};
|
||||||
22
common/config/IConfig.hpp
Normal file
22
common/config/IConfig.hpp
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <libconfig.h++>
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class IConfig {
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
using Params = T;
|
||||||
|
|
||||||
|
IConfig(Params* params) : params_(params) { }
|
||||||
|
~IConfig() = default;
|
||||||
|
|
||||||
|
virtual bool parseConfig(const libconfig::Setting& setting) = 0;
|
||||||
|
Params* params() { return params_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Params* params_;
|
||||||
|
|
||||||
|
};
|
||||||
49
common/config/LoggerConfig.hpp
Normal file
49
common/config/LoggerConfig.hpp
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "IConfig.hpp"
|
||||||
|
|
||||||
|
struct LoggerParams {
|
||||||
|
std::string id;
|
||||||
|
std::vector<std::string> flagsEnabled;
|
||||||
|
bool showTime = false;
|
||||||
|
bool showSourceTrace = false;
|
||||||
|
bool coutEnabled = false;
|
||||||
|
bool fileEnabled = false;
|
||||||
|
std::string filePath;
|
||||||
|
std::string fileName;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LoggerConfig : public IConfig<LoggerParams> {
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
using IConfig<LoggerParams>::IConfig;
|
||||||
|
|
||||||
|
bool parseConfig(const libconfig::Setting& setting) override {
|
||||||
|
|
||||||
|
if (!setting.lookupValue("Id", params_->id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const libconfig::Setting& flags = setting.lookup("FlagsEnabled");
|
||||||
|
for (int index = 0; index < flags.getLength(); ++index) {
|
||||||
|
params_->flagsEnabled.push_back(static_cast<const char*>(flags[index]));
|
||||||
|
}
|
||||||
|
} catch (const libconfig::SettingException&) {
|
||||||
|
params_->flagsEnabled.clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setting.lookupValue("ShowTime", params_->showTime);
|
||||||
|
setting.lookupValue("ShowSourceTrace", params_->showSourceTrace);
|
||||||
|
setting.lookupValue("CoutEnabled", params_->coutEnabled);
|
||||||
|
setting.lookupValue("FileEnabled", params_->fileEnabled);
|
||||||
|
setting.lookupValue("FilePath", params_->filePath);
|
||||||
|
setting.lookupValue("FileName", params_->fileName);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
|
|
||||||
cmake_minimum_required(VERSION 3.21)
|
|
||||||
|
|
||||||
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
|
||||||
project(accordion-server LANGUAGES CXX)
|
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_executable(accordion-server
|
add_executable(accordion-server
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
|
# other server source files go here
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(accordion-server PRIVATE
|
||||||
|
${ROOT_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(accordion-server PRIVATE
|
||||||
|
accordion-common
|
||||||
)
|
)
|
||||||
|
|||||||
21
server/config/main.cfg
Normal file
21
server/config/main.cfg
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
Logger = (
|
||||||
|
{
|
||||||
|
Id = "main";
|
||||||
|
|
||||||
|
FlagsEnabled = (
|
||||||
|
"Debug",
|
||||||
|
"Info",
|
||||||
|
"Warning",
|
||||||
|
"Error"
|
||||||
|
);
|
||||||
|
|
||||||
|
ShowTime = false;
|
||||||
|
ShowSourceTrace = false;
|
||||||
|
CoutEnabled = true;
|
||||||
|
|
||||||
|
FileEnabled = true;
|
||||||
|
FilePath = "build/server/logs";
|
||||||
|
FileName = "app.log";
|
||||||
|
}
|
||||||
|
);
|
||||||
0
server/src/App.cpp
Normal file
0
server/src/App.cpp
Normal file
0
server/src/App.hpp
Normal file
0
server/src/App.hpp
Normal file
@@ -1,8 +1,15 @@
|
|||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "common/config/ConfigService.hpp"
|
||||||
|
#include "common/config/LoggerConfig.hpp"
|
||||||
|
#include "common/LoggerService.hpp"
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
|
|
||||||
std::cout << "hi mom from server!" << std::endl;
|
ConfigService config {"server/config/main.cfg"}; // TODO: main config file should be the foremost cli argument
|
||||||
|
LoggerService logger {&config, "main"};
|
||||||
|
|
||||||
|
logger.log("main", LogFlag::Debug, "hello world from the server!");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
0
server/test/.gitkeep
Normal file
0
server/test/.gitkeep
Normal file
Reference in New Issue
Block a user