From 3a3f67c4efe82aad589bc0a7c79d03902a264710 Mon Sep 17 00:00:00 2001 From: Blitblank Date: Thu, 16 Jul 2026 22:58:57 -0500 Subject: [PATCH] scaffold some common services --- .gitignore | 2 +- CMakeLists.txt | 15 ++++- client/CMakeLists.txt | 20 ++++--- client/config/main.cfg | 21 +++++++ client/src/App.cpp | 0 client/src/App.hpp | 0 client/src/main.cpp | 9 ++- client/test/.gitkeep | 0 common/CMakeLists.txt | 20 +++++++ common/LoggerService.cpp | 103 ++++++++++++++++++++++++++++++++ common/LoggerService.hpp | 41 +++++++++++++ common/Messages.hpp | 8 +++ common/config/ConfigService.cpp | 35 +++++++++++ common/config/ConfigService.hpp | 56 +++++++++++++++++ common/config/IConfig.hpp | 22 +++++++ common/config/LoggerConfig.hpp | 49 +++++++++++++++ server/CMakeLists.txt | 18 +++--- server/config/main.cfg | 21 +++++++ server/src/App.cpp | 0 server/src/App.hpp | 0 server/src/main.cpp | 9 ++- server/test/.gitkeep | 0 22 files changed, 427 insertions(+), 22 deletions(-) create mode 100644 client/config/main.cfg create mode 100644 client/src/App.cpp create mode 100644 client/src/App.hpp create mode 100644 client/test/.gitkeep create mode 100644 common/CMakeLists.txt create mode 100644 common/LoggerService.cpp create mode 100644 common/LoggerService.hpp create mode 100644 common/Messages.hpp create mode 100644 common/config/ConfigService.cpp create mode 100644 common/config/ConfigService.hpp create mode 100644 common/config/IConfig.hpp create mode 100644 common/config/LoggerConfig.hpp create mode 100644 server/config/main.cfg create mode 100644 server/src/App.cpp create mode 100644 server/src/App.hpp create mode 100644 server/test/.gitkeep diff --git a/.gitignore b/.gitignore index 0e00ad1..d6dc188 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -*/build/* +build/* *.log diff --git a/CMakeLists.txt b/CMakeLists.txt index d99a2cf..82b0ca4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,23 @@ 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_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(server) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 7290c3c..5d7b908 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -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 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 ) diff --git a/client/config/main.cfg b/client/config/main.cfg new file mode 100644 index 0000000..1cc0b78 --- /dev/null +++ b/client/config/main.cfg @@ -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"; + } +); diff --git a/client/src/App.cpp b/client/src/App.cpp new file mode 100644 index 0000000..e69de29 diff --git a/client/src/App.hpp b/client/src/App.hpp new file mode 100644 index 0000000..e69de29 diff --git a/client/src/main.cpp b/client/src/main.cpp index 6bb9e4f..4a52f21 100644 --- a/client/src/main.cpp +++ b/client/src/main.cpp @@ -1,8 +1,15 @@ #include +#include "common/config/ConfigService.hpp" +#include "common/config/LoggerConfig.hpp" +#include "common/LoggerService.hpp" + 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!"); } diff --git a/client/test/.gitkeep b/client/test/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt new file mode 100644 index 0000000..2574952 --- /dev/null +++ b/common/CMakeLists.txt @@ -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 +) diff --git a/common/LoggerService.cpp b/common/LoggerService.cpp new file mode 100644 index 0000000..cd220a8 --- /dev/null +++ b/common/LoggerService.cpp @@ -0,0 +1,103 @@ + +#include // Tracking the time when the log function is called +#include +#include +#include "LoggerService.hpp" +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +LoggerService::LoggerService(ConfigService* config, const std::string& loggerId) { + + if(!(config->getConfig("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(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; +} diff --git a/common/LoggerService.hpp b/common/LoggerService.hpp new file mode 100644 index 0000000..4fb35f4 --- /dev/null +++ b/common/LoggerService.hpp @@ -0,0 +1,41 @@ + +#pragma once +#include +#include +#include +#include + +#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 + +private: + + std::ofstream outfile_; + std::vector activeFlags_; + + LoggerParams configuration_; +}; diff --git a/common/Messages.hpp b/common/Messages.hpp new file mode 100644 index 0000000..f9f6dff --- /dev/null +++ b/common/Messages.hpp @@ -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 diff --git a/common/config/ConfigService.cpp b/common/config/ConfigService.cpp new file mode 100644 index 0000000..f62fc58 --- /dev/null +++ b/common/config/ConfigService.cpp @@ -0,0 +1,35 @@ + +#include "ConfigService.hpp" + +#include + +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_; +} + diff --git a/common/config/ConfigService.hpp b/common/config/ConfigService.hpp new file mode 100644 index 0000000..10707e6 --- /dev/null +++ b/common/config/ConfigService.hpp @@ -0,0 +1,56 @@ + +#pragma once + +#include + +#include +#include +#include +#include +#include + +#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 + 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_; +}; diff --git a/common/config/IConfig.hpp b/common/config/IConfig.hpp new file mode 100644 index 0000000..5eb6bdc --- /dev/null +++ b/common/config/IConfig.hpp @@ -0,0 +1,22 @@ + +#pragma once + +#include + +template +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_; + +}; diff --git a/common/config/LoggerConfig.hpp b/common/config/LoggerConfig.hpp new file mode 100644 index 0000000..d95226b --- /dev/null +++ b/common/config/LoggerConfig.hpp @@ -0,0 +1,49 @@ + +#pragma once + +#include "IConfig.hpp" + +struct LoggerParams { + std::string id; + std::vector flagsEnabled; + bool showTime = false; + bool showSourceTrace = false; + bool coutEnabled = false; + bool fileEnabled = false; + std::string filePath; + std::string fileName; +}; + +class LoggerConfig : public IConfig { + +public: + + using IConfig::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(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; + } + +}; diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 9584ff4..019810d 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -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 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 ) diff --git a/server/config/main.cfg b/server/config/main.cfg new file mode 100644 index 0000000..0ddef13 --- /dev/null +++ b/server/config/main.cfg @@ -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"; + } +); diff --git a/server/src/App.cpp b/server/src/App.cpp new file mode 100644 index 0000000..e69de29 diff --git a/server/src/App.hpp b/server/src/App.hpp new file mode 100644 index 0000000..e69de29 diff --git a/server/src/main.cpp b/server/src/main.cpp index 2c64d89..db0a94b 100644 --- a/server/src/main.cpp +++ b/server/src/main.cpp @@ -1,8 +1,15 @@ #include +#include "common/config/ConfigService.hpp" +#include "common/config/LoggerConfig.hpp" +#include "common/LoggerService.hpp" + 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!"); } diff --git a/server/test/.gitkeep b/server/test/.gitkeep new file mode 100644 index 0000000..e69de29