quill
SignalHandler.h
1 
7 #pragma once
8 
9 #include "quill/backend/ThreadUtilities.h"
10 
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"
18 
19 #include <atomic>
20 #include <csignal>
21 #include <cstdint>
22 #include <cstdlib>
23 #include <cstring>
24 #include <mutex>
25 #include <string>
26 #include <vector>
27 
28 #if defined(_WIN32)
29  #if !defined(WIN32_LEAN_AND_MEAN)
30  #define WIN32_LEAN_AND_MEAN
31  #endif
32 
33  #if !defined(NOMINMAX)
34  // Mingw already defines this, so no need to redefine
35  #define NOMINMAX
36  #endif
37 
38  #include <windows.h>
39 #else
40  #include <unistd.h>
41 #endif
42 
43 QUILL_BEGIN_NAMESPACE
44 
48 QUILL_BEGIN_EXPORT
49 
51 {
58  std::vector<int> catchable_signals{SIGTERM, SIGINT, SIGABRT, SIGFPE, SIGILL, SIGSEGV};
59 
67  uint32_t timeout_seconds = 20u;
68 
79  std::string logger_name;
80 
89  std::vector<std::string> excluded_logger_substrings{"__csv__"};
90 };
91 
92 QUILL_END_EXPORT
93 
94 namespace detail
95 {
96 using signal_handler_t = void (*)(int);
97 
99 {
100  int signal_number;
101  signal_handler_t previous_handler;
102 };
103 
104 inline void restore_signal_handler_entries(std::vector<SignalHandlerRestoreEntry> const& previous_signal_handlers) noexcept
105 {
106  for (auto it = previous_signal_handlers.rbegin(); it != previous_signal_handlers.rend(); ++it)
107  {
108  std::signal(it->signal_number, it->previous_handler);
109  }
110 }
111 
112 /***/
113 QUILL_NODISCARD inline bool is_synchronous_fault_signal(int signal_number) noexcept
114 {
115  return signal_number == SIGSEGV || signal_number == SIGFPE || signal_number == SIGILL
116 #if defined(SIGBUS)
117  || signal_number == SIGBUS
118 #endif
119 #if defined(SIGTRAP)
120  || signal_number == SIGTRAP
121 #endif
122  ;
123 }
124 
125 /***/
126 QUILL_NODISCARD inline char const* get_signal_description(int32_t signal_number) noexcept
127 {
128  switch (signal_number)
129  {
130  case SIGABRT:
131  return "SIGABRT";
132  case SIGFPE:
133  return "SIGFPE";
134  case SIGILL:
135  return "SIGILL";
136  case SIGINT:
137  return "SIGINT";
138  case SIGSEGV:
139  return "SIGSEGV";
140  case SIGTERM:
141  return "SIGTERM";
142 #if defined(SIGBUS)
143  case SIGBUS:
144  return "SIGBUS";
145 #endif
146 #if defined(SIGHUP)
147  case SIGHUP:
148  return "SIGHUP";
149 #endif
150 #if defined(SIGQUIT)
151  case SIGQUIT:
152  return "SIGQUIT";
153 #endif
154 #if defined(SIGTRAP)
155  case SIGTRAP:
156  return "SIGTRAP";
157 #endif
158 #if defined(SIGPIPE)
159  case SIGPIPE:
160  return "SIGPIPE";
161 #endif
162 #if defined(SIGALRM)
163  case SIGALRM:
164  return "SIGALRM";
165 #endif
166 #if defined(SIGUSR1)
167  case SIGUSR1:
168  return "SIGUSR1";
169 #endif
170 #if defined(SIGUSR2)
171  case SIGUSR2:
172  return "SIGUSR2";
173 #endif
174 #if defined(SIGXCPU)
175  case SIGXCPU:
176  return "SIGXCPU";
177 #endif
178 #if defined(SIGXFSZ)
179  case SIGXFSZ:
180  return "SIGXFSZ";
181 #endif
182 #if defined(SIGVTALRM)
183  case SIGVTALRM:
184  return "SIGVTALRM";
185 #endif
186 #if defined(SIGPROF)
187  case SIGPROF:
188  return "SIGPROF";
189 #endif
190  default:
191  return "UNKNOWN";
192  }
193 }
194 
195 /***/
197 {
198 public:
200  SignalHandlerContext& operator=(SignalHandlerContext const&) = delete;
201 
202  /***/
203  QUILL_EXPORT static SignalHandlerContext& instance() noexcept
204  {
205  static SignalHandlerContext instance;
206  return instance;
207  }
208 
209  /***/
210  QUILL_NODISCARD static LoggerBase* get_logger() noexcept
211  {
212  LoggerBase* logger_base{nullptr};
213 
214  if (!instance().logger_name.empty())
215  {
216  logger_base = LoggerManager::instance().get_logger(instance().logger_name);
217  }
218 
219  // This also checks if the logger was found above
220  if (!logger_base || !logger_base->is_valid_logger())
221  {
222  logger_base = LoggerManager::instance().get_valid_logger(instance().excluded_logger_substrings);
223  }
224 
225  return logger_base;
226  }
227 
228  std::vector<std::string> excluded_logger_substrings{};
229  std::string logger_name{};
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{};
238 #if defined(_WIN32)
239  LPTOP_LEVEL_EXCEPTION_FILTER previous_exception_filter{nullptr};
240  void (*exception_handler_deinit_callback)() = nullptr;
241  bool console_ctrl_handler_installed{false};
242 #endif
243 
244 private:
245  SignalHandlerContext() = default;
246  ~SignalHandlerContext() = default;
247 };
248 
249 #define QUILL_SIGNAL_HANDLER_LOG(logger, log_level, fmt, ...) \
250  do \
251  { \
252  if (logger->template should_log_statement<log_level>()) \
253  { \
254  static constexpr quill::MacroMetadata macro_metadata{ \
255  "SignalHandler:~", "", fmt, nullptr, log_level, quill::MacroMetadata::Event::Log}; \
256  \
257  logger->template log_statement<false>(&macro_metadata, ##__VA_ARGS__); \
258  } \
259  } while (0)
260 
261 /***/
262 template <typename TFrontendOptions>
263 void on_signal(int32_t signal_number)
264 {
265  // This handler can be entered by multiple threads.
266  uint32_t const lock = SignalHandlerContext::instance().lock.fetch_add(1);
267 
268  if (lock != 0)
269  {
270  // We only allow the first thread to enter the signal handler
271 
272  // sleep until a signal is delivered that either terminates the process or causes the
273  // invocation of a signal-catching function.
274 
275 #if defined(_WIN32)
276  detail::sleep_for_ns(24'000ull * 3'600ull * 1'000'000'000ull); // 24000 hours
277 #else
278  pause();
279 #endif
280  }
281 
282 #if defined(_WIN32)
283  // nothing to do, windows do not have alarm
284 #else
285  // Store the original signal number for the alarm
286  SignalHandlerContext::instance().signal_number.store(signal_number);
287 
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());
291 #endif
292 
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();
297 
298  if ((backend_thread_id == 0) || (current_thread_id == backend_thread_id))
299  {
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)
302  {
303  std::_Exit(EXIT_SUCCESS);
304  }
305 
306  if (should_reraise_signal)
307  {
308  // for other signals expect SIGINT and SIGTERM we re-raise
309  std::signal(signal_number, SIG_DFL);
310  std::raise(signal_number);
311  }
312 
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))
316  {
317  std::_Exit(EXIT_FAILURE);
318  }
319  }
320  else
321  {
322  // This means signal handler is running on a frontend thread, we can log and flush
323  LoggerBase* logger_base = SignalHandlerContext::instance().get_logger();
324 
325  if (logger_base)
326  {
327  char const* const signal_desc = get_signal_description(signal_number);
328 
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);
332 
333  if (signal_number == SIGINT || signal_number == SIGTERM)
334  {
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);
339  }
340 
341  if (should_reraise_signal)
342  {
343  QUILL_SIGNAL_HANDLER_LOG(logger, LogLevel::Critical,
344  "Program terminated unexpectedly due to signal: {} (signum: {})",
345  signal_desc, signal_number);
346 
347  // This is here in order to flush the above log statement
348  logger->flush_log(0);
349 
350  // Reset to the default signal handler and re-raise the signal
351  std::signal(signal_number, SIG_DFL);
352  std::raise(signal_number);
353  }
354  else
355  {
356  logger->flush_log(0);
357  }
358  }
359 
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))
363  {
364  std::_Exit(EXIT_FAILURE);
365  }
366  }
367 }
368 } // namespace detail
369 
382 #if defined(_WIN32)
383 namespace detail
384 {
385 /***/
386 inline char const* get_error_message(DWORD ex_code)
387 {
388  switch (ex_code)
389  {
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";
430  default:
431  return "Unrecognized Exception";
432  }
433 }
434 
435 /***/
436 template <typename TFrontendOptions>
437 BOOL WINAPI on_console_signal(DWORD signal)
438 {
439  uint32_t const backend_thread_id = SignalHandlerContext::instance().backend_thread_id.load();
440  uint32_t const current_thread_id = get_thread_id();
441 
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))
445  {
446  // Log the interruption and flush log messages
447  LoggerBase* logger_base = SignalHandlerContext::instance().get_logger();
448 
449  if (logger_base)
450  {
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");
454 
455  // Pass `0` to avoid calling std::this_thread::sleep_for()
456  logger->flush_log(0);
457  std::_Exit(EXIT_SUCCESS);
458  }
459  }
460 
461  return FALSE;
462 }
463 
464 /***/
465 template <typename TFrontendOptions>
466 LONG WINAPI on_exception(EXCEPTION_POINTERS* exception_p)
467 {
468  uint32_t const backend_thread_id = SignalHandlerContext::instance().backend_thread_id.load();
469  uint32_t const current_thread_id = get_thread_id();
470 
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))
473  {
474  // Log the interruption and flush log messages
475  LoggerBase* logger_base = SignalHandlerContext::instance().get_logger();
476 
477  if (logger_base)
478  {
479  auto logger = static_cast<LoggerImpl<TFrontendOptions>*>(logger_base);
480 
481  QUILL_SIGNAL_HANDLER_LOG(logger, LogLevel::Info, "Received exception: {} (Code: {})",
482  get_error_message(exception_p->ExceptionRecord->ExceptionCode),
483  exception_p->ExceptionRecord->ExceptionCode);
484 
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);
489 
490  // Pass `0` to avoid calling std::this_thread::sleep_for()
491  logger->flush_log(0);
492  }
493  }
494 
495  // FATAL Exception: It doesn't necessarily stop here. we pass on continue search
496  // If nobody catches it, then it will exit anyhow.
497  // The risk here is if someone is catching this and returning "EXCEPTION_EXECUTE_HANDLER"
498  // but won't shut down then the app will be running with quill shutdown.
499  return EXCEPTION_CONTINUE_SEARCH;
500 }
501 
502 /***/
503 template <typename TFrontendOptions>
504 void deinit_exception_handler()
505 {
506  auto& ctx = detail::SignalHandlerContext::instance();
507  std::lock_guard<std::mutex> const lock{ctx.signal_handlers_mutex};
508 
509  if (ctx.console_ctrl_handler_installed)
510  {
511  SetConsoleCtrlHandler(on_console_signal<TFrontendOptions>, FALSE);
512  ctx.console_ctrl_handler_installed = false;
513  }
514 
515  SetUnhandledExceptionFilter(ctx.previous_exception_filter);
516  ctx.previous_exception_filter = nullptr;
517  ctx.exception_handler_deinit_callback = nullptr;
518 }
519 
520 /***/
521 template <typename TFrontendOptions>
522 void init_exception_handler()
523 {
524  auto& ctx = detail::SignalHandlerContext::instance();
525  std::lock_guard<std::mutex> const lock{ctx.signal_handlers_mutex};
526 
527  ctx.exception_handler_deinit_callback = nullptr;
528  ctx.previous_exception_filter = SetUnhandledExceptionFilter(on_exception<TFrontendOptions>);
529 
530  if (!SetConsoleCtrlHandler(on_console_signal<TFrontendOptions>, TRUE))
531  {
532  SetUnhandledExceptionFilter(ctx.previous_exception_filter);
533  ctx.previous_exception_filter = nullptr;
534  QUILL_THROW(QuillError{"Failed to call SetConsoleCtrlHandler"});
535  }
536 
537  ctx.exception_handler_deinit_callback = &deinit_exception_handler<TFrontendOptions>;
538  ctx.console_ctrl_handler_installed = true;
539 }
540 } // namespace detail
541 
542 QUILL_BEGIN_EXPORT
543 
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})
553 {
554  auto& ctx = detail::SignalHandlerContext::instance();
555  std::lock_guard<std::mutex> const lock{ctx.signal_handlers_mutex};
556 
557  std::vector<detail::SignalHandlerRestoreEntry> previous_signal_handlers;
558  previous_signal_handlers.reserve(catchable_signals.size());
559 
560  for (auto const& catchable_signal : catchable_signals)
561  {
562  // setup a signal handler per signal in the array
563  auto const previous_handler = std::signal(catchable_signal, detail::on_signal<TFrontendOptions>);
564  if (previous_handler == SIG_ERR)
565  {
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)});
568  }
569 
570  previous_signal_handlers.push_back({catchable_signal, previous_handler});
571  }
572 
573  ctx.registered_signal_handlers = catchable_signals;
574  ctx.previous_signal_handlers = std::move(previous_signal_handlers);
575 }
576 
577 QUILL_END_EXPORT
578 
579 namespace detail
580 {
581 inline void deinit_signal_handler()
582 {
583  auto& ctx = SignalHandlerContext::instance();
584  std::lock_guard<std::mutex> const lock{ctx.signal_handlers_mutex};
585 
586  for (auto const& signal_number : ctx.registered_signal_handlers)
587  {
588  std::signal(signal_number, SIG_DFL);
589  }
590 
591  ctx.registered_signal_handlers.clear();
592  ctx.previous_signal_handlers.clear();
593 }
594 
595 inline void restore_signal_handlers()
596 {
597  auto& ctx = SignalHandlerContext::instance();
598  std::lock_guard<std::mutex> const lock{ctx.signal_handlers_mutex};
599 
600  restore_signal_handler_entries(ctx.previous_signal_handlers);
601  ctx.registered_signal_handlers.clear();
602  ctx.previous_signal_handlers.clear();
603 }
604 } // namespace detail
605 #else
606 namespace detail
607 {
608 /***/
609 inline void on_alarm(int32_t signal_number)
610 {
611  if (SignalHandlerContext::instance().signal_number.load() == 0)
612  {
613  // Will only happen if SIGALRM is the first signal we receive
614  SignalHandlerContext::instance().signal_number = signal_number;
615  }
616 
617  // We will raise the original signal back
618  std::signal(SignalHandlerContext::instance().signal_number, SIG_DFL);
619  std::raise(SignalHandlerContext::instance().signal_number);
620 }
621 
622 template <typename TFrontendOptions>
623 void init_signal_handler(std::vector<int> const& catchable_signals)
624 {
625  auto& ctx = SignalHandlerContext::instance();
626  std::lock_guard<std::mutex> const lock{ctx.signal_handlers_mutex};
627 
628  std::vector<SignalHandlerRestoreEntry> previous_signal_handlers;
629  previous_signal_handlers.reserve(catchable_signals.size() + 1);
630 
631  for (auto const& catchable_signal : catchable_signals)
632  {
633  if (catchable_signal == SIGALRM)
634  {
635  restore_signal_handler_entries(previous_signal_handlers);
636  QUILL_THROW(QuillError{"SIGALRM can not be part of catchable_signals."});
637  }
638 
639  // set up a signal handler per signal in the array
640  auto const previous_handler = std::signal(catchable_signal, on_signal<TFrontendOptions>);
641  if (previous_handler == SIG_ERR)
642  {
643  restore_signal_handler_entries(previous_signal_handlers);
644  QUILL_THROW(QuillError{"Failed to setup signal handler for signal: " + std::to_string(catchable_signal)});
645  }
646 
647  previous_signal_handlers.push_back({catchable_signal, previous_handler});
648  }
649 
650  /* Register the alarm handler */
651  auto const previous_alarm_handler = std::signal(SIGALRM, on_alarm);
652  if (previous_alarm_handler == SIG_ERR)
653  {
654  restore_signal_handler_entries(previous_signal_handlers);
655  QUILL_THROW(QuillError{"Failed to setup signal handler for signal: SIGALRM"});
656  }
657 
658  previous_signal_handlers.push_back({SIGALRM, previous_alarm_handler});
659 
660  ctx.registered_signal_handlers = catchable_signals;
661  ctx.registered_signal_handlers.push_back(SIGALRM);
662  ctx.previous_signal_handlers = std::move(previous_signal_handlers);
663 }
664 
665 inline void deinit_signal_handler()
666 {
667  auto& ctx = SignalHandlerContext::instance();
668  std::lock_guard<std::mutex> const lock{ctx.signal_handlers_mutex};
669 
670  for (auto const& signal_number : ctx.registered_signal_handlers)
671  {
672  std::signal(signal_number, SIG_DFL);
673  }
674 
675  ctx.registered_signal_handlers.clear();
676  ctx.previous_signal_handlers.clear();
677 }
678 
679 inline void restore_signal_handlers()
680 {
681  auto& ctx = SignalHandlerContext::instance();
682  std::lock_guard<std::mutex> const lock{ctx.signal_handlers_mutex};
683 
684  restore_signal_handler_entries(ctx.previous_signal_handlers);
685  ctx.registered_signal_handlers.clear();
686  ctx.previous_signal_handlers.clear();
687 }
688 } // namespace detail
689 #endif
690 
691 QUILL_END_NAMESPACE
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