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 
34 {
36 public:
40  enum class TimestampPrecision : uint8_t
41  {
42  None,
43  MilliSeconds,
44  MicroSeconds,
45  NanoSeconds
46  };
47 
48  enum Attribute : uint8_t
49  {
50  Time = 0,
51  FileName,
52  CallerFunction,
53  LogLevel,
54  LogLevelShortCode,
55  LineNumber,
56  Logger,
57  FullPath,
58  ThreadId,
59  ThreadName,
60  ProcessId,
62  ShortSourceLocation,
63  Message,
64  Tags,
65  NamedArgs,
66  ATTR_NR_ITEMS
67  };
68 
70 public:
80  : _options(std::move(options)),
81  _timestamp_formatter(_options.timestamp_pattern, _options.timestamp_timezone)
82  {
83  _set_pattern();
84  }
85 
86  /***/
87  PatternFormatter(PatternFormatter const& other) = delete;
88  PatternFormatter& operator=(PatternFormatter const& other) = delete;
89 
90  /***/
91  PatternFormatter& operator=(PatternFormatter&& other) noexcept = default;
92  PatternFormatter(PatternFormatter&& other) noexcept = default;
93 
94  /***/
95  ~PatternFormatter() = default;
96 
97  QUILL_NODISCARD QUILL_ATTRIBUTE_HOT std::string_view format(
98  uint64_t timestamp, std::string_view thread_id, std::string_view thread_name,
99  std::string_view process_id, std::string_view logger, std::string_view log_level_description,
100  std::string_view log_level_short_code, MacroMetadata const& log_statement_metadata,
101  std::vector<std::pair<std::string, std::string>> const* named_args, std::string_view log_msg)
102  {
103  if (_options.format_pattern.empty())
104  {
105  // No formatting is needed when the format pattern is empty.
106  // For example, in JsonFileSink, we can retrieve the MacroMetadata and the named arguments as
107  // key-value pairs, but we do not need to format the log statement.
108  return std::string_view{};
109  }
110 
111  // clear out the existing buffer
112  _formatted_log_message_buffer.clear();
113 
114  if (QUILL_UNLIKELY(log_msg.empty()))
115  {
116  // Process an empty message
117  return _format(timestamp, thread_id, thread_name, process_id, logger, log_level_description,
118  log_level_short_code, log_statement_metadata, named_args, log_msg);
119  }
120 
121  std::string_view formatted_log_msg;
122 
123  // Check if we need to handle multi-line formatting
124  if (_options.add_metadata_to_multi_line_logs && (!named_args || named_args->empty()))
125  {
126  // multi line metadata only supported when named_args are not used
127  size_t start = 0;
128 
129  while (start < log_msg.size())
130  {
131  size_t const end = log_msg.find_first_of('\n', start);
132 
133  if (end == std::string_view::npos)
134  {
135  // Handle the last line or a single line without a newline
136  formatted_log_msg =
137  _format(timestamp, thread_id, thread_name, process_id, logger, log_level_description,
138  log_level_short_code, log_statement_metadata, named_args,
139  std::string_view(log_msg.data() + start, log_msg.size() - start));
140  break;
141  }
142 
143  // Write the current line
144  formatted_log_msg = _format(timestamp, thread_id, thread_name, process_id, logger, log_level_description,
145  log_level_short_code, log_statement_metadata, named_args, std::string_view(log_msg.data() + start, end - start));
146 
147  start = end + 1;
148  }
149  }
150  else
151  {
152  // Use the regular format method for single-line messages
153 
154  // if the log_message ends with \n we should exclude it
155  assert(!log_msg.empty() && "Already checked non empty message earlier");
156  size_t const log_message_size =
157  (log_msg[log_msg.size() - 1] == '\n') ? log_msg.size() - 1 : log_msg.size();
158 
159  formatted_log_msg = _format(timestamp, thread_id, thread_name, process_id, logger, log_level_description,
160  log_level_short_code, log_statement_metadata, named_args, std::string_view{log_msg.data(), log_message_size});
161  }
162 
163  return formatted_log_msg;
164  }
165 
166  /***/
167  QUILL_NODISCARD PatternFormatterOptions const& get_options() const noexcept { return _options; }
168 
169 protected:
170  /***/
171  QUILL_NODISCARD static std::string_view _process_source_location_path(std::string_view source_location,
172  std::string const& strip_prefix,
173  bool remove_relative_paths)
174  {
175  std::string_view result = source_location;
176 
177  // First, handle removal of relative paths if requested
178  if (remove_relative_paths)
179  {
180  // Remove any relative paths (e.g., relative paths can appear when using a mounted volume under docker)
181 
182 #if defined(_WIN32) && defined(_MSC_VER) && !defined(__GNUC__)
183  static constexpr std::string_view relative_path = "..\\";
184 #else
185  static constexpr std::string_view relative_path = "../";
186 #endif
187 
188  if (size_t n = result.rfind(relative_path); n != std::string_view::npos)
189  {
190  result = result.substr(n + relative_path.size());
191  }
192  }
193 
194  // Then handle prefix stripping
195  if (!strip_prefix.empty())
196  {
197  // Find the last occurrence of the prefix in the path
198  size_t prefix_pos = result.rfind(strip_prefix);
199 
200  if (prefix_pos != std::string_view::npos)
201  {
202  size_t after_prefix_pos = prefix_pos + strip_prefix.size();
203 
204  // If the prefix doesn't end with a separator and there is a character after the prefix
205  // and that character is a separator, skip it as well
206  if (after_prefix_pos < result.length() && result[after_prefix_pos] == detail::PATH_PREFERRED_SEPARATOR)
207  {
208  after_prefix_pos++;
209  }
210 
211  return result.substr(after_prefix_pos);
212  }
213  // Prefix not found, use the full path
214  }
215 
216  // No prefix set or prefix not found, use the full path
217  return result;
218  }
219 
220 private:
221  void _set_pattern()
222  {
223  // the order we pass the arguments here must match with the order of Attribute enum
224  using namespace fmtquill::literals;
225  std::tie(_fmt_format, _order_index) = _generate_fmt_format_string(
226  _is_set_in_pattern, _options.format_pattern, "time"_a = "", "file_name"_a = "",
227  "caller_function"_a = "", "log_level"_a = "", "log_level_short_code"_a = "",
228  "line_number"_a = "", "logger"_a = "", "full_path"_a = "", "thread_id"_a = "",
229  "thread_name"_a = "", "process_id"_a = "", "source_location"_a = "",
230  "short_source_location"_a = "", "message"_a = "", "tags"_a = "", "named_args"_a = "");
231 
232  _set_arg<Attribute::Time>(std::string_view("time"));
233  _set_arg<Attribute::FileName>(std::string_view("file_name"));
234  _set_arg<Attribute::CallerFunction>(std::string_view("caller_function"));
235  _set_arg<Attribute::LogLevel>(std::string_view("log_level"));
236  _set_arg<Attribute::LogLevelShortCode>(std::string_view("log_level_short_code"));
237  _set_arg<Attribute::LineNumber>("line_number");
238  _set_arg<Attribute::Logger>(std::string_view("logger"));
239  _set_arg<Attribute::FullPath>(std::string_view("full_path"));
240  _set_arg<Attribute::ThreadId>(std::string_view("thread_id"));
241  _set_arg<Attribute::ThreadName>(std::string_view("thread_name"));
242  _set_arg<Attribute::ProcessId>(std::string_view("process_id"));
243  _set_arg<Attribute::SourceLocation>("source_location");
244  _set_arg<Attribute::ShortSourceLocation>("short_source_location");
245  _set_arg<Attribute::Message>(std::string_view("message"));
246  _set_arg<Attribute::Tags>(std::string_view("tags"));
247  _set_arg<Attribute::NamedArgs>(std::string_view("named_args"));
248  }
249 
250  /***/
251  template <size_t I, typename T>
252  void _set_arg(T const& arg)
253  {
254  _args[_order_index[I]] = arg;
255  }
256 
257  template <size_t I, typename T>
258  void _set_arg_val(T const& arg)
259  {
260  fmtquill::detail::value<fmtquill::format_context>& value_ =
261  *(reinterpret_cast<fmtquill::detail::value<fmtquill::format_context>*>(
262  std::addressof(_args[_order_index[I]])));
263 
264  value_ = fmtquill::detail::value<fmtquill::format_context>(arg);
265  }
266 
267  /***/
268  PatternFormatter::Attribute static _attribute_from_string(std::string const& attribute_name)
269  {
270  // don't make this static as it breaks on windows with atexit when backend worker stops
271  std::unordered_map<std::string, PatternFormatter::Attribute> const attr_map = {
272  {"time", PatternFormatter::Attribute::Time},
273  {"file_name", PatternFormatter::Attribute::FileName},
274  {"caller_function", PatternFormatter::Attribute::CallerFunction},
275  {"log_level", PatternFormatter::Attribute::LogLevel},
276  {"log_level_short_code", PatternFormatter::Attribute::LogLevelShortCode},
277  {"line_number", PatternFormatter::Attribute::LineNumber},
278  {"logger", PatternFormatter::Attribute::Logger},
279  {"full_path", PatternFormatter::Attribute::FullPath},
280  {"thread_id", PatternFormatter::Attribute::ThreadId},
281  {"thread_name", PatternFormatter::Attribute::ThreadName},
282  {"process_id", PatternFormatter::Attribute::ProcessId},
283  {"source_location", PatternFormatter::Attribute::SourceLocation},
284  {"short_source_location", PatternFormatter::Attribute::ShortSourceLocation},
285  {"message", PatternFormatter::Attribute::Message},
286  {"tags", PatternFormatter::Attribute::Tags},
287  {"named_args", PatternFormatter::Attribute::NamedArgs}};
288 
289  auto const search = attr_map.find(attribute_name);
290 
291  if (QUILL_UNLIKELY(search == attr_map.cend()))
292  {
293  QUILL_THROW(QuillError{
294  std::string{"Attribute enum value does not exist for attribute with name " + attribute_name}});
295  }
296 
297  return search->second;
298  }
299 
300  /***/
301  template <size_t, size_t>
302  constexpr void _store_named_args(std::array<fmtquill::detail::named_arg_info<char>, PatternFormatter::Attribute::ATTR_NR_ITEMS>&)
303  {
304  }
305 
306  /***/
307  template <size_t Idx, size_t NamedIdx, typename Arg, typename... Args>
308  constexpr void _store_named_args(
309  std::array<fmtquill::detail::named_arg_info<char>, PatternFormatter::Attribute::ATTR_NR_ITEMS>& named_args_store,
310  Arg const& arg, Args const&... args)
311  {
312  named_args_store[NamedIdx] = {arg.name, Idx};
313  _store_named_args<Idx + 1, NamedIdx + 1>(named_args_store, args...);
314  }
315 
342  template <typename... Args>
343  QUILL_NODISCARD std::pair<std::string, std::array<size_t, PatternFormatter::Attribute::ATTR_NR_ITEMS>> _generate_fmt_format_string(
344  std::bitset<PatternFormatter::Attribute::ATTR_NR_ITEMS>& is_set_in_pattern, std::string pattern,
345  Args const&... args)
346  {
347  // Attribute enum and the args we are passing here must be in sync
348  static_assert(PatternFormatter::Attribute::ATTR_NR_ITEMS == sizeof...(Args));
349 
350  pattern += "\n";
351 
352  std::array<size_t, PatternFormatter::Attribute::ATTR_NR_ITEMS> order_index{};
353  order_index.fill(PatternFormatter::Attribute::ATTR_NR_ITEMS - 1);
354 
355  std::array<fmtquill::detail::named_arg_info<char>, PatternFormatter::Attribute::ATTR_NR_ITEMS> named_args{};
356  _store_named_args<0, 0>(named_args, args...);
357  uint8_t arg_idx = 0;
358 
359  // we will replace all %(....) with {} to construct a string to pass to fmt library
360  size_t arg_identifier_pos = pattern.find_first_of('%');
361  while (arg_identifier_pos != std::string::npos)
362  {
363  if (size_t const open_paren_pos = pattern.find_first_of('(', arg_identifier_pos);
364  open_paren_pos != std::string::npos && (open_paren_pos - arg_identifier_pos) == 1)
365  {
366  // if we found '%(' we have a matching pattern and we now need to get the closed paren
367  size_t const closed_paren_pos = pattern.find_first_of(')', open_paren_pos);
368 
369  if (closed_paren_pos == std::string::npos)
370  {
371  QUILL_THROW(QuillError{"Invalid format pattern"});
372  }
373 
374  // We have everything, get the substring, this time including '%( )'
375  std::string attr = pattern.substr(arg_identifier_pos, (closed_paren_pos + 1) - arg_identifier_pos);
376 
377  // find any user format specifiers
378  size_t const pos = attr.find(':');
379  std::string attr_name;
380 
381  if (pos != std::string::npos)
382  {
383  // we found user format specifiers that we want to keep.
384  // e.g. %(short_source_location:<32)
385 
386  // Get only the format specifier
387  // e.g. :<32
388  std::string custom_format_specifier = attr.substr(pos);
389  custom_format_specifier.pop_back(); // remove the ")"
390 
391  // replace with the pattern with the correct value
392  std::string value;
393  value += "{";
394  value += custom_format_specifier;
395  value += "}";
396 
397  // e.g. {:<32}
398  pattern.replace(arg_identifier_pos, attr.length(), value);
399 
400  // Get the part that is the named argument
401  // e.g. short_source_location
402  attr_name = attr.substr(2, pos - 2);
403  }
404  else
405  {
406  // Make the replacement.
407  pattern.replace(arg_identifier_pos, attr.length(), "{}");
408 
409  // Get the part that is the named argument
410  // e.g. short_source_location
411  attr.pop_back(); // remove the ")"
412 
413  attr_name = attr.substr(2, attr.size());
414  }
415 
416  // reorder
417  int id = -1;
418 
419  for (size_t i = 0; i < PatternFormatter::Attribute::ATTR_NR_ITEMS; ++i)
420  {
421  if (named_args[i].name == attr_name)
422  {
423  id = named_args[i].id;
424  break;
425  }
426  }
427 
428  if (id < 0)
429  {
430  QUILL_THROW(QuillError{"Invalid format pattern, attribute with name \"" + attr_name + "\" is invalid"});
431  }
432 
433  order_index[static_cast<size_t>(id)] = arg_idx++;
434 
435  // Also set the value as used in the pattern in our bitset for lazy evaluation
436  PatternFormatter::Attribute const attr_enum_value = _attribute_from_string(attr_name);
437  is_set_in_pattern.set(attr_enum_value);
438 
439  // Look for the next pattern to replace
440  arg_identifier_pos = pattern.find_first_of('%');
441  }
442  else
443  {
444  // search for the next '%'
445  arg_identifier_pos = pattern.find_first_of('%', arg_identifier_pos + 1);
446  }
447  }
448 
449  return std::make_pair(pattern, order_index);
450  }
451 
452  /***/
453  QUILL_ATTRIBUTE_HOT std::string_view _format(
454  uint64_t timestamp, std::string_view thread_id, std::string_view thread_name,
455  std::string_view process_id, std::string_view logger, std::string_view log_level_description,
456  std::string_view log_level_short_code, MacroMetadata const& log_statement_metadata,
457  std::vector<std::pair<std::string, std::string>> const* named_args, std::string_view log_msg)
458  {
459  if (_is_set_in_pattern[Attribute::Time])
460  {
461  _set_arg_val<Attribute::Time>(_timestamp_formatter.format_timestamp(std::chrono::nanoseconds{timestamp}));
462  }
463 
464  if (_is_set_in_pattern[Attribute::FileName])
465  {
466  _set_arg_val<Attribute::FileName>(log_statement_metadata.file_name());
467  }
468 
469  if (_is_set_in_pattern[Attribute::CallerFunction])
470  {
471  std::string_view const function_name = _options.process_function_name
472  ? _options.process_function_name(log_statement_metadata.caller_function())
473  : std::string_view{log_statement_metadata.caller_function()};
474 
475  _set_arg_val<Attribute::CallerFunction>(function_name);
476  }
477 
478  if (_is_set_in_pattern[Attribute::LogLevel])
479  {
480  _set_arg_val<Attribute::LogLevel>(log_level_description);
481  }
482 
483  if (_is_set_in_pattern[Attribute::LogLevelShortCode])
484  {
485  _set_arg_val<Attribute::LogLevelShortCode>(log_level_short_code);
486  }
487 
488  if (_is_set_in_pattern[Attribute::LineNumber])
489  {
490  _set_arg_val<Attribute::LineNumber>(log_statement_metadata.line());
491  }
492 
493  if (_is_set_in_pattern[Attribute::Logger])
494  {
495  _set_arg_val<Attribute::Logger>(logger);
496  }
497 
498  if (_is_set_in_pattern[Attribute::FullPath])
499  {
500  _set_arg_val<Attribute::FullPath>(log_statement_metadata.full_path());
501  }
502 
503  if (_is_set_in_pattern[Attribute::ThreadId])
504  {
505  _set_arg_val<Attribute::ThreadId>(thread_id);
506  }
507 
508  if (_is_set_in_pattern[Attribute::ThreadName])
509  {
510  _set_arg_val<Attribute::ThreadName>(thread_name);
511  }
512 
513  if (_is_set_in_pattern[Attribute::ProcessId])
514  {
515  _set_arg_val<Attribute::ProcessId>(process_id);
516  }
517 
518  if (_is_set_in_pattern[Attribute::SourceLocation])
519  {
520  _set_arg_val<Attribute::SourceLocation>(_process_source_location_path(
521  log_statement_metadata.source_location(), _options.source_location_path_strip_prefix,
522  _options.source_location_remove_relative_paths));
523  ;
524  }
525 
526  if (_is_set_in_pattern[Attribute::ShortSourceLocation])
527  {
528  _set_arg_val<Attribute::ShortSourceLocation>(log_statement_metadata.short_source_location());
529  }
530 
531  if (_is_set_in_pattern[Attribute::NamedArgs])
532  {
533  _formatted_named_args_buffer.clear();
534 
535  if (named_args)
536  {
537  for (size_t i = 0; i < named_args->size(); ++i)
538  {
539  _formatted_named_args_buffer.append((*named_args)[i].first);
540  _formatted_named_args_buffer.append(std::string_view{": "});
541  _formatted_named_args_buffer.append((*named_args)[i].second);
542 
543  if (i != named_args->size() - 1)
544  {
545  _formatted_named_args_buffer.append(std::string_view{", "});
546  }
547  }
548  }
549 
550  _set_arg_val<Attribute::NamedArgs>(
551  std::string_view{_formatted_named_args_buffer.data(), _formatted_named_args_buffer.size()});
552  }
553 
554  if (_is_set_in_pattern[Attribute::Tags])
555  {
556  if (log_statement_metadata.tags())
557  {
558  _set_arg_val<Attribute::Tags>(std::string_view{log_statement_metadata.tags()});
559  }
560  else
561  {
562  _set_arg_val<Attribute::Tags>(std::string_view{});
563  }
564  }
565 
566  _set_arg_val<Attribute::Message>(log_msg);
567 
568  fmtquill::vformat_to(std::back_inserter(_formatted_log_message_buffer), _fmt_format,
569  fmtquill::basic_format_args(_args.data(), static_cast<int>(_args.size())));
570 
571  return std::string_view{_formatted_log_message_buffer.data(), _formatted_log_message_buffer.size()};
572  }
573 
574 private:
575  PatternFormatterOptions _options;
576  std::string _fmt_format;
577 
579  std::array<size_t, Attribute::ATTR_NR_ITEMS> _order_index{};
580  std::array<fmtquill::basic_format_arg<fmtquill::format_context>, Attribute::ATTR_NR_ITEMS> _args{};
581  std::bitset<Attribute::ATTR_NR_ITEMS> _is_set_in_pattern;
582 
584  detail::TimestampFormatter _timestamp_formatter;
585 
588  fmtquill::basic_memory_buffer<char, 512> _formatted_log_message_buffer;
589  fmtquill::basic_memory_buffer<char, 512> _formatted_named_args_buffer;
590 };
591 
592 QUILL_END_NAMESPACE
Definition: format.h:126
Captures and stores information about a logging event in compile time.
Definition: MacroMetadata.h:22
Formats a timestamp given a format string as a pattern.
Definition: TimestampFormatter.h:39
Definition: PatternFormatter.h:33
Configuration options for the PatternFormatter.
Definition: PatternFormatterOptions.h:23
PatternFormatter(PatternFormatterOptions options)
Main PatternFormatter class.
Definition: PatternFormatter.h:79
custom exception
Definition: QuillError.h:45
Definition: SourceLocation.h:40
TimestampPrecision
Public classes.
Definition: PatternFormatter.h:40
Macro-Free Logging Interface
Definition: LogFunctions.h:53