diff --git a/.vscode/settings.json b/.vscode/settings.json index 306bcdb..9720b9a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,6 +12,7 @@ "nosan", "pugi", "SIGUSR", + "spdlog", "Struct", "vararg" ], @@ -25,7 +26,8 @@ "functional": "cpp", "memory": "cpp", "optional": "cpp", - "sstream": "cpp" + "sstream": "cpp", + "*.tcc": "cpp" }, "testMate.cpp.test.executables": "build_*/**/*_tests", "cmake.buildDirectory": "${workspaceFolder}/build_debug_cpu_native", diff --git a/TODO b/TODO index 0044509..1dd28d5 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,3 @@ - examples of configuration (nginx, cron) - make cross compilation work again - consider removing ECB namespace -- consider using spdlog diff --git a/cmake/externals.cmake b/cmake/externals.cmake index 8bd8ccd..b160c6b 100644 --- a/cmake/externals.cmake +++ b/cmake/externals.cmake @@ -64,6 +64,18 @@ message(STATUS "${ColorGreen}mongoose ready${ColorReset}") add_library(mongoose STATIC ${mongoose_SOURCE_DIR}/mongoose.c) target_include_directories(mongoose PUBLIC ${mongoose_SOURCE_DIR}) +# SPDLOG +FetchContent_Declare( + spdlog + GIT_REPOSITORY https://github.com/gabime/spdlog.git + GIT_TAG v1.12.0 + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE) + +message(STATUS "${ColorYellow}Making spdlog available...${ColorReset}") +FetchContent_MakeAvailable(spdlog) +message(STATUS "${ColorGreen}spdlog ready${ColorReset}") + # GOOGLE TEST if(ECB_PARAM_TESTS) FetchContent_Declare( diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 05ac146..6858aff 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,6 @@ -add_library(ecb_lib STATIC Logging.cpp Rates.cpp Record.cpp Time_Point.cpp Utils.cpp) +add_library(ecb_lib STATIC Log.cpp Rates.cpp Record.cpp Time_Point.cpp Utils.cpp) target_include_directories(ecb_lib PUBLIC .) -target_link_libraries(ecb_lib PRIVATE ecb_compiler_flags) +target_link_libraries(ecb_lib PRIVATE ecb_compiler_flags PUBLIC spdlog) add_library(ecb_data_loader STATIC Data_Loader.cpp) target_include_directories(ecb_data_loader PUBLIC .) diff --git a/src/Data_Loader.cpp b/src/Data_Loader.cpp index 92b7b72..5b93750 100644 --- a/src/Data_Loader.cpp +++ b/src/Data_Loader.cpp @@ -1,5 +1,5 @@ #include "Data_Loader.hpp" -#include "Logging.hpp" +#include "Log.hpp" #include "cpr/cpr.h" #include "pugixml.hpp" @@ -21,7 +21,7 @@ namespace ECB } catch (...) { - Log("Error loading from a file '%s'\n", file_name.c_str()); + Log::error("Error loading from a file '{}'\n", file_name); } return {}; } @@ -44,12 +44,12 @@ namespace ECB } else { - Log("Failed getting data from url '%s', status code = %d\n", url.c_str(), r.status_code); + Log::error("Failed getting data from url '{}', status code = {}\n", url, r.status_code); } } catch (...) { - Log("Error loading from url '%s'\n", url.c_str()); + Log::error("Error loading from url '{}'\n", url); } return {}; } @@ -71,7 +71,7 @@ namespace ECB if (!parse_result) { - Log("Error parsing xml: %s\n", parse_result.description()); + Log::error("Error parsing xml: {}\n", parse_result.description()); return records; } diff --git a/src/Log.cpp b/src/Log.cpp new file mode 100644 index 0000000..14dbed0 --- /dev/null +++ b/src/Log.cpp @@ -0,0 +1,62 @@ +#include "Log.hpp" + +#include "spdlog/sinks/basic_file_sink.h" +#include "spdlog/sinks/stdout_color_sinks.h" + +namespace ECB +{ + // static members + std::shared_ptr Log::logger_ = nullptr; + + void Log::init(const std::string &logFile) + { + std::vector sinks; + + auto consoleLogger = std::make_shared(); + // timestamp with ms, colored log level, message + consoleLogger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] %v"); + sinks.push_back(consoleLogger); + + if (!logFile.empty()) + { + constexpr bool TRUNCATE = true; + try + { + auto fileLogger = std::make_shared(logFile, TRUNCATE); + // timestamp with ms, log level, message + fileLogger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] %v"); + sinks.push_back(fileLogger); + } + catch (const spdlog::spdlog_ex &ex) + { + spdlog::error("Failed to create file logger: {}", ex.what()); + } + } + + logger_ = std::make_shared("logger", sinks.begin(), sinks.end()); + spdlog::register_logger(logger_); + } + + void Log::setLevel(const std::string &level) + { + constexpr auto DEFAULT_LEVEL = spdlog::level::info; + spdlog::level::level_enum finalLevel = DEFAULT_LEVEL; + + if (level == "off") + finalLevel = spdlog::level::off; + else if (level == "error") + finalLevel = spdlog::level::err; + else if (level == "warning") + finalLevel = spdlog::level::warn; + else if (level == "info") + finalLevel = spdlog::level::info; + + setLevel(finalLevel); + } + + void Log::setLevel(spdlog::level::level_enum level) + { + if (logger_ != nullptr) + logger_->set_level(level); + } +} // namespace ECB diff --git a/src/Log.hpp b/src/Log.hpp new file mode 100644 index 0000000..1f5da35 --- /dev/null +++ b/src/Log.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "spdlog/spdlog.h" + +namespace ECB +{ + class Log + { + public: + Log() = delete; + + static void init(const std::string &logFile = ""); + + static void setLevel(const std::string &level); + static void setLevel(spdlog::level::level_enum level); + + // convenient aliases + template + static void info(const char *format, Args... args) + { + log(spdlog::level::level_enum::info, format, args...); + } + + template + static void warn(const char *format, Args... args) + { + log(spdlog::level::level_enum::warn, format, args...); + } + + template + static void error(const char *format, Args... args) + { + log(spdlog::level::level_enum::err, format, args...); + } + + private: + template + static void log(spdlog::level::level_enum level, const char *format, Args... args) + { + if (logger_ != nullptr) + logger_->log(level, format, args...); + } + + static std::shared_ptr logger_; + }; +} // namespace ECB diff --git a/src/Logging.cpp b/src/Logging.cpp deleted file mode 100644 index 20acf76..0000000 --- a/src/Logging.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "Logging.hpp" - -#ifndef NDEBUG - #include - #include -#endif - -namespace ECB -{ - void Log(const char *a_format, ...) - { -#ifndef NDEBUG - va_list args; - va_start(args, a_format); - vprintf(a_format, args); - va_end(args); -#else - (void)a_format; // don't warn about unused variable -#endif - } - -} // namespace ECB diff --git a/src/Logging.hpp b/src/Logging.hpp deleted file mode 100644 index f22b78a..0000000 --- a/src/Logging.hpp +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -namespace ECB -{ - extern void Log(const char *a_format, ...); -} // namespace ECB diff --git a/src/Main_Fetcher.cpp b/src/Main_Fetcher.cpp index a5c04a1..410c2ae 100644 --- a/src/Main_Fetcher.cpp +++ b/src/Main_Fetcher.cpp @@ -1,5 +1,5 @@ #include "Data_Loader.hpp" -#include "Logging.hpp" +#include "Log.hpp" #include "Urls.hpp" #include "Version_Info.hpp" @@ -30,6 +30,14 @@ int main(int argc, char *argv[]) return !param.empty(); // valid data is not empty }); + options.add_optional("log_file", "Log file", "", [](const std::string ¶m) { + return !param.empty(); // valid data is not empty + }); + + options.add_optional("log_level", "Log level (off, info, warn, error)", "info", [](const std::string ¶m) { + return param == "off" || param == "info" || param == "warn" || param == "error"; + }); + options.add_flag("help", "Show help"); const bool parse_result = options.parse(argc, argv); @@ -47,25 +55,28 @@ int main(int argc, char *argv[]) return -1; } + ECB::Log::init(options.as_string("log_file")); + ECB::Log::setLevel(options.as_string("log_level")); + const bool hist_mode = (options.as_string("mode") == "hist"); const std::string &url = (hist_mode) ? options.as_string("hist_url") : options.as_string("daily_url"); const std::string &save_to_file = options.as_string("save_file"); - cout << "Getting " << (hist_mode ? "historical" : "daily") << " data" - << (!save_to_file.empty() ? " and saving to " + save_to_file : "") << endl; + ECB::Log::info("Getting {} data{}", hist_mode ? "historical" : "daily", + !save_to_file.empty() ? std::string(" and saving to ") + save_to_file : ""); const auto records = ECB::Data_Loader::load_from_url(url, save_to_file); if (records.empty()) { - ECB::Log("Failed to load data from url: %s\n", url.c_str()); + ECB::Log::error("Failed to load data from url: {}\n", url); return -1; } for (const auto &record: records) cout << record.as_string_without_base() << endl; - cout << "Got " << records.size() << " records\n"; + ECB::Log::info("Got {} records", records.size()); return 0; } diff --git a/src/Main_Server.cpp b/src/Main_Server.cpp index f1b26c5..fbe7aba 100644 --- a/src/Main_Server.cpp +++ b/src/Main_Server.cpp @@ -1,5 +1,5 @@ #include "Data_Loader.hpp" -#include "Logging.hpp" +#include "Log.hpp" #include "Main_Signals.hpp" #include "Rates.hpp" #include "Server.hpp" @@ -42,6 +42,14 @@ int main(int argc, char *argv[]) return num >= 1 && num <= 20; // NOLINT }); + options.add_optional("log_file", "Log file", "", [](const std::string ¶m) { + return !param.empty(); // valid data is not empty + }); + + options.add_optional("log_level", "Log level (off, info, warn, error)", "info", [](const std::string ¶m) { + return param == "off" || param == "info" || param == "warn" || param == "error"; + }); + options.add_flag("listen_all", "Listen on all interfaces"); options.add_flag("pretty", "Pretty print JSON output"); options.add_flag("help", "Show help"); @@ -62,6 +70,9 @@ int main(int argc, char *argv[]) return -1; } + ECB::Log::init(options.as_string("log_file")); + ECB::Log::setLevel(options.as_string("log_level")); + auto rates = std::make_shared(); if (!options.as_string("xml_file").empty()) @@ -108,13 +119,13 @@ int main(int argc, char *argv[]) // start thread for serving web requests std::thread web_thread([&server]() { - ECB::Log("Starting web server in separate thread\n"); + ECB::Log::info("Starting web server in separate thread"); server.start(); - ECB::Log("Stopping web server\n"); + ECB::Log::info("Stopping web server"); server.stop(); }); - ECB::Log("Starting loop in main thread\n"); + ECB::Log::info("Starting loop in main thread"); while (Main_Signals::keep_running()) { if (Main_Signals::load_more_data()) @@ -123,7 +134,7 @@ int main(int argc, char *argv[]) const auto data = ECB::Data_Loader::load_from_url(options.as_string("signal_xml_url")); if (!data.empty()) { - ECB::Log("Loaded %u records.\n", data.size()); + ECB::Log::info("Loaded {} records", data.size()); rates->add(data); } } @@ -131,12 +142,12 @@ int main(int argc, char *argv[]) std::this_thread::sleep_for(1s); } - ECB::Log("Stopping the web thread\n"); + ECB::Log::info("Stopping the web thread"); server.stop(); - ECB::Log("Waiting for web thread to join..."); + ECB::Log::info("Waiting for web thread to join..."); web_thread.join(); - ECB::Log("done\n"); + ECB::Log::info("done"); return 0; } diff --git a/src/Rates.cpp b/src/Rates.cpp index 0983b2d..26fb89f 100644 --- a/src/Rates.cpp +++ b/src/Rates.cpp @@ -1,5 +1,4 @@ #include "Rates.hpp" -#include "Logging.hpp" namespace ECB { @@ -7,12 +6,12 @@ namespace ECB { std::scoped_lock lock(m_mutex); - const auto it = m_data.find(tp); + const auto IT = m_data.find(tp); - if (it == m_data.end()) + if (IT == m_data.end()) return {}; - Record ret = it->second; + Record ret = IT->second; if (!base || ret.base() == base) return ret; diff --git a/src/Server_Impl.cpp b/src/Server_Impl.cpp index 96a4e5d..57b9bf3 100644 --- a/src/Server_Impl.cpp +++ b/src/Server_Impl.cpp @@ -1,5 +1,5 @@ #include "Server_Impl.hpp" -#include "Logging.hpp" +#include "Log.hpp" #include "Rates.hpp" #include "Utils.hpp" @@ -25,7 +25,7 @@ namespace ECB { if (m_conn != nullptr) // already created { - Log("Server_Impl::initialize: already initialized\n"); + Log::warn("Server_Impl::initialize() - already initialized"); return false; } @@ -192,11 +192,11 @@ namespace ECB { if (m_conn == nullptr || m_running) { - Log("Server_Impl::start_polling: already running\n"); + Log::warn("Server_Impl::start_polling() already running"); return; } - Log("Starting loop\n"); + Log::info("Server_Impl::start_polling() starting loop"); m_running = true; while (m_running) mg_mgr_poll(&m_mgr, 500); diff --git a/src/Time_Point.cpp b/src/Time_Point.cpp index a1ea5b4..a879e49 100644 --- a/src/Time_Point.cpp +++ b/src/Time_Point.cpp @@ -1,7 +1,5 @@ -#include - -#include "Logging.hpp" #include "Time_Point.hpp" +#include "Log.hpp" namespace ECB { @@ -11,7 +9,7 @@ namespace ECB if (value.length() != TEMPLATE.length()) { - Log("Initializing Time_Point with a string of length %u failed\n", value.length()); + Log::error("Initializing Time_Point with a string of length {} failed\n", value.length()); return; } @@ -29,7 +27,7 @@ namespace ECB } else { - Log("Initializing Time_Point with a string '%s' failed\n", value.c_str()); + Log::error("Initializing Time_Point with a string '{}' failed\n", value); return; } }