quill
Backend.h
1 
7 #pragma once
8 
9 #include "quill/backend/BackendManager.h"
10 #include "quill/backend/BackendOptions.h"
11 #include "quill/backend/SignalHandler.h"
12 #include "quill/core/Attributes.h"
13 #include "quill/core/MetricManager.h"
14 #include "quill/core/QuillError.h"
15 
16 #include <atomic>
17 #include <csignal>
18 #include <cstdint>
19 #include <cstdlib>
20 #include <mutex>
21 
22 QUILL_BEGIN_NAMESPACE
23 
24 QUILL_BEGIN_EXPORT
25 
27 constexpr uint32_t VersionMajor{12};
28 constexpr uint32_t VersionMinor{0};
29 constexpr uint32_t VersionPatch{0};
30 constexpr uint32_t Version{VersionMajor * 10000 + VersionMinor * 100 + VersionPatch};
31 
32 class Backend
33 {
34 public:
40  QUILL_ATTRIBUTE_COLD static void start(BackendOptions const& options = BackendOptions{})
41  {
42  std::call_once(detail::BackendManager::instance().get_start_once_flag(),
43  [options]()
44  {
45  // Static-destruction ordering matters here because Backend::stop() runs via
46  // std::atexit. Any singleton the backend may touch during shutdown must be
47  // constructed before we register the atexit handler, otherwise it could be
48  // destroyed first and the backend could dereference freed state while draining
49  // queues.
50  //
51  // BackendManager construction already pins ThreadContextManager,
52  // SinkManager, and LoggerManager because BackendWorker stores references to
53  // them as members. SignalHandlerContext and MetricManager are not pinned that
54  // way, so construct them explicitly before atexit registration.
55  (void)detail::SignalHandlerContext::instance();
56  (void)detail::MetricManager::instance();
57 
58  // Run the backend worker thread, we wait here until the thread enters the main loop
59  detail::BackendManager::instance().start_backend_thread(options);
60 
61  // Set up an exit handler to call stop when the main application exits.
62  // always call stop on destruction to log everything. std::atexit seems to be
63  // working better with dll on windows compared to using ~LogManagerSingleton().
64  if (!detail::BackendManager::instance().is_atexit_registered())
65  {
66  detail::BackendManager::instance().set_atexit_registered();
67  std::atexit([]() { Backend::stop(); });
68  }
69  });
70  }
71 
98  template <typename TFrontendOptions>
99  QUILL_ATTRIBUTE_COLD static void start(BackendOptions const& backend_options,
100  SignalHandlerOptions const& signal_handler_options)
101  {
102  std::call_once(
103  detail::BackendManager::instance().get_start_once_flag(),
104  [backend_options, signal_handler_options]()
105  {
106  // These flags are only read in the catch block; unused in no-exception builds.
107  QUILL_MAYBE_UNUSED bool signal_handler_initialized{false};
108 #if defined(_WIN32)
109  QUILL_MAYBE_UNUSED bool exception_handler_initialized{false};
110 #else
111  sigset_t set, oldset;
112  QUILL_MAYBE_UNUSED bool signal_mask_modified{false};
113 #endif
114  QUILL_TRY
115  {
116  // See Backend::start(BackendOptions) for the shutdown-order rationale.
117  // BackendManager construction already pins ThreadContextManager,
118  // SinkManager, and LoggerManager through BackendWorker member references.
119  (void)detail::MetricManager::instance();
120 
121 #if defined(_WIN32)
122  detail::init_exception_handler<TFrontendOptions>();
123  exception_handler_initialized = true;
124 #else
125  // We do not want the signal handler to run in the backend worker thread.
126  // Block signals in the caller thread so the backend worker inherits that mask.
127  sigfillset(&set);
128  if (sigprocmask(SIG_SETMASK, &set, &oldset) != 0)
129  {
130  QUILL_THROW(QuillError{"Failed to block signals before starting the backend thread"});
131  }
132  signal_mask_modified = true;
133  detail::init_signal_handler<TFrontendOptions>(signal_handler_options.catchable_signals);
134  signal_handler_initialized = true;
135 #endif
136 
137  auto& signal_handler_context = detail::SignalHandlerContext::instance();
138  signal_handler_context.logger_name = signal_handler_options.logger_name;
139  signal_handler_context.excluded_logger_substrings = signal_handler_options.excluded_logger_substrings;
140  signal_handler_context.signal_handler_timeout_seconds.store(signal_handler_options.timeout_seconds);
141 
142  // Run the backend worker thread, we wait here until the thread enters the main loop
143  detail::BackendManager::instance().start_backend_thread(backend_options);
144 
145  // We need to update the signal handler with some backend thread details
146  signal_handler_context.backend_thread_id.store(
147  detail::BackendManager::instance().get_backend_thread_id());
148 
149 #if defined(_WIN32)
150  // nothing to do
151 #else
152  // Unblock signals in the caller thread so subsequent threads do not inherit the blocked mask
153  if (sigprocmask(SIG_SETMASK, &oldset, nullptr) != 0)
154  {
155  QUILL_THROW(
156  QuillError{"Failed to restore the caller signal mask after backend startup"});
157  }
158  signal_mask_modified = false;
159 #endif
160 
161  // Set up an exit handler to call stop when the main application exits.
162  // always call stop on destruction to log everything. std::atexit seems to be
163  // working better with dll on windows compared to using ~LogManagerSingleton().
164  if (!detail::BackendManager::instance().is_atexit_registered())
165  {
166  detail::BackendManager::instance().set_atexit_registered();
167  std::atexit([]() { Backend::stop(); });
168  }
169  }
170 #if !defined(QUILL_NO_EXCEPTIONS)
171  QUILL_CATCH(...)
172  {
173  if (signal_handler_initialized)
174  {
175  detail::restore_signal_handlers();
176  }
177  #if defined(_WIN32)
178  if (exception_handler_initialized)
179  {
180  detail::deinit_exception_handler<TFrontendOptions>();
181  }
182  #else
183  if (signal_mask_modified)
184  {
185  sigprocmask(SIG_SETMASK, &oldset, nullptr);
186  }
187  #endif
188  throw;
189  }
190 #endif
191  });
192  }
193 
205  QUILL_ATTRIBUTE_COLD static void stop()
206  {
207  uint32_t const backend_thread_id = detail::BackendManager::instance().get_backend_thread_id();
208  if (QUILL_UNLIKELY((backend_thread_id != 0) && (backend_thread_id == detail::get_thread_id())))
209  {
210  QUILL_THROW(QuillError{"Backend::stop() cannot be called from the backend worker thread"});
211  }
212 
213  detail::SignalHandlerContext::instance().backend_thread_id.store(0);
214  detail::BackendManager::instance().stop_backend_thread();
215 #if defined(_WIN32)
216  if (auto const fn = detail::SignalHandlerContext::instance().exception_handler_deinit_callback)
217  {
218  fn();
219  }
220 #endif
221  detail::deinit_signal_handler();
222  }
223 
231  static void notify() noexcept { detail::BackendManager::instance().notify_backend_thread(); }
232 
237  QUILL_NODISCARD static bool is_running() noexcept
238  {
239  return detail::BackendManager::instance().is_backend_thread_running();
240  }
241 
246  QUILL_NODISCARD static uint32_t get_thread_id() noexcept
247  {
248  return detail::BackendManager::instance().get_backend_thread_id();
249  }
250 
262  QUILL_NODISCARD static uint64_t convert_rdtsc_to_epoch_time(uint64_t rdtsc_value)
263  {
264  return detail::BackendManager::instance().convert_rdtsc_to_epoch_time(rdtsc_value);
265  }
266 
311  QUILL_ATTRIBUTE_COLD static ManualBackendWorker* acquire_manual_backend_worker()
312  {
313  ManualBackendWorker* manual_backend_worker{nullptr};
314 
315  // If a caller forgets to perform explicit ManualBackendWorker::shutdown(), the
316  // ManualBackendWorker destructor can still drain queued metric events during static
317  // destruction. Construct MetricManager before BackendManager so MetricMetadata stays alive
318  // for that fallback drain path.
319  (void)detail::MetricManager::instance();
320 
321  std::call_once(
322  detail::BackendManager::instance().get_start_once_flag(), [&manual_backend_worker]() mutable
323  { manual_backend_worker = detail::BackendManager::instance().get_manual_backend_worker(); });
324 
325  if (!manual_backend_worker)
326  {
327  QUILL_THROW(
328  QuillError{"acquire_manual_backend_worker() can only be called once per process. "
329  "Additionally, it should not be "
330  "called when start() has already been invoked"});
331  }
332 
333  return manual_backend_worker;
334  }
335 };
336 
337 QUILL_END_EXPORT
338 
339 QUILL_END_NAMESPACE
static QUILL_NODISCARD uint32_t get_thread_id() noexcept
Retrieves the ID of the backend thread.
Definition: Backend.h:246
static QUILL_NODISCARD bool is_running() noexcept
Checks if the backend is currently running.
Definition: Backend.h:237
static QUILL_ATTRIBUTE_COLD void start(BackendOptions const &backend_options, SignalHandlerOptions const &signal_handler_options)
Starts the backend thread and initialises a signal handler.
Definition: Backend.h:99
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
static void notify() noexcept
Notifies the backend thread to wake up.
Definition: Backend.h:231
static QUILL_ATTRIBUTE_COLD void stop()
Stops the backend thread.
Definition: Backend.h:205
static QUILL_ATTRIBUTE_COLD ManualBackendWorker * acquire_manual_backend_worker()
This feature is designed for advanced users who need to run the backend worker on their own thread...
Definition: Backend.h:311
Definition: Backend.h:32
This class can be used when you want to run the backend worker on your own thread.
Definition: ManualBackendWorker.h:30
static QUILL_ATTRIBUTE_COLD void start(BackendOptions const &options=BackendOptions{})
Starts the backend thread.
Definition: Backend.h:40
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
static QUILL_NODISCARD uint64_t convert_rdtsc_to_epoch_time(uint64_t rdtsc_value)
Converts an rdtsc value to epoch time.
Definition: Backend.h:262
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
QUILL_NODISCARD QUILL_EXPORT QUILL_ATTRIBUTE_USED uint32_t get_thread_id() noexcept
Returns the os assigned ID of the thread.
Definition: ThreadUtilities.h:213
Configuration options for the backend.
Definition: BackendOptions.h:51