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 <chrono>
22 #include <cstddef>
23 #include <cstdint>
24 #include <cstring>
25 #include <memory>
26 #include <string>
27 #include <thread>
28 #include <vector>
29 
30 QUILL_BEGIN_NAMESPACE
31 
33 class Sink;
34 
35 namespace detail
36 {
37 class LoggerManager;
38 
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 
60  LoggerImpl& operator=(LoggerImpl const&) = delete;
61 
62  ~LoggerImpl() override = default;
63 
75  template <bool enable_immediate_flush, typename... Args>
76  QUILL_ATTRIBUTE_HOT bool log_statement(MacroMetadata const* macro_metadata, Args&&... fmt_args)
77  {
78  QUILL_ASSERT(
79  macro_metadata->event() != MacroMetadata::Event::LogWithRuntimeMetadataDeepCopy &&
80  macro_metadata->event() != MacroMetadata::Event::LogWithRuntimeMetadataShallowCopy &&
81  macro_metadata->event() != MacroMetadata::Event::LogWithRuntimeMetadataHybridCopy,
82  "log_statement() should not be called with MacroMetadata::Event::LogWithRuntimeMetadata");
83 
84  QUILL_ASSERT(_valid.load(std::memory_order_acquire),
85  "Attempting to log with an invalidated logger");
86 
87  // Store the timestamp of the log statement at the start of the call. This gives a more accurate
88  // timestamp, especially if the queue is full
89  uint64_t const current_timestamp = _get_message_timestamp();
90 
91  if (QUILL_UNLIKELY(_thread_context == nullptr))
92  {
93  // This caches the ThreadContext pointer to avoid repeatedly calling get_local_thread_context()
94  _thread_context = detail::get_local_thread_context<frontend_options_t>();
95  }
96 
97  // Need to know how much size we need from the queue
98  size_t total_size = sizeof(current_timestamp) + (sizeof(uintptr_t) * 3) +
100  _thread_context->get_conditional_arg_size_cache(), fmt_args...);
101 
102  std::byte* write_buffer = _reserve_queue_space(total_size, macro_metadata);
103 
104  if (QUILL_UNLIKELY(write_buffer == nullptr))
105  {
106  return false;
107  }
108 
109  // we have enough space in this buffer, and we will write to the buffer
110 
111 #if defined(QUILL_ENABLE_ASSERTIONS) || !defined(NDEBUG)
112  std::byte const* const write_begin = write_buffer;
113  QUILL_ASSERT(write_begin, "Reserved queue space returned nullptr in log_statement()");
114 #endif
115 
116  // first encode a header
117  write_buffer = _encode_header(write_buffer, current_timestamp, macro_metadata, this,
118  detail::decode_and_store_args<detail::remove_cvref_t<Args>...>);
119 
120  // encode remaining arguments
121  detail::encode(write_buffer, _thread_context->get_conditional_arg_size_cache(),
122  static_cast<decltype(fmt_args)&&>(fmt_args)...);
123 
124  QUILL_ASSERT_WITH_FMT(
125  write_buffer > write_begin,
126  "write_buffer must be greater than write_begin after encoding in log_statement(): msg=\"%s\"",
127  macro_metadata->message_format());
128  QUILL_ASSERT_WITH_FMT(
129  total_size == static_cast<size_t>(write_buffer - write_begin),
130  "Encoded bytes mismatch in log_statement(): total_size=%zu, actual_encoded=%zu, msg=\"%s\"",
131  total_size, static_cast<size_t>(write_buffer - write_begin), macro_metadata->message_format());
132 
133  _commit_log_statement<enable_immediate_flush>(total_size);
134 
135  return true;
136  }
137 
155  template <bool enable_immediate_flush, typename... Args>
156  QUILL_ATTRIBUTE_HOT bool log_statement_runtime_metadata(MacroMetadata const* macro_metadata,
157  char const* fmt, char const* file_path,
158  char const* function_name,
159  char const* tags, uint32_t line_number,
160  LogLevel log_level, Args&&... fmt_args)
161  {
162  QUILL_ASSERT(macro_metadata->event() == MacroMetadata::Event::LogWithRuntimeMetadataDeepCopy ||
163  macro_metadata->event() == MacroMetadata::Event::LogWithRuntimeMetadataHybridCopy ||
164  macro_metadata->event() == MacroMetadata::Event::LogWithRuntimeMetadataShallowCopy,
165  "log_statement_runtime_metadata() should only be called with "
166  "MacroMetadata::Event::LogWithRuntimeMetadata");
167 
168  QUILL_ASSERT(_valid.load(std::memory_order_acquire),
169  "Attempting to log with an invalidated logger");
170 
171  // Store the timestamp of the log statement at the start of the call. This gives a more accurate
172  // timestamp, especially if the queue is full
173  uint64_t const current_timestamp = _get_message_timestamp();
174 
175  if (QUILL_UNLIKELY(_thread_context == nullptr))
176  {
177  // This caches the ThreadContext pointer to avoid repeatedly calling get_local_thread_context()
178  _thread_context = detail::get_local_thread_context<frontend_options_t>();
179  }
180 
181  // Need to know how much size we need from the queue
182  // Here we need extra size for the metadata
183  size_t total_size{sizeof(current_timestamp) + (sizeof(uintptr_t) * 3)};
184 
185  if (macro_metadata->event() == MacroMetadata::Event::LogWithRuntimeMetadataDeepCopy)
186  {
188  _thread_context->get_conditional_arg_size_cache(), fmt, file_path, function_name, tags,
189  line_number, log_level, fmt_args...);
190  }
191  else if (macro_metadata->event() == MacroMetadata::Event::LogWithRuntimeMetadataShallowCopy)
192  {
194  _thread_context->get_conditional_arg_size_cache(), static_cast<void const*>(fmt),
195  static_cast<void const*>(file_path), static_cast<void const*>(function_name),
196  static_cast<void const*>(tags), line_number, log_level, fmt_args...);
197  }
198  else if (macro_metadata->event() == MacroMetadata::Event::LogWithRuntimeMetadataHybridCopy)
199  {
201  _thread_context->get_conditional_arg_size_cache(), fmt, static_cast<void const*>(file_path),
202  static_cast<void const*>(function_name), tags, line_number, log_level, fmt_args...);
203  }
204  else
205  {
206  return false;
207  }
208 
209  std::byte* write_buffer = _reserve_queue_space(total_size, macro_metadata);
210 
211  if (QUILL_UNLIKELY(write_buffer == nullptr))
212  {
213  return false;
214  }
215 
216  // we have enough space in this buffer, and we will write to the buffer
217 
218 #if defined(QUILL_ENABLE_ASSERTIONS) || !defined(NDEBUG)
219  std::byte const* const write_begin = write_buffer;
220  QUILL_ASSERT(write_begin,
221  "Reserved queue space returned nullptr in log_statement_runtime_metadata()");
222 #endif
223 
224  // first encode a header
225  write_buffer = _encode_header(write_buffer, current_timestamp, macro_metadata, this,
226  detail::decode_and_store_args<detail::remove_cvref_t<Args>...>);
227 
228  if (macro_metadata->event() == MacroMetadata::Event::LogWithRuntimeMetadataDeepCopy)
229  {
230  // encode runtime metadata and remaining arguments
231  detail::encode(write_buffer, _thread_context->get_conditional_arg_size_cache(), fmt, file_path, function_name,
232  tags, line_number, log_level, static_cast<decltype(fmt_args)&&>(fmt_args)...);
233  }
234  else if (macro_metadata->event() == MacroMetadata::Event::LogWithRuntimeMetadataShallowCopy)
235  {
236  detail::encode(write_buffer, _thread_context->get_conditional_arg_size_cache(),
237  static_cast<void const*>(fmt), static_cast<void const*>(file_path),
238  static_cast<void const*>(function_name), static_cast<void const*>(tags),
239  line_number, log_level, static_cast<decltype(fmt_args)&&>(fmt_args)...);
240  }
241  else if (macro_metadata->event() == MacroMetadata::Event::LogWithRuntimeMetadataHybridCopy)
242  {
243  detail::encode(write_buffer, _thread_context->get_conditional_arg_size_cache(), fmt,
244  static_cast<void const*>(file_path), static_cast<void const*>(function_name),
245  tags, line_number, log_level, static_cast<decltype(fmt_args)&&>(fmt_args)...);
246  }
247 
248  QUILL_ASSERT_WITH_FMT(write_buffer > write_begin,
249  "write_buffer must be greater than write_begin after encoding in "
250  "log_statement_runtime_metadata(): fmt=\"%s\"",
251  fmt);
252  QUILL_ASSERT_WITH_FMT(total_size == static_cast<size_t>(write_buffer - write_begin),
253  "Encoded bytes mismatch in log_statement_runtime_metadata(): "
254  "total_size=%zu, actual_encoded=%zu, fmt=\"%s\"",
255  total_size, static_cast<size_t>(write_buffer - write_begin), fmt);
256 
257  _commit_log_statement<enable_immediate_flush>(total_size);
258 
259  return true;
260  }
261 
269  void init_backtrace(uint32_t max_capacity, LogLevel flush_level = LogLevel::None)
270  {
271  // we do not care about the other fields, except MacroMetadata::Event::InitBacktrace
272  static constexpr MacroMetadata macro_metadata{
273  "", "", "{}", nullptr, LogLevel::Critical, MacroMetadata::Event::InitBacktrace};
274 
275  // we pass this message to the queue and also pass capacity as arg
276  // We do not want to drop the message if a dropping queue is used
277  while (!this->template log_statement<false>(&macro_metadata, max_capacity))
278  {
279  std::this_thread::sleep_for(std::chrono::nanoseconds{100});
280  }
281 
282  // Also store the desired flush log level
283  _backtrace_flush_level.store(flush_level, std::memory_order_relaxed);
284  }
285 
290  {
291  // we do not care about the other fields, except MacroMetadata::Event::Flush
292  static constexpr MacroMetadata macro_metadata{
293  "", "", "", nullptr, LogLevel::Critical, MacroMetadata::Event::FlushBacktrace};
294 
295  // We do not want to drop the message if a dropping queue is used
296  while (!this->template log_statement<false>(&macro_metadata))
297  {
298  std::this_thread::sleep_for(std::chrono::nanoseconds{100});
299  }
300  }
301 
319  void flush_log(uint32_t sleep_duration_ns = 100)
320  {
321  static constexpr MacroMetadata macro_metadata{
322  "", "", "", nullptr, LogLevel::Critical, MacroMetadata::Event::Flush};
323 
324  std::atomic<bool> backend_thread_flushed{false};
325  std::atomic<bool>* backend_thread_flushed_ptr = &backend_thread_flushed;
326 
327  // We do not want to drop the message if a dropping queue is used
328  while (!this->log_statement<false>(&macro_metadata, reinterpret_cast<uintptr_t>(backend_thread_flushed_ptr)))
329  {
330  if (sleep_duration_ns > 0)
331  {
332  std::this_thread::sleep_for(std::chrono::nanoseconds{sleep_duration_ns});
333  }
334  else
335  {
336  std::this_thread::yield();
337  }
338  }
339 
340  // The caller thread keeps checking the flag until the backend thread flushes
341  while (!backend_thread_flushed.load())
342  {
343  if (sleep_duration_ns > 0)
344  {
345  std::this_thread::sleep_for(std::chrono::nanoseconds{sleep_duration_ns});
346  }
347  else
348  {
349  std::this_thread::yield();
350  }
351  }
352  }
353 
354 private:
355  friend class detail::LoggerManager;
356 
357  friend class detail::BackendWorker;
358 
359  /***/
360  LoggerImpl(std::string logger_name, std::vector<std::shared_ptr<Sink>> sinks,
361  PatternFormatterOptions pattern_formatter_options, ClockSourceType clock_source,
362  UserClockSource* user_clock)
364  static_cast<std::string&&>(logger_name), static_cast<std::vector<std::shared_ptr<Sink>>&&>(sinks),
365  static_cast<PatternFormatterOptions&&>(pattern_formatter_options), clock_source, user_clock)
366  {
367  if (this->_user_clock)
368  {
369  // if a user clock is provided then set the ClockSourceType to User
370  this->_clock_source = ClockSourceType::User;
371  }
372  }
373 
378  QUILL_NODISCARD QUILL_ATTRIBUTE_HOT uint64_t _get_message_timestamp() const
379  {
380  // This is very rare but might lead to out-of-order timestamp in the log file if a thread blocks
381  // on _reserve_queue_space for too long
382 
383  if (_clock_source == ClockSourceType::Tsc)
384  {
385  return detail::rdtsc();
386  }
387 
388  if (_clock_source == ClockSourceType::System)
389  {
390  return detail::get_timestamp_ns<std::chrono::system_clock>();
391  }
392 
393  if (_user_clock)
394  {
395  return _user_clock->now();
396  }
397 
398  // not expected
399  return 0;
400  }
401 
408  QUILL_NODISCARD QUILL_ATTRIBUTE_HOT std::byte* _reserve_queue_space(size_t total_size,
409  MacroMetadata const* macro_metadata)
410  {
411  std::byte* write_buffer =
412  _thread_context->get_spsc_queue<frontend_options_t::queue_type>().prepare_write(total_size);
413 
414  if constexpr ((frontend_options_t::queue_type == QueueType::BoundedDropping) ||
415  (frontend_options_t::queue_type == QueueType::UnboundedDropping))
416  {
417  if (QUILL_UNLIKELY(write_buffer == nullptr))
418  {
419  // not enough space to push to queue message is dropped
420  if (macro_metadata->event() == MacroMetadata::Event::Log)
421  {
422  _thread_context->increment_failure_counter();
423  }
424  }
425  }
426  else if constexpr ((frontend_options_t::queue_type == QueueType::BoundedBlocking) ||
427  (frontend_options_t::queue_type == QueueType::UnboundedBlocking))
428  {
429  if (QUILL_UNLIKELY(write_buffer == nullptr))
430  {
431  if (macro_metadata->event() == MacroMetadata::Event::Log)
432  {
433  _thread_context->increment_failure_counter();
434  }
435 
436  do
437  {
438  if constexpr (frontend_options_t::blocking_queue_retry_interval_ns > 0)
439  {
440  std::this_thread::sleep_for(std::chrono::nanoseconds{frontend_options_t::blocking_queue_retry_interval_ns});
441  }
442 
443  // not enough space to push to queue, keep trying
444  write_buffer =
445  _thread_context->get_spsc_queue<frontend_options_t::queue_type>().prepare_write(total_size);
446  } while (write_buffer == nullptr);
447  }
448  }
449 
450  return write_buffer;
451  }
452 
457  template <bool enable_immediate_flush>
458  QUILL_ATTRIBUTE_HOT void _commit_log_statement(size_t total_size)
459  {
460  _thread_context->get_spsc_queue<frontend_options_t::queue_type>().finish_and_commit_write(total_size);
461 
462  if constexpr (enable_immediate_flush)
463  {
464  uint32_t const threshold = _message_flush_threshold.load(std::memory_order_relaxed);
465  if (QUILL_UNLIKELY(threshold != 0))
466  {
467  uint32_t const prev = _messages_since_last_flush.fetch_add(1, std::memory_order_relaxed);
468  if ((prev + 1) >= threshold)
469  {
470  _messages_since_last_flush.store(0, std::memory_order_relaxed);
471  this->flush_log();
472  }
473  }
474  }
475  }
476 
485  QUILL_NODISCARD QUILL_ATTRIBUTE_HOT static std::byte* _encode_header(std::byte* write_buffer, uint64_t timestamp,
486  MacroMetadata const* metadata,
487  detail::LoggerBase* logger_ctx,
488  detail::FormatArgsDecoder decoder) noexcept
489  {
490  std::memcpy(write_buffer, &timestamp, sizeof(timestamp));
491  write_buffer += sizeof(timestamp);
492 
493  std::memcpy(write_buffer, &metadata, sizeof(uintptr_t));
494  write_buffer += sizeof(uintptr_t);
495 
496  std::memcpy(write_buffer, &logger_ctx, sizeof(uintptr_t));
497  write_buffer += sizeof(uintptr_t);
498 
499  std::memcpy(write_buffer, &decoder, sizeof(uintptr_t));
500  write_buffer += sizeof(uintptr_t);
501 
502  return write_buffer;
503  }
504 };
505 
506 using Logger = LoggerImpl<FrontendOptions>;
507 
508 QUILL_END_NAMESPACE
void init_backtrace(uint32_t max_capacity, LogLevel flush_level=LogLevel::None)
Init a backtrace for this logger.
Definition: Logger.h:269
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:399
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:354
void flush_backtrace()
Dump any stored backtrace messages.
Definition: Logger.h:289
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:76
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:156
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT uint64_t rdtsc() noexcept
Get the TSC counter.
Definition: Rdtsc.h:109
Definition: LoggerBase.h:35
QUILL_ATTRIBUTE_HOT void encode(std::byte *&buffer, SizeCacheVector const &conditional_arg_size_cache, Args &&... args)
Encodes multiple arguments into a buffer.
Definition: Codec.h:381
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:319
Definition: BackendWorker.h:77
Definition: LoggerManager.h:33