From 7949f0c6c23721db75e855ee5c880ee4abae45f6 Mon Sep 17 00:00:00 2001 From: Aster Seker Date: Mon, 15 Sep 2025 19:04:56 +0300 Subject: [PATCH] feat(logging): add printf scope macros and clarify formatting --- README-RU.md | 20 +++---- README.md | 20 +++---- docs/mainpage.dox | 23 ++------ examples/example_logit_basic.cpp | 3 + include/logit_cpp/logit/LogMacros.hpp | 71 ++++++++++++++++++++++-- include/logit_cpp/logit/utils/format.hpp | 16 +----- tests/compiled_level_test.cpp | 4 ++ tests/printf_format_macros_test.cpp | 23 ++++++++ 8 files changed, 123 insertions(+), 57 deletions(-) create mode 100644 tests/printf_format_macros_test.cpp diff --git a/README-RU.md b/README-RU.md index b01c387..36f33d0 100644 --- a/README-RU.md +++ b/README-RU.md @@ -28,7 +28,10 @@ try { - **Логирование с использованием макросов**: -Легко логируйте переменные и сообщения с помощью макросов. Просто выберите подходящий макрос и передайте переменные или аргументы. Для printf-подобного форматирования используйте `LOGIT_FORMAT_`. +Легко логируйте переменные и сообщения с помощью макросов. Выберите макрос в зависимости от стиля форматирования: + +* `LOGIT_PRINTF_` повторяет поведение функции `printf` и позволяет задавать формат для каждого аргумента. +* `LOGIT_FORMAT_` применяет один и тот же формат ко всему списку аргументов. ``` float someFloat = 123.456f; @@ -37,7 +40,8 @@ LOGIT_INFO(someFloat, someInt); auto now = std::chrono::system_clock::now(); LOGIT_PRINT_INFO("TimePoint example: ", now); -LOGIT_FORMAT_INFO("%s: %d", "status", 200); // printf-подобное форматирование +LOGIT_PRINTF_INFO("%.2f %d", someFloat, someInt); // как printf +LOGIT_FORMAT_INFO("%.2f", someFloat, 654.321f); // один формат для всех аргументов ``` - **Фильтры и ограничение частоты логов**: @@ -111,7 +115,8 @@ LOGIT_ADD_LOGGER(CustomLogger, (), logit::SimpleLogFormatter, ("%v")); | --------------- | -------- | | `LOGIT_(...)` | Логирование с указанным уровнем (`TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL`). | | `LOGIT_PRINT_(...)` | Логирование заранее сформированной строки или сообщения, собранного через потоки. | -| `LOGIT_FORMAT_(fmt, ...)` | printf-подобное форматирование с форматной строкой и аргументами. | +| `LOGIT_PRINTF_(fmt, ...)` | форматирование в стиле `printf` с плейсхолдерами для каждого аргумента. | +| `LOGIT_FORMAT_(fmt, ...)` | применение одного и того же формата ко всем аргументам. | | `LOGIT_STREAM_()` | Потоковое логирование через `<<`; короткие версии `LOG_S_()` доступны при определении `LOGIT_SHORT_NAME`. | | `LOGIT__IF(condition, ...)` | Логирование только если условие истинно. | | `LOGIT__ONCE(...)` | Логирование только при первом вызове. | @@ -461,7 +466,6 @@ LogIt++ предоставляет несколько макросов, кото - **LOGIT_SHORT_NAME**: Включает короткие имена для макросов логирования, таких как `LOG_T`, `LOG_D`, `LOG_E` и другие, для более лаконичных записей логов. -- **LOGIT_USE_FMT_LIB**: Включает использование библиотеки fmt для форматирования строк. --- @@ -548,13 +552,9 @@ git clone --recurse-submodules https://github.com/NewYaroslav/log-it-cpp.git LogIt++ зависит от *time-shield-cpp*, который находится в папке `libs` как подмодуль. Убедитесь, что путь к `libs\time-shield-cpp\include` добавлен в каталоги заголовков вашего проекта. Если вы используете IDE, такие как *Visual Studio* или *CLion*, вы можете добавить этот путь в настройках проекта. -4. (Необязательно) Включите поддержку библиотеки fmt: - -LogIt++ поддерживает библиотеку *fmt* для продвинутого форматирования строк, которая также включена как подмодуль. Чтобы включить *fmt* в LogIt++, определите макрос `LOGIT_USE_FMT_LIB` в вашем проекте: +4. (Необязательно) Включите макросы fmt: -```cpp -#define LOGIT_USE_FMT_LIB -``` +LogIt++ включает библиотеку *fmt* для форматирования с `{}`. Чтобы использовать макросы `LOGIT_FMT_*` и `LOGIT_SCOPE_FMT_*`, соберите библиотеку с опцией CMake `-DLOGIT_WITH_FMT=ON`. ## Системные бэкенды diff --git a/README.md b/README.md index 73e01f5..8eee008 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,10 @@ try { - **Macro-Based Logging**: -Easily log variables and messages using macros. Simply choose the appropriate macro and pass variables or arguments to it. Use `LOGIT_FORMAT_` for printf-style formatting. +Easily log variables and messages using macros. Choose the macro that matches the desired formatting style: + +* `LOGIT_PRINTF_` mimics `printf`, where the format string controls each argument. +* `LOGIT_FORMAT_` applies the same format to every argument in the list. ``` float someFloat = 123.456f; @@ -49,7 +52,8 @@ LOGIT_INFO(someFloat, someInt); auto now = std::chrono::system_clock::now(); LOGIT_PRINT_INFO("TimePoint example: ", now); -LOGIT_FORMAT_INFO("%s: %d", "status", 200); // printf-style +LOGIT_PRINTF_INFO("%.2f %d", someFloat, someInt); // printf-style +LOGIT_FORMAT_INFO("%.2f", someFloat, 654.321f); // same format for all args ``` - **Log Filters and Throttling**: @@ -439,7 +443,6 @@ LogIt++ provides several macros that allow for customization and configuration. - **LOGIT_SHORT_NAME**: Enables short names for logging macros, such as `LOG_T`, `LOG_D`, `LOG_E`, etc., for more concise logging statements. -- **LOGIT_USE_FMT_LIB**: Enables the use of the fmt library for string formatting. --- @@ -511,7 +514,8 @@ public: | ------------- | ----------- | | `LOGIT_(...)` | Log a message with the given level (`TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL`). | | `LOGIT_PRINT_(...)` | Log a pre-formatted string or stream-built message. | -| `LOGIT_FORMAT_(fmt, ...)` | printf-style formatting with a format string and arguments. | +| `LOGIT_PRINTF_(fmt, ...)` | `printf`-style formatting with placeholders for each argument. | +| `LOGIT_FORMAT_(fmt, ...)` | Apply the same format string to every argument. | | `LOGIT_STREAM_()` | Stream-style logging with `<<` operators; short aliases `LOG_S_()` when `LOGIT_SHORT_NAME` is defined. | | `LOGIT__IF(condition, ...)` | Log only when `condition` is true. | | `LOGIT__ONCE(...)` | Log only the first time the macro is executed. | @@ -576,13 +580,9 @@ git clone --recurse-submodules https://github.com/NewYaroslav/log-it-cpp.git LogIt++ depends on **time-shield-cpp**, which is located in the `libs` folder as a submodule. Ensure that the path to `libs\time-shield-cpp\include` is added to your project's include directories. If you are using an IDE like **Visual Studio** or **CLion**, you can add the include path in the project settings. -4. (Optional) Enable fmt support: - -LogIt++ supports the *fmt* library for advanced string formatting, which is also included as a submodule. To enable *fmt* in LogIt++, define the macro `LOGIT_USE_FMT_LIB` in your project: +4. (Optional) Enable fmt-style macros: -```cpp -#define LOGIT_USE_FMT_LIB -``` +LogIt++ includes the *fmt* library for `{}`-based formatting. To use the `LOGIT_FMT_*` and `LOGIT_SCOPE_FMT_*` macros, build the library with the CMake option `-DLOGIT_WITH_FMT=ON`. ## System Backends diff --git a/docs/mainpage.dox b/docs/mainpage.dox index 254be60..b7a235f 100644 --- a/docs/mainpage.dox +++ b/docs/mainpage.dox @@ -33,7 +33,7 @@ try { \subsection macro_logging Logging with Macros -Log variables and messages easily using macros. Simply select the appropriate macro and pass variables or arguments. +Log variables and messages easily using macros. Simply select the appropriate macro and pass variables or arguments. Use `LOGIT_PRINTF_` for `printf`-style formatting and `LOGIT_FORMAT_` to apply the same format to every argument. \code{.cpp} float someFloat = 123.456f; @@ -42,6 +42,8 @@ LOGIT_INFO(someFloat, someInt); auto now = std::chrono::system_clock::now(); LOGIT_PRINT_INFO("TimePoint example: ", now); +LOGIT_PRINTF_INFO("%.2f %d", someFloat, someInt); // printf-style +LOGIT_FORMAT_INFO("%.2f", someFloat, 654.321f); // same format for all args \endcode \subsection multiple_backends Support for Multiple Backends @@ -449,15 +451,6 @@ Enables short names for logging macros, such as `LOG_T`, `LOG_D`, `LOG_E`, etc., #define LOGIT_SHORT_NAME \endcode -- **LOGIT_USE_FMT_LIB**: - -Enables the use of the fmt library for string formatting. If defined, the logging system will use fmt for advanced formatting of log messages. - -\code{.cpp} -// Enable use of the fmt library for formatting. -#define LOGIT_USE_FMT_LIB -\endcode - \section custom_backend_sec Custom Logger Backend and Formatter LogIt++ allows you to extend the logging system by creating your own loggers and formatters. This section explains how to implement a custom backend for logging and a custom log formatter. @@ -574,7 +567,7 @@ LogIt++ is a header-only library, which means it can be easily included in your \subsection step1 Step 1: Clone the Repository -First, clone the LogIt++ repository from GitHub along with its submodules. The library has dependencies on other header-only libraries, such as **time-shield-cpp** (for time utilities) and **fmt** (for string formatting, if `LOGIT_USE_FMT_LIB` is enabled). +First, clone the LogIt++ repository from GitHub along with its submodules. The library has dependencies on other header-only libraries, such as **time-shield-cpp** (for time utilities) and **fmt** (used by `LOGIT_FMT_*` macros). To clone the repository with submodules, use the following command: @@ -606,13 +599,7 @@ If you are using an IDE like **Visual Studio** or **CLion**, you can add the inc \subsection step4 Step 4: Using fmt (Optional) -LogIt++ supports the **fmt** library for advanced string formatting, which is also included as a submodule. To enable `fmt` in LogIt++, define the macro `LOGIT_USE_FMT_LIB` in your project: - -\code{cpp} -#define LOGIT_USE_FMT_LIB -\endcode - -This will allow you to use `fmt`-style formatting within your log messages. The `fmt` library is also located in the `libs` folder of the repository. +LogIt++ ships with the **fmt** library for `{}`-style formatting. To use the `LOGIT_FMT_*` and `LOGIT_SCOPE_FMT_*` macros, build the library with the CMake option `-DLOGIT_WITH_FMT=ON`. \subsection step5 Step 5: Build and Run Your Project diff --git a/examples/example_logit_basic.cpp b/examples/example_logit_basic.cpp index fbb92df..ac4188b 100644 --- a/examples/example_logit_basic.cpp +++ b/examples/example_logit_basic.cpp @@ -123,6 +123,9 @@ int main() { LOGIT_PRINT_ERROR("An error has occurred during processing with color: ", color); LOGIT_FATAL("Fatal error! Immediate attention required!"); + // printf-style formatting + LOGIT_PRINTF_INFO("%.2f %d", someFloat, someInt); + // Demonstrating formatted logging for homogeneous variables LOGIT_FORMAT_INFO("%.2f", someFloat, 654.321f); // Two float values LOGIT_FORMAT_INFO("%.4d", someInt, 999); // Two int values diff --git a/include/logit_cpp/logit/LogMacros.hpp b/include/logit_cpp/logit/LogMacros.hpp index 4de9daa..1df27e2 100644 --- a/include/logit_cpp/logit/LogMacros.hpp +++ b/include/logit_cpp/logit/LogMacros.hpp @@ -128,16 +128,19 @@ static_assert(LOGIT_LEVEL_FATAL == static_cast(logit::LogLevel::LOG_LVL_FAT #define LOGIT_DETAIL_SCOPE_T(level, threshold_ms, phase) \ ::logit::detail::ScopeTimer LOGIT_CONCAT(_logit_scope_, __COUNTER__)(level, (phase), __FILE__, __LINE__, LOGIT_FUNCTION, -1, (threshold_ms)) +#define LOGIT_DETAIL_SCOPE_PRINTF(level, fmt_str, ...) \ + ::logit::detail::ScopeTimer LOGIT_CONCAT(_logit_scope_, __COUNTER__)(level, logit::format(fmt_str, __VA_ARGS__), __FILE__, __LINE__, LOGIT_FUNCTION, -1, 0) +#define LOGIT_DETAIL_SCOPE_PRINTF_T(level, threshold_ms, fmt_str, ...) \ + ::logit::detail::ScopeTimer LOGIT_CONCAT(_logit_scope_, __COUNTER__)(level, logit::format(fmt_str, __VA_ARGS__), __FILE__, __LINE__, LOGIT_FUNCTION, -1, (threshold_ms)) + #ifdef LOGIT_WITH_FMT #define LOGIT_DETAIL_SCOPE_FMT(level, fmt_str, ...) \ ::logit::detail::ScopeTimer LOGIT_CONCAT(_logit_scope_, __COUNTER__)(level, fmt::format(fmt_str, __VA_ARGS__), __FILE__, __LINE__, LOGIT_FUNCTION, -1, 0) #define LOGIT_DETAIL_SCOPE_FMT_T(level, threshold_ms, fmt_str, ...) \ ::logit::detail::ScopeTimer LOGIT_CONCAT(_logit_scope_, __COUNTER__)(level, fmt::format(fmt_str, __VA_ARGS__), __FILE__, __LINE__, LOGIT_FUNCTION, -1, (threshold_ms)) #else -#define LOGIT_DETAIL_SCOPE_FMT(level, fmt_str, ...) \ - ::logit::detail::ScopeTimer LOGIT_CONCAT(_logit_scope_, __COUNTER__)(level, logit::format(fmt_str, __VA_ARGS__), __FILE__, __LINE__, LOGIT_FUNCTION, -1, 0) -#define LOGIT_DETAIL_SCOPE_FMT_T(level, threshold_ms, fmt_str, ...) \ - ::logit::detail::ScopeTimer LOGIT_CONCAT(_logit_scope_, __COUNTER__)(level, logit::format(fmt_str, __VA_ARGS__), __FILE__, __LINE__, LOGIT_FUNCTION, -1, (threshold_ms)) +#define LOGIT_DETAIL_SCOPE_FMT(level, fmt_str, ...) do { } while (0) +#define LOGIT_DETAIL_SCOPE_FMT_T(level, threshold_ms, fmt_str, ...) do { } while (0) #endif #if LOGIT_COMPILED_LEVEL <= LOGIT_LEVEL_TRACE @@ -194,6 +197,66 @@ static_assert(LOGIT_LEVEL_FATAL == static_cast(logit::LogLevel::LOG_LVL_FAT #define LOGIT_SCOPE_FATAL_T(threshold_ms, phase) do { } while (0) #endif +#if LOGIT_COMPILED_LEVEL <= LOGIT_LEVEL_TRACE +#define LOGIT_SCOPE_PRINTF_TRACE(fmt_str, ...) \ + LOGIT_DETAIL_SCOPE_PRINTF(::logit::LogLevel::LOG_LVL_TRACE, fmt_str, __VA_ARGS__) +#define LOGIT_SCOPE_PRINTF_TRACE_T(threshold_ms, fmt_str, ...) \ + LOGIT_DETAIL_SCOPE_PRINTF_T(::logit::LogLevel::LOG_LVL_TRACE, threshold_ms, fmt_str, __VA_ARGS__) +#else +#define LOGIT_SCOPE_PRINTF_TRACE(fmt_str, ...) do { } while (0) +#define LOGIT_SCOPE_PRINTF_TRACE_T(threshold_ms, fmt_str, ...) do { } while (0) +#endif + +#if LOGIT_COMPILED_LEVEL <= LOGIT_LEVEL_DEBUG +#define LOGIT_SCOPE_PRINTF_DEBUG(fmt_str, ...) \ + LOGIT_DETAIL_SCOPE_PRINTF(::logit::LogLevel::LOG_LVL_DEBUG, fmt_str, __VA_ARGS__) +#define LOGIT_SCOPE_PRINTF_DEBUG_T(threshold_ms, fmt_str, ...) \ + LOGIT_DETAIL_SCOPE_PRINTF_T(::logit::LogLevel::LOG_LVL_DEBUG, threshold_ms, fmt_str, __VA_ARGS__) +#else +#define LOGIT_SCOPE_PRINTF_DEBUG(fmt_str, ...) do { } while (0) +#define LOGIT_SCOPE_PRINTF_DEBUG_T(threshold_ms, fmt_str, ...) do { } while (0) +#endif + +#if LOGIT_COMPILED_LEVEL <= LOGIT_LEVEL_INFO +#define LOGIT_SCOPE_PRINTF_INFO(fmt_str, ...) \ + LOGIT_DETAIL_SCOPE_PRINTF(::logit::LogLevel::LOG_LVL_INFO, fmt_str, __VA_ARGS__) +#define LOGIT_SCOPE_PRINTF_INFO_T(threshold_ms, fmt_str, ...) \ + LOGIT_DETAIL_SCOPE_PRINTF_T(::logit::LogLevel::LOG_LVL_INFO, threshold_ms, fmt_str, __VA_ARGS__) +#else +#define LOGIT_SCOPE_PRINTF_INFO(fmt_str, ...) do { } while (0) +#define LOGIT_SCOPE_PRINTF_INFO_T(threshold_ms, fmt_str, ...) do { } while (0) +#endif + +#if LOGIT_COMPILED_LEVEL <= LOGIT_LEVEL_WARN +#define LOGIT_SCOPE_PRINTF_WARN(fmt_str, ...) \ + LOGIT_DETAIL_SCOPE_PRINTF(::logit::LogLevel::LOG_LVL_WARN, fmt_str, __VA_ARGS__) +#define LOGIT_SCOPE_PRINTF_WARN_T(threshold_ms, fmt_str, ...) \ + LOGIT_DETAIL_SCOPE_PRINTF_T(::logit::LogLevel::LOG_LVL_WARN, threshold_ms, fmt_str, __VA_ARGS__) +#else +#define LOGIT_SCOPE_PRINTF_WARN(fmt_str, ...) do { } while (0) +#define LOGIT_SCOPE_PRINTF_WARN_T(threshold_ms, fmt_str, ...) do { } while (0) +#endif + +#if LOGIT_COMPILED_LEVEL <= LOGIT_LEVEL_ERROR +#define LOGIT_SCOPE_PRINTF_ERROR(fmt_str, ...) \ + LOGIT_DETAIL_SCOPE_PRINTF(::logit::LogLevel::LOG_LVL_ERROR, fmt_str, __VA_ARGS__) +#define LOGIT_SCOPE_PRINTF_ERROR_T(threshold_ms, fmt_str, ...) \ + LOGIT_DETAIL_SCOPE_PRINTF_T(::logit::LogLevel::LOG_LVL_ERROR, threshold_ms, fmt_str, __VA_ARGS__) +#else +#define LOGIT_SCOPE_PRINTF_ERROR(fmt_str, ...) do { } while (0) +#define LOGIT_SCOPE_PRINTF_ERROR_T(threshold_ms, fmt_str, ...) do { } while (0) +#endif + +#if LOGIT_COMPILED_LEVEL <= LOGIT_LEVEL_FATAL +#define LOGIT_SCOPE_PRINTF_FATAL(fmt_str, ...) \ + LOGIT_DETAIL_SCOPE_PRINTF(::logit::LogLevel::LOG_LVL_FATAL, fmt_str, __VA_ARGS__) +#define LOGIT_SCOPE_PRINTF_FATAL_T(threshold_ms, fmt_str, ...) \ + LOGIT_DETAIL_SCOPE_PRINTF_T(::logit::LogLevel::LOG_LVL_FATAL, threshold_ms, fmt_str, __VA_ARGS__) +#else +#define LOGIT_SCOPE_PRINTF_FATAL(fmt_str, ...) do { } while (0) +#define LOGIT_SCOPE_PRINTF_FATAL_T(threshold_ms, fmt_str, ...) do { } while (0) +#endif + #if LOGIT_COMPILED_LEVEL <= LOGIT_LEVEL_TRACE #define LOGIT_SCOPE_FMT_TRACE(fmt_str, ...) \ LOGIT_DETAIL_SCOPE_FMT(::logit::LogLevel::LOG_LVL_TRACE, fmt_str, __VA_ARGS__) diff --git a/include/logit_cpp/logit/utils/format.hpp b/include/logit_cpp/logit/utils/format.hpp index 8ad1b8b..cb35e06 100644 --- a/include/logit_cpp/logit/utils/format.hpp +++ b/include/logit_cpp/logit/utils/format.hpp @@ -9,30 +9,17 @@ #include #include -#ifdef LOGIT_USE_FMT_LIB -#include -#endif - namespace logit { /// \brief Formats a string according to the specified format. /// - /// This function formats a string using either the custom implementation - /// based on `vsnprintf`) or the `fmt` library, depending on whether - /// the macro `LOGIT_USE_FMT_LIB` is defined. + /// This function uses a `vsnprintf`-based implementation. /// /// \param fmt The format string (similar to printf format). /// \param ... A variable number of arguments matching the format string. /// \see https://habr.com/ru/articles/318962/ /// \return A formatted std::string. inline std::string format(const char *fmt, ...) { -# ifdef LOGIT_USE_FMT_LIB - va_list args; - va_start(args, fmt); - std::string result = fmt::vformat(fmt, fmt::make_format_args(args)); - va_end(args); - return result; -# else va_list args; va_start(args, fmt); std::vector buffer(1024); @@ -52,7 +39,6 @@ namespace logit { buffer.clear(); buffer.resize(size); } -# endif } }; // namespace logit diff --git a/tests/compiled_level_test.cpp b/tests/compiled_level_test.cpp index 313dab4..870a77e 100644 --- a/tests/compiled_level_test.cpp +++ b/tests/compiled_level_test.cpp @@ -43,6 +43,8 @@ int main() { LOGIT_SCOPE_TRACE_T(0, should_not_compile()); LOGIT_SCOPE_FMT_TRACE("{}", should_not_compile()); LOGIT_SCOPE_FMT_TRACE_T(0, "{}", should_not_compile()); + LOGIT_SCOPE_PRINTF_TRACE("%d", should_not_compile()); + LOGIT_SCOPE_PRINTF_TRACE_T(0, "%d", should_not_compile()); // DEBUG macros LOGIT_DEBUG(should_not_compile()); @@ -79,5 +81,7 @@ int main() { LOGIT_SCOPE_DEBUG_T(0, should_not_compile()); LOGIT_SCOPE_FMT_DEBUG("{}", should_not_compile()); LOGIT_SCOPE_FMT_DEBUG_T(0, "{}", should_not_compile()); + LOGIT_SCOPE_PRINTF_DEBUG("%d", should_not_compile()); + LOGIT_SCOPE_PRINTF_DEBUG_T(0, "%d", should_not_compile()); return 0; } diff --git a/tests/printf_format_macros_test.cpp b/tests/printf_format_macros_test.cpp new file mode 100644 index 0000000..f36a691 --- /dev/null +++ b/tests/printf_format_macros_test.cpp @@ -0,0 +1,23 @@ +#define LOGIT_FILE_LOGGER_PATH "." +#include +#include +#include + +int main() { + LOGIT_ADD_FILE_LOGGER_DEFAULT(); + float f = 123.456f; + int i = 789; + LOGIT_PRINTF_INFO("%.2f %d", f, i); + LOGIT_FORMAT_INFO("%.1f", f, 654.321f); + LOGIT_WAIT(); + std::ifstream in(LOGIT_GET_LAST_FILE_PATH(0)); + std::string line; + bool found_printf = false; + bool found_format = false; + while (std::getline(in, line)) { + if (line.find("123.46 789") != std::string::npos) found_printf = true; + if (line.find("f: 123.5, 654.3") != std::string::npos) found_format = true; + } + LOGIT_SHUTDOWN(); + return (found_printf && found_format) ? 0 : 1; +}