From 0b6cadb87d42dcb87c23d18d0471253195643c4d Mon Sep 17 00:00:00 2001 From: Odysseas Georgoudis Date: Fri, 27 Sep 2024 19:48:17 +0100 Subject: [PATCH] Add overload to create_or_get_logger for creating a logger with inherited options. --- CHANGELOG.md | 2 + include/quill/Frontend.h | 17 ++++ include/quill/core/LoggerBase.h | 2 + include/quill/core/LoggerManager.h | 13 +++ test/integration_tests/CMakeLists.txt | 1 + ...ingleFrontendThreadMultipleLoggersTest.cpp | 80 +++++++++++++++++++ 6 files changed, 115 insertions(+) create mode 100644 test/integration_tests/SingleFrontendThreadMultipleLoggersTest.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 545cc8b8..f73cd0d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -90,6 +90,8 @@ - Introduced `SignalHandlerOptions` to simplify and unify the API. `Backend::start_with_signal_handler` is now deprecated, replaced by a new `Backend::start` overload that accepts `SignalHandlerOptions` for enabling signal handling. +- Added a new overload to `create_or_get_logger` to create a logger that inherits configuration options from a specified + logger. ([#596](https://github.com/odygrd/quill/issues/596)) ## v7.2.2 diff --git a/include/quill/Frontend.h b/include/quill/Frontend.h index 8de8d13c..17d60074 100644 --- a/include/quill/Frontend.h +++ b/include/quill/Frontend.h @@ -138,6 +138,23 @@ class FrontendImpl pattern_formatter_options, clock_source, user_clock); } + /** + * @brief Creates a new logger or retrieves an existing one that shares the same options as the specified logger. + * + * This function allows you to create or obtain a logger identified by `logger_name`. If a logger with the + * same name already exists, its configuration options will be used. If it does not exist, a new logger + * will be created with the same options as the provided `source_logger`. + * + * @param logger_name The name of the logger to create or retrieve. + * @param source_logger The logger from which to copy the configuration options. + * @return A pointer to the logger instance, either newly created or retrieved. + */ + static logger_t* create_or_get_logger(std::string const& logger_name, detail::LoggerBase* source_logger = nullptr) + { + return _cast_to_logger( + detail::LoggerManager::instance().create_or_get_logger(logger_name, source_logger)); + } + /** * @brief Asynchronously removes the specified logger. * When a logger is removed, any files associated with its sinks are also closed. diff --git a/include/quill/core/LoggerBase.h b/include/quill/core/LoggerBase.h index 7b1ea835..54122c4c 100644 --- a/include/quill/core/LoggerBase.h +++ b/include/quill/core/LoggerBase.h @@ -30,6 +30,7 @@ namespace detail { class BackendWorker; class BacktraceStorage; +class LoggerManager; /***/ class LoggerBase @@ -124,6 +125,7 @@ class LoggerBase protected: friend class BackendWorker; + friend class LoggerManager; static inline QUILL_THREAD_LOCAL ThreadContext* thread_context = nullptr; /* Set and accessed by the frontend */ std::shared_ptr pattern_formatter; /* The backend thread will set this once, we never access it on the frontend */ diff --git a/include/quill/core/LoggerManager.h b/include/quill/core/LoggerManager.h index a06cba8b..bb022183 100644 --- a/include/quill/core/LoggerManager.h +++ b/include/quill/core/LoggerManager.h @@ -150,6 +150,19 @@ class LoggerManager return logger_ptr; } + /***/ + template + LoggerBase* create_or_get_logger(std::string const& logger_name, LoggerBase* source_logger) + { + if (!source_logger) + { + return get_logger(logger_name); + } + + return create_or_get_logger(logger_name, source_logger->sinks, source_logger->pattern_formatter_options, + source_logger->clock_source, source_logger->user_clock); + } + /***/ void remove_logger(LoggerBase* logger) { diff --git a/test/integration_tests/CMakeLists.txt b/test/integration_tests/CMakeLists.txt index c4844422..750546e5 100644 --- a/test/integration_tests/CMakeLists.txt +++ b/test/integration_tests/CMakeLists.txt @@ -81,6 +81,7 @@ quill_add_test(TEST_RotatingSinkKeepOldest RotatingSinkKeepOldestTest.cpp) quill_add_test(TEST_RotatingSinkOverwriteOldest RotatingSinkOverwriteOldestTest.cpp) quill_add_test(TEST_SignalHandler SignalHandlerTest.cpp) quill_add_test(TEST_SignalHandlerLogger SignalHandlerLoggerTest.cpp) +quill_add_test(TEST_SingleFrontendThreadMultipleLoggers SingleFrontendThreadMultipleLoggersTest.cpp) quill_add_test(TEST_SingleFrontendThread SingleFrontendThreadTest.cpp) quill_add_test(TEST_SinkFilter SinkFilterTest.cpp) quill_add_test(TEST_StdArrayLogging StdArrayLoggingTest.cpp) diff --git a/test/integration_tests/SingleFrontendThreadMultipleLoggersTest.cpp b/test/integration_tests/SingleFrontendThreadMultipleLoggersTest.cpp new file mode 100644 index 00000000..57ee241c --- /dev/null +++ b/test/integration_tests/SingleFrontendThreadMultipleLoggersTest.cpp @@ -0,0 +1,80 @@ +#include "doctest/doctest.h" + +#include "misc/TestUtilities.h" +#include "quill/Backend.h" +#include "quill/Frontend.h" +#include "quill/LogMacros.h" +#include "quill/sinks/FileSink.h" + +#include +#include +#include + +using namespace quill; + +/***/ +TEST_CASE("single_frontend_thread_multiple_loggers") +{ + static constexpr size_t number_of_messages = 500; + static constexpr char const* filename = "single_frontend_thread_multiple_loggers.log"; + static std::string const logger_name_a = "logger_a"; + static std::string const logger_name_b = "logger_b"; + + // Start the logging backend thread + Backend::start(); + + // Set writing logging to a file + auto file_sink = Frontend::create_or_get_sink( + filename, + []() + { + FileSinkConfig cfg; + cfg.set_open_mode('w'); + + // For this test only we use the default buffer size, it should not make any difference it is just for testing the default behaviour and code coverage + cfg.set_write_buffer_size(0); + + return cfg; + }(), + FileEventNotifier{}); + + Logger* logger_a = Frontend::create_or_get_logger( + logger_name_a, std::move(file_sink), quill::PatternFormatterOptions("[%(logger)] %(message)")); + + // Take properties from logger_a + Logger* logger_b = Frontend::create_or_get_logger(logger_name_b, logger_a); + + for (size_t i = 0; i < number_of_messages; ++i) + { + LOG_INFO(logger_a, "This is message {}", i); + LOG_INFO(logger_b, "This is message {}", i); + } + + logger_a->flush_log(); + Frontend::remove_logger(logger_a); + Frontend::remove_logger(logger_b); + + // Wait until the backend thread stops for test stability + Backend::stop(); + + // Read file and check + std::vector const file_contents = quill::testing::file_contents(filename); + REQUIRE_EQ(file_contents.size(), number_of_messages * 2); + + for (size_t i = 0; i < number_of_messages; ++i) + { + { + std::string expected_string = + std::string("[") + logger_name_a + "] This is message " + std::to_string(i); + REQUIRE(quill::testing::file_contains(file_contents, expected_string)); + } + + { + std::string expected_string = + std::string("[") + logger_name_b + "] This is message " + std::to_string(i); + REQUIRE(quill::testing::file_contains(file_contents, expected_string)); + } + } + + testing::remove_file(filename); +} \ No newline at end of file