quill
Logger.h
1 
7 #pragma once
8 
9 #include "quill/UserClockSource.h"
10 #include "quill/core/Attributes.h"
11 #include "quill/core/ChronoTimeUtils.h"
12 #include "quill/core/Codec.h"
13 #include "quill/core/Common.h"
14 #include "quill/core/FrontendOptions.h"
15 #include "quill/core/LogLevel.h"
16 #include "quill/core/LoggerBase.h"
17 #include "quill/core/MacroMetadata.h"
18 #include "quill/core/Rdtsc.h"
19 
20 #include <atomic>
21 #include <cassert>
22 #include <chrono>
23 #include <cstddef>
24 #include <cstdint>
25 #include <cstring>
26 #include <memory>
27 #include <string>
28 #include <thread>
29 #include <vector>
30 
31 QUILL_BEGIN_NAMESPACE
32 
34 class Sink;
35 
36 namespace detail
37 {
38 class LoggerManager;
39 class BackendWorker;
40 } // namespace detail
41 
47 template <typename TFrontendOptions>
49 {
50 public:
51  using frontend_options_t = TFrontendOptions;
52 
53  static constexpr bool using_unbounded_queue =
54  (frontend_options_t::queue_type == QueueType::UnboundedBlocking) ||
55  (frontend_options_t::queue_type == QueueType::UnboundedDropping);
56 
57  /***/
58  LoggerImpl(LoggerImpl const&) = delete;
59  LoggerImpl& operator=(LoggerImpl const&) = delete;
60 
61  ~LoggerImpl() override = default;
62 
74  template <bool enable_immediate_flush, typename... Args>
75  QUILL_ATTRIBUTE_HOT bool log_statement(MacroMetadata const* macro_metadata, Args&&... fmt_args)
76  {
77 #ifndef NDEBUG
78  assert((macro_metadata->event() != MacroMetadata::Event::LogWithRuntimeMetadataDeepCopy &&
79  macro_metadata->event() != MacroMetadata::Event::LogWithRuntimeMetadataShallowCopy &&
80  macro_metadata->event() != MacroMetadata::Event::LogWithRuntimeMetadataHybridCopy) &&
81  "Should not be called with MacroMetadata::Event::LogWithRuntimeMetadata");
82 
83  assert(_valid.load(std::memory_order_acquire) && "Invalidated loggers can not log");
84 #endif
85 
86  // Store the timestamp of the log statement at the start of the call. This gives a more accurate
87  // timestamp, especially if the queue is full
88  uint64_t const current_timestamp = _get_message_timestamp();
89 
90  if (QUILL_UNLIKELY(_thread_context == nullptr))
91  {
92  // This caches the ThreadContext pointer to avoid repeatedly calling get_local_thread_context()
93  _thread_context = detail::get_local_thread_context<frontend_options_t>();
94  }
95 
96  // Need to know how much size we need from the queue
97  size_t total_size = sizeof(current_timestamp) + (sizeof(uintptr_t) * 3) +
99  _thread_context->get_conditional_arg_size_cache(), fmt_args...);
100 
101  std::byte* write_buffer = _reserve_queue_space(total_size, macro_metadata);
102 
103  if (QUILL_UNLIKELY(write_buffer == nullptr))
104  {
105  return false;
106  }
107 
108  // we have enough space in this buffer, and we will write to the buffer
109 
110 #ifndef NDEBUG
111  std::byte const* const write_begin = write_buffer;
112  assert(write_begin);
113 #endif
114 
115  // first encode a header
116  write_buffer = _encode_header(write_buffer, current_timestamp, macro_metadata, this,
117  detail::decode_and_store_args<detail::remove_cvref_t<Args>...>);
118 
119  // encode remaining arguments
120  detail::encode(write_buffer, _thread_context->get_conditional_arg_size_cache(), fmt_args...);
121 
122 #ifndef NDEBUG
123  assert((write_buffer > write_begin) && "write_buffer must be greater than write_begin");
124  assert(total_size == (static_cast<size_t>(write_buffer - write_begin)) &&
125  "The committed write bytes must be equal to the total_size requested bytes");
126 #endif
127 
128  _commit_log_statement<enable_immediate_flush>(total_size);
129 
130  return true;
131  }
132 
150  template <bool enable_immediate_flush, typename... Args>
151  QUILL_ATTRIBUTE_HOT bool log_statement_runtime_metadata(MacroMetadata const* macro_metadata,
152  char const* fmt, char const* file_path,
153  char const* function_name,
154  char const* tags, uint32_t line_number,
155  LogLevel log_level, Args&&... fmt_args)
156  {
157 #ifndef NDEBUG
158  assert((macro_metadata->event() == MacroMetadata::Event::LogWithRuntimeMetadataDeepCopy ||
159  macro_metadata->event() == MacroMetadata::Event::LogWithRuntimeMetadataHybridCopy ||
160  macro_metadata->event() == MacroMetadata::Event::LogWithRuntimeMetadataShallowCopy) &&
161  "Should only be called with MacroMetadata::Event::LogWithRuntimeMetadata");
162 
163  assert(_valid.load(std::memory_order_acquire) && "Invalidated loggers can not log");
164 #endif
165 
166  // Store the timestamp of the log statement at the start of the call. This gives a more accurate
167  // timestamp, especially if the queue is full
168  uint64_t const current_timestamp = _get_message_timestamp();
169 
170  if (QUILL_UNLIKELY(_thread_context == nullptr))
171  {
172  // This caches the ThreadContext pointer to avoid repeatedly calling get_local_thread_context()
173  _thread_context = detail::get_local_thread_context<frontend_options_t>();
174  }
175 
176  // Need to know how much size we need from the queue
177  // Here we need extra size for the metadata
178  size_t total_size{sizeof(current_timestamp) + (sizeof(uintptr_t) * 3)};
179 
180  if (macro_metadata->event() == MacroMetadata::Event::LogWithRuntimeMetadataDeepCopy)
181  {
183  _thread_context->get_conditional_arg_size_cache(), fmt, file_path, function_name, tags,
184  line_number, log_level, fmt_args...);
185  }
186  else if (macro_metadata->event() == MacroMetadata::Event::LogWithRuntimeMetadataShallowCopy)
187  {
189  _thread_context->get_conditional_arg_size_cache(), static_cast<void const*>(fmt),
190  static_cast<void const*>(file_path), static_cast<void const*>(function_name),
191  static_cast<void const*>(tags), line_number, log_level, fmt_args...);
192  }
193  else if (macro_metadata->event() == MacroMetadata::Event::LogWithRuntimeMetadataHybridCopy)
194  {
196  _thread_context->get_conditional_arg_size_cache(), fmt, static_cast<void const*>(file_path),
197  static_cast<void const*>(function_name), tags, line_number, log_level, fmt_args...);
198  }
199  else
200  {
201  return false;
202  }
203 
204  std::byte* write_buffer = _reserve_queue_space(total_size, macro_metadata);
205 
206  if (QUILL_UNLIKELY(write_buffer == nullptr))
207  {
208  return false;
209  }
210 
211  // we have enough space in this buffer, and we will write to the buffer
212 
213 #ifndef NDEBUG
214  std::byte const* const write_begin = write_buffer;
215  assert(write_begin);
216 #endif
217 
218  // first encode a header
219  write_buffer = _encode_header(write_buffer, current_timestamp, macro_metadata, this,
220  detail::decode_and_store_args<detail::remove_cvref_t<Args>...>);
221 
222  if (macro_metadata->event() == MacroMetadata::Event::LogWithRuntimeMetadataDeepCopy)
223  {
224  // encode runtime metadata and remaining arguments
225  detail::encode(write_buffer, _thread_context->get_conditional_arg_size_cache(), fmt,
226  file_path, function_name, tags, line_number, log_level, fmt_args...);
227  }
228  else if (macro_metadata->event() == MacroMetadata::Event::LogWithRuntimeMetadataShallowCopy)
229  {
230  detail::encode(write_buffer, _thread_context->get_conditional_arg_size_cache(),
231  static_cast<void const*>(fmt), static_cast<void const*>(file_path),
232  static_cast<void const*>(function_name), static_cast<void const*>(tags),
233  line_number, log_level, fmt_args...);
234  }
235  else if (macro_metadata->event() == MacroMetadata::Event::LogWithRuntimeMetadataHybridCopy)
236  {
237  detail::encode(write_buffer, _thread_context->get_conditional_arg_size_cache(), fmt,
238  static_cast<void const*>(file_path), static_cast<void const*>(function_name),
239  tags, line_number, log_level, fmt_args...);
240  }
241 
242 #ifndef NDEBUG
243  assert((write_buffer > write_begin) && "write_buffer must be greater than write_begin");
244  assert(total_size == (static_cast<size_t>(write_buffer - write_begin)) &&
245  "The committed write bytes must be equal to the total_size requested bytes");
246 #endif
247 
248  _commit_log_statement<enable_immediate_flush>(total_size);
249 
250  return true;
251  }
252 
260  void init_backtrace(uint32_t max_capacity, LogLevel flush_level = LogLevel::None)
261  {
262  // we do not care about the other fields, except MacroMetadata::Event::InitBacktrace
263  static constexpr MacroMetadata macro_metadata{
264  "", "", "{}", nullptr, LogLevel::Critical, MacroMetadata::Event::InitBacktrace};
265 
266  // we pass this message to the queue and also pass capacity as arg
267  // We do not want to drop the message if a dropping queue is used
268  while (!this->template log_statement<false>(&macro_metadata, max_capacity))
269  {
270  std::this_thread::sleep_for(std::chrono::nanoseconds{100});
271  }
272 
273  // Also store the desired flush log level
274  _backtrace_flush_level.store(flush_level, std::memory_order_relaxed);
275  }
276 
281  {
282  // we do not care about the other fields, except MacroMetadata::Event::Flush
283  static constexpr MacroMetadata macro_metadata{
284  "", "", "", nullptr, LogLevel::Critical, MacroMetadata::Event::FlushBacktrace};
285 
286  // We do not want to drop the message if a dropping queue is used
287  while (!this->template log_statement<false>(&macro_metadata))
288  {
289  std::this_thread::sleep_for(std::chrono::nanoseconds{100});
290  }
291  }
292 
310  void flush_log(uint32_t sleep_duration_ns = 100)
311  {
312  static constexpr MacroMetadata macro_metadata{
313  "", "", "", nullptr, LogLevel::Critical, MacroMetadata::Event::Flush};
314 
315  std::atomic<bool> backend_thread_flushed{false};
316  std::atomic<bool>* backend_thread_flushed_ptr = &backend_thread_flushed;
317 
318  // We do not want to drop the message if a dropping queue is used
319  while (!this->log_statement<false>(&macro_metadata, reinterpret_cast<uintptr_t>(backend_thread_flushed_ptr)))
320  {
321  if (sleep_duration_ns > 0)
322  {
323  std::this_thread::sleep_for(std::chrono::nanoseconds{sleep_duration_ns});
324  }
325  else
326  {
327  std::this_thread::yield();
328  }
329  }
330 
331  // The caller thread keeps checking the flag until the backend thread flushes
332  while (!backend_thread_flushed.load())
333  {
334  if (sleep_duration_ns > 0)
335  {
336  std::this_thread::sleep_for(std::chrono::nanoseconds{sleep_duration_ns});
337  }
338  else
339  {
340  std::this_thread::yield();
341  }
342  }
343  }
344 
345 private:
346  friend class detail::LoggerManager;
347  friend class detail::BackendWorker;
348 
349  /***/
350  LoggerImpl(std::string logger_name, std::vector<std::shared_ptr<Sink>> sinks,
351  PatternFormatterOptions pattern_formatter_options, ClockSourceType clock_source,
352  UserClockSource* user_clock)
354  static_cast<std::string&&>(logger_name), static_cast<std::vector<std::shared_ptr<Sink>>&&>(sinks),
355  static_cast<PatternFormatterOptions&&>(pattern_formatter_options), clock_source, user_clock)
356 
357  {
358  if (this->_user_clock)
359  {
360  // if a user clock is provided then set the ClockSourceType to User
361  this->_clock_source = ClockSourceType::User;
362  }
363  }
364 
369  QUILL_NODISCARD QUILL_ATTRIBUTE_HOT uint64_t _get_message_timestamp() const
370  {
371  // This is very rare but might lead to out-of-order timestamp in the log file if a thread blocks
372  // on _reserve_queue_space for too long
373 
374  if (_clock_source == ClockSourceType::Tsc)
375  {
376  return detail::rdtsc();
377  }
378 
379  if (_clock_source == ClockSourceType::System)
380  {
381  return detail::get_timestamp_ns<std::chrono::system_clock>();
382  }
383 
384  if (_user_clock)
385  {
386  return _user_clock->now();
387  }
388 
389  // not expected
390  return 0;
391  }
392 
399  QUILL_NODISCARD QUILL_ATTRIBUTE_HOT std::byte* _reserve_queue_space(size_t total_size,
400  MacroMetadata const* macro_metadata)
401  {
402  std::byte* write_buffer =
403  _thread_context->get_spsc_queue<frontend_options_t::queue_type>().prepare_write(total_size);
404 
405  if constexpr ((frontend_options_t::queue_type == QueueType::BoundedDropping) ||
406  (frontend_options_t::queue_type == QueueType::UnboundedDropping))
407  {
408  if (QUILL_UNLIKELY(write_buffer == nullptr))
409  {
410  // not enough space to push to queue message is dropped
411  if (macro_metadata->event() == MacroMetadata::Event::Log)
412  {
413  _thread_context->increment_failure_counter();
414  }
415  }
416  }
417  else if constexpr ((frontend_options_t::queue_type == QueueType::BoundedBlocking) ||
418  (frontend_options_t::queue_type == QueueType::UnboundedBlocking))
419  {
420  if (QUILL_UNLIKELY(write_buffer == nullptr))
421  {
422  if (macro_metadata->event() == MacroMetadata::Event::Log)
423  {
424  _thread_context->increment_failure_counter();
425  }
426 
427  do
428  {
429  if constexpr (frontend_options_t::blocking_queue_retry_interval_ns > 0)
430  {
431  std::this_thread::sleep_for(std::chrono::nanoseconds{frontend_options_t::blocking_queue_retry_interval_ns});
432  }
433 
434  // not enough space to push to queue, keep trying
435  write_buffer =
436  _thread_context->get_spsc_queue<frontend_options_t::queue_type>().prepare_write(total_size);
437  } while (write_buffer == nullptr);
438  }
439  }
440 
441  return write_buffer;
442  }
443 
448  template <bool enable_immediate_flush>
449  QUILL_ATTRIBUTE_HOT void _commit_log_statement(size_t total_size)
450  {
451  _thread_context->get_spsc_queue<frontend_options_t::queue_type>().finish_and_commit_write(total_size);
452 
453  if constexpr (enable_immediate_flush)
454  {
455  uint32_t const threshold = _message_flush_threshold.load(std::memory_order_relaxed);
456  if (QUILL_UNLIKELY(threshold != 0))
457  {
458  uint32_t const prev = _messages_since_last_flush.fetch_add(1, std::memory_order_relaxed);
459  if ((prev + 1) >= threshold)
460  {
461  _messages_since_last_flush.store(0, std::memory_order_relaxed);
462  this->flush_log();
463  }
464  }
465  }
466  }
467 
476  QUILL_NODISCARD QUILL_ATTRIBUTE_HOT static std::byte* _encode_header(std::byte* write_buffer, uint64_t timestamp,
477  MacroMetadata const* metadata,
478  detail::LoggerBase* logger_ctx,
479  detail::FormatArgsDecoder decoder) noexcept
480  {
481  std::memcpy(write_buffer, &timestamp, sizeof(timestamp));
482  write_buffer += sizeof(timestamp);
483 
484  std::memcpy(write_buffer, &metadata, sizeof(uintptr_t));
485  write_buffer += sizeof(uintptr_t);
486 
487  std::memcpy(write_buffer, &logger_ctx, sizeof(uintptr_t));
488  write_buffer += sizeof(uintptr_t);
489 
490  std::memcpy(write_buffer, &decoder, sizeof(uintptr_t));
491  write_buffer += sizeof(uintptr_t);
492 
493  return write_buffer;
494  }
495 };
496 
497 using Logger = LoggerImpl<FrontendOptions>;
498 
499 QUILL_END_NAMESPACE
void init_backtrace(uint32_t max_capacity, LogLevel flush_level=LogLevel::None)
Init a backtrace for this logger.
Definition: Logger.h:260
Base class for sinks.
Definition: Sink.h:40
Captures and stores information about a logging event in compile time.
Definition: MacroMetadata.h:22
void(*)(std::byte *&data, DynamicFormatArgStore &args_store) FormatArgsDecoder
Decode functions.
Definition: Codec.h:382
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT size_t compute_encoded_size_and_cache_string_lengths(QUILL_MAYBE_UNUSED SizeCacheVector &conditional_arg_size_cache, Args const &... args)
Calculates the total size required to encode the provided arguments.
Definition: Codec.h:337
void flush_backtrace()
Dump any stored backtrace messages.
Definition: Logger.h:280
Configuration options for the PatternFormatter.
Definition: PatternFormatterOptions.h:23
Setups a signal handler to handle fatal signals.
Definition: BackendManager.h:24
QUILL_ATTRIBUTE_HOT bool log_statement(MacroMetadata const *macro_metadata, Args &&... fmt_args)
Push a log message to the spsc queue to be logged by the backend thread.
Definition: Logger.h:75
Thread safe logger.
Definition: Logger.h:48
QUILL_ATTRIBUTE_HOT bool log_statement_runtime_metadata(MacroMetadata const *macro_metadata, char const *fmt, char const *file_path, char const *function_name, char const *tags, uint32_t line_number, LogLevel log_level, Args &&... fmt_args)
Push a log message with runtime metadata to the spsc queue to be logged by the backend thread...
Definition: Logger.h:151
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT uint64_t rdtsc() noexcept
Get the TSC counter.
Definition: Rdtsc.h:109
QUILL_ATTRIBUTE_HOT void encode(std::byte *&buffer, SizeCacheVector const &conditional_arg_size_cache, Args const &... args)
Encodes multiple arguments into a buffer.
Definition: Codec.h:365
Definition: LoggerBase.h:36
Base class that provides a timestamp for log statements based on a user-provided clock source...
Definition: UserClockSource.h:25
void flush_log(uint32_t sleep_duration_ns=100)
Blocks the calling thread until all log messages up to the current timestamp are flushed.
Definition: Logger.h:310
Definition: BackendWorker.h:72
Definition: LoggerManager.h:34