quill
PatternFormatter.h
1 
7 #pragma once
8 
9 #include "quill/backend/TimestampFormatter.h"
10 #include "quill/bundled/fmt/base.h"
11 #include "quill/bundled/fmt/format.h"
12 #include "quill/core/Attributes.h"
13 #include "quill/core/Common.h"
14 #include "quill/core/MacroMetadata.h"
15 #include "quill/core/PatternFormatterOptions.h"
16 #include "quill/core/QuillError.h"
17 
18 #include <array>
19 #include <bitset>
20 #include <chrono>
21 #include <cstddef>
22 #include <cstdint>
23 #include <iterator>
24 #include <string>
25 #include <string_view>
26 #include <tuple>
27 #include <unordered_map>
28 #include <utility>
29 #include <vector>
30 
31 QUILL_BEGIN_NAMESPACE
32 
33 QUILL_BEGIN_EXPORT
34 
36 {
38 public:
42  enum class TimestampPrecision : uint8_t
43  {
44  None,
45  MilliSeconds,
46  MicroSeconds,
47  NanoSeconds
48  };
49 
50  enum Attribute : uint8_t
51  {
52  Time = 0,
53  FileName,
54  CallerFunction,
55  LogLevel,
56  LogLevelShortCode,
57  LineNumber,
58  Logger,
59  FullPath,
60  ThreadId,
61  ThreadName,
62  ProcessId,
64  ShortSourceLocation,
65  Message,
66  Mdc,
67  Tags,
68  NamedArgs,
69  ATTR_NR_ITEMS
70  };
71 
73 public:
83  : _options(std::move(options)),
84  _timestamp_formatter(_options.timestamp_pattern, _options.timestamp_timezone)
85  {
86  _set_pattern();
87  }
88 
89  /***/
90  PatternFormatter(PatternFormatter const& other) = delete;
91  PatternFormatter& operator=(PatternFormatter const& other) = delete;
92 
93  /***/
94  PatternFormatter& operator=(PatternFormatter&& other) noexcept = default;
95  PatternFormatter(PatternFormatter&& other) noexcept = default;
96 
97  /***/
98  ~PatternFormatter() = default;
99 
100  QUILL_NODISCARD QUILL_ATTRIBUTE_HOT std::string_view format(
101  uint64_t timestamp, std::string_view thread_id, std::string_view thread_name,
102  std::string_view process_id, std::string_view logger, std::string_view log_level_description,
103  std::string_view log_level_short_code, MacroMetadata const& log_statement_metadata,
104  std::vector<std::pair<std::string, std::string>> const* named_args, std::string_view log_msg,
105  std::string_view mdc)
106  {
107  if (_options.format_pattern.empty())
108  {
109  // No formatting is needed when the format pattern is empty.
110  // For example, in JsonFileSink, we can retrieve the MacroMetadata and the named arguments as
111  // key-value pairs, but we do not need to format the log statement.
112  return std::string_view{};
113  }
114 
115  // clear out the existing buffer
116  _formatted_log_message_buffer.clear();
117 
118  if (QUILL_UNLIKELY(log_msg.empty()))
119  {
120  // Process an empty message
121  return _format(timestamp, thread_id, thread_name, process_id, logger, log_level_description,
122  log_level_short_code, log_statement_metadata, named_args, log_msg, mdc);
123  }
124 
125  std::string_view formatted_log_msg;
126 
127  // Check if we need to handle multi-line formatting
128  if (_options.add_metadata_to_multi_line_logs && (!named_args || named_args->empty()))
129  {
130  // multi line metadata only supported when named_args are not used
131  size_t start = 0;
132 
133  while (start < log_msg.size())
134  {
135  size_t const end = log_msg.find_first_of('\n', start);
136 
137  if (end == std::string_view::npos)
138  {
139  // Handle the last line or a single line without a newline
140  formatted_log_msg =
141  _format(timestamp, thread_id, thread_name, process_id, logger, log_level_description,
142  log_level_short_code, log_statement_metadata, named_args,
143  std::string_view(log_msg.data() + start, log_msg.size() - start), mdc);
144  break;
145  }
146 
147  // Write the current line
148  size_t line_length = end - start;
149  if (_options.pattern_suffix != '\n')
150  {
151  // When suffix is not '\n', include the newline character in the message
152  line_length++;
153  }
154 
155  formatted_log_msg = _format(timestamp, thread_id, thread_name, process_id, logger, log_level_description,
156  log_level_short_code, log_statement_metadata, named_args,
157  std::string_view(log_msg.data() + start, line_length), mdc);
158  start = end + 1;
159  }
160  }
161  else
162  {
163  // Use the regular format method for single-line messages
164  QUILL_ASSERT(
165  !log_msg.empty(),
166  "log_msg should not be empty, already checked earlier in PatternFormatter::format()");
167  size_t log_message_size = log_msg.size();
168 
169  if (_options.pattern_suffix == '\n' && log_msg[log_msg.size() - 1] == '\n')
170  {
171  // if the log_message ends with \n we exclude it (only when using newline suffix)
172  log_message_size = log_msg.size() - 1;
173  }
174 
175  formatted_log_msg = _format(timestamp, thread_id, thread_name, process_id, logger,
176  log_level_description, log_level_short_code, log_statement_metadata,
177  named_args, std::string_view{log_msg.data(), log_message_size}, mdc);
178  }
179 
180  return formatted_log_msg;
181  }
182 
183  /***/
184  QUILL_NODISCARD PatternFormatterOptions const& get_options() const noexcept { return _options; }
185 
186 protected:
187  /***/
188  QUILL_NODISCARD static std::string_view _process_source_location_path(std::string_view source_location,
189  std::string const& strip_prefix,
190  bool remove_relative_paths)
191  {
192  std::string_view result = source_location;
193 
194  // First, handle removal of relative paths if requested
195  if (remove_relative_paths)
196  {
197  // Remove any relative paths (e.g., relative paths can appear when using a mounted volume under docker)
198 
199 #if defined(_WIN32) && defined(_MSC_VER) && !defined(__GNUC__)
200  static constexpr std::string_view relative_path = "..\\";
201 #else
202  static constexpr std::string_view relative_path = "../";
203 #endif
204 
205  if (size_t n = result.rfind(relative_path); n != std::string_view::npos)
206  {
207  result = result.substr(n + relative_path.size());
208  }
209  }
210 
211  // Then handle prefix stripping
212  if (!strip_prefix.empty())
213  {
214  // Find the last occurrence of the prefix in the path
215  size_t prefix_pos = result.rfind(strip_prefix);
216 
217  if (prefix_pos != std::string_view::npos)
218  {
219  size_t after_prefix_pos = prefix_pos + strip_prefix.size();
220 
221  // If the prefix doesn't end with a separator and there is a character after the prefix
222  // and that character is a separator, skip it as well
223  if (after_prefix_pos < result.length() && result[after_prefix_pos] == detail::PATH_PREFERRED_SEPARATOR)
224  {
225  after_prefix_pos++;
226  }
227 
228  return result.substr(after_prefix_pos);
229  }
230  // Prefix not found, use the full path
231  }
232 
233  // No prefix set or prefix not found, use the full path
234  return result;
235  }
236 
237 private:
238  void _set_pattern()
239  {
240  // the order we pass the arguments here must match with the order of Attribute enum
241  using namespace fmtquill::literals;
242  std::tie(_fmt_format, _order_index) = _generate_fmt_format_string(
243  _is_set_in_pattern, _options.format_pattern, "time"_a = "", "file_name"_a = "",
244  "caller_function"_a = "", "log_level"_a = "", "log_level_short_code"_a = "",
245  "line_number"_a = "", "logger"_a = "", "full_path"_a = "", "thread_id"_a = "",
246  "thread_name"_a = "", "process_id"_a = "", "source_location"_a = "",
247  "short_source_location"_a = "", "message"_a = "", "mdc"_a = "", "tags"_a = "",
248  "named_args"_a = "");
249 
250  _set_arg<Attribute::Time>(std::string_view("time"));
251  _set_arg<Attribute::FileName>(std::string_view("file_name"));
252  _set_arg<Attribute::CallerFunction>(std::string_view("caller_function"));
253  _set_arg<Attribute::LogLevel>(std::string_view("log_level"));
254  _set_arg<Attribute::LogLevelShortCode>(std::string_view("log_level_short_code"));
255  _set_arg<Attribute::LineNumber>("line_number");
256  _set_arg<Attribute::Logger>(std::string_view("logger"));
257  _set_arg<Attribute::FullPath>(std::string_view("full_path"));
258  _set_arg<Attribute::ThreadId>(std::string_view("thread_id"));
259  _set_arg<Attribute::ThreadName>(std::string_view("thread_name"));
260  _set_arg<Attribute::ProcessId>(std::string_view("process_id"));
261  _set_arg<Attribute::SourceLocation>("source_location");
262  _set_arg<Attribute::ShortSourceLocation>("short_source_location");
263  _set_arg<Attribute::Message>(std::string_view("message"));
264  _set_arg<Attribute::Mdc>(std::string_view("mdc"));
265  _set_arg<Attribute::Tags>(std::string_view("tags"));
266  _set_arg<Attribute::NamedArgs>(std::string_view("named_args"));
267  }
268 
269  /***/
270  template <size_t I, typename T>
271  void _set_arg(T const& arg)
272  {
273  _args[_order_index[I]] = arg;
274  }
275 
276  template <size_t I, typename T>
277  void _set_arg_val(T const& arg)
278  {
279  // This relies on the internal layout used by the bundled fmtquill copy
280  fmtquill::detail::value<fmtquill::format_context>& value_ =
281  *(reinterpret_cast<fmtquill::detail::value<fmtquill::format_context>*>(
282  std::addressof(_args[_order_index[I]])));
283 
284  value_ = fmtquill::detail::value<fmtquill::format_context>(arg);
285  }
286 
287  /***/
288  PatternFormatter::Attribute static _attribute_from_string(std::string const& attribute_name)
289  {
290  if (attribute_name == "time")
291  {
292  return PatternFormatter::Attribute::Time;
293  }
294  if (attribute_name == "file_name")
295  {
296  return PatternFormatter::Attribute::FileName;
297  }
298  if (attribute_name == "caller_function")
299  {
300  return PatternFormatter::Attribute::CallerFunction;
301  }
302  if (attribute_name == "log_level")
303  {
304  return PatternFormatter::Attribute::LogLevel;
305  }
306  if (attribute_name == "log_level_short_code")
307  {
308  return PatternFormatter::Attribute::LogLevelShortCode;
309  }
310  if (attribute_name == "line_number")
311  {
312  return PatternFormatter::Attribute::LineNumber;
313  }
314  if (attribute_name == "logger")
315  {
316  return PatternFormatter::Attribute::Logger;
317  }
318  if (attribute_name == "full_path")
319  {
320  return PatternFormatter::Attribute::FullPath;
321  }
322  if (attribute_name == "thread_id")
323  {
324  return PatternFormatter::Attribute::ThreadId;
325  }
326  if (attribute_name == "thread_name")
327  {
328  return PatternFormatter::Attribute::ThreadName;
329  }
330  if (attribute_name == "process_id")
331  {
332  return PatternFormatter::Attribute::ProcessId;
333  }
334  if (attribute_name == "source_location")
335  {
336  return PatternFormatter::Attribute::SourceLocation;
337  }
338  if (attribute_name == "short_source_location")
339  {
340  return PatternFormatter::Attribute::ShortSourceLocation;
341  }
342  if (attribute_name == "message")
343  {
344  return PatternFormatter::Attribute::Message;
345  }
346  if (attribute_name == "mdc")
347  {
348  return PatternFormatter::Attribute::Mdc;
349  }
350  if (attribute_name == "tags")
351  {
352  return PatternFormatter::Attribute::Tags;
353  }
354  if (attribute_name == "named_args")
355  {
356  return PatternFormatter::Attribute::NamedArgs;
357  }
358 
359  QUILL_THROW(QuillError{
360  std::string{"Attribute enum value does not exist for attribute with name " + attribute_name}});
361  }
362 
363  /***/
364  template <size_t, size_t>
365  constexpr void _store_named_args(std::array<fmtquill::detail::named_arg_info<char>, PatternFormatter::Attribute::ATTR_NR_ITEMS>&)
366  {
367  }
368 
369  /***/
370  template <size_t Idx, size_t NamedIdx, typename Arg, typename... Args>
371  constexpr void _store_named_args(
372  std::array<fmtquill::detail::named_arg_info<char>, PatternFormatter::Attribute::ATTR_NR_ITEMS>& named_args_store,
373  Arg const& arg, Args const&... args)
374  {
375  named_args_store[NamedIdx] = {arg.name, Idx};
376  _store_named_args<Idx + 1, NamedIdx + 1>(named_args_store, args...);
377  }
378 
405  template <typename... Args>
406  QUILL_NODISCARD std::pair<std::string, std::array<size_t, PatternFormatter::Attribute::ATTR_NR_ITEMS>> _generate_fmt_format_string(
407  std::bitset<PatternFormatter::Attribute::ATTR_NR_ITEMS>& is_set_in_pattern, std::string pattern,
408  Args const&... args)
409  {
410  // Attribute enum and the args we are passing here must be in sync
411  static_assert(PatternFormatter::Attribute::ATTR_NR_ITEMS == sizeof...(Args));
412 
413  if (_options.pattern_suffix != PatternFormatterOptions::NO_SUFFIX)
414  {
415  pattern += _options.pattern_suffix;
416  }
417 
418  std::array<size_t, PatternFormatter::Attribute::ATTR_NR_ITEMS> order_index{};
419  order_index.fill(PatternFormatter::Attribute::ATTR_NR_ITEMS - 1);
420 
421  std::array<fmtquill::detail::named_arg_info<char>, PatternFormatter::Attribute::ATTR_NR_ITEMS> named_args{};
422  _store_named_args<0, 0>(named_args, args...);
423  uint8_t arg_idx = 0;
424 
425  // we will replace all %(....) with {} to construct a string to pass to fmt library
426  size_t arg_identifier_pos = pattern.find_first_of('%');
427  while (arg_identifier_pos != std::string::npos)
428  {
429  if (size_t const open_paren_pos = pattern.find_first_of('(', arg_identifier_pos);
430  open_paren_pos != std::string::npos && (open_paren_pos - arg_identifier_pos) == 1)
431  {
432  // if we found '%(' we have a matching pattern and we now need to get the closed paren
433  size_t const closed_paren_pos = pattern.find_first_of(')', open_paren_pos);
434 
435  if (closed_paren_pos == std::string::npos)
436  {
437  QUILL_THROW(QuillError{"Invalid format pattern"});
438  }
439 
440  // We have everything, get the substring, this time including '%( )'
441  std::string attr = pattern.substr(arg_identifier_pos, (closed_paren_pos + 1) - arg_identifier_pos);
442 
443  // find any user format specifiers
444  size_t const pos = attr.find(':');
445  std::string attr_name;
446 
447  if (pos != std::string::npos)
448  {
449  // we found user format specifiers that we want to keep.
450  // e.g. %(short_source_location:<32)
451 
452  // Get only the format specifier
453  // e.g. :<32
454  std::string custom_format_specifier = attr.substr(pos);
455  custom_format_specifier.pop_back(); // remove the ")"
456 
457  // replace with the pattern with the correct value
458  std::string value;
459  value += "{";
460  value += custom_format_specifier;
461  value += "}";
462 
463  // e.g. {:<32}
464  pattern.replace(arg_identifier_pos, attr.length(), value);
465 
466  // Get the part that is the named argument
467  // e.g. short_source_location
468  attr_name = attr.substr(2, pos - 2);
469  }
470  else
471  {
472  // Make the replacement.
473  pattern.replace(arg_identifier_pos, attr.length(), "{}");
474 
475  // Get the part that is the named argument
476  // e.g. short_source_location
477  attr.pop_back(); // remove the ")"
478 
479  attr_name = attr.substr(2, attr.size());
480  }
481 
482  // reorder
483  int id = -1;
484 
485  for (size_t i = 0; i < PatternFormatter::Attribute::ATTR_NR_ITEMS; ++i)
486  {
487  if (named_args[i].name == attr_name)
488  {
489  id = named_args[i].id;
490  break;
491  }
492  }
493 
494  if (id < 0)
495  {
496  QUILL_THROW(QuillError{"Invalid format pattern, attribute with name \"" + attr_name + "\" is invalid"});
497  }
498 
499  // Also set the value as used in the pattern in our bitset for lazy evaluation
500  PatternFormatter::Attribute const attr_enum_value = _attribute_from_string(attr_name);
501  if (is_set_in_pattern.test(attr_enum_value))
502  {
503  QUILL_THROW(QuillError{"Invalid format pattern, attribute with name \"" + attr_name +
504  "\" is used more than once"});
505  }
506 
507  order_index[static_cast<size_t>(id)] = arg_idx++;
508  is_set_in_pattern.set(attr_enum_value);
509 
510  // Look for the next pattern to replace
511  arg_identifier_pos = pattern.find_first_of('%');
512  }
513  else
514  {
515  // search for the next '%'
516  arg_identifier_pos = pattern.find_first_of('%', arg_identifier_pos + 1);
517  }
518  }
519 
520  return std::make_pair(pattern, order_index);
521  }
522 
523  /***/
524  QUILL_ATTRIBUTE_HOT std::string_view _format(uint64_t timestamp, std::string_view thread_id,
525  std::string_view thread_name, std::string_view process_id,
526  std::string_view logger, std::string_view log_level_description,
527  std::string_view log_level_short_code,
528  MacroMetadata const& log_statement_metadata,
529  std::vector<std::pair<std::string, std::string>> const* named_args,
530  std::string_view log_msg, std::string_view mdc)
531  {
532  if (_is_set_in_pattern[Attribute::Time])
533  {
534  _set_arg_val<Attribute::Time>(_timestamp_formatter.format_timestamp(std::chrono::nanoseconds{timestamp}));
535  }
536 
537  if (_is_set_in_pattern[Attribute::FileName])
538  {
539  _set_arg_val<Attribute::FileName>(log_statement_metadata.file_name());
540  }
541 
542  if (_is_set_in_pattern[Attribute::CallerFunction])
543  {
544  std::string_view const function_name = _options.process_function_name
545  ? _options.process_function_name(log_statement_metadata.caller_function())
546  : std::string_view{log_statement_metadata.caller_function()};
547 
548  _set_arg_val<Attribute::CallerFunction>(function_name);
549  }
550 
551  if (_is_set_in_pattern[Attribute::LogLevel])
552  {
553  _set_arg_val<Attribute::LogLevel>(log_level_description);
554  }
555 
556  if (_is_set_in_pattern[Attribute::LogLevelShortCode])
557  {
558  _set_arg_val<Attribute::LogLevelShortCode>(log_level_short_code);
559  }
560 
561  if (_is_set_in_pattern[Attribute::LineNumber])
562  {
563  _set_arg_val<Attribute::LineNumber>(log_statement_metadata.line());
564  }
565 
566  if (_is_set_in_pattern[Attribute::Logger])
567  {
568  _set_arg_val<Attribute::Logger>(logger);
569  }
570 
571  if (_is_set_in_pattern[Attribute::FullPath])
572  {
573  _set_arg_val<Attribute::FullPath>(log_statement_metadata.full_path());
574  }
575 
576  if (_is_set_in_pattern[Attribute::ThreadId])
577  {
578  _set_arg_val<Attribute::ThreadId>(thread_id);
579  }
580 
581  if (_is_set_in_pattern[Attribute::ThreadName])
582  {
583  _set_arg_val<Attribute::ThreadName>(thread_name);
584  }
585 
586  if (_is_set_in_pattern[Attribute::ProcessId])
587  {
588  _set_arg_val<Attribute::ProcessId>(process_id);
589  }
590 
591  if (_is_set_in_pattern[Attribute::SourceLocation])
592  {
593  _set_arg_val<Attribute::SourceLocation>(_process_source_location_path(
594  log_statement_metadata.source_location(), _options.source_location_path_strip_prefix,
595  _options.source_location_remove_relative_paths));
596  }
597 
598  if (_is_set_in_pattern[Attribute::ShortSourceLocation])
599  {
600  _set_arg_val<Attribute::ShortSourceLocation>(log_statement_metadata.short_source_location());
601  }
602 
603  if (_is_set_in_pattern[Attribute::NamedArgs])
604  {
605  _formatted_named_args_buffer.clear();
606 
607  if (named_args)
608  {
609  for (size_t i = 0; i < named_args->size(); ++i)
610  {
611  _formatted_named_args_buffer.append((*named_args)[i].first);
612  _formatted_named_args_buffer.append(std::string_view{": "});
613  _formatted_named_args_buffer.append((*named_args)[i].second);
614 
615  if (i != named_args->size() - 1)
616  {
617  _formatted_named_args_buffer.append(std::string_view{", "});
618  }
619  }
620  }
621 
622  _set_arg_val<Attribute::NamedArgs>(
623  std::string_view{_formatted_named_args_buffer.data(), _formatted_named_args_buffer.size()});
624  }
625 
626  if (_is_set_in_pattern[Attribute::Mdc])
627  {
628  _set_arg_val<Attribute::Mdc>(mdc);
629  }
630 
631  if (_is_set_in_pattern[Attribute::Tags])
632  {
633  if (log_statement_metadata.tags())
634  {
635  _set_arg_val<Attribute::Tags>(std::string_view{log_statement_metadata.tags()});
636  }
637  else
638  {
639  _set_arg_val<Attribute::Tags>(std::string_view{});
640  }
641  }
642 
643  _set_arg_val<Attribute::Message>(log_msg);
644 
645  fmtquill::vformat_to(std::back_inserter(_formatted_log_message_buffer), _fmt_format,
646  fmtquill::basic_format_args(_args.data(), static_cast<int>(_args.size())));
647 
648  return std::string_view{_formatted_log_message_buffer.data(), _formatted_log_message_buffer.size()};
649  }
650 
651 private:
652  PatternFormatterOptions _options;
653  std::string _fmt_format;
654 
656  std::array<size_t, Attribute::ATTR_NR_ITEMS> _order_index{};
657  std::array<fmtquill::basic_format_arg<fmtquill::format_context>, Attribute::ATTR_NR_ITEMS> _args{};
658  std::bitset<Attribute::ATTR_NR_ITEMS> _is_set_in_pattern;
659 
661  detail::TimestampFormatter _timestamp_formatter;
662 
665  fmtquill::basic_memory_buffer<char, 512> _formatted_log_message_buffer;
666  fmtquill::basic_memory_buffer<char, 512> _formatted_named_args_buffer;
667 };
668 
669 QUILL_END_EXPORT
670 
671 QUILL_END_NAMESPACE
static constexpr char NO_SUFFIX
Special value to indicate no pattern suffix should be appended Using -1 cast to char ensures this val...
Definition: PatternFormatterOptions.h:157
Definition: UserDefinedDirectFormatFuzzer.cpp:81
Captures and stores information about a logging event in compile time.
Definition: MacroMetadata.h:24
Formats a timestamp given a format string as a pattern.
Definition: TimestampFormatter.h:38
Definition: PatternFormatter.h:35
Configuration options for the PatternFormatter.
Definition: PatternFormatterOptions.h:24
PatternFormatter(PatternFormatterOptions options)
Main PatternFormatter class.
Definition: PatternFormatter.h:82
custom exception
Definition: QuillError.h:47
Definition: SourceLocation.h:40
TimestampPrecision
Public classes.
Definition: PatternFormatter.h:42
Macro-Free Logging Interface
Definition: LogFunctions.h:55