From 5cd65ed92c1397c53b3ca312ffb5d2df2d8f62f1 Mon Sep 17 00:00:00 2001 From: Aster Seker Date: Sun, 14 Sep 2025 07:45:20 +0300 Subject: [PATCH] feat(logging): add system loggers Add SyslogLogger and EventLogLogger backends with build options and macros. Update tests, documentation and vcpkg port. --- .github/workflows/ci.yml | 6 +- CMakeLists.txt | 11 +++ README-RU.md | 30 +++++- README.md | 30 +++++- include/logit_cpp/logit/LogMacros.hpp | 52 ++++++++++ include/logit_cpp/logit/loggers.hpp | 3 + .../logit/loggers/EventLogLogger.hpp | 99 +++++++++++++++++++ .../logit_cpp/logit/loggers/SyslogLogger.hpp | 92 +++++++++++++++++ .../logit_cpp/logit/loggers/SystemLogger.hpp | 12 +++ tests/install_consumer/main.cpp | 11 ++- vcpkg-overlay/ports/log-it-cpp/portfile.cmake | 5 +- 11 files changed, 343 insertions(+), 8 deletions(-) create mode 100644 include/logit_cpp/logit/loggers/EventLogLogger.hpp create mode 100644 include/logit_cpp/logit/loggers/SyslogLogger.hpp create mode 100644 include/logit_cpp/logit/loggers/SystemLogger.hpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d0d30c..6a60ae1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: submodules: true - run: git submodule update --init --recursive - name: Configure - run: cmake -S . -B build -DLOG_IT_CPP_BUILD_TESTS=ON -DCMAKE_CXX_STANDARD=${{ matrix.std }} + run: cmake -S . -B build -DLOG_IT_CPP_BUILD_TESTS=ON -DCMAKE_CXX_STANDARD=${{ matrix.std }} -DLOGIT_WITH_SYSLOG=ON -DLOGIT_WITH_WIN_EVENT_LOG=OFF - name: Build run: cmake --build build - name: Install @@ -41,7 +41,7 @@ jobs: submodules: true - run: git submodule update --init --recursive - name: Configure - run: cmake -S . -B build -DLOG_IT_CPP_BUILD_TESTS=ON -DCMAKE_CXX_STANDARD=${{ matrix.std }} + run: cmake -S . -B build -DLOG_IT_CPP_BUILD_TESTS=ON -DCMAKE_CXX_STANDARD=${{ matrix.std }} -DLOGIT_WITH_SYSLOG=OFF -DLOGIT_WITH_WIN_EVENT_LOG=ON - name: Build run: cmake --build build --config Release - name: Install @@ -64,7 +64,7 @@ jobs: submodules: true - run: git submodule update --init --recursive - name: Configure - run: cmake -S . -B build -DLOG_IT_CPP_BUILD_TESTS=ON -DCMAKE_CXX_STANDARD=${{ matrix.std }} + run: cmake -S . -B build -DLOG_IT_CPP_BUILD_TESTS=ON -DCMAKE_CXX_STANDARD=${{ matrix.std }} -DLOGIT_WITH_SYSLOG=ON -DLOGIT_WITH_WIN_EVENT_LOG=OFF - name: Build run: cmake --build build - name: Install diff --git a/CMakeLists.txt b/CMakeLists.txt index 518bb15..9d2ce19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,8 @@ option(LOG_IT_CPP_BUILD_EXAMPLES "Build log-it-cpp examples" OFF) option(LOGIT_WITH_GZIP "Enable gzip via zlib" OFF) option(LOGIT_WITH_ZSTD "Enable zstd" OFF) option(LOGIT_USE_SUBMODULES "Allow bundled third_party fallback" OFF) +option(LOGIT_WITH_SYSLOG "Enable POSIX syslog backend" ON) +option(LOGIT_WITH_WIN_EVENT_LOG "Enable Windows Event Log backend" ON) if(NOT DEFINED CMAKE_CXX_STANDARD) set(CMAKE_CXX_STANDARD 11) @@ -33,6 +35,15 @@ target_include_directories(log-it-cpp INTERFACE target_link_libraries(log-it-cpp INTERFACE time_shield::time_shield) +if(LOGIT_WITH_SYSLOG AND (UNIX OR APPLE) AND NOT EMSCRIPTEN) + target_compile_definitions(log-it-cpp INTERFACE LOGIT_HAS_SYSLOG=1) +endif() + +if(LOGIT_WITH_WIN_EVENT_LOG AND WIN32) + target_compile_definitions(log-it-cpp INTERFACE LOGIT_HAS_WIN_EVENT_LOG=1) + target_link_libraries(log-it-cpp INTERFACE advapi32) +endif() + # ---------- GZIP (zlib) ---------- if(LOGIT_WITH_GZIP) if(NOT TARGET ZLIB::ZLIB) diff --git a/README-RU.md b/README-RU.md index d056938..30d3d1a 100644 --- a/README-RU.md +++ b/README-RU.md @@ -50,7 +50,11 @@ LOGIT_ADD_FILE_LOGGER_DEFAULT(); LOGIT_ADD_UNIQUE_FILE_LOGGER_DEFAULT_SINGLE_MODE(); ``` -- **Асинхронное логирование**: +- **Системные бэкенды**: + +Использование системных журналов: `SyslogLogger` для POSIX `syslog` и `EventLogLogger` для Windows Event Log. + +- **Асинхронное логирование**: Улучшите производительность приложения с помощью асинхронного логирования. Все логгеры по умолчанию обрабатывают сообщения в отдельном потоке. @@ -486,6 +490,30 @@ LogIt++ поддерживает библиотеку *fmt* для продви #define LOGIT_USE_FMT_LIB ``` +## Системные бэкенды + +LogIt++ может отправлять сообщения в системные журналы. + +### Syslog (Unix) + +Доступен при `LOGIT_WITH_SYSLOG=ON` на Unix-подобных системах. Соответствие уровней: TRACE/DEBUG → `LOG_DEBUG`, INFO → `LOG_INFO`, WARN → `LOG_WARNING`, ERROR/FATAL → `LOG_ERR`/`LOG_CRIT`. + +```cpp +LOGIT_ADD_SYSLOG_DEFAULT(); +LOGIT_INFO("Syslog работает"); +``` + +### Windows Event Log + +Работает при `LOGIT_WITH_WIN_EVENT_LOG=ON` в Windows. Уровни: TRACE/DEBUG/INFO → `INFORMATION`, WARN → `WARNING`, ERROR/FATAL → `ERROR`. + +```cpp +LOGIT_ADD_EVENT_LOG_DEFAULT(); +LOGIT_ERROR("Что-то пошло не так"); +``` + +На неподдерживаемых платформах оба логгера компилируются в заглушки. + ### Emscripten При сборке с Emscripten библиотека работает в однопоточном режиме. ConsoleLogger diff --git a/README.md b/README.md index 28787d7..d5bf55e 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,11 @@ LOGIT_ADD_FILE_LOGGER_DEFAULT(); LOGIT_ADD_UNIQUE_FILE_LOGGER_DEFAULT_SINGLE_MODE(); ``` -- **Asynchronous Logging**: +- **System Backends**: + +Use the host OS logging facility. `SyslogLogger` works with POSIX `syslog`, while `EventLogLogger` writes to the Windows Event Log. + +- **Asynchronous Logging**: Improve application performance with asynchronous logging. All loggers handle messages in a separate thread by default. @@ -498,6 +502,30 @@ LogIt++ supports the *fmt* library for advanced string formatting, which is also #define LOGIT_USE_FMT_LIB ``` +## System Backends + +LogIt++ can forward messages to system logging facilities. + +### Syslog (Unix) + +Available when `LOGIT_WITH_SYSLOG=ON` on Unix-like systems. Log levels are mapped as follows: TRACE/DEBUG → `LOG_DEBUG`, INFO → `LOG_INFO`, WARN → `LOG_WARNING`, ERROR/FATAL → `LOG_ERR`/`LOG_CRIT`. + +```cpp +LOGIT_ADD_SYSLOG_DEFAULT(); +LOGIT_INFO("Syslog is alive"); +``` + +### Windows Event Log + +Enabled with `LOGIT_WITH_WIN_EVENT_LOG=ON` on Windows. Levels map TRACE/DEBUG/INFO → `INFORMATION`, WARN → `WARNING`, ERROR/FATAL → `ERROR`. + +```cpp +LOGIT_ADD_EVENT_LOG_DEFAULT(); +LOGIT_ERROR("Something went wrong"); +``` + +Both loggers compile to no-ops on unsupported platforms. + ### Emscripten When building with Emscripten the library runs without threads. Console logging diff --git a/include/logit_cpp/logit/LogMacros.hpp b/include/logit_cpp/logit/LogMacros.hpp index e37ab63..87a1b28 100644 --- a/include/logit_cpp/logit/LogMacros.hpp +++ b/include/logit_cpp/logit/LogMacros.hpp @@ -994,6 +994,30 @@ static_assert(LOGIT_LEVEL_FATAL == static_cast(logit::LogLevel::LOG_LVL_FAT std::make_unique(LOGIT_UNIQUE_FILE_LOGGER_PATTERN), \ true) +#define LOGIT_ADD_SYSLOG(ident, facility, async) \ + logit::Logger::get_instance().add_logger( \ + std::make_unique(ident, facility, async), \ + std::make_unique(LOGIT_CONSOLE_PATTERN), false) + +#define LOGIT_ADD_SYSLOG_SINGLE_MODE(ident, facility, async) \ + logit::Logger::get_instance().add_logger( \ + std::make_unique(ident, facility, async), \ + std::make_unique(LOGIT_CONSOLE_PATTERN), true) + +#define LOGIT_ADD_SYSLOG_DEFAULT() LOGIT_ADD_SYSLOG("log-it", LOG_USER, true) + +#define LOGIT_ADD_EVENT_LOG(source_wide, async) \ + logit::Logger::get_instance().add_logger( \ + std::make_unique(source_wide, async), \ + std::make_unique(LOGIT_CONSOLE_PATTERN), false) + +#define LOGIT_ADD_EVENT_LOG_SINGLE_MODE(source_wide, async) \ + logit::Logger::get_instance().add_logger( \ + std::make_unique(source_wide, async), \ + std::make_unique(LOGIT_CONSOLE_PATTERN), true) + +#define LOGIT_ADD_EVENT_LOG_DEFAULT() LOGIT_ADD_EVENT_LOG(L"LogIt", true) + #else // C++11 fallback /// \brief Macro for adding a logger with a specific formatter. @@ -1185,6 +1209,34 @@ static_assert(LOGIT_LEVEL_FATAL == static_cast(logit::LogLevel::LOG_LVL_FAT std::unique_ptr(new logit::SimpleLogFormatter(LOGIT_UNIQUE_FILE_LOGGER_PATTERN)), \ true) +#define LOGIT_ADD_SYSLOG(ident, facility, async) \ + logit::Logger::get_instance().add_logger( \ + std::unique_ptr(new logit::SyslogLogger(ident, facility, async)), \ + std::unique_ptr(new logit::SimpleLogFormatter(LOGIT_CONSOLE_PATTERN)), \ + false) + +#define LOGIT_ADD_SYSLOG_SINGLE_MODE(ident, facility, async) \ + logit::Logger::get_instance().add_logger( \ + std::unique_ptr(new logit::SyslogLogger(ident, facility, async)), \ + std::unique_ptr(new logit::SimpleLogFormatter(LOGIT_CONSOLE_PATTERN)), \ + true) + +#define LOGIT_ADD_SYSLOG_DEFAULT() LOGIT_ADD_SYSLOG("log-it", LOG_USER, true) + +#define LOGIT_ADD_EVENT_LOG(source_wide, async) \ + logit::Logger::get_instance().add_logger( \ + std::unique_ptr(new logit::EventLogLogger(source_wide, async)), \ + std::unique_ptr(new logit::SimpleLogFormatter(LOGIT_CONSOLE_PATTERN)), \ + false) + +#define LOGIT_ADD_EVENT_LOG_SINGLE_MODE(source_wide, async) \ + logit::Logger::get_instance().add_logger( \ + std::unique_ptr(new logit::EventLogLogger(source_wide, async)), \ + std::unique_ptr(new logit::SimpleLogFormatter(LOGIT_CONSOLE_PATTERN)), \ + true) + +#define LOGIT_ADD_EVENT_LOG_DEFAULT() LOGIT_ADD_EVENT_LOG(L"LogIt", true) + #endif // C++ version check /// \name Logger Management diff --git a/include/logit_cpp/logit/loggers.hpp b/include/logit_cpp/logit/loggers.hpp index 820e267..bd775ec 100644 --- a/include/logit_cpp/logit/loggers.hpp +++ b/include/logit_cpp/logit/loggers.hpp @@ -10,5 +10,8 @@ #include "loggers/ConsoleLogger.hpp" #include "loggers/FileLogger.hpp" #include "loggers/UniqueFileLogger.hpp" +#include "loggers/SyslogLogger.hpp" +#include "loggers/EventLogLogger.hpp" +#include "loggers/SystemLogger.hpp" #endif // _LOGIT_LOGGERS_HPP_INCLUDED diff --git a/include/logit_cpp/logit/loggers/EventLogLogger.hpp b/include/logit_cpp/logit/loggers/EventLogLogger.hpp new file mode 100644 index 0000000..c51b8d0 --- /dev/null +++ b/include/logit_cpp/logit/loggers/EventLogLogger.hpp @@ -0,0 +1,99 @@ +#pragma once +#ifndef LOGIT_EVENT_LOG_LOGGER_HPP_INCLUDED +#define LOGIT_EVENT_LOG_LOGGER_HPP_INCLUDED + +#include "ILogger.hpp" +#include +#include + +#if defined(_WIN32) && defined(LOGIT_HAS_WIN_EVENT_LOG) +#include +#define LOGIT_WIN_EVENT_ENABLED 1 +#else +#define LOGIT_WIN_EVENT_ENABLED 0 +#endif + +namespace logit { + +#if LOGIT_WIN_EVENT_ENABLED + +class EventLogLogger : public ILogger { +public: + struct Config { + const wchar_t* source; + bool async; + Config(const wchar_t* s = L"LogIt", bool a = true) : source(s), async(a) {} + }; + EventLogLogger() : EventLogLogger(Config()) {} + explicit EventLogLogger(const Config& c) : cfg(c) { + hsrc = RegisterEventSourceW(nullptr, cfg.source); + } + EventLogLogger(const wchar_t* source, bool async) : EventLogLogger(Config(source, async)) {} + ~EventLogLogger() override { if (hsrc) DeregisterEventSource(hsrc); } + + void log(const LogRecord& rec, const std::string& msg) override { + LogLevel lvl = rec.log_level; + std::string s = msg; + auto task = [this, lvl, s]() { + if ((int)lvl < level.load()) return; + if (!hsrc) return; + WORD type = map(lvl); + int n = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), nullptr, 0); + std::wstring wmsg; wmsg.resize(n); + MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), &wmsg[0], n); + LPCWSTR arr[1] = { wmsg.c_str() }; + ReportEventW(hsrc, type, 0, 0, nullptr, 1, 0, arr, nullptr); + }; + if (cfg.async) { detail::TaskExecutor::get_instance().add_task(task); } + else { task(); } + last_ts.store(rec.timestamp_ms); + } + + std::string get_string_param(const LoggerParam&) const override { return {}; } + int64_t get_int_param(const LoggerParam&) const override { return 0; } + double get_float_param(const LoggerParam&) const override { return 0.0; } + void set_log_level(LogLevel l) override { level.store((int)l); } + LogLevel get_log_level() const override { return (LogLevel)level.load(); } + void wait() override { if (cfg.async) detail::TaskExecutor::get_instance().wait(); } + +private: + static WORD map(LogLevel l) { + switch (l) { + case LogLevel::LOG_LVL_TRACE: + case LogLevel::LOG_LVL_DEBUG: + case LogLevel::LOG_LVL_INFO: return EVENTLOG_INFORMATION_TYPE; + case LogLevel::LOG_LVL_WARN: return EVENTLOG_WARNING_TYPE; + case LogLevel::LOG_LVL_ERROR: return EVENTLOG_ERROR_TYPE; + case LogLevel::LOG_LVL_FATAL: return EVENTLOG_ERROR_TYPE; + } return EVENTLOG_INFORMATION_TYPE; + } + Config cfg{}; + HANDLE hsrc = nullptr; + std::atomic level{(int)LogLevel::LOG_LVL_TRACE}; + std::atomic last_ts{0}; +}; + +#else // stub +class EventLogLogger : public ILogger { +public: + struct Config { + const wchar_t* source; + bool async; + Config(const wchar_t* s = L"", bool a = false) : source(s), async(a) {} + }; + EventLogLogger() {} + explicit EventLogLogger(const Config&) {} + EventLogLogger(const wchar_t*, bool) {} + void log(const LogRecord&, const std::string&) override {} + std::string get_string_param(const LoggerParam&) const override { return {}; } + int64_t get_int_param(const LoggerParam&) const override { return 0; } + double get_float_param(const LoggerParam&) const override { return 0.0; } + void set_log_level(LogLevel) override {} + LogLevel get_log_level() const override { return LogLevel::LOG_LVL_TRACE; } + void wait() override {} +}; +#endif + +} // namespace logit +#endif + diff --git a/include/logit_cpp/logit/loggers/SyslogLogger.hpp b/include/logit_cpp/logit/loggers/SyslogLogger.hpp new file mode 100644 index 0000000..739468c --- /dev/null +++ b/include/logit_cpp/logit/loggers/SyslogLogger.hpp @@ -0,0 +1,92 @@ +#pragma once +#ifndef LOGIT_SYSLOG_LOGGER_HPP_INCLUDED +#define LOGIT_SYSLOG_LOGGER_HPP_INCLUDED + +#include "ILogger.hpp" +#include +#include + +#if (defined(__unix__) || defined(__APPLE__)) && defined(LOGIT_HAS_SYSLOG) +#include +#define LOGIT_SYSLOG_ENABLED 1 +#else +#define LOGIT_SYSLOG_ENABLED 0 +#endif + +namespace logit { + +#if LOGIT_SYSLOG_ENABLED + +class SyslogLogger : public ILogger { +public: + struct Config { + const char* ident; + int facility; + bool async; + Config(const char* i = "log-it", int f = LOG_USER, bool a = true) + : ident(i), facility(f), async(a) {} + }; + SyslogLogger() : SyslogLogger(Config()) {} + explicit SyslogLogger(const Config& c) : cfg(c) { openlog(cfg.ident, LOG_PID | LOG_NDELAY, cfg.facility); } + SyslogLogger(const char* ident, int facility, bool async) : SyslogLogger(Config(ident, facility, async)) {} + ~SyslogLogger() override { closelog(); } + + void log(const LogRecord& rec, const std::string& msg) override { + LogLevel lvl = rec.log_level; + std::string s = msg; + auto task = [this, lvl, s]() { + if ((int)lvl < level.load()) return; + syslog(map(lvl), "%s", s.c_str()); + }; + if (cfg.async) { detail::TaskExecutor::get_instance().add_task(task); } + else { task(); } + last_ts.store(rec.timestamp_ms); + } + std::string get_string_param(const LoggerParam&) const override { return {}; } + int64_t get_int_param(const LoggerParam&) const override { return 0; } + double get_float_param(const LoggerParam&) const override { return 0.0; } + void set_log_level(LogLevel l) override { level.store((int)l); } + LogLevel get_log_level() const override { return (LogLevel)level.load(); } + void wait() override { if (cfg.async) detail::TaskExecutor::get_instance().wait(); } + +private: + static int map(LogLevel l) { + switch (l) { + case LogLevel::LOG_LVL_TRACE: + case LogLevel::LOG_LVL_DEBUG: return LOG_DEBUG; + case LogLevel::LOG_LVL_INFO: return LOG_INFO; + case LogLevel::LOG_LVL_WARN: return LOG_WARNING; + case LogLevel::LOG_LVL_ERROR: return LOG_ERR; + case LogLevel::LOG_LVL_FATAL: return LOG_CRIT; + } return LOG_INFO; + } + Config cfg{}; + std::atomic level{(int)LogLevel::LOG_LVL_TRACE}; + std::atomic last_ts{0}; +}; + +#else // stub on unsupported +class SyslogLogger : public ILogger { +public: + struct Config { + const char* ident; + int facility; + bool async; + Config(const char* i="", int f=0, bool a=false) : ident(i), facility(f), async(a) {} + }; + SyslogLogger() {} + explicit SyslogLogger(const Config&) {} + SyslogLogger(const char*,int,bool) {} + void log(const LogRecord&, const std::string&) override {} + std::string get_string_param(const LoggerParam&) const override { return {}; } + int64_t get_int_param(const LoggerParam&) const override { return 0; } + double get_float_param(const LoggerParam&) const override { return 0.0; } + void set_log_level(LogLevel) override {} + LogLevel get_log_level() const override { return LogLevel::LOG_LVL_TRACE; } + void wait() override {} +}; +#endif + +} // namespace logit +#endif + diff --git a/include/logit_cpp/logit/loggers/SystemLogger.hpp b/include/logit_cpp/logit/loggers/SystemLogger.hpp new file mode 100644 index 0000000..feeda41 --- /dev/null +++ b/include/logit_cpp/logit/loggers/SystemLogger.hpp @@ -0,0 +1,12 @@ +#pragma once +#include "SyslogLogger.hpp" +#include "EventLogLogger.hpp" + +namespace logit { +#if defined(_WIN32) +using SystemLogger = EventLogLogger; +#else +using SystemLogger = SyslogLogger; +#endif +} + diff --git a/tests/install_consumer/main.cpp b/tests/install_consumer/main.cpp index b604ffa..855671b 100644 --- a/tests/install_consumer/main.cpp +++ b/tests/install_consumer/main.cpp @@ -1,9 +1,16 @@ -#define LOGIT_SHORT_NAME #include int main() { +#if LOGIT_SYSLOG_ENABLED + LOGIT_ADD_SYSLOG_DEFAULT(); + LOGIT_INFO("hello"); +#elif LOGIT_WIN_EVENT_ENABLED + LOGIT_ADD_EVENT_LOG_DEFAULT(); + LOGIT_ERROR("hello"); +#else LOGIT_ADD_CONSOLE_DEFAULT(); - LOG_INFO("consumer works"); + LOGIT_INFO("consumer works"); +#endif LOGIT_WAIT(); return 0; } diff --git a/vcpkg-overlay/ports/log-it-cpp/portfile.cmake b/vcpkg-overlay/ports/log-it-cpp/portfile.cmake index 7f8dee0..1ff8968 100644 --- a/vcpkg-overlay/ports/log-it-cpp/portfile.cmake +++ b/vcpkg-overlay/ports/log-it-cpp/portfile.cmake @@ -8,7 +8,10 @@ vcpkg_from_github( vcpkg_cmake_configure( SOURCE_PATH "${SOURCE_PATH}" - OPTIONS -DLOG_IT_CPP_BUILD_TESTS=OFF + OPTIONS + -DLOG_IT_CPP_BUILD_TESTS=OFF + -DLOGIT_WITH_SYSLOG=ON + -DLOGIT_WITH_WIN_EVENT_LOG=ON ) vcpkg_cmake_install()