9 #include "quill/backend/ThreadUtilities.h" 11 #include "quill/Logger.h" 12 #include "quill/core/Attributes.h" 13 #include "quill/core/LogLevel.h" 14 #include "quill/core/LoggerBase.h" 15 #include "quill/core/LoggerManager.h" 16 #include "quill/core/MacroMetadata.h" 17 #include "quill/core/QuillError.h" 29 #if !defined(WIN32_LEAN_AND_MEAN) 30 #define WIN32_LEAN_AND_MEAN 33 #if !defined(NOMINMAX) 96 using signal_handler_t = void (*)(int);
101 signal_handler_t previous_handler;
104 inline void restore_signal_handler_entries(std::vector<SignalHandlerRestoreEntry>
const& previous_signal_handlers) noexcept
106 for (
auto it = previous_signal_handlers.rbegin(); it != previous_signal_handlers.rend(); ++it)
108 std::signal(it->signal_number, it->previous_handler);
113 QUILL_NODISCARD
inline bool is_synchronous_fault_signal(
int signal_number) noexcept
115 return signal_number == SIGSEGV || signal_number == SIGFPE || signal_number == SIGILL
117 || signal_number == SIGBUS
120 || signal_number == SIGTRAP
126 QUILL_NODISCARD
inline char const* get_signal_description(int32_t signal_number) noexcept
128 switch (signal_number)
182 #if defined(SIGVTALRM) 210 QUILL_NODISCARD
static LoggerBase* get_logger() noexcept
216 logger_base = LoggerManager::instance().get_logger(instance().
logger_name);
220 if (!logger_base || !logger_base->is_valid_logger())
230 std::atomic<int32_t> signal_number{0};
231 std::atomic<uint32_t> lock{0};
232 std::atomic<uint32_t> backend_thread_id{0};
233 std::atomic<uint32_t> signal_handler_timeout_seconds{20};
234 std::atomic<bool> should_reraise_signal{
true};
235 std::mutex signal_handlers_mutex;
236 std::vector<int> registered_signal_handlers{};
237 std::vector<SignalHandlerRestoreEntry> previous_signal_handlers{};
239 LPTOP_LEVEL_EXCEPTION_FILTER previous_exception_filter{
nullptr};
240 void (*exception_handler_deinit_callback)() =
nullptr;
241 bool console_ctrl_handler_installed{
false};
249 #define QUILL_SIGNAL_HANDLER_LOG(logger, log_level, fmt, ...) \ 252 if (logger->template should_log_statement<log_level>()) \ 254 static constexpr quill::MacroMetadata macro_metadata{ \ 255 "SignalHandler:~", "", fmt, nullptr, log_level, quill::MacroMetadata::Event::Log}; \ 257 logger->template log_statement<false>(¯o_metadata, ##__VA_ARGS__); \ 262 template <
typename TFrontendOptions>
263 void on_signal(int32_t signal_number)
266 uint32_t
const lock = SignalHandlerContext::instance().lock.fetch_add(1);
283 // nothing to do, windows do not have alarm 285 // Store the original signal number for the alarm 286 SignalHandlerContext::instance().signal_number.store(signal_number); 288 // Set up an alarm to crash after 20 seconds by redelivering the original signal, 289 // in case anything else goes wrong 290 alarm(SignalHandlerContext::instance().signal_handler_timeout_seconds.load()); 293 // Get the id of this thread in the handler and make sure it is not the backend worker thread 294 uint32_t const backend_thread_id = SignalHandlerContext::instance().backend_thread_id.load(); 295 uint32_t const current_thread_id = get_thread_id(); 296 bool const should_reraise_signal = SignalHandlerContext::instance().should_reraise_signal.load(); 298 if ((backend_thread_id == 0) || (current_thread_id == backend_thread_id)) 300 // backend worker thread is not running or the signal handler is called in the backend worker thread 301 if (signal_number == SIGINT || signal_number == SIGTERM) 303 std::_Exit(EXIT_SUCCESS); 306 if (should_reraise_signal) 308 // for other signals expect SIGINT and SIGTERM we re-raise 309 std::signal(signal_number, SIG_DFL); 310 std::raise(signal_number); 313 // For synchronous fault signals (SIGSEGV, SIGFPE, etc.) we must not return — 314 // returning re-executes the faulting instruction, causing an infinite loop. 315 if (is_synchronous_fault_signal(signal_number)) 317 std::_Exit(EXIT_FAILURE); 322 // This means signal handler is running on a frontend thread, we can log and flush 323 LoggerBase* logger_base = SignalHandlerContext::instance().get_logger(); 327 char const* const signal_desc = get_signal_description(signal_number); 329 auto logger = static_cast<LoggerImpl<TFrontendOptions>*>(logger_base); 330 QUILL_SIGNAL_HANDLER_LOG(logger, LogLevel::Info, "Received signal: {} (signum: {})", 331 signal_desc, signal_number); 333 if (signal_number == SIGINT || signal_number == SIGTERM) 335 // For SIGINT and SIGTERM, we are shutting down gracefully 336 // Pass `0` to avoid calling std::this_thread::sleep_for() 337 logger->flush_log(0); 338 std::_Exit(EXIT_SUCCESS); 341 if (should_reraise_signal) 343 QUILL_SIGNAL_HANDLER_LOG(logger, LogLevel::Critical, 344 "Program terminated unexpectedly due to signal: {} (signum: {})", 345 signal_desc, signal_number); 347 // This is here in order to flush the above log statement 348 logger->flush_log(0); 350 // Reset to the default signal handler and re-raise the signal 351 std::signal(signal_number, SIG_DFL); 352 std::raise(signal_number); 356 logger->flush_log(0); 360 // If we reach here it means we have no valid logger or should_reraise_signal is false. 361 // For synchronous fault signals we must not return to avoid re-executing the faulting instruction. 362 if (is_synchronous_fault_signal(signal_number)) 364 std::_Exit(EXIT_FAILURE); 368 } // namespace detail 386 inline char const* get_error_message(DWORD ex_code) 390 case EXCEPTION_ACCESS_VIOLATION: 391 return "EXCEPTION_ACCESS_VIOLATION"; 392 case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: 393 return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"; 394 case EXCEPTION_BREAKPOINT: 395 return "EXCEPTION_BREAKPOINT"; 396 case EXCEPTION_DATATYPE_MISALIGNMENT: 397 return "EXCEPTION_DATATYPE_MISALIGNMENT"; 398 case EXCEPTION_FLT_DENORMAL_OPERAND: 399 return "EXCEPTION_FLT_DENORMAL_OPERAND"; 400 case EXCEPTION_FLT_DIVIDE_BY_ZERO: 401 return "EXCEPTION_FLT_DIVIDE_BY_ZERO"; 402 case EXCEPTION_FLT_INEXACT_RESULT: 403 return "EXCEPTION_FLT_INEXACT_RESULT"; 404 case EXCEPTION_FLT_INVALID_OPERATION: 405 return "EXCEPTION_FLT_INVALID_OPERATION"; 406 case EXCEPTION_FLT_OVERFLOW: 407 return "EXCEPTION_FLT_OVERFLOW"; 408 case EXCEPTION_FLT_STACK_CHECK: 409 return "EXCEPTION_FLT_STACK_CHECK"; 410 case EXCEPTION_FLT_UNDERFLOW: 411 return "EXCEPTION_FLT_UNDERFLOW"; 412 case EXCEPTION_ILLEGAL_INSTRUCTION: 413 return "EXCEPTION_ILLEGAL_INSTRUCTION"; 414 case EXCEPTION_IN_PAGE_ERROR: 415 return "EXCEPTION_IN_PAGE_ERROR"; 416 case EXCEPTION_INT_DIVIDE_BY_ZERO: 417 return "EXCEPTION_INT_DIVIDE_BY_ZERO"; 418 case EXCEPTION_INT_OVERFLOW: 419 return "EXCEPTION_INT_OVERFLOW"; 420 case EXCEPTION_INVALID_DISPOSITION: 421 return "EXCEPTION_INVALID_DISPOSITION"; 422 case EXCEPTION_NONCONTINUABLE_EXCEPTION: 423 return "EXCEPTION_NONCONTINUABLE_EXCEPTION"; 424 case EXCEPTION_PRIV_INSTRUCTION: 425 return "EXCEPTION_PRIV_INSTRUCTION"; 426 case EXCEPTION_SINGLE_STEP: 427 return "EXCEPTION_SINGLE_STEP"; 428 case EXCEPTION_STACK_OVERFLOW: 429 return "EXCEPTION_STACK_OVERFLOW"; 431 return "Unrecognized Exception"; 436 template <typename TFrontendOptions> 437 BOOL WINAPI on_console_signal(DWORD signal) 439 uint32_t const backend_thread_id = SignalHandlerContext::instance().backend_thread_id.load(); 440 uint32_t const current_thread_id = get_thread_id(); 442 // Check if the signal handler is running from a caller thread and if the signal is CTRL+C or CTRL+BREAK 443 if ((backend_thread_id != 0) && (current_thread_id != backend_thread_id) && 444 (signal == CTRL_C_EVENT || signal == CTRL_BREAK_EVENT)) 446 // Log the interruption and flush log messages 447 LoggerBase* logger_base = SignalHandlerContext::instance().get_logger(); 451 auto logger = static_cast<LoggerImpl<TFrontendOptions>*>(logger_base); 452 QUILL_SIGNAL_HANDLER_LOG(logger, LogLevel::Info, 453 "Program interrupted by Ctrl+C or Ctrl+Break signal"); 455 // Pass `0` to avoid calling std::this_thread::sleep_for() 456 logger->flush_log(0); 457 std::_Exit(EXIT_SUCCESS); 465 template <typename TFrontendOptions> 466 LONG WINAPI on_exception(EXCEPTION_POINTERS* exception_p) 468 uint32_t const backend_thread_id = SignalHandlerContext::instance().backend_thread_id.load(); 469 uint32_t const current_thread_id = get_thread_id(); 471 // Check if the signal handler is running from a caller thread and if the signal is CTRL+C or CTRL+BREAK 472 if ((backend_thread_id != 0) && (current_thread_id != backend_thread_id)) 474 // Log the interruption and flush log messages 475 LoggerBase* logger_base = SignalHandlerContext::instance().get_logger(); 479 auto logger = static_cast<LoggerImpl<TFrontendOptions>*>(logger_base); 481 QUILL_SIGNAL_HANDLER_LOG(logger, LogLevel::Info, "Received exception: {} (Code: {})", 482 get_error_message(exception_p->ExceptionRecord->ExceptionCode), 483 exception_p->ExceptionRecord->ExceptionCode); 485 QUILL_SIGNAL_HANDLER_LOG(logger, LogLevel::Critical, 486 "Program terminated unexpectedly due to exception: {} (Code: {})", 487 get_error_message(exception_p->ExceptionRecord->ExceptionCode), 488 exception_p->ExceptionRecord->ExceptionCode); 490 // Pass `0` to avoid calling std::this_thread::sleep_for() 491 logger->flush_log(0); 495 // FATAL Exception: It doesn't necessarily stop here. we pass on
continue search
499 return EXCEPTION_CONTINUE_SEARCH;
503 template <
typename TFrontendOptions>
504 void deinit_exception_handler()
506 auto& ctx = detail::SignalHandlerContext::instance();
507 std::lock_guard<std::mutex>
const lock{ctx.signal_handlers_mutex};
509 if (ctx.console_ctrl_handler_installed)
511 SetConsoleCtrlHandler(on_console_signal<TFrontendOptions>, FALSE);
512 ctx.console_ctrl_handler_installed =
false;
515 SetUnhandledExceptionFilter(ctx.previous_exception_filter);
516 ctx.previous_exception_filter =
nullptr;
517 ctx.exception_handler_deinit_callback =
nullptr;
521 template <
typename TFrontendOptions>
522 void init_exception_handler()
524 auto& ctx = detail::SignalHandlerContext::instance();
525 std::lock_guard<std::mutex>
const lock{ctx.signal_handlers_mutex};
527 ctx.exception_handler_deinit_callback =
nullptr;
528 ctx.previous_exception_filter = SetUnhandledExceptionFilter(on_exception<TFrontendOptions>);
530 if (!SetConsoleCtrlHandler(on_console_signal<TFrontendOptions>, TRUE))
532 SetUnhandledExceptionFilter(ctx.previous_exception_filter);
533 ctx.previous_exception_filter =
nullptr;
534 QUILL_THROW(
QuillError{
"Failed to call SetConsoleCtrlHandler"});
537 ctx.exception_handler_deinit_callback = &deinit_exception_handler<TFrontendOptions>;
538 ctx.console_ctrl_handler_installed =
true;
550 template <
typename TFrontendOptions>
551 void init_signal_handler(std::vector<int>
const&
catchable_signals = std::vector<int>{
552 SIGTERM, SIGINT, SIGABRT, SIGFPE, SIGILL, SIGSEGV})
554 auto& ctx = detail::SignalHandlerContext::instance();
555 std::lock_guard<std::mutex>
const lock{ctx.signal_handlers_mutex};
557 std::vector<detail::SignalHandlerRestoreEntry> previous_signal_handlers;
563 auto const previous_handler = std::signal(catchable_signal, detail::on_signal<TFrontendOptions>);
564 if (previous_handler == SIG_ERR)
566 detail::restore_signal_handler_entries(previous_signal_handlers);
567 QUILL_THROW(
QuillError{
"Failed to setup signal handler for signal: " + std::to_string(catchable_signal)});
570 previous_signal_handlers.push_back({catchable_signal, previous_handler});
574 ctx.previous_signal_handlers = std::move(previous_signal_handlers);
581 inline void deinit_signal_handler()
583 auto& ctx = SignalHandlerContext::instance();
584 std::lock_guard<std::mutex>
const lock{ctx.signal_handlers_mutex};
586 for (
auto const& signal_number : ctx.registered_signal_handlers)
588 std::signal(signal_number, SIG_DFL);
591 ctx.registered_signal_handlers.clear();
592 ctx.previous_signal_handlers.clear();
595 inline void restore_signal_handlers()
597 auto& ctx = SignalHandlerContext::instance();
598 std::lock_guard<std::mutex>
const lock{ctx.signal_handlers_mutex};
600 restore_signal_handler_entries(ctx.previous_signal_handlers);
601 ctx.registered_signal_handlers.clear();
602 ctx.previous_signal_handlers.clear();
609 inline void on_alarm(int32_t signal_number)
611 if (SignalHandlerContext::instance().signal_number.load() == 0)
614 SignalHandlerContext::instance().signal_number = signal_number;
618 std::signal(SignalHandlerContext::instance().signal_number, SIG_DFL);
619 std::raise(SignalHandlerContext::instance().signal_number);
622 template <
typename TFrontendOptions>
625 auto& ctx = SignalHandlerContext::instance();
626 std::lock_guard<std::mutex>
const lock{ctx.signal_handlers_mutex};
628 std::vector<SignalHandlerRestoreEntry> previous_signal_handlers;
629 previous_signal_handlers.reserve(catchable_signals.size() + 1);
631 for (
auto const& catchable_signal : catchable_signals)
633 if (catchable_signal == SIGALRM)
635 restore_signal_handler_entries(previous_signal_handlers);
636 QUILL_THROW(
QuillError{
"SIGALRM can not be part of catchable_signals."});
640 auto const previous_handler = std::signal(catchable_signal, on_signal<TFrontendOptions>);
641 if (previous_handler == SIG_ERR)
643 restore_signal_handler_entries(previous_signal_handlers);
644 QUILL_THROW(
QuillError{
"Failed to setup signal handler for signal: " + std::to_string(catchable_signal)});
647 previous_signal_handlers.push_back({catchable_signal, previous_handler});
651 auto const previous_alarm_handler = std::signal(SIGALRM, on_alarm);
652 if (previous_alarm_handler == SIG_ERR)
654 restore_signal_handler_entries(previous_signal_handlers);
655 QUILL_THROW(
QuillError{
"Failed to setup signal handler for signal: SIGALRM"});
658 previous_signal_handlers.push_back({SIGALRM, previous_alarm_handler});
661 ctx.registered_signal_handlers.push_back(SIGALRM);
662 ctx.previous_signal_handlers = std::move(previous_signal_handlers);
665 inline void deinit_signal_handler()
667 auto& ctx = SignalHandlerContext::instance();
668 std::lock_guard<std::mutex>
const lock{ctx.signal_handlers_mutex};
670 for (
auto const& signal_number : ctx.registered_signal_handlers)
672 std::signal(signal_number, SIG_DFL);
675 ctx.registered_signal_handlers.clear();
676 ctx.previous_signal_handlers.clear();
679 inline void restore_signal_handlers()
681 auto& ctx = SignalHandlerContext::instance();
682 std::lock_guard<std::mutex>
const lock{ctx.signal_handlers_mutex};
684 restore_signal_handler_entries(ctx.previous_signal_handlers);
685 ctx.registered_signal_handlers.clear();
686 ctx.previous_signal_handlers.clear();
QUILL_ATTRIBUTE_HOT void sleep_for_ns(uint64_t ns) noexcept
Mirrors std::this_thread::sleep_for(std::chrono::nanoseconds{ns}).
Definition: ThreadPrimitives.h:43
uint32_t timeout_seconds
Defines the timeout duration in seconds for the signal handler alarm.
Definition: SignalHandler.h:67
std::vector< std::string > excluded_logger_substrings
List of substrings used to exclude loggers during automatic logger selection.
Definition: SignalHandler.h:89
Setups a signal handler to handle fatal signals.
Definition: BackendManager.h:28
Definition: SignalHandler.h:196
Definition: LoggerBase.h:39
custom exception
Definition: QuillError.h:47
std::vector< int > catchable_signals
List of signals that the backend should catch if with_signal_handler is enabled.
Definition: SignalHandler.h:58
Struct to hold options for the signal handler.
Definition: SignalHandler.h:50
std::string logger_name
The name of the logger instance that the signal handler will use to log errors when the application c...
Definition: SignalHandler.h:79
Definition: SignalHandler.h:98