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 <chrono>
39  #include <thread>
40  #include <windows.h>
41 #else
42  #include <unistd.h>
43 #endif
44 
45 QUILL_BEGIN_NAMESPACE
46 
51 {
56  std::vector<int> catchable_signals{SIGTERM, SIGINT, SIGABRT, SIGFPE, SIGILL, SIGSEGV};
57 
65  uint32_t timeout_seconds = 20u;
66 
77  std::string logger_name;
78 
87  std::vector<std::string> excluded_logger_substrings{"__csv__"};
88 };
89 
90 namespace detail
91 {
92 /***/
94 {
95 public:
97  SignalHandlerContext& operator=(SignalHandlerContext const&) = delete;
98 
99  /***/
100  QUILL_EXPORT static SignalHandlerContext& instance() noexcept
101  {
102  static SignalHandlerContext instance;
103  return instance;
104  }
105 
106  /***/
107  QUILL_NODISCARD static LoggerBase* get_logger() noexcept
108  {
109  LoggerBase* logger_base{nullptr};
110 
111  if (!instance().logger_name.empty())
112  {
113  logger_base = LoggerManager::instance().get_logger(instance().logger_name);
114  }
115 
116  // This also checks if the logger was found above
117  if (!logger_base || !logger_base->is_valid_logger())
118  {
119  logger_base = LoggerManager::instance().get_valid_logger(instance().excluded_logger_substrings);
120  }
121 
122  return logger_base;
123  }
124 
125  std::vector<std::string> excluded_logger_substrings{};
126  std::string logger_name{};
127  std::atomic<int32_t> signal_number{0};
128  std::atomic<uint32_t> lock{0};
129  std::atomic<uint32_t> backend_thread_id{0};
130  std::atomic<uint32_t> signal_handler_timeout_seconds{20};
131  std::atomic<bool> should_reraise_signal{true};
132  std::mutex signal_handlers_mutex;
133  std::vector<int> registered_signal_handlers{};
134 
135 private:
136  SignalHandlerContext() = default;
137  ~SignalHandlerContext() = default;
138 };
139 
140 #define QUILL_SIGNAL_HANDLER_LOG(logger, log_level, fmt, ...) \
141  do \
142  { \
143  if (logger->template should_log_statement<log_level>()) \
144  { \
145  static constexpr quill::MacroMetadata macro_metadata{ \
146  "SignalHandler:~", "", fmt, nullptr, log_level, quill::MacroMetadata::Event::Log}; \
147  \
148  logger->template log_statement<false>(&macro_metadata, ##__VA_ARGS__); \
149  } \
150  } while (0)
151 
152 /***/
153 template <typename TFrontendOptions>
154 void on_signal(int32_t signal_number)
155 {
156  // This handler can be entered by multiple threads.
157  uint32_t const lock = SignalHandlerContext::instance().lock.fetch_add(1);
158 
159  if (lock != 0)
160  {
161  // We only allow the first thread to enter the signal handler
162 
163  // sleep until a signal is delivered that either terminates the process or causes the
164  // invocation of a signal-catching function.
165 
166 #if defined(_WIN32)
167  std::this_thread::sleep_for(std::chrono::hours{24000});
168 #else
169  pause();
170 #endif
171  }
172 
173 #if defined(_WIN32)
174  // nothing to do, windows do not have alarm
175 #else
176  // Store the original signal number for the alarm
177  SignalHandlerContext::instance().signal_number.store(signal_number);
178 
179  // Set up an alarm to crash after 20 seconds by redelivering the original signal,
180  // in case anything else goes wrong
181  alarm(SignalHandlerContext::instance().signal_handler_timeout_seconds.load());
182 #endif
183 
184  // Get the id of this thread in the handler and make sure it is not the backend worker thread
185  uint32_t const backend_thread_id = SignalHandlerContext::instance().backend_thread_id.load();
186  uint32_t const current_thread_id = get_thread_id();
187  bool const should_reraise_signal = SignalHandlerContext::instance().should_reraise_signal.load();
188 
189  if ((backend_thread_id == 0) || (current_thread_id == backend_thread_id))
190  {
191  // backend worker thread is not running or the signal handler is called in the backend worker thread
192  if (signal_number == SIGINT || signal_number == SIGTERM)
193  {
194  std::exit(EXIT_SUCCESS);
195  }
196 
197  if (should_reraise_signal)
198  {
199  // for other signals expect SIGINT and SIGTERM we re-raise
200  std::signal(signal_number, SIG_DFL);
201  std::raise(signal_number);
202  }
203  }
204  else
205  {
206  // This means signal handler is running on a frontend thread, we can log and flush
207  LoggerBase* logger_base = SignalHandlerContext::instance().get_logger();
208 
209  if (logger_base)
210  {
211 #if defined(_WIN32)
212  int32_t const signal_desc = signal_number;
213 #else
214  char const* const signal_desc = ::strsignal(signal_number);
215 #endif
216 
217  auto logger = reinterpret_cast<LoggerImpl<TFrontendOptions>*>(logger_base);
218  QUILL_SIGNAL_HANDLER_LOG(logger, LogLevel::Info, "Received signal: {} (signum: {})",
219  signal_desc, signal_number);
220 
221  if (signal_number == SIGINT || signal_number == SIGTERM)
222  {
223  // For SIGINT and SIGTERM, we are shutting down gracefully
224  // Pass `0` to avoid calling std::this_thread::sleep_for()
225  logger->flush_log(0);
226  std::exit(EXIT_SUCCESS);
227  }
228 
229  if (should_reraise_signal)
230  {
231  QUILL_SIGNAL_HANDLER_LOG(logger, LogLevel::Critical,
232  "Program terminated unexpectedly due to signal: {} (signum: {})",
233  signal_desc, signal_number);
234 
235  // This is here in order to flush the above log statement
236  logger->flush_log(0);
237 
238  // Reset to the default signal handler and re-raise the signal
239  std::signal(signal_number, SIG_DFL);
240  std::raise(signal_number);
241  }
242  else
243  {
244  logger->flush_log(0);
245  }
246  }
247  }
248 }
249 } // namespace detail
250 
254 #if defined(_WIN32)
255 namespace detail
256 {
257 /***/
258 inline char const* get_error_message(DWORD ex_code)
259 {
260  switch (ex_code)
261  {
262  case EXCEPTION_ACCESS_VIOLATION:
263  return "EXCEPTION_ACCESS_VIOLATION";
264  case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
265  return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED";
266  case EXCEPTION_BREAKPOINT:
267  return "EXCEPTION_BREAKPOINT";
268  case EXCEPTION_DATATYPE_MISALIGNMENT:
269  return "EXCEPTION_DATATYPE_MISALIGNMENT";
270  case EXCEPTION_FLT_DENORMAL_OPERAND:
271  return "EXCEPTION_FLT_DENORMAL_OPERAND";
272  case EXCEPTION_FLT_DIVIDE_BY_ZERO:
273  return "EXCEPTION_FLT_DIVIDE_BY_ZERO";
274  case EXCEPTION_FLT_INEXACT_RESULT:
275  return "EXCEPTION_FLT_INEXACT_RESULT";
276  case EXCEPTION_FLT_INVALID_OPERATION:
277  return "EXCEPTION_FLT_INVALID_OPERATION";
278  case EXCEPTION_FLT_OVERFLOW:
279  return "EXCEPTION_FLT_OVERFLOW";
280  case EXCEPTION_FLT_STACK_CHECK:
281  return "EXCEPTION_FLT_STACK_CHECK";
282  case EXCEPTION_FLT_UNDERFLOW:
283  return "EXCEPTION_FLT_UNDERFLOW";
284  case EXCEPTION_ILLEGAL_INSTRUCTION:
285  return "EXCEPTION_ILLEGAL_INSTRUCTION";
286  case EXCEPTION_IN_PAGE_ERROR:
287  return "EXCEPTION_IN_PAGE_ERROR";
288  case EXCEPTION_INT_DIVIDE_BY_ZERO:
289  return "EXCEPTION_INT_DIVIDE_BY_ZERO";
290  case EXCEPTION_INT_OVERFLOW:
291  return "EXCEPTION_INT_OVERFLOW";
292  case EXCEPTION_INVALID_DISPOSITION:
293  return "EXCEPTION_INVALID_DISPOSITION";
294  case EXCEPTION_NONCONTINUABLE_EXCEPTION:
295  return "EXCEPTION_NONCONTINUABLE_EXCEPTION";
296  case EXCEPTION_PRIV_INSTRUCTION:
297  return "EXCEPTION_PRIV_INSTRUCTION";
298  case EXCEPTION_SINGLE_STEP:
299  return "EXCEPTION_SINGLE_STEP";
300  case EXCEPTION_STACK_OVERFLOW:
301  return "EXCEPTION_STACK_OVERFLOW";
302  default:
303  return "Unrecognized Exception";
304  }
305 }
306 
307 /***/
308 template <typename TFrontendOptions>
309 BOOL WINAPI on_console_signal(DWORD signal)
310 {
311  uint32_t const backend_thread_id = SignalHandlerContext::instance().backend_thread_id.load();
312  uint32_t const current_thread_id = get_thread_id();
313 
314  // Check if the signal handler is running from a caller thread and if the signal is CTRL+C or CTRL+BREAK
315  if ((backend_thread_id != 0) && (current_thread_id != backend_thread_id) &&
316  (signal == CTRL_C_EVENT || signal == CTRL_BREAK_EVENT))
317  {
318  // Log the interruption and flush log messages
319  LoggerBase* logger_base = SignalHandlerContext::instance().get_logger();
320 
321  if (logger_base)
322  {
323  auto logger = reinterpret_cast<LoggerImpl<TFrontendOptions>*>(logger_base);
324  QUILL_SIGNAL_HANDLER_LOG(logger, LogLevel::Info,
325  "Program interrupted by Ctrl+C or Ctrl+Break signal");
326 
327  // Pass `0` to avoid calling std::this_thread::sleep_for()
328  logger->flush_log(0);
329  std::exit(EXIT_SUCCESS);
330  }
331  }
332 
333  return TRUE;
334 }
335 
336 /***/
337 template <typename TFrontendOptions>
338 LONG WINAPI on_exception(EXCEPTION_POINTERS* exception_p)
339 {
340  uint32_t const backend_thread_id = SignalHandlerContext::instance().backend_thread_id.load();
341  uint32_t const current_thread_id = get_thread_id();
342 
343  // Check if the signal handler is running from a caller thread and if the signal is CTRL+C or CTRL+BREAK
344  if ((backend_thread_id != 0) && (current_thread_id != backend_thread_id))
345  {
346  // Log the interruption and flush log messages
347  LoggerBase* logger_base = SignalHandlerContext::instance().get_logger();
348 
349  if (logger_base)
350  {
351  auto logger = reinterpret_cast<LoggerImpl<TFrontendOptions>*>(logger_base);
352 
353  QUILL_SIGNAL_HANDLER_LOG(logger, LogLevel::Info, "Received exception: {} (Code: {})",
354  get_error_message(exception_p->ExceptionRecord->ExceptionCode),
355  exception_p->ExceptionRecord->ExceptionCode);
356 
357  QUILL_SIGNAL_HANDLER_LOG(logger, LogLevel::Critical,
358  "Program terminated unexpectedly due to exception: {} (Code: {})",
359  get_error_message(exception_p->ExceptionRecord->ExceptionCode),
360  exception_p->ExceptionRecord->ExceptionCode);
361 
362  // Pass `0` to avoid calling std::this_thread::sleep_for()
363  logger->flush_log(0);
364  }
365  }
366 
367  // FATAL Exception: It doesn't necessarily stop here. we pass on continue search
368  // If nobody catches it, then it will exit anyhow.
369  // The risk here is if someone is catching this and returning "EXCEPTION_EXECUTE_HANDLER"
370  // but won't shut down then the app will be running with quill shutdown.
371  return EXCEPTION_CONTINUE_SEARCH;
372 }
373 
374 /***/
375 template <typename TFrontendOptions>
376 void init_exception_handler()
377 {
378  SetUnhandledExceptionFilter(on_exception<TFrontendOptions>);
379 
380  if (!SetConsoleCtrlHandler(on_console_signal<TFrontendOptions>, TRUE))
381  {
382  QUILL_THROW(QuillError{"Failed to call SetConsoleCtrlHandler"});
383  }
384 }
385 } // namespace detail
386 
391 template <typename TFrontendOptions>
392 void init_signal_handler(std::vector<int> const& catchable_signals = std::vector<int>{
393  SIGTERM, SIGINT, SIGABRT, SIGFPE, SIGILL, SIGSEGV})
394 {
395  auto& ctx = detail::SignalHandlerContext::instance();
396  std::lock_guard<std::mutex> const lock{ctx.signal_handlers_mutex};
397 
398  ctx.registered_signal_handlers = catchable_signals;
399 
400  for (auto const& catchable_signal : catchable_signals)
401  {
402  // setup a signal handler per signal in the array
403  if (std::signal(catchable_signal, detail::on_signal<TFrontendOptions>) == SIG_ERR)
404  {
405  QUILL_THROW(QuillError{"Failed to setup signal handler for signal: " + std::to_string(catchable_signal)});
406  }
407  }
408 }
409 
410 namespace detail
411 {
412 inline void deinit_signal_handler()
413 {
414  auto& ctx = SignalHandlerContext::instance();
415  std::lock_guard<std::mutex> const lock{ctx.signal_handlers_mutex};
416 
417  for (auto const& signal_number : ctx.registered_signal_handlers)
418  {
419  std::signal(signal_number, SIG_DFL);
420  }
421 
422  ctx.registered_signal_handlers.clear();
423 }
424 } // namespace detail
425 #else
426 namespace detail
427 {
428 /***/
429 inline void on_alarm(int32_t signal_number)
430 {
431  if (SignalHandlerContext::instance().signal_number.load() == 0)
432  {
433  // Will only happen if SIGALRM is the first signal we receive
434  SignalHandlerContext::instance().signal_number = signal_number;
435  }
436 
437  // We will raise the original signal back
438  std::signal(SignalHandlerContext::instance().signal_number, SIG_DFL);
439  std::raise(SignalHandlerContext::instance().signal_number);
440 }
441 
442 template <typename TFrontendOptions>
443 void init_signal_handler(std::vector<int> const& catchable_signals)
444 {
445  auto& ctx = SignalHandlerContext::instance();
446  std::lock_guard<std::mutex> const lock{ctx.signal_handlers_mutex};
447 
448  ctx.registered_signal_handlers = catchable_signals;
449 
450  for (auto const& catchable_signal : catchable_signals)
451  {
452  if (catchable_signal == SIGALRM)
453  {
454  QUILL_THROW(QuillError{"SIGALRM can not be part of catchable_signals."});
455  }
456 
457  // set up a signal handler per signal in the array
458  if (std::signal(catchable_signal, on_signal<TFrontendOptions>) == SIG_ERR)
459  {
460  QUILL_THROW(QuillError{"Failed to setup signal handler for signal: " + std::to_string(catchable_signal)});
461  }
462  }
463 
464  /* Register the alarm handler */
465  if (std::signal(SIGALRM, on_alarm) == SIG_ERR)
466  {
467  QUILL_THROW(QuillError{"Failed to setup signal handler for signal: SIGALRM"});
468  }
469 
470  ctx.registered_signal_handlers.push_back(SIGALRM);
471 }
472 
473 inline void deinit_signal_handler()
474 {
475  auto& ctx = SignalHandlerContext::instance();
476  std::lock_guard<std::mutex> const lock{ctx.signal_handlers_mutex};
477 
478  for (auto const& signal_number : ctx.registered_signal_handlers)
479  {
480  std::signal(signal_number, SIG_DFL);
481  }
482 
483  ctx.registered_signal_handlers.clear();
484 }
485 } // namespace detail
486 #endif
487 
488 QUILL_END_NAMESPACE
uint32_t timeout_seconds
Defines the timeout duration in seconds for the signal handler alarm.
Definition: SignalHandler.h:65
std::vector< std::string > excluded_logger_substrings
List of substrings used to exclude loggers during automatic logger selection.
Definition: SignalHandler.h:87
Setups a signal handler to handle fatal signals.
Definition: BackendManager.h:24
Thread safe logger.
Definition: Logger.h:48
Definition: SignalHandler.h:93
Definition: LoggerBase.h:35
custom exception
Definition: QuillError.h:45
std::vector< int > catchable_signals
List of signals that the backend should catch if with_signal_handler is enabled.
Definition: SignalHandler.h:56
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:77
QUILL_NODISCARD QUILL_EXPORT QUILL_ATTRIBUTE_USED uint32_t get_thread_id() noexcept
Returns the os assigned ID of the thread.
Definition: ThreadUtilities.h:198