diff --git a/CHANGELOG.md b/CHANGELOG.md index d504e325..545cc8b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,9 @@ - Implemented a workaround to resolve false positive warnings from `clang-tidy` on Windows. - Added a new `create_or_get_logger` overload that accepts a `std::vector>`, improving flexibility by allowing a variable number of sinks to be passed at runtime when creating a logger. +- 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. ## v7.2.2 @@ -118,7 +121,8 @@ introducing support for custom buffer sizes in file streams and tuning `transit_events_soft_limit` and `transit_events_hard_limit` default values - Improved readability of queue allocation notification messages. Capacities are now displayed in KiB, - e.g., `20:59:25 Quill INFO: Allocated a new SPSC queue with a capacity of 1024 KB (previously 512 KB) from thread 31158`. + e.g., + `20:59:25 Quill INFO: Allocated a new SPSC queue with a capacity of 1024 KB (previously 512 KB) from thread 31158`. **New Features:** diff --git a/docs/frontend_options.rst b/docs/frontend_options.rst index edce4b6a..a46cdc32 100644 --- a/docs/frontend_options.rst +++ b/docs/frontend_options.rst @@ -49,7 +49,7 @@ For example, to use a `BoundedDropping` queue with a fixed capacity of `131'072` { // Start the backend thread quill::BackendOptions backend_options; - quill::Backend::start(backend_options); // or quill::Backend::start_with_signal_handler(); + quill::Backend::start(backend_options); // or quill::Backend::start(backend_options, signal_handler_options); // All frontend operations and Logger must utilize the CustomFrontend instead of quill::Frontend auto console_sink = CustomFrontend::create_or_get_sink("sink_id_1"); diff --git a/docs/overview.rst b/docs/overview.rst index 19adde83..1d8596c3 100644 --- a/docs/overview.rst +++ b/docs/overview.rst @@ -57,7 +57,7 @@ However, in the event of an application crash, some log messages may be lost. To prevent message loss during crashes caused by signal interrupts, users should set up a signal handler and invoke :cpp:func:`LoggerImpl::flush_log` within it. -The library provides a built-in signal handler that ensures crash-safe behavior, which can be enabled via :cpp:func:`Backend::start_with_signal_handler`. +The library provides a built-in signal handler that ensures crash-safe behavior, which can be enabled via passing :cpp:struct:`SignalHandlerOptions` to :cpp:func:`Backend::start`. Log Messages Timestamp Order ---------------------------- diff --git a/docs/users-api.rst b/docs/users-api.rst index fd07012b..8548d40a 100644 --- a/docs/users-api.rst +++ b/docs/users-api.rst @@ -21,6 +21,12 @@ BackendTscClock Class .. doxygenclass:: BackendTscClock :members: +SignalHandler Options +--------------- + +.. doxygenstruct:: SignalHandlerOptions + :members: + Frontend Options ---------------- diff --git a/examples/custom_frontend_options.cpp b/examples/custom_frontend_options.cpp index 88ea1c52..73a41645 100644 --- a/examples/custom_frontend_options.cpp +++ b/examples/custom_frontend_options.cpp @@ -35,7 +35,7 @@ int main() { // Start the backend thread quill::BackendOptions backend_options; - quill::Backend::start(backend_options); // or quill::Backend::start_with_signal_handler(); + quill::Backend::start(backend_options); // or quill::Backend::start(backend_options, signal_handler_options); // All frontend operations must utilize CustomFrontend instead of quill::Frontend auto console_sink = CustomFrontend::create_or_get_sink("sink_id_1"); diff --git a/examples/signal_handler.cpp b/examples/signal_handler.cpp index 9dda2b66..17688e84 100644 --- a/examples/signal_handler.cpp +++ b/examples/signal_handler.cpp @@ -50,7 +50,7 @@ int main() // Start the logging backend thread with a signal handler // On Linux/Macos one signal handler is set to handle POSIX style signals // On Windows an exception handler and a Ctrl-C handler is set. - quill::Backend::start_with_signal_handler(); + quill::Backend::start(quill::BackendOptions{}, quill::SignalHandlerOptions{}); auto console_sink = quill::Frontend::create_or_get_sink("sink_id_1"); quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(console_sink)); diff --git a/include/quill/Backend.h b/include/quill/Backend.h index 4c6564bc..6cb02fd6 100644 --- a/include/quill/Backend.h +++ b/include/quill/Backend.h @@ -48,27 +48,18 @@ class Backend std::atexit([]() { detail::BackendManager::instance().stop_backend_thread(); }); }); } - /** * Starts the backend thread and initialises a signal handler * - * @param options Backend options to configure the backend behavior. - * @param catchable_signals List of signals that the backend should catch if with_signal_handler + * @param backend_options Backend options to configure the backend behavior. + * @param signal_handler_options SignalHandler options to configure the signal handler behavior. * is enabled. - * @param signal_handler_timeout_seconds This variable defines the timeout duration in seconds for - * the signal handler alarm. It is only available on Linux, as Windows does not support the alarm - * function. The signal handler sets up an alarm to ensure that the process will terminate if it - * does not complete within the specified time frame. This is particularly useful to prevent the - * process from hanging indefinitely in case the signal handler encounters an issue. - * @param signal_handler_logger The logger instance that the signal handler will use to log errors when the application crashes. - * The logger is accessed by the signal handler and must be created by your application using Frontend::create_or_get_logger(...). - * If the specified logger is not found, or if this parameter is left empty, the signal handler will default to using the first valid logger it finds. * * @note When using the SignalHandler on Linux/MacOS, ensure that each spawned thread in your * application has performed one of the following actions: * i) Logged at least once. - * ii) Called Frontend::preallocate(). - * iii) Blocked signals on that thread to prevent the signal handler from running on it. + * or ii) Called Frontend::preallocate(). + * or iii) Blocked signals on that thread to prevent the signal handler from running on it. * This requirement is because the built-in signal handler utilizes a lock-free queue to issue log * statements and await the log flushing. The queue is constructed on its first use with `new()`. * Failing to meet any of the above criteria means the queue was never used, and it will be @@ -78,17 +69,13 @@ class Backend * safe. */ template - 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 = {}) + QUILL_ATTRIBUTE_COLD static void start(BackendOptions const& backend_options, + SignalHandlerOptions const& signal_handler_options) { std::call_once(detail::BackendManager::instance().get_start_once_flag(), - [options, catchable_signals, signal_handler_timeout_seconds, signal_handler_logger]() + [backend_options, signal_handler_options]() { #if defined(_WIN32) - (void)catchable_signals; detail::init_exception_handler(); #else // We do not want signal handler to run in the backend worker thread @@ -97,16 +84,16 @@ class Backend sigset_t set, oldset; sigfillset(&set); sigprocmask(SIG_SETMASK, &set, &oldset); - detail::init_signal_handler(catchable_signals); + detail::init_signal_handler(signal_handler_options.catchable_signals); #endif // Run the backend worker thread, we wait here until the thread enters the main loop - detail::BackendManager::instance().start_backend_thread(options); + detail::BackendManager::instance().start_backend_thread(backend_options); - detail::SignalHandlerContext::instance().logger_name = signal_handler_logger; + detail::SignalHandlerContext::instance().logger_name = signal_handler_options.logger; detail::SignalHandlerContext::instance().signal_handler_timeout_seconds.store( - signal_handler_timeout_seconds); + signal_handler_options.timeout_seconds); // We need to update the signal handler with some backend thread details detail::SignalHandlerContext::instance().backend_thread_id.store( @@ -115,8 +102,8 @@ class Backend #if defined(_WIN32) // nothing to do #else - // Unblock signals in the main thread so subsequent threads do not inherit the blocked mask - sigprocmask(SIG_SETMASK, &oldset, nullptr); + // Unblock signals in the main thread so subsequent threads do not inherit the blocked mask + sigprocmask(SIG_SETMASK, &oldset, nullptr); #endif // Set up an exit handler to call stop when the main application exits. @@ -126,6 +113,21 @@ 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 @@ -193,7 +195,7 @@ class Backend * multiple threads calling `poll()` simultaneously. * - The built-in signal handler is not set up with `ManualBackendWorker`. If signal handling is * required, you must manually set up the signal handler and block signals from reaching the `ManualBackendWorker` thread. - * See the `start_with_signal_handler()` implementation for guidance on how to do this. + * See the `start(BackendOptions, SignalHandlerOptions)` implementation for guidance on how to do this. * - The following options are not supported when using `ManualBackendWorker`: `cpu_affinity`, * `thread_name`, `sleep_duration`, and `enable_yield_when_idle`. * - Avoid performing very heavy tasks in your custom thread. Significant delays in calling `poll()` @@ -227,8 +229,8 @@ class Backend { QUILL_THROW( QuillError{"acquire_manual_backend_worker() can only be called once per process. " - "Additionally, it should " - "not be called when start() or start_with_signal_handler() has been invoked"}); + "Additionally, it should not be " + "called when start() has already been invoked"}); } return manual_backend_worker; diff --git a/include/quill/backend/SignalHandler.h b/include/quill/backend/SignalHandler.h index bc4d12db..4f532763 100644 --- a/include/quill/backend/SignalHandler.h +++ b/include/quill/backend/SignalHandler.h @@ -42,6 +42,35 @@ QUILL_BEGIN_NAMESPACE +/** + * Struct to hold options for the signal handler. + */ +struct SignalHandlerOptions +{ + /** + * List of signals that the backend should catch if with_signal_handler is enabled. + */ + std::vector catchable_signals{SIGTERM, SIGINT, SIGABRT, SIGFPE, SIGILL, SIGSEGV}; + + /** + * Defines the timeout duration in seconds for the signal handler alarm. + * It is only available on Linux, as Windows does not support the alarm function. + * The signal handler sets up an alarm to ensure that the process will terminate if it does not + * complete within the specified time frame. This is particularly useful to prevent the + * process from hanging indefinitely in case the signal handler encounters an issue. + */ + uint32_t timeout_seconds = 20u; + + /** + * The logger instance that the signal handler will use to log errors when the application crashes. + * The logger is accessed by the signal handler and must be created by your application using + * Frontend::create_or_get_logger(...). + * If the specified logger is not found, or if this parameter is left empty, the signal handler + * will default to using the first valid logger it finds. + */ + std::string logger; +}; + namespace detail { /***/ @@ -343,7 +372,7 @@ void init_exception_handler() * @param catchable_signals the signals we are catching */ template -void init_signal_handler(std::initializer_list const& catchable_signals = std::initializer_list{ +void init_signal_handler(std::vector const& catchable_signals = std::vector{ SIGTERM, SIGINT, SIGABRT, SIGFPE, SIGILL, SIGSEGV}) { for (auto const& catchable_signal : catchable_signals) @@ -373,7 +402,7 @@ inline void on_alarm(int32_t signal_number) } template -void init_signal_handler(std::initializer_list const& catchable_signals) +void init_signal_handler(std::vector const& catchable_signals) { for (auto const& catchable_signal : catchable_signals) { diff --git a/test/integration_tests/SignalHandlerLoggerTest.cpp b/test/integration_tests/SignalHandlerLoggerTest.cpp index 912bbf08..bead501e 100644 --- a/test/integration_tests/SignalHandlerLoggerTest.cpp +++ b/test/integration_tests/SignalHandlerLoggerTest.cpp @@ -31,8 +31,8 @@ TEST_CASE("signal_handler_logger") // Start the logging backend thread, we expect the signal handler to catch the signal, // flush the log and raise the signal back - Backend::start_with_signal_handler( - BackendOptions{}, std::initializer_list{SIGABRT}, 40, logger_name_b); + Backend::start( + BackendOptions{}, SignalHandlerOptions{std::vector{SIGABRT}, 40, logger_name_b}); // For testing purposes we want to keep the application running, we do not reraise the signal detail::SignalHandlerContext::instance().should_reraise_signal.store(false); diff --git a/test/integration_tests/SignalHandlerTest.cpp b/test/integration_tests/SignalHandlerTest.cpp index 44530119..952ca6d3 100644 --- a/test/integration_tests/SignalHandlerTest.cpp +++ b/test/integration_tests/SignalHandlerTest.cpp @@ -33,8 +33,10 @@ TEST_CASE("signal_handler") // Start the logging backend thread, we expect the signal handler to catch the signal, // flush the log and raise the signal back - Backend::start_with_signal_handler(BackendOptions{}, - std::initializer_list{SIGABRT}, 40); + SignalHandlerOptions sho{}; + sho.catchable_signals = std::vector{SIGABRT}; + sho.timeout_seconds = 40; + Backend::start(BackendOptions{}, sho); // For testing purposes we want to keep the application running, we do not reraise the signal detail::SignalHandlerContext::instance().should_reraise_signal.store(false);