diff --git a/CHANGELOG.md b/CHANGELOG.md index b4096378..e0ddb17c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +- [v8.0.0](#v800) - [v7.5.0](#v750) - [v7.4.0](#v740) - [v7.3.0](#v730) @@ -80,6 +81,21 @@ - [v1.1.0](#v110) - [v1.0.0](#v100) +## v8.0.0 + +- Unified `JsonFileSink.h` and `JsonConsoleSink.h` into a single header, `JsonSink.h`, with both classes now sharing a + common implementation +- Users can now inherit from `JsonFileSink` or `JsonConsoleSink` and override the `generate_json_message(...)` function + to implement their own custom JSON log formats +- Removed `JsonFileSinkConfig`. Please rename it to `FileSinkConfig`, which retains the same API and is fully + compatible. +- Added `RotatingJsonFileSink`. Functions like `RotatingFileSink`, but specifically designed for rotating JSON log + files. +- Simplified `ConsoleSink` by applying ANSI colour codes universally across all platforms, including Windows. The + previous Windows-specific implementation has been removed. Note that `quill::ConsoleColours` has been replaced with + `quill::ConsoleSink::Colours`, and `quill::ConsoleColours::ColourMode` has been renamed to + `quill::ConsoleSink::ColourMode`. + ## TBD - Suppress `-Wredundant-decls` warning in GCC builds. @@ -112,7 +128,7 @@ - Fixed a build issue when compiling with `-fno-rtti`. This ensures compatibility with projects that disable `RTTI`. ([#604](https://github.com/odygrd/quill/issues/604)) - Fixed an incorrectly triggered assertion in debug builds when `BackendOptions::log_timestamp_ordering_grace_period` is - set to 0. ([#605](https://github.com/odygrd/quill/issues/605)) + set to 0 ([#605](https://github.com/odygrd/quill/issues/605)) - Fixed a compile-time error in `CsvWriter` that occurred when passing a custom `FrontendOptions` type as a template parameter. ([#609](https://github.com/odygrd/quill/issues/609)) - Added accessors to `Logger` for sinks, user clock source, clock source type, and pattern formatter options that can be @@ -125,6 +141,7 @@ ```cpp quill::Frontend::create_or_get_sink( "sink_id_1", quill::ConsoleColours::ColourMode::Automatic); + ``` ## v7.3.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ad89de8..5bff1123 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -216,10 +216,11 @@ set(HEADER_FILES include/quill/sinks/ConsoleSink.h include/quill/sinks/FileSink.h - include/quill/sinks/JsonConsoleSink.h - include/quill/sinks/JsonFileSink.h + include/quill/sinks/JsonSink.h include/quill/sinks/NullSink.h include/quill/sinks/RotatingFileSink.h + include/quill/sinks/RotatingJsonFileSink.h + include/quill/sinks/RotatingSink.h include/quill/sinks/Sink.h include/quill/sinks/StreamSink.h diff --git a/MODULE.bazel b/MODULE.bazel index ecae93a8..aad5ff3b 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,6 +1,6 @@ module( name = "quill", - version = "7.5.0", + version = "8.0.0", compatibility_level = 1, ) diff --git a/docs/conf.py b/docs/conf.py index 27b9bb0d..a97eabd3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -35,7 +35,7 @@ def configureDoxyfile(input_dir, output_dir): project = 'Quill' copyright = '2024, Odysseas Georgoudis' author = 'Odysseas Georgoudis' -release = 'v7.5.0' +release = 'v8.0.0' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/docs/json_logging.rst b/docs/json_logging.rst index bef1d34e..1d5445ab 100644 --- a/docs/json_logging.rst +++ b/docs/json_logging.rst @@ -18,7 +18,7 @@ Logging Json to Console #include "quill/Frontend.h" #include "quill/LogMacros.h" #include "quill/Logger.h" - #include "quill/sinks/JsonConsoleSink.h" + #include "quill/sinks/JsonSink.h" #include int main() @@ -28,7 +28,8 @@ Logging Json to Console auto json_sink = quill::Frontend::create_or_get_sink("json_sink_1"); - // When logging json, it is ideal to set the logging pattern to empty to avoid unnecessary message formatting. + // PatternFormatter is only used for non-structured logs formatting + // When logging only json, it is ideal to set the logging pattern to empty to avoid unnecessary message formatting. quill::Logger* logger = quill::Frontend::create_or_get_logger( "json_logger", std::move(json_sink), quill::PatternFormatterOptions { "", "%H:%M:%S.%Qns", quill::Timezone::GmtTime }); @@ -51,7 +52,7 @@ Logging Json to File #include "quill/Frontend.h" #include "quill/LogMacros.h" #include "quill/Logger.h" - #include "quill/sinks/JsonFileSink.h" + #include "quill/sinks/JsonSink.h" #include int main() @@ -68,7 +69,8 @@ Logging Json to File return config; }()); - // When logging json, it is ideal to set the logging pattern to empty to avoid unnecessary message formatting. + // PatternFormatter is only used for non-structured logs formatting + // When logging only json, it is ideal to set the logging pattern to empty to avoid unnecessary message formatting. quill::Logger* logger = quill::Frontend::create_or_get_logger( "json_logger", std::move(json_sink), quill::PatternFormatterOptions { "", "%H:%M:%S.%Qns", quill::Timezone::GmtTime }); @@ -82,6 +84,74 @@ Logging Json to File LOG_INFO(logger, "A json message with {var_1} and {var_2}", var_a, var_b); } +Customising Json Format +----------------------- + +To customize the JSON format, define a custom sink that derives from one of the following classes: + +- :cpp:class:`JsonFileSink` +- :cpp:class:`JsonConsoleSink` +- :cpp:class:`RotatingJsonFileSink` + +.. code:: cpp + + #include "quill/Backend.h" + #include "quill/Frontend.h" + #include "quill/LogMacros.h" + #include "quill/Logger.h" + #include "quill/sinks/JsonSink.h" + + class MyJsonConsoleSink : public quill::JsonConsoleSink + { + void generate_json_message(quill::MacroMetadata const* /** log_metadata **/, uint64_t log_timestamp, + std::string_view /** thread_id **/, std::string_view /** thread_name **/, + std::string const& /** process_id **/, std::string_view /** logger_name **/, + quill::LogLevel /** log_level **/, std::string_view log_level_description, + std::string_view /** log_level_short_code **/, + std::vector> const* named_args, + std::string_view /** log_message **/, + std::string_view /** log_statement **/, char const* message_format) override + { + // format json as desired + _json_message.append(fmtquill::format(R"({{"timestamp":"{}","log_level":"{}","message":"{}")", + std::to_string(log_timestamp), log_level_description, message_format)); + + // add log statement arguments as key-values to the json + if (named_args) + { + for (auto const& [key, value] : *named_args) + { + _json_message.append(std::string_view{",\""}); + _json_message.append(key); + _json_message.append(std::string_view{"\":\""}); + _json_message.append(value); + _json_message.append(std::string_view{"\""}); + } + } + } + }; + + int main() + { + // Start the backend thread + quill::BackendOptions backend_options; + quill::Backend::start(backend_options); + + // Frontend + auto json_sink = quill::Frontend::create_or_get_sink("json_sink_1"); + + // PatternFormatter is only used for non-structured logs formatting + // When logging only json, it is ideal to set the logging pattern to empty to avoid unnecessary message formatting. + quill::Logger* logger = quill::Frontend::create_or_get_logger( + "json_logger", std::move(json_sink), + quill::PatternFormatterOptions{"", "%H:%M:%S.%Qns", quill::Timezone::GmtTime}); + + int var_a = 123; + std::string var_b = "test"; + + LOG_INFO(logger, "A json message with {var_1} and {var_2}", var_a, var_b); + } + Combining JSON and Standard Log Patterns ---------------------------------------- @@ -92,7 +162,7 @@ Combining JSON and Standard Log Patterns #include "quill/LogMacros.h" #include "quill/Logger.h" #include "quill/sinks/ConsoleSink.h" - #include "quill/sinks/JsonFileSink.h" + #include "quill/sinks/JsonSink.h" #include int main() @@ -105,7 +175,7 @@ Combining JSON and Standard Log Patterns "example_json.log", []() { - quill::JsonFileSinkConfig cfg; + quill::FileSinkConfig cfg; cfg.set_open_mode('w'); cfg.set_filename_append_option(quill::FilenameAppendOption::None); return cfg; diff --git a/docs/sink_types.rst b/docs/sink_types.rst index f10e1708..64239279 100644 --- a/docs/sink_types.rst +++ b/docs/sink_types.rst @@ -89,7 +89,7 @@ The :cpp:class:`JsonFileSink` and :cpp:class:`JsonConsoleSink` enable the creati "json_sink_logging.log", []() { - quill::JsonFileSinkConfig cfg; + quill::FileSinkConfig cfg; cfg.set_open_mode('w'); cfg.set_filename_append_option(quill::FilenameAppendOption::StartDateTime); return cfg; @@ -119,4 +119,9 @@ The :cpp:class:`JsonFileSink` and :cpp:class:`JsonConsoleSink` enable the creati for (int i = 0; i < 2; ++i) { LOG_INFO(hybrid_logger, "{method} to {endpoint} took {elapsed} ms", "POST", "http://", 10 * i); - } \ No newline at end of file + } + +RotatingJsonFileSink +~~~~~~~~~~~~~~~~~~~~ + +The :cpp:class:`RotatingJsonFileSink` is built on top of the `JsonFileSink` and provides log file rotation based on specified time intervals, file sizes, or daily schedules. \ No newline at end of file diff --git a/docs/users-api.rst b/docs/users-api.rst index 8548d40a..1604cd2c 100644 --- a/docs/users-api.rst +++ b/docs/users-api.rst @@ -86,6 +86,12 @@ FileSink Class .. doxygenclass:: FileSink :members: +RotatingSink Class +---------------------------- + +.. doxygenclass:: RotatingSink + :members: + RotatingFileSinkConfig Class ---------------------------- @@ -110,6 +116,12 @@ JsonConsoleSink Class .. doxygenclass:: JsonConsoleSink :members: +RotatingJsonFileSink Class +---------------------------- + +.. doxygenclass:: RotatingJsonFileSink + :members: + CsvWriter Class --------------- diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 62e5c74e..3aed8647 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -12,6 +12,8 @@ set(EXAMPLE_TARGETS quill_example_console_logging quill_example_custom_console_colours quill_example_rotating_file_logging + quill_example_rotating_json_file_logging + quill_example_rotating_json_file_logging_custom_json quill_example_signal_handler quill_example_logger_removal_with_file_event_notifier quill_example_custom_frontend_options @@ -23,6 +25,7 @@ set(EXAMPLE_TARGETS quill_example_user_defined_sink quill_example_tags_logging quill_example_json_console_logging + quill_example_json_console_logging_custom_json quill_example_csv_writing quill_example_json_file_logging quill_example_user_defined_types_logging diff --git a/examples/advanced/quill_wrapper/include/quill_wrapper/quill_wrapper.cpp b/examples/advanced/quill_wrapper/include/quill_wrapper/quill_wrapper.cpp index 1fe08c44..b37569d0 100644 --- a/examples/advanced/quill_wrapper/include/quill_wrapper/quill_wrapper.cpp +++ b/examples/advanced/quill_wrapper/include/quill_wrapper/quill_wrapper.cpp @@ -17,6 +17,6 @@ void setup_quill(char const* log_file) quill::Frontend::create_or_get_logger( "root", std::move(console_sink), quill::PatternFormatterOptions{"%(time) [%(thread_id)] %(short_source_location:<28) " - "LOG_%(log_level:<9) %(logger:<12) %(message)", + "LOG_%(log_level:<9) %(logger:<12) %(message)", "%H:%M:%S.%Qns", quill::Timezone::GmtTime}); } \ No newline at end of file diff --git a/examples/backend_tsc_clock.cpp b/examples/backend_tsc_clock.cpp index e9b8827c..5cff4d42 100644 --- a/examples/backend_tsc_clock.cpp +++ b/examples/backend_tsc_clock.cpp @@ -52,7 +52,7 @@ int main() auto const tsc_start_seconds = std::chrono::duration_cast( quill::BackendTscClock::to_time_point(tsc_start).time_since_epoch()) .count(); - + auto const tsc_end_seconds = std::chrono::duration_cast( quill::BackendTscClock::to_time_point(tsc_end).time_since_epoch()) .count(); diff --git a/examples/console_logging.cpp b/examples/console_logging.cpp index 8f9053c4..a6b283f9 100644 --- a/examples/console_logging.cpp +++ b/examples/console_logging.cpp @@ -25,7 +25,7 @@ int main() // Frontend auto console_sink = quill::Frontend::create_or_get_sink( - "sink_id_1", quill::ConsoleColours::ColourMode::Automatic); + "sink_id_1", quill::ConsoleSink::ColourMode::Automatic); quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(console_sink)); diff --git a/examples/custom_console_colours.cpp b/examples/custom_console_colours.cpp index 5de62869..1bb4af8e 100644 --- a/examples/custom_console_colours.cpp +++ b/examples/custom_console_colours.cpp @@ -18,13 +18,12 @@ int main() quill::Backend::start(backend_options); // Frontend - quill::ConsoleColours custom_console_colours; - custom_console_colours.set_default_colours(); - custom_console_colours.set_colour(quill::LogLevel::Info, quill::ConsoleColours::blue); // overwrite the colour for INFO + quill::ConsoleSink::Colours colours; + colours.apply_default_colours(); + colours.assign_colour_to_log_level(quill::LogLevel::Info, quill::ConsoleSink::Colours::blue); // overwrite the colour for INFO // Create the sink - auto console_sink = - quill::Frontend::create_or_get_sink("sink_id_1", custom_console_colours); + auto console_sink = quill::Frontend::create_or_get_sink("sink_id_1", colours); quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(console_sink)); diff --git a/examples/file_logging.cpp b/examples/file_logging.cpp index 8545be02..4f1dcd0c 100644 --- a/examples/file_logging.cpp +++ b/examples/file_logging.cpp @@ -47,7 +47,7 @@ int main() quill::Logger* logger = quill::Frontend::create_or_get_logger( "root", std::move(file_sink), quill::PatternFormatterOptions{"%(time) [%(thread_id)] %(short_source_location:<28) " - "LOG_%(log_level:<9) %(logger:<12) %(message)", + "LOG_%(log_level:<9) %(logger:<12) %(message)", "%H:%M:%S.%Qns", quill::Timezone::GmtTime}); // set the log level of the logger to debug (default is info) diff --git a/examples/json_console_logging.cpp b/examples/json_console_logging.cpp index 1cb0420f..c12c19e1 100644 --- a/examples/json_console_logging.cpp +++ b/examples/json_console_logging.cpp @@ -2,7 +2,7 @@ #include "quill/Frontend.h" #include "quill/LogMacros.h" #include "quill/Logger.h" -#include "quill/sinks/JsonConsoleSink.h" +#include "quill/sinks/JsonSink.h" #include @@ -16,8 +16,9 @@ int main() // Create a json sink auto json_sink = quill::Frontend::create_or_get_sink("json_sink_1"); - - // When logging json, it is ideal to set the logging pattern to empty to avoid unnecessary message formatting. + + // PatternFormatter is only used for non-structured logs formatting + // When logging only json, it is ideal to set the logging pattern to empty to avoid unnecessary message formatting. quill::Logger* logger = quill::Frontend::create_or_get_logger( "json_logger", std::move(json_sink), quill::PatternFormatterOptions{"", "%H:%M:%S.%Qns", quill::Timezone::GmtTime}); diff --git a/examples/json_console_logging_custom_json.cpp b/examples/json_console_logging_custom_json.cpp new file mode 100644 index 00000000..d630b703 --- /dev/null +++ b/examples/json_console_logging_custom_json.cpp @@ -0,0 +1,67 @@ +#include "quill/Backend.h" +#include "quill/Frontend.h" +#include "quill/LogMacros.h" +#include "quill/Logger.h" +#include "quill/sinks/JsonSink.h" + +#include + +/** + * Overrides generate_json_message to use a custom json format + */ +class MyJsonConsoleSink : public quill::JsonConsoleSink +{ + void generate_json_message(quill::MacroMetadata const* /** log_metadata **/, uint64_t log_timestamp, + std::string_view /** thread_id **/, std::string_view /** thread_name **/, + std::string const& /** process_id **/, std::string_view /** logger_name **/, + quill::LogLevel /** log_level **/, std::string_view log_level_description, + std::string_view /** log_level_short_code **/, + std::vector> const* named_args, + std::string_view /** log_message **/, + std::string_view /** log_statement **/, char const* message_format) override + { + // format json as desired + _json_message.append(fmtquill::format(R"({{"timestamp":"{}","log_level":"{}","message":"{}")", + std::to_string(log_timestamp), log_level_description, message_format)); + + // add log statement arguments as key-values to the json + if (named_args) + { + for (auto const& [key, value] : *named_args) + { + _json_message.append(std::string_view{",\""}); + _json_message.append(key); + _json_message.append(std::string_view{"\":\""}); + _json_message.append(value); + _json_message.append(std::string_view{"\""}); + } + } + } +}; + +int main() +{ + // Start the backend thread + quill::BackendOptions backend_options; + quill::Backend::start(backend_options); + + // Frontend + + // Create a json sink + auto json_sink = quill::Frontend::create_or_get_sink("json_sink_1"); + + // PatternFormatter is only used for non-structured logs formatting + // When logging only json, it is ideal to set the logging pattern to empty to avoid unnecessary message formatting. + quill::Logger* logger = quill::Frontend::create_or_get_logger( + "json_logger", std::move(json_sink), + quill::PatternFormatterOptions{"", "%H:%M:%S.%Qns", quill::Timezone::GmtTime}); + + int var_a = 123; + std::string var_b = "test"; + + // Log via the convenient LOGJ_ macros + LOGJ_INFO(logger, "A json message", var_a, var_b); + + // Or manually specify the desired names of each variable + LOG_INFO(logger, "A json message with {var_1} and {var_2}", var_a, var_b); +} diff --git a/examples/json_file_logging.cpp b/examples/json_file_logging.cpp index bb804f60..ccc0723f 100644 --- a/examples/json_file_logging.cpp +++ b/examples/json_file_logging.cpp @@ -3,7 +3,7 @@ #include "quill/LogMacros.h" #include "quill/Logger.h" #include "quill/sinks/ConsoleSink.h" -#include "quill/sinks/JsonFileSink.h" +#include "quill/sinks/JsonSink.h" #include @@ -29,7 +29,7 @@ int main() "example_json.log", []() { - quill::JsonFileSinkConfig cfg; + quill::FileSinkConfig cfg; cfg.set_open_mode('w'); cfg.set_filename_append_option(quill::FilenameAppendOption::None); return cfg; diff --git a/examples/recommended_usage/quill_wrapper/include/quill_wrapper/quill_wrapper.cpp b/examples/recommended_usage/quill_wrapper/include/quill_wrapper/quill_wrapper.cpp index de8e9f21..8200b5de 100644 --- a/examples/recommended_usage/quill_wrapper/include/quill_wrapper/quill_wrapper.cpp +++ b/examples/recommended_usage/quill_wrapper/include/quill_wrapper/quill_wrapper.cpp @@ -30,6 +30,6 @@ void setup_quill(char const* log_file) global_logger_a = quill::Frontend::create_or_get_logger( "root", std::move(file_sink), quill::PatternFormatterOptions{"%(time) [%(thread_id)] %(short_source_location:<28) " - "LOG_%(log_level:<9) %(logger:<12) %(message)", + "LOG_%(log_level:<9) %(logger:<12) %(message)", "%H:%M:%S.%Qns", quill::Timezone::GmtTime}); } \ No newline at end of file diff --git a/examples/rotating_file_logging.cpp b/examples/rotating_file_logging.cpp index 4fcbfbec..22707d28 100644 --- a/examples/rotating_file_logging.cpp +++ b/examples/rotating_file_logging.cpp @@ -34,7 +34,7 @@ int main() quill::Logger* logger = quill::Frontend::create_or_get_logger( "root", std::move(rotating_file_sink), quill::PatternFormatterOptions{"%(time) [%(thread_id)] %(short_source_location:<28) " - "LOG_%(log_level:<9) %(logger:<12) %(message)", + "LOG_%(log_level:<9) %(logger:<12) %(message)", "%H:%M:%S.%Qns", quill::Timezone::GmtTime}); for (int i = 0; i < 20; ++i) diff --git a/examples/rotating_json_file_logging.cpp b/examples/rotating_json_file_logging.cpp new file mode 100644 index 00000000..c92750c7 --- /dev/null +++ b/examples/rotating_json_file_logging.cpp @@ -0,0 +1,44 @@ +#include "quill/Backend.h" +#include "quill/Frontend.h" +#include "quill/LogMacros.h" +#include "quill/Logger.h" +#include "quill/sinks/RotatingJsonFileSink.h" + +#include + +/** + * This example demonstrates how to create a RotatingFileSink with daily rotation and automatic rotation based on maximum file size. + * For additional configuration options, refer to RotatingFileSinkConfig. + */ + +int main() +{ + // Start the backend thread + quill::BackendOptions backend_options; + quill::Backend::start(backend_options); + + // Frontend + auto rotating_json_sink = quill::Frontend::create_or_get_sink( + "rotating_json.log", + []() + { + // See RotatingFileSinkConfig for more options + quill::RotatingFileSinkConfig cfg; + cfg.set_open_mode('w'); + cfg.set_filename_append_option(quill::FilenameAppendOption::StartDateTime); + cfg.set_rotation_time_daily("18:30"); + cfg.set_rotation_max_file_size(1024); // small value to demonstrate the example + return cfg; + }()); + + quill::Logger* logger = quill::Frontend::create_or_get_logger( + "root", std::move(rotating_json_sink), + quill::PatternFormatterOptions{"%(time) [%(thread_id)] %(short_source_location:<28) " + "LOG_%(log_level:<9) %(logger:<12) %(message)", + "%H:%M:%S.%Qns", quill::Timezone::GmtTime}); + + for (int i = 0; i < 20; ++i) + { + LOG_INFO(logger, "Hello from rotating logger {index}", i); + } +} diff --git a/examples/rotating_json_file_logging_custom_json.cpp b/examples/rotating_json_file_logging_custom_json.cpp new file mode 100644 index 00000000..27e20de0 --- /dev/null +++ b/examples/rotating_json_file_logging_custom_json.cpp @@ -0,0 +1,91 @@ +#include "quill/Backend.h" +#include "quill/Frontend.h" +#include "quill/LogMacros.h" +#include "quill/Logger.h" +#include "quill/sinks/RotatingJsonFileSink.h" + +#include +#include + +/** + * This example demonstrates how to create a RotatingJsonFileSink with daily rotation and automatic + * rotation based on maximum file size, providing your own custom json formatting + * For additional configuration options, refer to RotatingFileSinkConfig. + */ + +class CustomJsonSink : public quill::RotatingJsonFileSink +{ +public: + using quill::RotatingJsonFileSink::RotatingJsonFileSink; + + void generate_json_message(quill::MacroMetadata const* log_metadata, uint64_t log_timestamp, + std::string_view thread_id, std::string_view /** thread_name **/, + std::string const& /** process_id **/, std::string_view logger_name, + quill::LogLevel /** log_level **/, std::string_view log_level_description, + std::string_view /** log_level_short_code **/, + std::vector> const* named_args, + std::string_view /** log_message **/, + std::string_view /** log_statement **/, char const* message_format) override + { + // e.g. custom time formatting + std::time_t log_timestamp_seconds = log_timestamp / 1'000'000'000; + + // Convert to localtime + std::tm* tm = std::localtime(&log_timestamp_seconds); + + // Format the time as YYYY-MM-DD HH:MM:SS + char formatted_time[20]; + std::strftime(formatted_time, sizeof(formatted_time), "%Y-%m-%d %H:%M:%S", tm); + + // format json as desired + _json_message.append(fmtquill::format( + R"({{"timestamp":"{}","file_name":"{}","line":"{}","thread_id":"{}","logger":"{}","log_level":"{}","message":"{}")", + formatted_time, log_metadata->file_name(), log_metadata->line(), thread_id, logger_name, + log_level_description, message_format)); + + // add log statement arguments as key-values to the json + if (named_args) + { + for (auto const& [key, value] : *named_args) + { + _json_message.append(std::string_view{",\""}); + _json_message.append(key); + _json_message.append(std::string_view{"\":\""}); + _json_message.append(value); + _json_message.append(std::string_view{"\""}); + } + } + } +}; + +int main() +{ + // Start the backend thread + quill::BackendOptions backend_options; + quill::Backend::start(backend_options); + + // Frontend + auto rotating_json_sink = quill::Frontend::create_or_get_sink( + "rotating_json.log", + []() + { + // See RotatingFileSinkConfig for more options + quill::RotatingFileSinkConfig cfg; + cfg.set_open_mode('w'); + cfg.set_filename_append_option(quill::FilenameAppendOption::StartDateTime); + cfg.set_rotation_time_daily("18:30"); + cfg.set_rotation_max_file_size(1024); // small value to demonstrate the example + return cfg; + }()); + + quill::Logger* logger = quill::Frontend::create_or_get_logger( + "root", std::move(rotating_json_sink), + quill::PatternFormatterOptions{"%(time) [%(thread_id)] %(short_source_location:<28) " + "LOG_%(log_level:<9) %(logger:<12) %(message)", + "%H:%M:%S.%Qns", quill::Timezone::GmtTime}); + + for (int i = 0; i < 20; ++i) + { + LOG_INFO(logger, "Hello from rotating logger {index}", i); + } +} diff --git a/examples/shared_library/example_shared.cpp b/examples/shared_library/example_shared.cpp index bcca7c41..4b4cf4b6 100644 --- a/examples/shared_library/example_shared.cpp +++ b/examples/shared_library/example_shared.cpp @@ -24,8 +24,8 @@ int main() // Change the LogLevel to print everything global_logger_a->set_log_level(quill::LogLevel::TraceL3); - std::string s {"string"}; - std::string_view sv {"string_view"}; + std::string s{"string"}; + std::string_view sv{"string_view"}; LOG_TRACE_L3(global_logger_a, "This is a log trace l3 example {}", 1); LOG_TRACE_L2(global_logger_a, "This is a log trace l2 example {} {}", 2, 2.3); diff --git a/examples/shared_library/quill_wrapper_shared/include/quill_wrapper_shared/quill_wrapper_shared.cpp b/examples/shared_library/quill_wrapper_shared/include/quill_wrapper_shared/quill_wrapper_shared.cpp index 9030a8d5..e84816f4 100644 --- a/examples/shared_library/quill_wrapper_shared/include/quill_wrapper_shared/quill_wrapper_shared.cpp +++ b/examples/shared_library/quill_wrapper_shared/include/quill_wrapper_shared/quill_wrapper_shared.cpp @@ -20,6 +20,6 @@ void setup_quill() global_logger_a = quill::Frontend::create_or_get_logger( "root", std::move(console_sink), quill::PatternFormatterOptions{"%(time) [%(thread_id)] %(short_source_location:<28) " - "LOG_%(log_level:<9) %(logger:<12) %(message)", + "LOG_%(log_level:<9) %(logger:<12) %(message)", "%H:%M:%S.%Qns", quill::Timezone::GmtTime}); } \ No newline at end of file diff --git a/examples/signal_handler.cpp b/examples/signal_handler.cpp index 17688e84..7b282e21 100644 --- a/examples/signal_handler.cpp +++ b/examples/signal_handler.cpp @@ -76,7 +76,7 @@ int main() } // After 10 messages Crash - Uncomment any of the below : - + // illegal_instruction(logger); // cause_segfault(logger); }); diff --git a/examples/single_logger_multiple_sink_formats/ConsoleSinkWithFormatter.h b/examples/single_logger_multiple_sink_formats/ConsoleSinkWithFormatter.h index 6a097f8c..1f624f0b 100644 --- a/examples/single_logger_multiple_sink_formats/ConsoleSinkWithFormatter.h +++ b/examples/single_logger_multiple_sink_formats/ConsoleSinkWithFormatter.h @@ -13,8 +13,9 @@ class ConsoleSinkWithFormatter : public quill::ConsoleSink { public: ConsoleSinkWithFormatter(quill::PatternFormatterOptions const& pattern_formater_options, - bool enable_colours = true, std::string const& stream = "stdout") - : quill::ConsoleSink(enable_colours, stream), _formatter(pattern_formater_options) + quill::ConsoleSink::ColourMode colour_mode = quill::ConsoleSink::ColourMode::Automatic, + std::string const& stream = "stdout") + : quill::ConsoleSink(colour_mode, stream), _formatter(pattern_formater_options) { } diff --git a/examples/single_logger_multiple_sink_formats/RotatingFileSinkWithFormatter.h b/examples/single_logger_multiple_sink_formats/RotatingFileSinkWithFormatter.h index 81d52498..cc3ce640 100644 --- a/examples/single_logger_multiple_sink_formats/RotatingFileSinkWithFormatter.h +++ b/examples/single_logger_multiple_sink_formats/RotatingFileSinkWithFormatter.h @@ -19,18 +19,19 @@ class RotatingFileSinkWithFormatter : public quill::RotatingFileSink { } - void write_log(quill::MacroMetadata const* log_metadata, uint64_t log_timestamp, std::string_view thread_id, - std::string_view thread_name, std::string const& process_id, - std::string_view logger_name, quill::LogLevel log_level, + void write_log(quill::MacroMetadata const* log_metadata, uint64_t log_timestamp, + std::string_view thread_id, std::string_view thread_name, + std::string const& process_id, std::string_view logger_name, quill::LogLevel log_level, std::string_view log_level_description, std::string_view log_level_short_code, std::vector> const* named_args, - std::string_view log_message, std::string_view) override + std::string_view log_message, std::string_view) override { std::string_view const formatted_log_statement = _formatter.format(log_timestamp, thread_id, thread_name, process_id, logger_name, log_level_description, log_level_short_code, *log_metadata, named_args, log_message); - quill::RotatingFileSink::write_log(log_metadata, log_timestamp, thread_id, thread_name, process_id, logger_name, log_level, + quill::RotatingFileSink::write_log( + log_metadata, log_timestamp, thread_id, thread_name, process_id, logger_name, log_level, log_level_description, log_level_short_code, named_args, log_message, formatted_log_statement); } diff --git a/examples/user_defined_filter.cpp b/examples/user_defined_filter.cpp index 5b44686b..7fd374b0 100644 --- a/examples/user_defined_filter.cpp +++ b/examples/user_defined_filter.cpp @@ -19,7 +19,7 @@ class UserFilter : public quill::Filter { public: - UserFilter() : quill::Filter("filter_1"){}; + UserFilter() : quill::Filter("filter_1") {}; bool filter(quill::MacroMetadata const* /** log_metadata **/, uint64_t /** log_timestamp **/, std::string_view /** thread_id **/, std::string_view /** thread_name **/, diff --git a/examples/user_defined_sink.cpp b/examples/user_defined_sink.cpp index cd4501f6..8cd662a2 100644 --- a/examples/user_defined_sink.cpp +++ b/examples/user_defined_sink.cpp @@ -15,10 +15,10 @@ * This example demonstrates how to implement a custom Sink */ -class UserSink final : public quill::Sink +class CustomSink final : public quill::Sink { public: - UserSink() = default; + CustomSink() = default; /***/ void write_log(quill::MacroMetadata const* /** log_metadata **/, uint64_t /** log_timestamp **/, @@ -78,7 +78,7 @@ int main() quill::BackendOptions backend_options; quill::Backend::start(backend_options); - auto file_sink = quill::Frontend::create_or_get_sink("sink_id_1"); + auto file_sink = quill::Frontend::create_or_get_sink("sink_id_1"); quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(file_sink)); LOG_INFO(logger, "Hello from {}", "sink example"); diff --git a/examples/user_defined_types_logging.cpp b/examples/user_defined_types_logging.cpp index 7c0b6173..013033b5 100644 --- a/examples/user_defined_types_logging.cpp +++ b/examples/user_defined_types_logging.cpp @@ -2,6 +2,7 @@ #include "quill/Frontend.h" #include "quill/LogMacros.h" #include "quill/Logger.h" +#include "quill/bundled/fmt/ostream.h" #include "quill/sinks/ConsoleSink.h" #include @@ -24,7 +25,7 @@ class User { public: User(std::string name, std::string surname, uint32_t age) - : name(std::move(name)), surname(std::move(surname)), age(age){}; + : name(std::move(name)), surname(std::move(surname)), age(age) {}; friend std::ostream& operator<<(std::ostream& os, User const& obj) { diff --git a/include/quill/Backend.h b/include/quill/Backend.h index e11a5446..77059bc0 100644 --- a/include/quill/Backend.h +++ b/include/quill/Backend.h @@ -16,14 +16,13 @@ #include #include #include -#include #include QUILL_BEGIN_NAMESPACE /** Version Info - When updating VersionMajor please also update the namespace in Attributes.h **/ -constexpr uint32_t VersionMajor{7}; -constexpr uint32_t VersionMinor{5}; +constexpr uint32_t VersionMajor{8}; +constexpr uint32_t VersionMinor{0}; constexpr uint32_t VersionPatch{0}; constexpr uint32_t Version{VersionMajor * 10000 + VersionMinor * 100 + VersionPatch}; @@ -113,21 +112,6 @@ class Backend }); } - /***/ - template - [[deprecated( - "This function is deprecated and will be removed in the next version. Use " - "start(BackendOptions, SignalHandlerOptions) instead ")]] QUILL_ATTRIBUTE_COLD static void - start_with_signal_handler(BackendOptions const& options = BackendOptions{}, - QUILL_MAYBE_UNUSED std::initializer_list const& catchable_signals = - std::initializer_list{SIGTERM, SIGINT, SIGABRT, SIGFPE, SIGILL, SIGSEGV}, - uint32_t signal_handler_timeout_seconds = 20u, - std::string const& signal_handler_logger = {}) - { - SignalHandlerOptions sho{catchable_signals, signal_handler_timeout_seconds, signal_handler_logger}; - start(options, sho); - } - /** * Stops the backend thread. * @note thread-safe diff --git a/include/quill/CsvWriter.h b/include/quill/CsvWriter.h index a676f2f6..b3ddac36 100644 --- a/include/quill/CsvWriter.h +++ b/include/quill/CsvWriter.h @@ -7,11 +7,9 @@ #include "quill/Frontend.h" #include "quill/core/Attributes.h" -#include "quill/core/LoggerBase.h" #include "quill/sinks/FileSink.h" #include "quill/sinks/Sink.h" -#include #include #include #include @@ -25,7 +23,7 @@ QUILL_BEGIN_NAMESPACE * and I/O operations are handled by the backend worker thread. * * @tparam TCsvSchema A user-defined struct specifying the CSV schema at compile-time. - * @tparam TFrontendOptions Custom frontend_t options if they are used application-wide. If no custom frontend_t options are used, then use quill::frontend_tOptions. + * @tparam TFrontendOptions Custom frontend options if they are used application-wide. If no custom frontend options are used, then use quill::FrontendOptions. * * The TCsvSchema struct should define the CSV header and format, for example: * @@ -42,7 +40,7 @@ class CsvWriter { public: using frontend_t = FrontendImpl; - + /** * Constructs a CsvWriter object that writes to a file. * diff --git a/include/quill/Frontend.h b/include/quill/Frontend.h index a4e2054b..166e957d 100644 --- a/include/quill/Frontend.h +++ b/include/quill/Frontend.h @@ -20,7 +20,6 @@ #include #include -#include #include #include #include diff --git a/include/quill/StringRef.h b/include/quill/StringRef.h index 35cad248..c280421a 100644 --- a/include/quill/StringRef.h +++ b/include/quill/StringRef.h @@ -32,10 +32,10 @@ namespace utility class StringRef { public: - explicit StringRef(std::string const& str) : _str_view(str){}; - explicit StringRef(std::string_view str) : _str_view(str){}; - explicit StringRef(char const* str) : _str_view(str, strlen(str)){}; - StringRef(char const* str, size_t size) : _str_view(str, size){}; + explicit StringRef(std::string const& str) : _str_view(str) {}; + explicit StringRef(std::string_view str) : _str_view(str) {}; + explicit StringRef(char const* str) : _str_view(str, strlen(str)) {}; + StringRef(char const* str, size_t size) : _str_view(str, size) {}; QUILL_NODISCARD std::string_view const& get_string_view() const noexcept { return _str_view; } diff --git a/include/quill/backend/BackendManager.h b/include/quill/backend/BackendManager.h index b31818ff..24506729 100644 --- a/include/quill/backend/BackendManager.h +++ b/include/quill/backend/BackendManager.h @@ -25,7 +25,7 @@ namespace detail /** * Provides access to common collection class that are used by both the frontend and the backend * components of the logging system - * There should only be only active active instance of this class which is achieved by the + * There should only be only active instance of this class which is achieved by the * LogSystemManagerSingleton */ class BackendManager diff --git a/include/quill/backend/BackendOptions.h b/include/quill/backend/BackendOptions.h index 6e330a28..cc23ca7d 100644 --- a/include/quill/backend/BackendOptions.h +++ b/include/quill/backend/BackendOptions.h @@ -205,8 +205,8 @@ struct BackendOptions * These names provide human-readable identifiers for each log level. */ std::array log_level_descriptions = { - "TRACE_L3", "TRACE_L2", "TRACE_L1", "DEBUG", "INFO", "NOTICE", "WARNING", - "ERROR", "CRITICAL", "BACKTRACE", "NONE", "DYNAMIC"}; + "TRACE_L3", "TRACE_L2", "TRACE_L1", "DEBUG", "INFO", "NOTICE", + "WARNING", "ERROR", "CRITICAL", "BACKTRACE", "NONE", "DYNAMIC"}; /** * @brief Short codes or identifiers for each log level. diff --git a/include/quill/backend/BackendWorker.h b/include/quill/backend/BackendWorker.h index 4c95fa3a..a26cc5e5 100644 --- a/include/quill/backend/BackendWorker.h +++ b/include/quill/backend/BackendWorker.h @@ -16,7 +16,6 @@ #include "quill/backend/PatternFormatter.h" #include "quill/backend/RdtscClock.h" #include "quill/backend/ThreadUtilities.h" -#include "quill/backend/TimestampFormatter.h" #include "quill/backend/TransitEvent.h" #include "quill/backend/TransitEventBuffer.h" @@ -55,7 +54,6 @@ #include #include #include -#include #include #include #include @@ -252,11 +250,11 @@ class BackendWorker #if defined(_WIN32) std::wstring const dummy = L"dummy"; - QUILL_MAYBE_UNUSED static auto encode1 = detail::utf8_encode(dummy); + QUILL_MAYBE_UNUSED static auto encode1 = utf8_encode(dummy); (void)encode1; QUILL_MAYBE_UNUSED static auto encode2 = - detail::utf8_encode(reinterpret_cast(dummy.data()), dummy.size()); + utf8_encode(reinterpret_cast(dummy.data()), dummy.size()); (void)encode2; #endif } @@ -582,7 +580,7 @@ class BackendWorker } } - detail::FormatArgsDecoder format_args_decoder; + FormatArgsDecoder format_args_decoder; std::memcpy(&format_args_decoder, read_pos, sizeof(format_args_decoder)); read_pos += sizeof(format_args_decoder); @@ -887,11 +885,11 @@ class BackendWorker // proceed after ensuring a pattern formatter exists std::string_view const log_level_description = - detail::log_level_to_string(transit_event.log_level(), _options.log_level_descriptions.data(), + log_level_to_string(transit_event.log_level(), _options.log_level_descriptions.data(), _options.log_level_descriptions.size()); std::string_view const log_level_short_code = - detail::log_level_to_string(transit_event.log_level(), _options.log_level_short_codes.data(), + log_level_to_string(transit_event.log_level(), _options.log_level_short_codes.data(), _options.log_level_short_codes.size()); if (transit_event.logger_base->pattern_formatter->get_options().add_metadata_to_multi_line_logs && diff --git a/include/quill/backend/SignalHandler.h b/include/quill/backend/SignalHandler.h index 42d66ef8..e07372ce 100644 --- a/include/quill/backend/SignalHandler.h +++ b/include/quill/backend/SignalHandler.h @@ -21,7 +21,6 @@ #include #include #include -#include #include #if defined(_WIN32) @@ -89,19 +88,19 @@ class SignalHandlerContext } /***/ - QUILL_NODISCARD LoggerBase* get_logger() noexcept + QUILL_NODISCARD static LoggerBase* get_logger() noexcept { LoggerBase* logger_base{nullptr}; - if (!SignalHandlerContext::instance().logger_name.empty()) + if (!instance().logger_name.empty()) { - logger_base = detail::LoggerManager::instance().get_logger(SignalHandlerContext::instance().logger_name); + logger_base = LoggerManager::instance().get_logger(instance().logger_name); } // This also checks if the logger was found above if (!logger_base || !logger_base->is_valid_logger()) { - logger_base = detail::LoggerManager::instance().get_valid_logger(SignalHandlerContext::excluded_logger_name_substr); + logger_base = LoggerManager::instance().get_valid_logger(excluded_logger_name_substr); } return logger_base; diff --git a/include/quill/backend/ThreadUtilities.h b/include/quill/backend/ThreadUtilities.h index d505cc7c..7ad6c993 100644 --- a/include/quill/backend/ThreadUtilities.h +++ b/include/quill/backend/ThreadUtilities.h @@ -95,9 +95,9 @@ ReturnT callRunTimeDynamicLinkedFunction(std::string const& dll_name, // Windows Server 2016, Windows 10 LTSB 2016 and Windows 10 version 1607: e.g. GetThreadDescription is only available by Run Time Dynamic Linking in KernelBase.dll. #ifdef UNICODE - HINSTANCE const hinstLibrary = LoadLibraryW(s2ws(dll_name).c_str()); + const HINSTANCE hinstLibrary = LoadLibraryW(s2ws(dll_name).c_str()); #else - HINSTANCE const hinstLibrary = LoadLibraryA(dll_name.c_str()); + const HINSTANCE hinstLibrary = LoadLibraryA(dll_name.c_str()); #endif if (QUILL_UNLIKELY(hinstLibrary == nullptr)) diff --git a/include/quill/backend/TimestampFormatter.h b/include/quill/backend/TimestampFormatter.h index e49e6f02..fb11a55c 100644 --- a/include/quill/backend/TimestampFormatter.h +++ b/include/quill/backend/TimestampFormatter.h @@ -34,7 +34,7 @@ namespace detail * 2) %Qus - Microseconds * 3) %Qns - Nanoseconds * @note %Qms, %Qus, %Qns specifiers are mutually exclusive - * e.g given : "%I:%M.%Qms%p" the output would be "03:21.343PM" + * e.g. given : "%I:%M.%Qms%p" the output would be "03:21.343PM" */ class TimestampFormatter { diff --git a/include/quill/backend/TransitEvent.h b/include/quill/backend/TransitEvent.h index bbe3923d..25b768dd 100644 --- a/include/quill/backend/TransitEvent.h +++ b/include/quill/backend/TransitEvent.h @@ -32,7 +32,7 @@ class LoggerBase; struct TransitEvent { using FormatBuffer = fmtquill::basic_memory_buffer; - + /***/ TransitEvent() = default; @@ -87,7 +87,7 @@ struct TransitEvent uint64_t timestamp{0}; MacroMetadata const* macro_metadata{nullptr}; - detail::LoggerBase* logger_base{nullptr}; + LoggerBase* logger_base{nullptr}; std::unique_ptr formatted_msg{std::make_unique()}; /** buffer for message **/ std::unique_ptr>> named_args; /** A unique ptr to save space as named args feature is not always used */ std::atomic* flush_flag{nullptr}; /** This is only used in the case of Event::Flush **/ diff --git a/include/quill/backend/TransitEventBuffer.h b/include/quill/backend/TransitEventBuffer.h index c86ef38f..be1987d1 100644 --- a/include/quill/backend/TransitEventBuffer.h +++ b/include/quill/backend/TransitEventBuffer.h @@ -11,10 +11,6 @@ #include "quill/core/Attributes.h" #include "quill/core/MathUtilities.h" -#include -#include -#include - QUILL_BEGIN_NAMESPACE namespace detail diff --git a/include/quill/backend/Utf8Conv.h b/include/quill/backend/Utf8Conv.h index a9d73e56..4d73bc03 100644 --- a/include/quill/backend/Utf8Conv.h +++ b/include/quill/backend/Utf8Conv.h @@ -31,8 +31,8 @@ namespace detail /** * @brief Convert a wide Unicode string to a UTF-8 encoded string. * - * @param input_string_data Pointer to the wide string data. - * @param input_string_length Length of the wide string. + * @param data Pointer to the wide string data. + * @param wide_str_len Length of the wide string. * @return The UTF-8 encoded string. * * @remarks If the input string is empty or the conversion fails, an empty string is returned. diff --git a/include/quill/core/Attributes.h b/include/quill/core/Attributes.h index 760f5417..fb6d2595 100644 --- a/include/quill/core/Attributes.h +++ b/include/quill/core/Attributes.h @@ -10,7 +10,7 @@ #define QUILL_BEGIN_NAMESPACE \ namespace quill \ { \ - inline namespace v7 \ + inline namespace v8 \ { #define QUILL_END_NAMESPACE \ } \ diff --git a/include/quill/core/BoundedSPSCQueue.h b/include/quill/core/BoundedSPSCQueue.h index fed8e951..fd6a23d0 100644 --- a/include/quill/core/BoundedSPSCQueue.h +++ b/include/quill/core/BoundedSPSCQueue.h @@ -229,6 +229,7 @@ class BoundedSPSCQueueImpl /** * align a pointer to the given alignment * @param pointer a pointer the object + * @param alignment a pointer the object * @return an aligned pointer for the given object */ QUILL_NODISCARD static std::byte* _align_pointer(void* pointer, size_t alignment) noexcept diff --git a/include/quill/core/Codec.h b/include/quill/core/Codec.h index 54e6e1e0..cd838c4e 100644 --- a/include/quill/core/Codec.h +++ b/include/quill/core/Codec.h @@ -20,7 +20,6 @@ #include #include #include -#include QUILL_BEGIN_NAMESPACE @@ -87,7 +86,7 @@ void codec_not_found_for_type() QUILL_NODISCARD inline size_t safe_strnlen(char const* str, size_t maxlen) noexcept { - char const* end = static_cast(std::memchr(str, '\0', maxlen)); + auto end = static_cast(std::memchr(str, '\0', maxlen)); return end ? static_cast(end - str) : maxlen; } @@ -216,7 +215,7 @@ struct Codec std::conjunction, std::is_same>, char>>>) { // c strings or char array - char const* arg = reinterpret_cast(buffer); + auto arg = reinterpret_cast(buffer); buffer += strlen(arg) + 1; // for c_strings we add +1 to the length as we also want to copy the null terminated char return arg; } @@ -226,7 +225,7 @@ struct Codec uint32_t len; std::memcpy(&len, buffer, sizeof(len)); buffer += sizeof(len); - std::string_view const arg = std::string_view{reinterpret_cast(buffer), len}; + auto const arg = std::string_view{reinterpret_cast(buffer), len}; buffer += len; return arg; } @@ -282,12 +281,12 @@ namespace detail */ template QUILL_NODISCARD QUILL_ATTRIBUTE_HOT size_t compute_encoded_size_and_cache_string_lengths( - QUILL_MAYBE_UNUSED detail::SizeCacheVector& conditional_arg_size_cache, Args const&... args) noexcept + QUILL_MAYBE_UNUSED SizeCacheVector& conditional_arg_size_cache, Args const&... args) noexcept { if constexpr (!std::conjunction_v>, std::is_enum>, - std::is_same, void const*>, detail::is_std_string>, - std::is_same, std::string_view>>...>) + std::is_arithmetic>, std::is_enum>, + std::is_same, void const*>, is_std_string>, + std::is_same, std::string_view>>...>) { // Clear the cache whenever processing involves non-fundamental types, // or when the arguments are not of type std::string or std::string_view. @@ -298,7 +297,7 @@ QUILL_NODISCARD QUILL_ATTRIBUTE_HOT size_t compute_encoded_size_and_cache_string // Avoid using a fold expression with '+ ...' because we require a guaranteed evaluation // order to ensure that each argument is processed in sequence. This is essential for // correctly populating the conditional_arg_size_cache - ((total_sum += Codec>::compute_encoded_size(conditional_arg_size_cache, args)), ...); + ((total_sum += Codec>::compute_encoded_size(conditional_arg_size_cache, args)), ...); return total_sum; } @@ -309,11 +308,11 @@ QUILL_NODISCARD QUILL_ATTRIBUTE_HOT size_t compute_encoded_size_and_cache_string * @param args The arguments to be encoded. */ template -QUILL_ATTRIBUTE_HOT void encode(std::byte*& buffer, detail::SizeCacheVector const& conditional_arg_size_cache, +QUILL_ATTRIBUTE_HOT void encode(std::byte*& buffer, SizeCacheVector const& conditional_arg_size_cache, Args const&... args) noexcept { QUILL_MAYBE_UNUSED uint32_t conditional_arg_size_cache_index{0}; - (Codec>::encode(buffer, conditional_arg_size_cache, + (Codec>::encode(buffer, conditional_arg_size_cache, conditional_arg_size_cache_index, args), ...); } diff --git a/include/quill/core/InlinedVector.h b/include/quill/core/InlinedVector.h index dacca17c..68d09d55 100644 --- a/include/quill/core/InlinedVector.h +++ b/include/quill/core/InlinedVector.h @@ -6,10 +6,8 @@ #pragma once -#include #include #include -#include #include #include "quill/core/Attributes.h" @@ -171,7 +169,7 @@ class InlinedVector * The capacity of 12 is chosen to fit within a full cache line for better performance. */ using SizeCacheVector = InlinedVector; -static_assert(sizeof(SizeCacheVector) <= detail::CACHE_LINE_SIZE, +static_assert(sizeof(SizeCacheVector) <= CACHE_LINE_SIZE, "SizeCacheVector should not exceed a cache line"); } // namespace detail diff --git a/include/quill/core/LoggerBase.h b/include/quill/core/LoggerBase.h index ccce6620..c841f5d7 100644 --- a/include/quill/core/LoggerBase.h +++ b/include/quill/core/LoggerBase.h @@ -72,9 +72,7 @@ class LoggerBase * Returns the user-defined clock source. * @return A pointer to the constant UserClockSource object. */ - QUILL_NODISCARD UserClockSource* get_user_clock_source() const noexcept { - return user_clock; - } + QUILL_NODISCARD UserClockSource* get_user_clock_source() const noexcept { return user_clock; } /** * Returns the type of clock source being used. diff --git a/include/quill/core/MacroMetadata.h b/include/quill/core/MacroMetadata.h index bc3faef5..a16c36ee 100644 --- a/include/quill/core/MacroMetadata.h +++ b/include/quill/core/MacroMetadata.h @@ -10,7 +10,6 @@ #include "quill/core/Common.h" #include "quill/core/LogLevel.h" -#include #include #include #include diff --git a/include/quill/core/PatternFormatterOptions.h b/include/quill/core/PatternFormatterOptions.h index 122e0562..88b54bda 100644 --- a/include/quill/core/PatternFormatterOptions.h +++ b/include/quill/core/PatternFormatterOptions.h @@ -8,7 +8,7 @@ #include "quill/core/Attributes.h" #include "quill/core/Common.h" -#include + #include QUILL_BEGIN_NAMESPACE diff --git a/include/quill/core/SinkManager.h b/include/quill/core/SinkManager.h index 87704c38..a1e1ee37 100644 --- a/include/quill/core/SinkManager.h +++ b/include/quill/core/SinkManager.h @@ -32,7 +32,7 @@ class SinkManager { explicit SinkInfo() = default; SinkInfo(std::string sid, std::weak_ptr sptr) - : sink_id(static_cast(sid)), sink_ptr(static_cast&&>(sptr)){}; + : sink_id(static_cast(sid)), sink_ptr(static_cast&&>(sptr)) {}; std::string sink_id; std::weak_ptr sink_ptr; diff --git a/include/quill/core/ThreadContextManager.h b/include/quill/core/ThreadContextManager.h index a44b6e5a..485f5232 100644 --- a/include/quill/core/ThreadContextManager.h +++ b/include/quill/core/ThreadContextManager.h @@ -186,9 +186,7 @@ class ThreadContext void mark_invalid() noexcept { _valid.store(false, std::memory_order_relaxed); } /***/ - QUILL_NODISCARD bool is_valid() const noexcept { - return _valid.load(std::memory_order_relaxed); - } + QUILL_NODISCARD bool is_valid() const noexcept { return _valid.load(std::memory_order_relaxed); } /***/ void increment_failure_counter() noexcept @@ -211,8 +209,8 @@ class ThreadContext SpscQueueUnion _spsc_queue_union; /**< queue for this thread */ SizeCacheVector _conditional_arg_size_cache; /**< cache for storing sizes needed for specific operations, such as when calling `strn` functions or when a loop is required e.g. caching the size of a type */ - std::string _thread_id = std::to_string(get_thread_id()); /**< cached thread pid */ - std::string _thread_name = get_thread_name(); /**< cached thread name */ + std::string _thread_id = std::to_string(get_thread_id()); /**< cached thread pid */ + std::string _thread_name = get_thread_name(); /**< cached thread name */ std::shared_ptr _transit_event_buffer; /**< backend thread buffer. this could be unique_ptr but it is shared_ptr because of the forward declaration */ QueueType _queue_type; std::atomic _valid{true}; /**< is this context valid, set by the frontend, read by the backend thread */ diff --git a/include/quill/core/UnboundedSPSCQueue.h b/include/quill/core/UnboundedSPSCQueue.h index deb2ded3..d8501f2f 100644 --- a/include/quill/core/UnboundedSPSCQueue.h +++ b/include/quill/core/UnboundedSPSCQueue.h @@ -9,13 +9,11 @@ #include "quill/core/Attributes.h" #include "quill/core/BoundedSPSCQueue.h" #include "quill/core/Common.h" -#include "quill/core/MathUtilities.h" #include "quill/core/QuillError.h" #include #include #include -#include #include QUILL_BEGIN_NAMESPACE diff --git a/include/quill/sinks/ConsoleSink.h b/include/quill/sinks/ConsoleSink.h index a0492022..14cad2b2 100644 --- a/include/quill/sinks/ConsoleSink.h +++ b/include/quill/sinks/ConsoleSink.h @@ -8,7 +8,6 @@ #include "quill/core/Attributes.h" #include "quill/core/LogLevel.h" -#include "quill/core/QuillError.h" #include "quill/sinks/StreamSink.h" #include @@ -43,11 +42,8 @@ QUILL_BEGIN_NAMESPACE /** Forward Declaration **/ class MacroMetadata; -#if defined(_WIN32) -/** - * Represents console colours - */ -class ConsoleColours +/***/ +class ConsoleSink : public StreamSink { public: enum class ColourMode @@ -57,357 +53,250 @@ class ConsoleColours Never }; - ConsoleColours() - { - // by default _using_colours is false - _colours.fill(white); - } - - ~ConsoleColours() = default; - - /** - * Sets some default colours for terminal - */ - void set_default_colours() noexcept - { - set_colour(LogLevel::TraceL3, white); - set_colour(LogLevel::TraceL2, white); - set_colour(LogLevel::TraceL1, white); - set_colour(LogLevel::Debug, cyan); - set_colour(LogLevel::Info, green); - set_colour(LogLevel::Notice, white | bold); - set_colour(LogLevel::Warning, yellow | bold); - set_colour(LogLevel::Error, red | bold); - set_colour(LogLevel::Critical, on_red | bold | white); // white bold on red background - set_colour(LogLevel::Backtrace, magenta); - } - - /** - * Sets a custom colour per log level - * @param log_level the log level - * @param colour the colour - */ - void set_colour(LogLevel log_level, WORD colour) noexcept - { - auto const log_lvl = static_cast(log_level); - _colours[log_lvl] = colour; - _using_colours = true; - } - /** - * @return true if we are in terminal and have also enabled colours + * Represents console colours */ - QUILL_NODISCARD bool can_use_colours() const noexcept + class Colours { - return _can_use_colours && _using_colours; - } - - /** - * @return true if we have setup colours - */ - QUILL_NODISCARD bool using_colours() const noexcept { return _using_colours; } - - /** - * The colour for the given log level - * @param log_level the message log level - * @return the configured colour for this log level - */ - QUILL_NODISCARD WORD colour_code(LogLevel log_level) const noexcept - { - auto const log_lvl = static_cast(log_level); - return _colours[log_lvl]; - } - - static constexpr WORD bold = FOREGROUND_INTENSITY; - - static constexpr WORD black = 0; - static constexpr WORD red = FOREGROUND_RED; - static constexpr WORD green = FOREGROUND_GREEN; - static constexpr WORD yellow = FOREGROUND_RED | FOREGROUND_GREEN; - static constexpr WORD blue = FOREGROUND_BLUE; - static constexpr WORD magenta = FOREGROUND_RED | FOREGROUND_BLUE; - static constexpr WORD cyan = FOREGROUND_GREEN | FOREGROUND_BLUE; - static constexpr WORD white = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; - - static constexpr WORD on_red = BACKGROUND_RED; - static constexpr WORD on_green = BACKGROUND_GREEN; - static constexpr WORD on_yellow = BACKGROUND_RED | BACKGROUND_GREEN; - static constexpr WORD on_blue = BACKGROUND_BLUE; - static constexpr WORD on_magenta = BACKGROUND_RED | BACKGROUND_BLUE; - static constexpr WORD on_cyan = BACKGROUND_GREEN | BACKGROUND_BLUE; - static constexpr WORD on_white = BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE; - -private: - friend class ConsoleSink; - - /***/ - QUILL_NODISCARD QUILL_ATTRIBUTE_COLD static bool _is_in_terminal(FILE* file) noexcept - { - bool const is_atty = _isatty(_fileno(file)) != 0; + public: + Colours() + { + // by default _using_colours is false + _log_level_colours.fill(white); + } - // ::GetConsoleMode() should return 0 if file is redirected or does not point to the actual console - DWORD console_mode; - bool const is_console = - GetConsoleMode(reinterpret_cast(_get_osfhandle(_fileno(file))), &console_mode) != 0; + ~Colours() = default; - return is_atty && is_console; - } + /** + * Sets some default colours for terminal + */ + void apply_default_colours() noexcept + { + assign_colour_to_log_level(LogLevel::TraceL3, white); + assign_colour_to_log_level(LogLevel::TraceL2, white); + assign_colour_to_log_level(LogLevel::TraceL1, white); + assign_colour_to_log_level(LogLevel::Debug, cyan); + assign_colour_to_log_level(LogLevel::Info, green); + assign_colour_to_log_level(LogLevel::Notice, white_bold); + assign_colour_to_log_level(LogLevel::Warning, yellow_bold); + assign_colour_to_log_level(LogLevel::Error, red_bold); + assign_colour_to_log_level(LogLevel::Critical, bold_on_red); + assign_colour_to_log_level(LogLevel::Backtrace, magenta); + } - void _set_can_use_colours(FILE* file, ColourMode colour_mode) noexcept - { - if (colour_mode == ColourMode::Always) + /** + * Sets a custom colour per log level + * @param log_level the log level + * @param colour the colour + */ + void assign_colour_to_log_level(LogLevel log_level, std::string_view colour) noexcept { - _can_use_colours = true; + auto const log_lvl = static_cast(log_level); + _log_level_colours[log_lvl] = colour; + _colours_enabled = true; } - else if (colour_mode == ColourMode::Automatic) + + /** + * @return true if we are in terminal and have also enabled colours + */ + QUILL_NODISCARD bool colours_enabled() const noexcept { - _can_use_colours = _is_in_terminal(file); + return _colour_output_supported && _colours_enabled; } - else + + /** + * The colour for the given log level + * @param log_level the message log level + * @return the configured colour for this log level + */ + QUILL_NODISCARD std::string_view log_level_colour(LogLevel log_level) const noexcept { - _can_use_colours = false; + auto const log_lvl = static_cast(log_level); + return _log_level_colours[log_lvl]; } - } -private: - std::array _colours = {0}; /**< Colours per log level */ - bool _using_colours{false}; - bool _can_use_colours{false}; -}; + // Formatting codes + static constexpr std::string_view reset{"\033[0m"}; + static constexpr std::string_view bold{"\033[1m"}; + static constexpr std::string_view dark{"\033[2m"}; + static constexpr std::string_view underline{"\033[4m"}; + static constexpr std::string_view blink{"\033[5m"}; + static constexpr std::string_view reverse{"\033[7m"}; + static constexpr std::string_view concealed{"\033[8m"}; + static constexpr std::string_view clear_line{"\033[K"}; + + // Foreground colors + static constexpr std::string_view black{"\033[30m"}; + static constexpr std::string_view red{"\033[31m"}; + static constexpr std::string_view green{"\033[32m"}; + static constexpr std::string_view yellow{"\033[33m"}; + static constexpr std::string_view blue{"\033[34m"}; + static constexpr std::string_view magenta{"\033[35m"}; + static constexpr std::string_view cyan{"\033[36m"}; + static constexpr std::string_view white{"\033[37m"}; + + /// Background colors + static constexpr std::string_view on_black{"\033[40m"}; + static constexpr std::string_view on_red{"\033[41m"}; + static constexpr std::string_view on_green{"\033[42m"}; + static constexpr std::string_view on_yellow{"\033[43m"}; + static constexpr std::string_view on_blue{"\033[44m"}; + static constexpr std::string_view on_magenta{"\033[45m"}; + static constexpr std::string_view on_cyan{"\033[46m"}; + static constexpr std::string_view on_white{"\033[47m"}; + + /// Bold colors + static constexpr std::string_view white_bold{"\033[97m\033[1m"}; + static constexpr std::string_view yellow_bold{"\033[33m\033[1m"}; + static constexpr std::string_view red_bold{"\033[31m\033[1m"}; + static constexpr std::string_view bold_on_red{"\033[1m\033[41m"}; + + private: + friend class ConsoleSink; + + /***/ + QUILL_NODISCARD QUILL_ATTRIBUTE_COLD static bool _supports_colour_output() noexcept + { +#ifdef _WIN32 + // On Windows 10 and later, ANSI colors are supported + return true; #else -/** - * Represents console colours - */ -class ConsoleColours -{ -public: - enum class ColourMode - { - Always, - Automatic, - Never - }; - - ConsoleColours() - { - // by default _using_colours is false - _colours.fill(white); - } - - ~ConsoleColours() = default; - - /** - * Sets some default colours for terminal - */ - void set_default_colours() noexcept - { - set_colour(LogLevel::TraceL3, white); - set_colour(LogLevel::TraceL2, white); - set_colour(LogLevel::TraceL1, white); - set_colour(LogLevel::Debug, cyan); - set_colour(LogLevel::Info, green); - set_colour(LogLevel::Notice, white_bold); - set_colour(LogLevel::Warning, yellow_bold); - set_colour(LogLevel::Error, red_bold); - set_colour(LogLevel::Critical, bold_on_red); - set_colour(LogLevel::Backtrace, magenta); - } - - /** - * Sets a custom colour per log level - * @param log_level the log level - * @param colour the colour - */ - void set_colour(LogLevel log_level, std::string_view colour) noexcept - { - auto const log_lvl = static_cast(log_level); - _colours[log_lvl] = colour; - _using_colours = true; - } - - /** - * @return true if we are in terminal and have also enabled colours - */ - QUILL_NODISCARD bool can_use_colours() const noexcept - { - return _can_use_colours && _using_colours; - } + // Get term from env + auto* env_p = std::getenv("TERM"); - /** - * @return true if we have setup colours - */ - QUILL_NODISCARD bool using_colours() const noexcept { return _using_colours; } + if (env_p == nullptr) + { + return false; + } - /** - * The colour for the given log level - * @param log_level the message log level - * @return the configured colour for this log level - */ - QUILL_NODISCARD std::string_view colour_code(LogLevel log_level) const noexcept - { - auto const log_lvl = static_cast(log_level); - return _colours[log_lvl]; - } + static constexpr const char* terms[] = { + "ansi", "color", "console", "cygwin", "gnome", + "konsole", "kterm", "linux", "msys", "putty", + "rxvt", "screen", "vt100", "xterm", "tmux", + "terminator", "alacritty", "gnome-terminal", "xfce4-terminal", "lxterminal", + "mate-terminal", "uxterm", "eterm", "tilix", "rxvt-unicode", + "kde-konsole"}; - // Formatting codes - static constexpr std::string_view reset{"\033[0m"}; - static constexpr std::string_view bold{"\033[1m"}; - static constexpr std::string_view dark{"\033[2m"}; - static constexpr std::string_view underline{"\033[4m"}; - static constexpr std::string_view blink{"\033[5m"}; - static constexpr std::string_view reverse{"\033[7m"}; - static constexpr std::string_view concealed{"\033[8m"}; - static constexpr std::string_view clear_line{"\033[K"}; - - // Foreground colors - static constexpr std::string_view black{"\033[30m"}; - static constexpr std::string_view red{"\033[31m"}; - static constexpr std::string_view green{"\033[32m"}; - static constexpr std::string_view yellow{"\033[33m"}; - static constexpr std::string_view blue{"\033[34m"}; - static constexpr std::string_view magenta{"\033[35m"}; - static constexpr std::string_view cyan{"\033[36m"}; - static constexpr std::string_view white{"\033[37m"}; - - /// Background colors - static constexpr std::string_view on_black{"\033[40m"}; - static constexpr std::string_view on_red{"\033[41m"}; - static constexpr std::string_view on_green{"\033[42m"}; - static constexpr std::string_view on_yellow{"\033[43m"}; - static constexpr std::string_view on_blue{"\033[44m"}; - static constexpr std::string_view on_magenta{"\033[45m"}; - static constexpr std::string_view on_cyan{"\033[46m"}; - static constexpr std::string_view on_white{"\033[47m"}; - - /// Bold colors - static constexpr std::string_view white_bold{"\033[97m\033[1m"}; - static constexpr std::string_view yellow_bold{"\033[33m\033[1m"}; - static constexpr std::string_view red_bold{"\033[31m\033[1m"}; - static constexpr std::string_view bold_on_red{"\033[1m\033[41m"}; - -private: - friend class ConsoleSink; - - /***/ - QUILL_NODISCARD QUILL_ATTRIBUTE_COLD static bool _is_colour_terminal() noexcept - { - // Get term from env - auto* env_p = std::getenv("TERM"); + // Loop through each term and check if it's found in env_p + for (const char* term : terms) + { + if (std::strstr(env_p, term) != nullptr) + { + // term found + return true; + } + } - if (env_p == nullptr) - { + // none of the terms are found return false; +#endif } - static constexpr const char* terms[] = { - "ansi", "color", "console", "cygwin", "gnome", "konsole", - "kterm", "linux", "msys", "putty", "rxvt", "screen", - "vt100", "xterm", "tmux", "terminator", "alacritty", "gnome-terminal", - "xfce4-terminal", "lxterminal", "mate-terminal", "uxterm", "eterm", "tilix", - "rxvt-unicode", "kde-konsole"}; + /***/ + QUILL_NODISCARD QUILL_ATTRIBUTE_COLD static bool _is_terminal_output(FILE* file) noexcept + { +#ifdef _WIN32 + return _isatty(_fileno(file)) != 0; +#else + return ::isatty(fileno(file)) != 0; +#endif + } - // Loop through each term and check if it's found in env_p - for (const char* term : terms) +#ifdef _WIN32 + /***/ + QUILL_ATTRIBUTE_COLD void _activate_ansi_support(FILE* file) const { - if (std::strstr(env_p, term) != nullptr) + if (!_colour_output_supported) { - // term found - return true; + return; } - } - // none of the terms are found - return false; - } + // Try to enable ANSI support for Windows console + auto const out_handle = reinterpret_cast(_get_osfhandle(_fileno(file))); + if (out_handle == INVALID_HANDLE_VALUE) + { + return; + } - /***/ - QUILL_NODISCARD QUILL_ATTRIBUTE_COLD static bool _is_in_terminal(FILE* file) noexcept - { - return ::isatty(fileno(file)) != 0; - } + DWORD dw_mode = 0; + if (!GetConsoleMode(out_handle, &dw_mode)) + { + return; + } - /***/ - void _set_can_use_colours(FILE* file, ColourMode colour_mode) noexcept - { - if (colour_mode == ColourMode::Always) - { - _can_use_colours = true; - } - else if (colour_mode == ColourMode::Automatic) - { - _can_use_colours = _is_in_terminal(file) && _is_colour_terminal(); + dw_mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + dw_mode |= ENABLE_PROCESSED_OUTPUT; + + SetConsoleMode(out_handle, dw_mode); } - else +#endif + + /***/ + void _configure_colour_support(FILE* file, ColourMode colour_mode) noexcept { - _can_use_colours = false; - } - } + if (colour_mode == ColourMode::Always) + { + _colour_output_supported = true; + } + else if (colour_mode == ColourMode::Automatic) + { + _colour_output_supported = _is_terminal_output(file) && _supports_colour_output(); + } + else + { + _colour_output_supported = false; + } -private: - std::array _colours; /**< Colours per log level */ - bool _using_colours{false}; - bool _can_use_colours{false}; -}; +#ifdef _WIN32 + // Enable ANSI color support on Windows + _activate_ansi_support(file); #endif + } + + private: + std::array _log_level_colours; /**< Colours per log level */ + bool _colours_enabled{false}; + bool _colour_output_supported{false}; + }; -/***/ -class ConsoleSink : public StreamSink -{ -public: /** - * @brief Constructor - * @param console_colours console colours instance - * @param stream stream name can only be "stdout" or "stderr" + * @brief Constructor with custom ConsoleColours + * @param colours console colours instance * @param colour_mode Determines when console colours are enabled. * - Always: Colours are always enabled. * - Automatic: Colours are enabled automatically based on the environment (e.g., terminal support). * - Never: Colours are never enabled. - */ - explicit ConsoleSink(ConsoleColours const& console_colours, std::string const& stream = "stdout", - ConsoleColours::ColourMode colour_mode = ConsoleColours::ColourMode::Automatic) - : StreamSink{stream, nullptr}, _console_colours(console_colours) - { - assert((stream == "stdout") || (stream == "stderr")); - - // In this ctor we take a full copy of console_colours and in our instance we modify it - _console_colours._set_can_use_colours(_file, colour_mode); - } - - /** - * @brief Constructor - * @param enable_colours enable or disable console colours * @param stream stream name can only be "stdout" or "stderr" */ - explicit ConsoleSink(bool enable_colours = true, std::string const& stream = "stdout") - : StreamSink{stream, nullptr} + explicit ConsoleSink(Colours const& colours, ColourMode colour_mode = ColourMode::Automatic, + std::string const& stream = "stdout") + : StreamSink{stream, nullptr}, _colours(colours) { assert((stream == "stdout") || (stream == "stderr")); - if (enable_colours) - { - _console_colours._set_can_use_colours(_file, ConsoleColours::ColourMode::Automatic); - _console_colours.set_default_colours(); - } + // In this ctor we take a full copy of colours and in our instance we modify it + _colours._configure_colour_support(_file, colour_mode); } /** - * @brief Constructor + * @brief Constructor with default ConsoleColours * @param colour_mode Determines when console colours are enabled. * - Always: Colours are always enabled. * - Automatic: Colours are enabled automatically based on the environment (e.g., terminal support). * - Never: Colours are never enabled. * @param stream stream name can only be "stdout" or "stderr" */ - explicit ConsoleSink(ConsoleColours::ColourMode colour_mode, std::string const& stream = "stdout") + explicit ConsoleSink(ColourMode colour_mode = ColourMode::Automatic, + std::string const& stream = "stdout") : StreamSink{stream, nullptr} { assert((stream == "stdout") || (stream == "stderr")); - _console_colours._set_can_use_colours(_file, colour_mode); + _colours._configure_colour_support(_file, colour_mode); - if (colour_mode != ConsoleColours::ColourMode::Never) + if (colour_mode != ColourMode::Never) { - _console_colours.set_default_colours(); + _colours.apply_default_colours(); } } @@ -426,6 +315,7 @@ class ConsoleSink : public StreamSink * @param log_level_short_code Short code representing the log level. * @param named_args vector of key-value pairs of named args * @param log_message log message + * @param log_statement log statement */ QUILL_ATTRIBUTE_HOT void write_log(MacroMetadata const* log_metadata, uint64_t log_timestamp, std::string_view thread_id, std::string_view thread_name, @@ -435,127 +325,32 @@ class ConsoleSink : public StreamSink std::vector> const* named_args, std::string_view log_message, std::string_view log_statement) override { -#if defined(_WIN32) - if (_console_colours.using_colours()) + if (_colours.colours_enabled()) { - WORD const colour_code = _console_colours.colour_code(log_level); - WORD orig_attribs{0}; - - QUILL_TRY - { - // Set foreground colour and store the original attributes - orig_attribs = _set_foreground_colour(colour_code); - } - #if !defined(QUILL_NO_EXCEPTIONS) - QUILL_CATCH(std::exception const& e) - { - // GetConsoleScreenBufferInfo can fail sometimes on windows, in that case still write - // the log without colours - StreamSink::write_log(log_metadata, log_timestamp, thread_id, thread_name, process_id, - logger_name, log_level, log_level_description, log_level_short_code, - named_args, log_message, log_statement); - - if (!_report_write_log_error_once) - { - // Report the error once - _report_write_log_error_once = true; - QUILL_THROW(QuillError{e.what()}); - } - - // do not resume further, we already wrote the log statement - return; - } - #endif - - auto out_handle = reinterpret_cast(_get_osfhandle(_fileno(_file))); - - // Write to console - bool const write_to_console = WriteConsoleA( - out_handle, log_statement.data(), static_cast(log_statement.size()), nullptr, nullptr); + // Write colour code + std::string_view const colour_code = _colours.log_level_colour(log_level); + safe_fwrite(colour_code.data(), sizeof(char), colour_code.size(), _file); - if (QUILL_UNLIKELY(!write_to_console)) - { - auto const error = std::error_code(GetLastError(), std::system_category()); - QUILL_THROW(QuillError{std::string{"WriteConsoleA failed. error: "} + error.message() + - std::string{" errno: "} + std::to_string(error.value())}); - } + // Write record to file + StreamSink::write_log(log_metadata, log_timestamp, thread_id, thread_name, process_id, + logger_name, log_level, log_level_description, log_level_short_code, + named_args, log_message, log_statement); - // reset to orig colors - bool const set_text_attr = SetConsoleTextAttribute(out_handle, orig_attribs); - if (QUILL_UNLIKELY(!set_text_attr)) - { - auto const error = std::error_code(GetLastError(), std::system_category()); - QUILL_THROW(QuillError{std::string{"SetConsoleTextAttribute failed. error: "} + error.message() + - std::string{" errno: "} + std::to_string(error.value())}); - } + // Reset colour code + safe_fwrite(Colours::reset.data(), sizeof(char), Colours::reset.size(), _file); } else { + // Write record to file StreamSink::write_log(log_metadata, log_timestamp, thread_id, thread_name, process_id, logger_name, log_level, log_level_description, log_level_short_code, named_args, log_message, log_statement); } -#else - if (_console_colours.can_use_colours()) - { - // Write colour code - std::string_view const colour_code = _console_colours.colour_code(log_level); - safe_fwrite(colour_code.data(), sizeof(char), colour_code.size(), _file); - } - - // Write record to file - StreamSink::write_log(log_metadata, log_timestamp, thread_id, thread_name, process_id, - logger_name, log_level, log_level_description, log_level_short_code, - named_args, log_message, log_statement); - - if (_console_colours.can_use_colours()) - { - safe_fwrite(ConsoleColours::reset.data(), sizeof(char), ConsoleColours::reset.size(), _file); - } -#endif } -private: -#if defined(_WIN32) - QUILL_NODISCARD WORD _set_foreground_colour(WORD attributes) - { - CONSOLE_SCREEN_BUFFER_INFO orig_buffer_info; - auto const out_handle = reinterpret_cast(_get_osfhandle(_fileno(_file))); - - bool const screen_buffer_info = GetConsoleScreenBufferInfo(out_handle, &orig_buffer_info); - if (QUILL_UNLIKELY(!screen_buffer_info)) - { - auto const error = std::error_code(GetLastError(), std::system_category()); - QUILL_THROW(QuillError{std::string{"GetConsoleScreenBufferInfo failed. error: "} + - error.message() + std::string{" errno: "} + std::to_string(error.value())}); - } - - WORD back_color = orig_buffer_info.wAttributes; - - // retrieve the current background color - back_color &= - static_cast(~(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY)); - - // keep the background color unchanged - bool const console_text_attr = SetConsoleTextAttribute(out_handle, attributes | back_color); - if (QUILL_UNLIKELY(!console_text_attr)) - { - auto const error = std::error_code(GetLastError(), std::system_category()); - QUILL_THROW(QuillError{std::string{"SetConsoleTextAttribute failed. error: "} + error.message() + - std::string{" errno: "} + std::to_string(error.value())}); - } - - return orig_buffer_info.wAttributes; // return orig attribs - } -#endif - protected: // protected in case someone wants to derive from this class and create a custom one, e.g. for json logging to stdout - ConsoleColours _console_colours; - -#if defined(_WIN32) - bool _report_write_log_error_once{false}; -#endif + Colours _colours; }; QUILL_END_NAMESPACE \ No newline at end of file diff --git a/include/quill/sinks/FileSink.h b/include/quill/sinks/FileSink.h index 28d69921..5f4118f9 100644 --- a/include/quill/sinks/FileSink.h +++ b/include/quill/sinks/FileSink.h @@ -125,14 +125,6 @@ class FileSinkConfig */ QUILL_ATTRIBUTE_COLD void set_fsync_enabled(bool value) { _fsync_enabled = value; } - [[deprecated( - "This function is deprecated and will be removed in the next version. Use set_fsync_enabled() " - "instead.")]] - QUILL_ATTRIBUTE_COLD void set_do_fsync(bool value) - { - _fsync_enabled = value; - } - /** * @brief Sets the open mode for the file. * Valid options for the open mode are 'a' or 'w'. The default value is 'a'. @@ -222,6 +214,7 @@ class FileSink : public StreamSink * @param config Configuration for the FileSink. * @param file_event_notifier Notifies on file events. * @param do_fopen If false, the file will not be opened. + * @param start_time start time */ explicit FileSink(fs::path const& filename, FileSinkConfig const& config = FileSinkConfig{}, FileEventNotifier file_event_notifier = FileEventNotifier{}, bool do_fopen = true, @@ -429,7 +422,9 @@ class FileSink : public StreamSink * Get the filename with appended date and/or time. * @param filename Path to the file. * @param append_to_filename_option Append option. + * @param append_filename_format_pattern Append filename format option. * @param time_zone Timezone to use. + * @param timestamp timestamp * @return Updated filename. */ QUILL_NODISCARD static fs::path _get_updated_filename_with_appended_datetime( diff --git a/include/quill/sinks/JsonConsoleSink.h b/include/quill/sinks/JsonConsoleSink.h deleted file mode 100644 index 9e115414..00000000 --- a/include/quill/sinks/JsonConsoleSink.h +++ /dev/null @@ -1,103 +0,0 @@ -/** - * @page copyright - * Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors. - * Distributed under the MIT License (http://opensource.org/licenses/MIT) - */ - -#pragma once - -#include "quill/bundled/fmt/base.h" -#include "quill/core/Attributes.h" -#include "quill/core/LogLevel.h" -#include "quill/core/MacroMetadata.h" -#include "quill/sinks/StreamSink.h" - -#include "quill/bundled/fmt/format.h" - -#include -#include -#include -#include -#include -#include - -QUILL_BEGIN_NAMESPACE - -class JsonConsoleSink : public StreamSink -{ -public: - JsonConsoleSink() : StreamSink("stdout", nullptr) {} - ~JsonConsoleSink() override = default; - - /** - * @brief Logs a formatted log message to the sink. - * @note Accessor for backend processing. - * @param log_metadata Pointer to the macro metadata. - * @param log_timestamp Timestamp of the log event. - * @param thread_id ID of the thread. - * @param thread_name Name of the thread. - * @param process_id Process Id - * @param logger_name Name of the logger. - * @param log_level Log level of the message. - * @param log_level_description Description of the log level. - * @param log_level_short_code Short code representing the log level. - * @param named_args Vector of key-value pairs of named args - */ - QUILL_ATTRIBUTE_HOT void write_log(MacroMetadata const* log_metadata, uint64_t log_timestamp, - std::string_view thread_id, std::string_view thread_name, - std::string const& process_id, std::string_view logger_name, LogLevel log_level, std::string_view log_level_description, - std::string_view log_level_short_code, - std::vector> const* named_args, - std::string_view, std::string_view) override - { - _json_message.clear(); - - char const* message_format; - - if (strchr(log_metadata->message_format(), '\n') != nullptr) - { - // The format string contains at least one new line and that would break the json message, it needs to be removed - _format = log_metadata->message_format(); - - for (size_t pos = 0; (pos = _format.find('\n', pos)) != std::string::npos; pos++) - { - _format.replace(pos, 1, " "); - } - - message_format = _format.data(); - } - else - { - message_format = log_metadata->message_format(); - } - - _json_message.append(fmtquill::format( - R"({{"timestamp":"{}","file_name":"{}","line":"{}","thread_id":"{}","logger":"{}","log_level":"{}","message":"{}")", - std::to_string(log_timestamp), log_metadata->file_name(), log_metadata->line(), thread_id, - logger_name, log_level_description, message_format)); - - if (named_args) - { - for (auto const& [key, value] : *named_args) - { - _json_message.append(std::string_view{",\""}); - _json_message.append(key); - _json_message.append(std::string_view{"\":\""}); - _json_message.append(value); - _json_message.append(std::string_view{"\""}); - } - } - - _json_message.append(std::string_view{"}\n"}); - - StreamSink::write_log(log_metadata, log_timestamp, thread_id, thread_name, process_id, logger_name, log_level, - log_level_description, log_level_short_code, named_args, std::string_view{}, - std::string_view{_json_message.data(), _json_message.size()}); - } - -private: - fmtquill::memory_buffer _json_message; - std::string _format; -}; - -QUILL_END_NAMESPACE \ No newline at end of file diff --git a/include/quill/sinks/JsonFileSink.h b/include/quill/sinks/JsonSink.h similarity index 57% rename from include/quill/sinks/JsonFileSink.h rename to include/quill/sinks/JsonSink.h index f5ecd688..074bc24f 100644 --- a/include/quill/sinks/JsonFileSink.h +++ b/include/quill/sinks/JsonSink.h @@ -25,25 +25,18 @@ QUILL_BEGIN_NAMESPACE -/** - * The JsonFileSinkConfig class holds the configuration options for the JsonFileSink - */ -class JsonFileSinkConfig : public FileSinkConfig +namespace detail { -public: - JsonFileSinkConfig() = default; -}; - -class JsonFileSink : public FileSink +template +class JsonSink : public TBase { public: - JsonFileSink(fs::path const& filename, JsonFileSinkConfig const& config, - FileEventNotifier file_event_notifier = FileEventNotifier{}) - : FileSink(filename, static_cast(config), std::move(file_event_notifier)) - { - } + using base_type = TBase; - ~JsonFileSink() override = default; + /** Inherit base constructors **/ + using base_type::base_type; + + ~JsonSink() override = default; /** * @brief Logs a formatted log message to the sink. @@ -58,17 +51,18 @@ class JsonFileSink : public FileSink * @param log_level_description Description of the log level. * @param log_level_short_code Short code representing the log level. * @param named_args Vector of key-value pairs of named args + * @param log_message log message + * @param log_statement log statement */ QUILL_ATTRIBUTE_HOT void write_log(MacroMetadata const* log_metadata, uint64_t log_timestamp, std::string_view thread_id, std::string_view thread_name, - std::string const& process_id, std::string_view logger_name, LogLevel log_level, std::string_view log_level_description, + std::string const& process_id, std::string_view logger_name, + LogLevel log_level, std::string_view log_level_description, std::string_view log_level_short_code, std::vector> const* named_args, - std::string_view, std::string_view) override + std::string_view log_message, std::string_view log_statement) override { - _json_message.clear(); - - char const* message_format; + char const* message_format = log_metadata->message_format(); if (strchr(log_metadata->message_format(), '\n') != nullptr) { @@ -80,18 +74,46 @@ class JsonFileSink : public FileSink _format.replace(pos, 1, " "); } + // we do not want newlines in the json message, use the modified message_format message_format = _format.data(); } - else - { - message_format = log_metadata->message_format(); - } + _json_message.clear(); + + generate_json_message(log_metadata, log_timestamp, thread_id, thread_name, process_id, + logger_name, log_level, log_level_description, log_level_short_code, + named_args, log_message, log_statement, message_format); + + _json_message.append(std::string_view{"}\n"}); + + StreamSink::write_log(log_metadata, log_timestamp, thread_id, thread_name, process_id, logger_name, log_level, + log_level_description, log_level_short_code, named_args, std::string_view{}, + std::string_view{_json_message.data(), _json_message.size()}); + } + + /** + * Generates a JSON-formatted log message. + * + * This function creates the default JSON structure for log messages, including the timestamp, + * file name, line number, thread information, logger name, log level, and message content. + * + * It is designed to be customizable by overriding in derived classes. Users can provide their own + * implementation to generate a log message in a custom format or to include additional fields. + */ + QUILL_ATTRIBUTE_HOT virtual void generate_json_message( + MacroMetadata const* log_metadata, uint64_t log_timestamp, std::string_view thread_id, + std::string_view /** thread_name **/, std::string const& /** process_id **/, + std::string_view logger_name, LogLevel /** log_level **/, + std::string_view log_level_description, std::string_view /** log_level_short_code **/, + std::vector> const* named_args, + std::string_view /** log_message **/, std::string_view /** log_statement **/, char const* message_format) + { _json_message.append(fmtquill::format( R"({{"timestamp":"{}","file_name":"{}","line":"{}","thread_id":"{}","logger":"{}","log_level":"{}","message":"{}")", std::to_string(log_timestamp), log_metadata->file_name(), log_metadata->line(), thread_id, logger_name, log_level_description, message_format)); + // Add args as key-values if (named_args) { for (auto const& [key, value] : *named_args) @@ -103,17 +125,39 @@ class JsonFileSink : public FileSink _json_message.append(std::string_view{"\""}); } } - - _json_message.append(std::string_view{"}\n"}); - - StreamSink::write_log(log_metadata, log_timestamp, thread_id, thread_name, process_id, logger_name, log_level, - log_level_description, log_level_short_code, named_args, std::string_view{}, - std::string_view{_json_message.data(), _json_message.size()}); } -private: +protected: fmtquill::memory_buffer _json_message; + +private: std::string _format; }; +} // namespace detail + +/** + * JSON File Sink + */ +class JsonFileSink : public detail::JsonSink +{ +public: + JsonFileSink(fs::path const& filename, FileSinkConfig const& config, + FileEventNotifier file_event_notifier = FileEventNotifier{}, bool do_fopen = true) + : detail::JsonSink(filename, static_cast(config), std::move(file_event_notifier), do_fopen) + { + } + + ~JsonFileSink() override = default; +}; + +/** + * JSON Console Sink + */ +class JsonConsoleSink : public detail::JsonSink +{ +public: + JsonConsoleSink() : detail::JsonSink("stdout", nullptr) {} + ~JsonConsoleSink() override = default; +}; QUILL_END_NAMESPACE \ No newline at end of file diff --git a/include/quill/sinks/NullSink.h b/include/quill/sinks/NullSink.h index f2ed3af5..505ad6cd 100644 --- a/include/quill/sinks/NullSink.h +++ b/include/quill/sinks/NullSink.h @@ -26,7 +26,8 @@ class NullSink : public Sink public: QUILL_ATTRIBUTE_HOT void write_log(MacroMetadata const* log_metadata, uint64_t log_timestamp, std::string_view thread_id, std::string_view thread_name, - std::string const& process_id, std::string_view logger_name, LogLevel log_level, std::string_view log_level_description, + std::string const& process_id, std::string_view logger_name, + LogLevel log_level, std::string_view log_level_description, std::string_view log_level_short_code, std::vector> const* named_args, std::string_view log_message, std::string_view log_statement) override diff --git a/include/quill/sinks/RotatingFileSink.h b/include/quill/sinks/RotatingFileSink.h index bfd1e864..fff336f1 100644 --- a/include/quill/sinks/RotatingFileSink.h +++ b/include/quill/sinks/RotatingFileSink.h @@ -6,799 +6,10 @@ #pragma once -#include "quill/core/Attributes.h" -#include "quill/core/Common.h" -#include "quill/core/Filesystem.h" -#include "quill/core/LogLevel.h" -#include "quill/core/QuillError.h" -#include "quill/core/TimeUtilities.h" -#include "quill/sinks/FileSink.h" -#include "quill/sinks/StreamSink.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "quill/sinks/RotatingSink.h" QUILL_BEGIN_NAMESPACE -/** Forward Declaration **/ -class MacroMetadata; - -/** - * @brief The configuration options for the RotatingFileSink - */ -class RotatingFileSinkConfig : public FileSinkConfig -{ -public: - /** - * @brief The frequency of log file rotation - */ - enum class RotationFrequency : uint8_t - { - Disabled, - Daily, - Hourly, - Minutely - }; - - /** - * @brief The naming scheme for rotated log files - */ - enum class RotationNamingScheme : uint8_t - { - Index, - Date, - DateAndTime - }; - - /** - * @brief Constructs a new RotatingFileSinkConfig object. - */ - RotatingFileSinkConfig() : _daily_rotation_time{_disabled_daily_rotation_time()} {} - - /** - * @brief Sets the maximum file size for rotation. - * @param value The maximum file size in bytes per file - */ - QUILL_ATTRIBUTE_COLD void set_rotation_max_file_size(size_t value) - { - if (value < 512) - { - QUILL_THROW(QuillError{"rotation_max_file_size must be greater than or equal to 512 bytes"}); - } - - _rotation_max_file_size = value; - } - - /** - * @brief Sets the frequency and interval of file rotation. - * @param frequency The frequency of file rotation ('M' for minutes, 'H' for hours) - * @param interval The rotation interval - */ - QUILL_ATTRIBUTE_COLD void set_rotation_frequency_and_interval(char frequency, uint32_t interval) - { - if (frequency == 'M' || frequency == 'm') - { - _rotation_frequency = RotationFrequency::Minutely; - } - else if (frequency == 'H' || frequency == 'h') - { - _rotation_frequency = RotationFrequency::Hourly; - } - else - { - QUILL_THROW(QuillError{ - "Invalid frequency. Valid values are 'M' or 'm' for minutes or 'H' or 'h' for hours"}); - } - - if (interval == 0) - { - QUILL_THROW(QuillError{"interval must be set to a value greater than 0"}); - } - - _rotation_interval = interval; - _daily_rotation_time = _disabled_daily_rotation_time(); - } - - /** - * @brief Sets the time of day for daily log file rotation. - * @param daily_rotation_time_str The time of day for rotation (format: "HH:MM") - */ - QUILL_ATTRIBUTE_COLD void set_rotation_time_daily(std::string const& daily_rotation_time_str) - { - _rotation_frequency = RotationFrequency::Daily; - _rotation_interval = 0; - _daily_rotation_time = _parse_daily_rotation_time(daily_rotation_time_str); - } - - /** - * @brief Sets the maximum number of log files to keep. - * @param value The maximum number of log files - */ - QUILL_ATTRIBUTE_COLD void set_max_backup_files(uint32_t value) { _max_backup_files = value; } - - /** - * @brief Sets whether the oldest rolled logs should be overwritten when the maximum backup count - * is reached. If set to false, the oldest logs will not be overwritten when the maximum backup - * count is reached, and log file rotation will stop. The default value is true. - * @param value True to overwrite the oldest logs, false otherwise. - */ - QUILL_ATTRIBUTE_COLD void set_overwrite_rolled_files(bool value) - { - _overwrite_rolled_files = value; - } - - /** - * @brief Sets whether previous rotated log files should be removed on process start up. - * @note This option works only when using the mode="w" - * This is useful to avoid conflicting file names when the process restarts and - * FilenameAppend::DateTime was not set. The default value is true. - * @param value True to remove old log files, false otherwise. - */ - QUILL_ATTRIBUTE_COLD void set_remove_old_files(bool value) { _remove_old_files = value; } - - /** - * @brief Sets the naming scheme for the rotated files. - * The default value is 'Index'. - * @param value The naming scheme to set. - */ - QUILL_ATTRIBUTE_COLD void set_rotation_naming_scheme(RotationNamingScheme value) - { - _rotation_naming_scheme = value; - } - - /** Getter methods **/ - QUILL_NODISCARD size_t rotation_max_file_size() const noexcept { return _rotation_max_file_size; } - QUILL_NODISCARD uint32_t max_backup_files() const noexcept { return _max_backup_files; } - QUILL_NODISCARD bool overwrite_rolled_files() const noexcept { return _overwrite_rolled_files; } - QUILL_NODISCARD bool remove_old_files() const noexcept { return _remove_old_files; } - QUILL_NODISCARD RotationFrequency rotation_frequency() const noexcept - { - return _rotation_frequency; - } - QUILL_NODISCARD uint32_t rotation_interval() const noexcept { return _rotation_interval; } - QUILL_NODISCARD std::pair daily_rotation_time() const noexcept - { - return _daily_rotation_time; - } - QUILL_NODISCARD RotationNamingScheme rotation_naming_scheme() const noexcept - { - return _rotation_naming_scheme; - } - -private: - /***/ - static std::pair _disabled_daily_rotation_time() noexcept - { - return std::make_pair(std::chrono::hours{std::numeric_limits::max()}, - std::chrono::minutes{std::numeric_limits::max()}); - } - - /***/ - static std::pair _parse_daily_rotation_time(std::string const& daily_rotation_time_str) - { - std::vector tokens; - std::string token; - size_t start = 0, end = 0; - - while ((end = daily_rotation_time_str.find(':', start)) != std::string::npos) - { - token = daily_rotation_time_str.substr(start, end - start); - tokens.push_back(token); - start = end + 1; - } - - // Add the last token (or the only token if there's no delimiter) - token = daily_rotation_time_str.substr(start); - tokens.push_back(token); - - if (tokens.size() != 2) - { - QUILL_THROW( - QuillError{"Invalid daily_rotation_time_str value format. The format should be `HH:MM`."}); - } - - for (auto const& parsed_token : tokens) - { - if (parsed_token.size() != 2) - { - QUILL_THROW(QuillError{ - "Invalid daily_rotation_time_str value format. Each component of the time (HH and MM) " - "should be two digits."}); - } - } - - auto const daily_rotation_time_str_tp = std::make_pair( - std::chrono::hours{std::stoi(tokens[0])}, std::chrono::minutes{std::stoi(tokens[1])}); - - if ((daily_rotation_time_str_tp.first > std::chrono::hours{23}) || - (daily_rotation_time_str_tp.second > std::chrono::minutes{59})) - { - QUILL_THROW( - QuillError("Invalid rotation values. The hour value should be between 00 and 23, and the " - "minute value should be between 00 and 59.")); - } - - return daily_rotation_time_str_tp; - } - -private: - std::pair _daily_rotation_time; - size_t _rotation_max_file_size{0}; // 0 means disabled - uint32_t _max_backup_files{std::numeric_limits::max()}; // max means disabled - uint32_t _rotation_interval{0}; // 0 means disabled - RotationFrequency _rotation_frequency{RotationFrequency::Disabled}; - RotationNamingScheme _rotation_naming_scheme{RotationNamingScheme::Index}; - bool _overwrite_rolled_files{true}; - bool _remove_old_files{true}; -}; - -/** - * @brief The RotatingFileSink class - */ -class RotatingFileSink : public FileSink -{ -public: - /** - * @brief Constructor. - * - * Creates a new instance of the RotatingFileSink class. - * - * @param filename The base file name to be used for logs. - * @param config The sink configuration. - * @param file_event_notifier file event notifier - * @param start_time start time - */ - RotatingFileSink(fs::path const& filename, RotatingFileSinkConfig const& config, - FileEventNotifier file_event_notifier = FileEventNotifier{}, - std::chrono::system_clock::time_point start_time = std::chrono::system_clock::now()) - : FileSink(filename, static_cast(config), std::move(file_event_notifier), false), - _config(config) - { - uint64_t const today_timestamp_ns = static_cast( - std::chrono::duration_cast(start_time.time_since_epoch()).count()); - - _clean_and_recover_files(filename, _config.open_mode(), today_timestamp_ns); - - if (_config.rotation_frequency() != RotatingFileSinkConfig::RotationFrequency::Disabled) - { - // Calculate next rotation time - _next_rotation_time = _calculate_initial_rotation_tp( - static_cast( - std::chrono::duration_cast(start_time.time_since_epoch()).count()), - config); - } - - // Open file for logging - open_file(_filename, _config.open_mode()); - _open_file_timestamp = static_cast( - std::chrono::duration_cast(start_time.time_since_epoch()).count()); - - _created_files.emplace_front(_filename, 0, std::string{}); - - if (!is_null()) - { - _file_size = _get_file_size(_filename); - } - } - - ~RotatingFileSink() override = default; - - /** - * @brief Writes a formatted log message to the stream - * @param log_metadata The metadata of the log message - * @param log_timestamp The timestamp of the log message - * @param thread_id The ID of the thread that generated the log message - * @param thread_name The name of the thread that generated the log message - * @param process_id Process Id - * @param log_level Log level of the message. - * @param log_level_description Description of the log level. - * @param log_level_short_code Short code representing the log level. - * @param named_args Structured key-value pairs associated with the log message - * @param log_message The log message to write - * @param log_statement The full log statement - */ - QUILL_ATTRIBUTE_HOT void write_log(MacroMetadata const* log_metadata, uint64_t log_timestamp, - std::string_view thread_id, std::string_view thread_name, - std::string const& process_id, std::string_view logger_name, LogLevel log_level, std::string_view log_level_description, - std::string_view log_level_short_code, - std::vector> const* named_args, - std::string_view log_message, std::string_view log_statement) override - { - if (is_null()) - { - StreamSink::write_log(log_metadata, log_timestamp, thread_id, thread_name, process_id, - logger_name, log_level, log_level_description, log_level_short_code, - named_args, log_message, log_statement); - return; - } - - bool time_rotation = false; - - if (_config.rotation_frequency() != RotatingFileSinkConfig::RotationFrequency::Disabled) - { - // Check if we need to rotate based on time - time_rotation = _time_rotation(log_timestamp); - } - - if (!time_rotation && _config.rotation_max_file_size() != 0) - { - // Check if we need to rotate based on size - _size_rotation(log_statement.size(), log_timestamp); - } - - // write to file - StreamSink::write_log(log_metadata, log_timestamp, thread_id, thread_name, process_id, - logger_name, log_level, log_level_description, log_level_short_code, - named_args, log_message, log_statement); - - _file_size += log_statement.size(); - } - -private: - /***/ - QUILL_NODISCARD bool _time_rotation(uint64_t record_timestamp_ns) - { - if (record_timestamp_ns >= _next_rotation_time) - { - _rotate_files(record_timestamp_ns); - _next_rotation_time = _calculate_rotation_tp(record_timestamp_ns, _config); - return true; - } - - return false; - } - - /***/ - void _size_rotation(size_t log_msg_size, uint64_t record_timestamp_ns) - { - // Calculate the new size of the file - if (_file_size + log_msg_size > _config.rotation_max_file_size()) - { - _rotate_files(record_timestamp_ns); - } - } - - /***/ - void _rotate_files(uint64_t record_timestamp_ns) - { - if ((_created_files.size() > _config.max_backup_files()) && !_config.overwrite_rolled_files()) - { - // We have reached the max number of backup files, and we are not allowed to overwrite the - // oldest file. We will stop rotating - return; - } - - // We need to flush and also fsync before actually getting the size of the file - FileSink::flush_sink(); - FileSink::fsync_file(true); - - if (_get_file_size(_filename) <= 0) - { - // Also check the file size is > 0 to better deal with full disk - return; - } - - close_file(); - - // datetime_suffix will be empty if we are using the default naming scheme - std::string datetime_suffix; - if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::Date) - { - datetime_suffix = format_datetime_string(_open_file_timestamp, _config.timezone(), "%Y%m%d"); - } - else if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::DateAndTime) - { - datetime_suffix = - format_datetime_string(_open_file_timestamp, _config.timezone(), "%Y%m%d_%H%M%S"); - } - - // We need to rotate the files and rename them with an index - for (auto it = _created_files.rbegin(); it != _created_files.rend(); ++it) - { - // Create each existing filename on disk with the existing index. - // when the index is 0 we want to rename the latest file - fs::path existing_file; - fs::path renamed_file; - - existing_file = _get_filename(it->base_filename, it->index, it->date_time); - - // increment the index if needed and rename the file - uint32_t index_to_use = it->index; - - if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::Index || - it->date_time == datetime_suffix) - { - // we are rotating and incrementing the index, or we have another file with the same date_time suffix - index_to_use += 1; - - renamed_file = _get_filename(it->base_filename, index_to_use, datetime_suffix); - - it->index = index_to_use; - it->date_time = datetime_suffix; - - _rename_file(existing_file, renamed_file); - } - else if (it->date_time.empty()) - { - // we are renaming the latest file - index_to_use = it->index; - - renamed_file = _get_filename(it->base_filename, index_to_use, datetime_suffix); - - it->index = index_to_use; - it->date_time = datetime_suffix; - - _rename_file(existing_file, renamed_file); - } - } - - // Check if we have too many files in the queue remove_file the oldest one - if (_created_files.size() > _config.max_backup_files()) - { - // remove_file that file from the system and also pop it from the queue - fs::path const removed_file = _get_filename( - _created_files.back().base_filename, _created_files.back().index, _created_files.back().date_time); - _remove_file(removed_file); - _created_files.pop_back(); - } - - // add the current file back to the list with index 0 - _created_files.emplace_front(_filename, 0, std::string{}); - - // Open file for logging - open_file(_filename, "w"); - _open_file_timestamp = record_timestamp_ns; - _file_size = 0; - } - - /***/ - void _clean_and_recover_files(fs::path const& filename, std::string const& open_mode, uint64_t today_timestamp_ns) - { - if ((_config.rotation_naming_scheme() != RotatingFileSinkConfig::RotationNamingScheme::Index) && - (_config.rotation_naming_scheme() != RotatingFileSinkConfig::RotationNamingScheme::Date)) - { - // clean and recover is only supported for index and date naming scheme, when using - // DateAndTime there are no collisions in the filenames - return; - } - - // if we are starting in "w" mode, then we also should clean all previous log files of the previous run - if (_config.remove_old_files() && (open_mode == "w")) - { - for (const auto& entry : fs::directory_iterator(fs::current_path() / filename.parent_path())) - { - if (entry.path().extension().string() != filename.extension().string()) - { - // we only check for the files of the same extension to remove - continue; - } - - // is_directory() does not exist in std::experimental::filesystem - if (entry.path().filename().string().find(filename.stem().string() + ".") != 0) - { - // expect to find filename.stem().string() exactly at the start of the filename - continue; - } - - if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::Index) - { - fs::remove(entry); - } - else if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::Date) - { - // Find the first dot in the filename - // stem will be something like `logfile.1` - if (size_t const pos = entry.path().stem().string().find_last_of('.'); pos != std::string::npos) - { - // Get the today's date, we won't remove the files of the previous dates as they won't collide - std::string const today_date = - format_datetime_string(today_timestamp_ns, _config.timezone(), "%Y%m%d"); - - if (std::string const index_or_date = - entry.path().stem().string().substr(pos + 1, entry.path().stem().string().length()); - (index_or_date.length() >= 8) && (index_or_date == today_date)) - { - // assume it is a date, no need to find the index - if (index_or_date == today_date) - { - fs::remove(entry); - } - } - else - { - // assume it is an index - // Find the second last dot to get the date - std::string const filename_with_date = entry.path().filename().string().substr(0, pos); - - if (size_t const second_last = filename_with_date.find_last_of('.'); second_last != std::string::npos) - { - if (std::string const date_part = - filename_with_date.substr(second_last + 1, filename_with_date.length()); - date_part == today_date) - { - fs::remove(entry); - } - } - } - } - } - } - } - else if (open_mode == "a") - { - // we need to recover the index from the existing files - for (const auto& entry : fs::directory_iterator(fs::current_path() / filename.parent_path())) - { - // is_directory() does not exist in std::experimental::filesystem - if (entry.path().extension().string() != filename.extension().string()) - { - // we only check for the files of the same extension to remove - continue; - } - - // is_directory() does not exist in std::experimental::filesystem - if (entry.path().filename().string().find(filename.stem().string() + ".") != 0) - { - // expect to find filename.stem().string() exactly at the start of the filename - continue; - } - - std::string const extension = entry.path().extension().string(); // e.g. ".log" - - // stem will be something like `logfile.1` - if (size_t const pos = entry.path().stem().string().find_last_of('.'); pos != std::string::npos) - { - if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::Index) - { - std::string const index = - entry.path().stem().string().substr(pos + 1, entry.path().stem().string().length()); - - std::string const current_filename = entry.path().filename().string().substr(0, pos) + extension; - fs::path current_file = entry.path().parent_path(); - current_file.append(current_filename); - - // Attempt to convert the index to a number - QUILL_TRY - { - _created_files.emplace_front(current_file, static_cast(std::stoul(index)), - std::string{}); - } - QUILL_CATCH_ALL() { continue; } - } - else if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::Date) - { - // Get the today's date, we won't remove the files of the previous dates as they won't collide - std::string const today_date = - format_datetime_string(today_timestamp_ns, _config.timezone(), "%Y%m%d"); - - if (std::string const index_or_date = - entry.path().stem().string().substr(pos + 1, entry.path().stem().string().length()); - (index_or_date.length() >= 8) && (index_or_date == today_date)) - { - // assume it is a date, no need to find the index - std::string const current_filename = entry.path().filename().string().substr(0, pos) + extension; - fs::path current_file = entry.path().parent_path(); - current_file.append(current_filename); - - _created_files.emplace_front(current_file, 0, index_or_date); - } - else - { - // assume it is an index - // Find the second last dot to get the date - std::string const filename_with_date = entry.path().filename().string().substr(0, pos); - - if (size_t const second_last = filename_with_date.find_last_of('.'); second_last != std::string::npos) - { - if (std::string const date_part = - filename_with_date.substr(second_last + 1, filename_with_date.length()); - date_part == today_date) - { - std::string const current_filename = filename_with_date.substr(0, second_last) + extension; - fs::path current_file = entry.path().parent_path(); - current_file.append(current_filename); - - // Attempt to convert the index to a number - QUILL_TRY - { - _created_files.emplace_front( - current_file, static_cast(std::stoul(index_or_date)), date_part); - } - QUILL_CATCH_ALL() { continue; } - } - } - } - } - } - } - - // finally we need to sort the deque - std::sort(_created_files.begin(), _created_files.end(), - [](FileInfo const& a, FileInfo const& b) { return a.index < b.index; }); - } - } - - /***/ - QUILL_NODISCARD static size_t _get_file_size(fs::path const& filename) - { - return static_cast(fs::file_size(filename)); - } - - /***/ - static bool _remove_file(fs::path const& filename) noexcept - { - std::error_code ec; - fs::remove(filename, ec); - - if (ec) - { - return false; - } - - return true; - } - - /***/ - bool static _rename_file(fs::path const& previous_file, fs::path const& new_file) noexcept - { - std::error_code ec; - fs::rename(previous_file, new_file, ec); - - if (ec) - { - return false; - } - - return true; - } - - /***/ - QUILL_NODISCARD static fs::path _append_index_to_filename(fs::path const& filename, uint32_t index) noexcept - { - if (index == 0u) - { - return filename; - } - - // Get base file and extension - auto const [stem, ext] = extract_stem_and_extension(filename); - return fs::path{stem + "." + std::to_string(index) + ext}; - } - - /***/ - QUILL_NODISCARD static fs::path _append_string_to_filename(fs::path const& filename, std::string const& text) noexcept - { - if (text.empty()) - { - return filename; - } - - // Get base file and extension - auto const [stem, ext] = extract_stem_and_extension(filename); - return fs::path{stem + "." + text + ext}; - } - - /***/ - static uint64_t _calculate_initial_rotation_tp(uint64_t start_time_ns, RotatingFileSinkConfig const& config) - { - time_t const time_now = static_cast(start_time_ns) / 1000000000; - tm date; - - // here we do this because of `daily_rotation_time_str` that might have specified the time in UTC - if (config.timezone() == Timezone::GmtTime) - { - detail::gmtime_rs(&time_now, &date); - } - else - { - detail::localtime_rs(&time_now, &date); - } - - // update to the desired date - if (config.rotation_frequency() == RotatingFileSinkConfig::RotationFrequency::Minutely) - { - date.tm_min += 1; - date.tm_sec = 0; - } - else if (config.rotation_frequency() == RotatingFileSinkConfig::RotationFrequency::Hourly) - { - date.tm_hour += 1; - date.tm_min = 0; - date.tm_sec = 0; - } - else if (config.rotation_frequency() == RotatingFileSinkConfig::RotationFrequency::Daily) - { - date.tm_hour = static_cast(config.daily_rotation_time().first.count()); - date.tm_min = static_cast(config.daily_rotation_time().second.count()); - date.tm_sec = 0; - } - else - { - QUILL_THROW(QuillError{"Invalid rotation frequency"}); - } - - // convert back to timestamp - time_t const rotation_time = - (config.timezone() == Timezone::GmtTime) ? detail::timegm(&date) : std::mktime(&date); - - uint64_t const rotation_time_seconds = (rotation_time > time_now) - ? static_cast(rotation_time) - : static_cast(rotation_time + std::chrono::seconds{std::chrono::hours{24}}.count()); - - return static_cast( - std::chrono::nanoseconds{std::chrono::seconds{rotation_time_seconds}}.count()); - } - - /***/ - static uint64_t _calculate_rotation_tp(uint64_t rotation_timestamp_ns, RotatingFileSinkConfig const& config) - { - if (config.rotation_frequency() == RotatingFileSinkConfig::RotationFrequency::Minutely) - { - return rotation_timestamp_ns + - static_cast( - std::chrono::nanoseconds{std::chrono::minutes{config.rotation_interval()}}.count()); - } - - if (config.rotation_frequency() == RotatingFileSinkConfig::RotationFrequency::Hourly) - { - return rotation_timestamp_ns + - static_cast( - std::chrono::nanoseconds{std::chrono::hours{config.rotation_interval()}}.count()); - } - - if (config.rotation_frequency() == RotatingFileSinkConfig::RotationFrequency::Daily) - { - return rotation_timestamp_ns + std::chrono::nanoseconds{std::chrono::hours{24}}.count(); - } - - QUILL_THROW(QuillError{"Invalid rotation frequency"}); - } - - /***/ - static fs::path _get_filename(fs::path filename, uint32_t index, std::string const& date_time) - { - if (!date_time.empty()) - { - filename = _append_string_to_filename(filename, date_time); - } - - if (index > 0) - { - filename = _append_index_to_filename(filename, index); - } - - return filename; - } - -private: - struct FileInfo - { - FileInfo(fs::path base_filename, uint32_t index, std::string date_time) - : base_filename{std::move(base_filename)}, date_time{std::move(date_time)}, index{index} - { - } - - fs::path base_filename; - std::string date_time; - uint32_t index; - }; - - FileEventNotifier _file_event_notifier; - std::deque _created_files; /**< We store in a queue the filenames we created, first: index, second: date/datetime, third: base_filename */ - uint64_t _next_rotation_time; /**< The next rotation time point */ - uint64_t _open_file_timestamp{0}; /**< The timestamp of the currently open file */ - size_t _file_size{0}; /**< The current file size */ - RotatingFileSinkConfig _config; -}; +using RotatingFileSink = RotatingSink; QUILL_END_NAMESPACE \ No newline at end of file diff --git a/include/quill/sinks/RotatingJsonFileSink.h b/include/quill/sinks/RotatingJsonFileSink.h new file mode 100644 index 00000000..28f6cdf9 --- /dev/null +++ b/include/quill/sinks/RotatingJsonFileSink.h @@ -0,0 +1,16 @@ +/** + * @page copyright + * Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors. + * Distributed under the MIT License (http://opensource.org/licenses/MIT) + */ + +#pragma once + +#include "quill/sinks/RotatingSink.h" +#include "quill/sinks/JsonSink.h" + +QUILL_BEGIN_NAMESPACE + +using RotatingJsonFileSink = RotatingSink; + +QUILL_END_NAMESPACE \ No newline at end of file diff --git a/include/quill/sinks/RotatingSink.h b/include/quill/sinks/RotatingSink.h new file mode 100644 index 00000000..0b000c99 --- /dev/null +++ b/include/quill/sinks/RotatingSink.h @@ -0,0 +1,810 @@ +/** + * @page copyright + * Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors. + * Distributed under the MIT License (http://opensource.org/licenses/MIT) + */ + +#pragma once + +#include "quill/core/Attributes.h" +#include "quill/core/Common.h" +#include "quill/core/Filesystem.h" +#include "quill/core/LogLevel.h" +#include "quill/core/QuillError.h" +#include "quill/core/TimeUtilities.h" +#include "quill/sinks/FileSink.h" +#include "quill/sinks/StreamSink.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QUILL_BEGIN_NAMESPACE + +/** Forward Declaration **/ +class MacroMetadata; + +/** + * @brief The configuration options for the RotatingSink + */ +class RotatingFileSinkConfig : public FileSinkConfig +{ +public: + /** + * @brief The frequency of log file rotation + */ + enum class RotationFrequency : uint8_t + { + Disabled, + Daily, + Hourly, + Minutely + }; + + /** + * @brief The naming scheme for rotated log files + */ + enum class RotationNamingScheme : uint8_t + { + Index, + Date, + DateAndTime + }; + + /** + * @brief Constructs a new RotatingFileSinkConfig object. + */ + RotatingFileSinkConfig() : _daily_rotation_time{_disabled_daily_rotation_time()} {} + + /** + * @brief Sets the maximum file size for rotation. + * @param value The maximum file size in bytes per file + */ + QUILL_ATTRIBUTE_COLD void set_rotation_max_file_size(size_t value) + { + if (value < 512) + { + QUILL_THROW(QuillError{"rotation_max_file_size must be greater than or equal to 512 bytes"}); + } + + _rotation_max_file_size = value; + } + + /** + * @brief Sets the frequency and interval of file rotation. + * @param frequency The frequency of file rotation ('M' for minutes, 'H' for hours) + * @param interval The rotation interval + */ + QUILL_ATTRIBUTE_COLD void set_rotation_frequency_and_interval(char frequency, uint32_t interval) + { + if (frequency == 'M' || frequency == 'm') + { + _rotation_frequency = RotationFrequency::Minutely; + } + else if (frequency == 'H' || frequency == 'h') + { + _rotation_frequency = RotationFrequency::Hourly; + } + else + { + QUILL_THROW(QuillError{ + "Invalid frequency. Valid values are 'M' or 'm' for minutes or 'H' or 'h' for hours"}); + } + + if (interval == 0) + { + QUILL_THROW(QuillError{"interval must be set to a value greater than 0"}); + } + + _rotation_interval = interval; + _daily_rotation_time = _disabled_daily_rotation_time(); + } + + /** + * @brief Sets the time of day for daily log file rotation. + * @param daily_rotation_time_str The time of day for rotation (format: "HH:MM") + */ + QUILL_ATTRIBUTE_COLD void set_rotation_time_daily(std::string const& daily_rotation_time_str) + { + _rotation_frequency = RotationFrequency::Daily; + _rotation_interval = 0; + _daily_rotation_time = _parse_daily_rotation_time(daily_rotation_time_str); + } + + /** + * @brief Sets the maximum number of log files to keep. + * @param value The maximum number of log files + */ + QUILL_ATTRIBUTE_COLD void set_max_backup_files(uint32_t value) { _max_backup_files = value; } + + /** + * @brief Sets whether the oldest rolled logs should be overwritten when the maximum backup count + * is reached. If set to false, the oldest logs will not be overwritten when the maximum backup + * count is reached, and log file rotation will stop. The default value is true. + * @param value True to overwrite the oldest logs, false otherwise. + */ + QUILL_ATTRIBUTE_COLD void set_overwrite_rolled_files(bool value) + { + _overwrite_rolled_files = value; + } + + /** + * @brief Sets whether previous rotated log files should be removed on process start up. + * @note This option works only when using the mode="w" + * This is useful to avoid conflicting file names when the process restarts and + * FilenameAppend::DateTime was not set. The default value is true. + * @param value True to remove old log files, false otherwise. + */ + QUILL_ATTRIBUTE_COLD void set_remove_old_files(bool value) { _remove_old_files = value; } + + /** + * @brief Sets the naming scheme for the rotated files. + * The default value is 'Index'. + * @param value The naming scheme to set. + */ + QUILL_ATTRIBUTE_COLD void set_rotation_naming_scheme(RotationNamingScheme value) + { + _rotation_naming_scheme = value; + } + + /** Getter methods **/ + QUILL_NODISCARD size_t rotation_max_file_size() const noexcept { return _rotation_max_file_size; } + QUILL_NODISCARD uint32_t max_backup_files() const noexcept { return _max_backup_files; } + QUILL_NODISCARD bool overwrite_rolled_files() const noexcept { return _overwrite_rolled_files; } + QUILL_NODISCARD bool remove_old_files() const noexcept { return _remove_old_files; } + QUILL_NODISCARD RotationFrequency rotation_frequency() const noexcept + { + return _rotation_frequency; + } + QUILL_NODISCARD uint32_t rotation_interval() const noexcept { return _rotation_interval; } + QUILL_NODISCARD std::pair daily_rotation_time() const noexcept + { + return _daily_rotation_time; + } + QUILL_NODISCARD RotationNamingScheme rotation_naming_scheme() const noexcept + { + return _rotation_naming_scheme; + } + +private: + /***/ + static std::pair _disabled_daily_rotation_time() noexcept + { + return std::make_pair(std::chrono::hours{std::numeric_limits::max()}, + std::chrono::minutes{std::numeric_limits::max()}); + } + + /***/ + static std::pair _parse_daily_rotation_time(std::string const& daily_rotation_time_str) + { + std::vector tokens; + std::string token; + size_t start = 0, end = 0; + + while ((end = daily_rotation_time_str.find(':', start)) != std::string::npos) + { + token = daily_rotation_time_str.substr(start, end - start); + tokens.push_back(token); + start = end + 1; + } + + // Add the last token (or the only token if there's no delimiter) + token = daily_rotation_time_str.substr(start); + tokens.push_back(token); + + if (tokens.size() != 2) + { + QUILL_THROW( + QuillError{"Invalid daily_rotation_time_str value format. The format should be `HH:MM`."}); + } + + for (auto const& parsed_token : tokens) + { + if (parsed_token.size() != 2) + { + QUILL_THROW(QuillError{ + "Invalid daily_rotation_time_str value format. Each component of the time (HH and MM) " + "should be two digits."}); + } + } + + auto const daily_rotation_time_str_tp = std::make_pair( + std::chrono::hours{std::stoi(tokens[0])}, std::chrono::minutes{std::stoi(tokens[1])}); + + if ((daily_rotation_time_str_tp.first > std::chrono::hours{23}) || + (daily_rotation_time_str_tp.second > std::chrono::minutes{59})) + { + QUILL_THROW( + QuillError("Invalid rotation values. The hour value should be between 00 and 23, and the " + "minute value should be between 00 and 59.")); + } + + return daily_rotation_time_str_tp; + } + +private: + std::pair _daily_rotation_time; + size_t _rotation_max_file_size{0}; // 0 means disabled + uint32_t _max_backup_files{std::numeric_limits::max()}; // max means disabled + uint32_t _rotation_interval{0}; // 0 means disabled + RotationFrequency _rotation_frequency{RotationFrequency::Disabled}; + RotationNamingScheme _rotation_naming_scheme{RotationNamingScheme::Index}; + bool _overwrite_rolled_files{true}; + bool _remove_old_files{true}; +}; + +/** + * @brief The RotatingSink class + */ +template +class RotatingSink : public TBase +{ +public: + using base_type = TBase; + + /** + * @brief Constructor. + * + * Creates a new instance of the RotatingSink class. + * + * @param filename The base file name to be used for logs. + * @param config The sink configuration. + * @param file_event_notifier file event notifier + * @param start_time start time + */ + RotatingSink(fs::path const& filename, RotatingFileSinkConfig const& config, + FileEventNotifier file_event_notifier = FileEventNotifier{}, + std::chrono::system_clock::time_point start_time = std::chrono::system_clock::now()) + : base_type(filename, static_cast(config), std::move(file_event_notifier), false), + _config(config) + { + uint64_t const today_timestamp_ns = static_cast( + std::chrono::duration_cast(start_time.time_since_epoch()).count()); + + _clean_and_recover_files(filename, _config.open_mode(), today_timestamp_ns); + + if (_config.rotation_frequency() != RotatingFileSinkConfig::RotationFrequency::Disabled) + { + // Calculate next rotation time + _next_rotation_time = _calculate_initial_rotation_tp( + static_cast( + std::chrono::duration_cast(start_time.time_since_epoch()).count()), + config); + } + + // Open file for logging + this->open_file(this->_filename, _config.open_mode()); + _open_file_timestamp = static_cast( + std::chrono::duration_cast(start_time.time_since_epoch()).count()); + + _created_files.emplace_front(this->_filename, 0, std::string{}); + + if (!this->is_null()) + { + _file_size = _get_file_size(this->_filename); + } + } + + ~RotatingSink() override = default; + + /** + * @brief Writes a formatted log message to the stream + * @param log_metadata The metadata of the log message + * @param log_timestamp The timestamp of the log message + * @param thread_id The ID of the thread that generated the log message + * @param thread_name The name of the thread that generated the log message + * @param process_id Process Id + * @param logger_name logger name + * @param logger_name logger name + * @param log_level Log level of the message. + * @param log_level_description Description of the log level. + * @param log_level_short_code Short code representing the log level. + * @param named_args Structured key-value pairs associated with the log message + * @param log_message The log message to write + * @param log_statement The full log statement + */ + QUILL_ATTRIBUTE_HOT void write_log(MacroMetadata const* log_metadata, uint64_t log_timestamp, + std::string_view thread_id, std::string_view thread_name, + std::string const& process_id, std::string_view logger_name, + LogLevel log_level, std::string_view log_level_description, + std::string_view log_level_short_code, + std::vector> const* named_args, + std::string_view log_message, std::string_view log_statement) override + { + if (this->is_null()) + { + base_type::write_log(log_metadata, log_timestamp, thread_id, thread_name, process_id, + logger_name, log_level, log_level_description, log_level_short_code, + named_args, log_message, log_statement); + return; + } + + bool time_rotation = false; + + if (_config.rotation_frequency() != RotatingFileSinkConfig::RotationFrequency::Disabled) + { + // Check if we need to rotate based on time + time_rotation = _time_rotation(log_timestamp); + } + + if (!time_rotation && _config.rotation_max_file_size() != 0) + { + // Check if we need to rotate based on size + _size_rotation(log_statement.size(), log_timestamp); + } + + // write to file + base_type::write_log(log_metadata, log_timestamp, thread_id, thread_name, process_id, + logger_name, log_level, log_level_description, log_level_short_code, + named_args, log_message, log_statement); + + _file_size += log_statement.size(); + } + +private: + /***/ + QUILL_NODISCARD bool _time_rotation(uint64_t record_timestamp_ns) + { + if (record_timestamp_ns >= _next_rotation_time) + { + _rotate_files(record_timestamp_ns); + _next_rotation_time = _calculate_rotation_tp(record_timestamp_ns, _config); + return true; + } + + return false; + } + + /***/ + void _size_rotation(size_t log_msg_size, uint64_t record_timestamp_ns) + { + // Calculate the new size of the file + if (_file_size + log_msg_size > _config.rotation_max_file_size()) + { + _rotate_files(record_timestamp_ns); + } + } + + /***/ + void _rotate_files(uint64_t record_timestamp_ns) + { + if ((_created_files.size() > _config.max_backup_files()) && !_config.overwrite_rolled_files()) + { + // We have reached the max number of backup files, and we are not allowed to overwrite the + // oldest file. We will stop rotating + return; + } + + // We need to flush and also fsync before actually getting the size of the file + base_type::flush_sink(); + base_type::fsync_file(true); + + if (_get_file_size(this->_filename) <= 0) + { + // Also check the file size is > 0 to better deal with full disk + return; + } + + this->close_file(); + + // datetime_suffix will be empty if we are using the default naming scheme + std::string datetime_suffix; + if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::Date) + { + datetime_suffix = + this->format_datetime_string(_open_file_timestamp, _config.timezone(), "%Y%m%d"); + } + else if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::DateAndTime) + { + datetime_suffix = + this->format_datetime_string(_open_file_timestamp, _config.timezone(), "%Y%m%d_%H%M%S"); + } + + // We need to rotate the files and rename them with an index + for (auto it = _created_files.rbegin(); it != _created_files.rend(); ++it) + { + // Create each existing filename on disk with the existing index. + // when the index is 0 we want to rename the latest file + fs::path existing_file; + fs::path renamed_file; + + existing_file = _get_filename(it->base_filename, it->index, it->date_time); + + // increment the index if needed and rename the file + uint32_t index_to_use = it->index; + + if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::Index || + it->date_time == datetime_suffix) + { + // we are rotating and incrementing the index, or we have another file with the same date_time suffix + index_to_use += 1; + + renamed_file = _get_filename(it->base_filename, index_to_use, datetime_suffix); + + it->index = index_to_use; + it->date_time = datetime_suffix; + + _rename_file(existing_file, renamed_file); + } + else if (it->date_time.empty()) + { + // we are renaming the latest file + index_to_use = it->index; + + renamed_file = _get_filename(it->base_filename, index_to_use, datetime_suffix); + + it->index = index_to_use; + it->date_time = datetime_suffix; + + _rename_file(existing_file, renamed_file); + } + } + + // Check if we have too many files in the queue remove_file the oldest one + if (_created_files.size() > _config.max_backup_files()) + { + // remove_file that file from the system and also pop it from the queue + fs::path const removed_file = _get_filename( + _created_files.back().base_filename, _created_files.back().index, _created_files.back().date_time); + _remove_file(removed_file); + _created_files.pop_back(); + } + + // add the current file back to the list with index 0 + _created_files.emplace_front(this->_filename, 0, std::string{}); + + // Open file for logging + this->open_file(this->_filename, "w"); + _open_file_timestamp = record_timestamp_ns; + _file_size = 0; + } + + /***/ + void _clean_and_recover_files(fs::path const& filename, std::string const& open_mode, uint64_t today_timestamp_ns) + { + if ((_config.rotation_naming_scheme() != RotatingFileSinkConfig::RotationNamingScheme::Index) && + (_config.rotation_naming_scheme() != RotatingFileSinkConfig::RotationNamingScheme::Date)) + { + // clean and recover is only supported for index and date naming scheme, when using + // DateAndTime there are no collisions in the filenames + return; + } + + // if we are starting in "w" mode, then we also should clean all previous log files of the previous run + if (_config.remove_old_files() && (open_mode == "w")) + { + for (const auto& entry : fs::directory_iterator(fs::current_path() / filename.parent_path())) + { + if (entry.path().extension().string() != filename.extension().string()) + { + // we only check for the files of the same extension to remove + continue; + } + + // is_directory() does not exist in std::experimental::filesystem + if (entry.path().filename().string().find(filename.stem().string() + ".") != 0) + { + // expect to find filename.stem().string() exactly at the start of the filename + continue; + } + + if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::Index) + { + fs::remove(entry); + } + else if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::Date) + { + // Find the first dot in the filename + // stem will be something like `logfile.1` + if (size_t const pos = entry.path().stem().string().find_last_of('.'); pos != std::string::npos) + { + // Get the today's date, we won't remove the files of the previous dates as they won't collide + std::string const today_date = + this->format_datetime_string(today_timestamp_ns, _config.timezone(), "%Y%m%d"); + + if (std::string const index_or_date = + entry.path().stem().string().substr(pos + 1, entry.path().stem().string().length()); + (index_or_date.length() >= 8) && (index_or_date == today_date)) + { + // assume it is a date, no need to find the index + if (index_or_date == today_date) + { + fs::remove(entry); + } + } + else + { + // assume it is an index + // Find the second last dot to get the date + std::string const filename_with_date = entry.path().filename().string().substr(0, pos); + + if (size_t const second_last = filename_with_date.find_last_of('.'); second_last != std::string::npos) + { + if (std::string const date_part = + filename_with_date.substr(second_last + 1, filename_with_date.length()); + date_part == today_date) + { + fs::remove(entry); + } + } + } + } + } + } + } + else if (open_mode == "a") + { + // we need to recover the index from the existing files + for (const auto& entry : fs::directory_iterator(fs::current_path() / filename.parent_path())) + { + // is_directory() does not exist in std::experimental::filesystem + if (entry.path().extension().string() != filename.extension().string()) + { + // we only check for the files of the same extension to remove + continue; + } + + // is_directory() does not exist in std::experimental::filesystem + if (entry.path().filename().string().find(filename.stem().string() + ".") != 0) + { + // expect to find filename.stem().string() exactly at the start of the filename + continue; + } + + std::string const extension = entry.path().extension().string(); // e.g. ".log" + + // stem will be something like `logfile.1` + if (size_t const pos = entry.path().stem().string().find_last_of('.'); pos != std::string::npos) + { + if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::Index) + { + std::string const index = + entry.path().stem().string().substr(pos + 1, entry.path().stem().string().length()); + + std::string const current_filename = entry.path().filename().string().substr(0, pos) + extension; + fs::path current_file = entry.path().parent_path(); + current_file.append(current_filename); + + // Attempt to convert the index to a number + QUILL_TRY + { + _created_files.emplace_front(current_file, static_cast(std::stoul(index)), + std::string{}); + } + QUILL_CATCH_ALL() { continue; } + } + else if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::Date) + { + // Get the today's date, we won't remove the files of the previous dates as they won't collide + std::string const today_date = + this->format_datetime_string(today_timestamp_ns, _config.timezone(), "%Y%m%d"); + + if (std::string const index_or_date = + entry.path().stem().string().substr(pos + 1, entry.path().stem().string().length()); + (index_or_date.length() >= 8) && (index_or_date == today_date)) + { + // assume it is a date, no need to find the index + std::string const current_filename = entry.path().filename().string().substr(0, pos) + extension; + fs::path current_file = entry.path().parent_path(); + current_file.append(current_filename); + + _created_files.emplace_front(current_file, 0, index_or_date); + } + else + { + // assume it is an index + // Find the second last dot to get the date + std::string const filename_with_date = entry.path().filename().string().substr(0, pos); + + if (size_t const second_last = filename_with_date.find_last_of('.'); second_last != std::string::npos) + { + if (std::string const date_part = + filename_with_date.substr(second_last + 1, filename_with_date.length()); + date_part == today_date) + { + std::string const current_filename = filename_with_date.substr(0, second_last) + extension; + fs::path current_file = entry.path().parent_path(); + current_file.append(current_filename); + + // Attempt to convert the index to a number + QUILL_TRY + { + _created_files.emplace_front( + current_file, static_cast(std::stoul(index_or_date)), date_part); + } + QUILL_CATCH_ALL() { continue; } + } + } + } + } + } + } + + // finally we need to sort the deque + std::sort(_created_files.begin(), _created_files.end(), + [](FileInfo const& a, FileInfo const& b) { return a.index < b.index; }); + } + } + + /***/ + QUILL_NODISCARD static size_t _get_file_size(fs::path const& filename) + { + return static_cast(fs::file_size(filename)); + } + + /***/ + static bool _remove_file(fs::path const& filename) noexcept + { + std::error_code ec; + fs::remove(filename, ec); + + if (ec) + { + return false; + } + + return true; + } + + /***/ + bool static _rename_file(fs::path const& previous_file, fs::path const& new_file) noexcept + { + std::error_code ec; + fs::rename(previous_file, new_file, ec); + + if (ec) + { + return false; + } + + return true; + } + + /***/ + QUILL_NODISCARD static fs::path _append_index_to_filename(fs::path const& filename, uint32_t index) noexcept + { + if (index == 0u) + { + return filename; + } + + // Get base file and extension + auto const [stem, ext] = base_type::extract_stem_and_extension(filename); + return fs::path{stem + "." + std::to_string(index) + ext}; + } + + /***/ + QUILL_NODISCARD static fs::path _append_string_to_filename(fs::path const& filename, std::string const& text) noexcept + { + if (text.empty()) + { + return filename; + } + + // Get base file and extension + auto const [stem, ext] = base_type::extract_stem_and_extension(filename); + return fs::path{stem + "." + text + ext}; + } + + /***/ + static uint64_t _calculate_initial_rotation_tp(uint64_t start_time_ns, RotatingFileSinkConfig const& config) + { + time_t const time_now = static_cast(start_time_ns) / 1000000000; + tm date; + + // here we do this because of `daily_rotation_time_str` that might have specified the time in UTC + if (config.timezone() == Timezone::GmtTime) + { + detail::gmtime_rs(&time_now, &date); + } + else + { + detail::localtime_rs(&time_now, &date); + } + + // update to the desired date + if (config.rotation_frequency() == RotatingFileSinkConfig::RotationFrequency::Minutely) + { + date.tm_min += 1; + date.tm_sec = 0; + } + else if (config.rotation_frequency() == RotatingFileSinkConfig::RotationFrequency::Hourly) + { + date.tm_hour += 1; + date.tm_min = 0; + date.tm_sec = 0; + } + else if (config.rotation_frequency() == RotatingFileSinkConfig::RotationFrequency::Daily) + { + date.tm_hour = static_cast(config.daily_rotation_time().first.count()); + date.tm_min = static_cast(config.daily_rotation_time().second.count()); + date.tm_sec = 0; + } + else + { + QUILL_THROW(QuillError{"Invalid rotation frequency"}); + } + + // convert back to timestamp + time_t const rotation_time = + (config.timezone() == Timezone::GmtTime) ? detail::timegm(&date) : std::mktime(&date); + + uint64_t const rotation_time_seconds = (rotation_time > time_now) + ? static_cast(rotation_time) + : static_cast(rotation_time + std::chrono::seconds{std::chrono::hours{24}}.count()); + + return static_cast( + std::chrono::nanoseconds{std::chrono::seconds{rotation_time_seconds}}.count()); + } + + /***/ + static uint64_t _calculate_rotation_tp(uint64_t rotation_timestamp_ns, RotatingFileSinkConfig const& config) + { + if (config.rotation_frequency() == RotatingFileSinkConfig::RotationFrequency::Minutely) + { + return rotation_timestamp_ns + + static_cast( + std::chrono::nanoseconds{std::chrono::minutes{config.rotation_interval()}}.count()); + } + + if (config.rotation_frequency() == RotatingFileSinkConfig::RotationFrequency::Hourly) + { + return rotation_timestamp_ns + + static_cast( + std::chrono::nanoseconds{std::chrono::hours{config.rotation_interval()}}.count()); + } + + if (config.rotation_frequency() == RotatingFileSinkConfig::RotationFrequency::Daily) + { + return rotation_timestamp_ns + std::chrono::nanoseconds{std::chrono::hours{24}}.count(); + } + + QUILL_THROW(QuillError{"Invalid rotation frequency"}); + } + + /***/ + static fs::path _get_filename(fs::path filename, uint32_t index, std::string const& date_time) + { + if (!date_time.empty()) + { + filename = _append_string_to_filename(filename, date_time); + } + + if (index > 0) + { + filename = _append_index_to_filename(filename, index); + } + + return filename; + } + +private: + struct FileInfo + { + FileInfo(fs::path base_filename, uint32_t index, std::string date_time) + : base_filename{std::move(base_filename)}, date_time{std::move(date_time)}, index{index} + { + } + + fs::path base_filename; + std::string date_time; + uint32_t index; + }; + + FileEventNotifier _file_event_notifier; + std::deque _created_files; /**< We store in a queue the filenames we created, first: index, second: date/datetime, third: base_filename */ + uint64_t _next_rotation_time; /**< The next rotation time point */ + uint64_t _open_file_timestamp{0}; /**< The timestamp of the currently open file */ + size_t _file_size{0}; /**< The current file size */ + RotatingFileSinkConfig _config; +}; + +QUILL_END_NAMESPACE \ No newline at end of file diff --git a/include/quill/sinks/StreamSink.h b/include/quill/sinks/StreamSink.h index ef314023..664011c8 100644 --- a/include/quill/sinks/StreamSink.h +++ b/include/quill/sinks/StreamSink.h @@ -126,7 +126,7 @@ class StreamSink : public Sink */ QUILL_ATTRIBUTE_HOT void write_log(MacroMetadata const* /* log_metadata */, uint64_t /* log_timestamp */, std::string_view /* thread_id */, - std::string_view /* thread_name */, std::string const& /* process_id */, + std::string_view /* thread_name */, std::string const& /* process_id */, std::string_view /* logger_name */, LogLevel /* log_level */, std::string_view /* log_level_description */, std::string_view /* log_level_short_code */, diff --git a/include/quill/std/Deque.h b/include/quill/std/Deque.h index 7bff6388..c8cc123c 100644 --- a/include/quill/std/Deque.h +++ b/include/quill/std/Deque.h @@ -11,8 +11,8 @@ #include "quill/core/DynamicFormatArgStore.h" #include "quill/core/InlinedVector.h" -#include "quill/bundled/fmt/ranges.h" #include "quill/bundled/fmt/format.h" +#include "quill/bundled/fmt/ranges.h" #include #include diff --git a/include/quill/std/FilesystemPath.h b/include/quill/std/FilesystemPath.h index 566611be..cf9fea42 100644 --- a/include/quill/std/FilesystemPath.h +++ b/include/quill/std/FilesystemPath.h @@ -12,8 +12,8 @@ #include "quill/core/Filesystem.h" #include "quill/core/InlinedVector.h" -#include "quill/bundled/fmt/std.h" #include "quill/bundled/fmt/format.h" +#include "quill/bundled/fmt/std.h" #include #include diff --git a/include/quill/std/ForwardList.h b/include/quill/std/ForwardList.h index f270cc3c..3d16f19a 100644 --- a/include/quill/std/ForwardList.h +++ b/include/quill/std/ForwardList.h @@ -11,8 +11,8 @@ #include "quill/core/DynamicFormatArgStore.h" #include "quill/core/InlinedVector.h" -#include "quill/bundled/fmt/ranges.h" #include "quill/bundled/fmt/format.h" +#include "quill/bundled/fmt/ranges.h" #include #include diff --git a/include/quill/std/List.h b/include/quill/std/List.h index 7cf86e90..cee1bae3 100644 --- a/include/quill/std/List.h +++ b/include/quill/std/List.h @@ -11,8 +11,8 @@ #include "quill/core/DynamicFormatArgStore.h" #include "quill/core/InlinedVector.h" -#include "quill/bundled/fmt/ranges.h" #include "quill/bundled/fmt/format.h" +#include "quill/bundled/fmt/ranges.h" #include #include diff --git a/include/quill/std/Map.h b/include/quill/std/Map.h index 2f1e696a..9f5b77e4 100644 --- a/include/quill/std/Map.h +++ b/include/quill/std/Map.h @@ -12,8 +12,8 @@ #include "quill/core/InlinedVector.h" #include "quill/std/Pair.h" -#include "quill/bundled/fmt/ranges.h" #include "quill/bundled/fmt/format.h" +#include "quill/bundled/fmt/ranges.h" #include #include diff --git a/include/quill/std/Optional.h b/include/quill/std/Optional.h index 92a32952..9fd2eb19 100644 --- a/include/quill/std/Optional.h +++ b/include/quill/std/Optional.h @@ -11,13 +11,12 @@ #include "quill/core/DynamicFormatArgStore.h" #include "quill/core/InlinedVector.h" -#include "quill/bundled/fmt/std.h" #include "quill/bundled/fmt/format.h" +#include "quill/bundled/fmt/std.h" #include #include #include -#include #if defined(_WIN32) #include @@ -31,8 +30,6 @@ struct Codec> static size_t compute_encoded_size(detail::SizeCacheVector& conditional_arg_size_cache, std::optional const& arg) noexcept { - // We need to store the size of the vector in the buffer, so we reserve space for it. - // We add sizeof(bool) bytes to accommodate the size information. size_t total_size{sizeof(bool)}; if (arg.has_value()) diff --git a/include/quill/std/Pair.h b/include/quill/std/Pair.h index 7ab40ed3..ade1e1c4 100644 --- a/include/quill/std/Pair.h +++ b/include/quill/std/Pair.h @@ -11,8 +11,8 @@ #include "quill/core/DynamicFormatArgStore.h" #include "quill/core/InlinedVector.h" -#include "quill/bundled/fmt/ranges.h" #include "quill/bundled/fmt/format.h" +#include "quill/bundled/fmt/ranges.h" #include #include diff --git a/include/quill/std/Set.h b/include/quill/std/Set.h index 4e50683e..97cad995 100644 --- a/include/quill/std/Set.h +++ b/include/quill/std/Set.h @@ -11,8 +11,8 @@ #include "quill/core/DynamicFormatArgStore.h" #include "quill/core/InlinedVector.h" -#include "quill/bundled/fmt/ranges.h" #include "quill/bundled/fmt/format.h" +#include "quill/bundled/fmt/ranges.h" #include #include diff --git a/include/quill/std/Tuple.h b/include/quill/std/Tuple.h index 27f2ca62..92a2beed 100644 --- a/include/quill/std/Tuple.h +++ b/include/quill/std/Tuple.h @@ -11,13 +11,12 @@ #include "quill/core/DynamicFormatArgStore.h" #include "quill/core/InlinedVector.h" -#include "quill/bundled/fmt/ranges.h" #include "quill/bundled/fmt/format.h" +#include "quill/bundled/fmt/ranges.h" #include #include #include -#include QUILL_BEGIN_NAMESPACE diff --git a/include/quill/std/UnorderedMap.h b/include/quill/std/UnorderedMap.h index 69345100..e6ce5865 100644 --- a/include/quill/std/UnorderedMap.h +++ b/include/quill/std/UnorderedMap.h @@ -12,8 +12,8 @@ #include "quill/core/InlinedVector.h" #include "quill/std/Pair.h" -#include "quill/bundled/fmt/ranges.h" #include "quill/bundled/fmt/format.h" +#include "quill/bundled/fmt/ranges.h" #include #include diff --git a/include/quill/std/UnorderedSet.h b/include/quill/std/UnorderedSet.h index 120f1c63..8a783237 100644 --- a/include/quill/std/UnorderedSet.h +++ b/include/quill/std/UnorderedSet.h @@ -11,8 +11,8 @@ #include "quill/core/DynamicFormatArgStore.h" #include "quill/core/InlinedVector.h" -#include "quill/bundled/fmt/ranges.h" #include "quill/bundled/fmt/format.h" +#include "quill/bundled/fmt/ranges.h" #include #include @@ -24,8 +24,9 @@ QUILL_BEGIN_NAMESPACE template