quill
JsonSink.h
1 
7 #pragma once
8 
9 #include "quill/bundled/fmt/base.h"
10 #include "quill/core/Attributes.h"
11 #include "quill/core/Filesystem.h"
12 #include "quill/core/LogLevel.h"
13 #include "quill/core/MacroMetadata.h"
14 #include "quill/sinks/FileSink.h"
15 #include "quill/sinks/StreamSink.h"
16 
17 #include "quill/bundled/fmt/format.h"
18 
19 #include <chrono>
20 #include <cstdint>
21 #include <cstring>
22 #include <string>
23 #include <string_view>
24 #include <utility>
25 #include <vector>
26 
27 QUILL_BEGIN_NAMESPACE
28 
29 QUILL_BEGIN_EXPORT
30 
31 namespace detail
32 {
33 template <typename TBase>
34 class JsonSink : public TBase
35 {
36 public:
37  using base_type = TBase;
38 
40  using base_type::base_type;
41 
42  ~JsonSink() override = default;
43 
60  QUILL_ATTRIBUTE_HOT void write_log(MacroMetadata const* log_metadata, uint64_t log_timestamp,
61  std::string_view thread_id, std::string_view thread_name,
62  std::string const& process_id, std::string_view logger_name,
63  LogLevel log_level, std::string_view log_level_description,
64  std::string_view log_level_short_code,
65  std::vector<std::pair<std::string, std::string>> const* named_args,
66  std::string_view log_message, std::string_view log_statement) override
67  {
68  if (!_json_message_ready)
69  {
70  char const* message_format = log_metadata->message_format();
71 
72  if (strchr(message_format, '\n') != nullptr)
73  {
74  // The format string contains at least one new line and that would break the json message, it needs to be removed
75  _format = message_format;
76 
77  for (size_t pos = 0; (pos = _format.find('\n', pos)) != std::string::npos; pos++)
78  {
79  _format.replace(pos, 1, " ");
80  }
81 
82  // we do not want newlines in the json message, use the modified message_format
83  message_format = _format.data();
84  }
85 
86  _json_message.clear();
87 
88  generate_json_message(log_metadata, log_timestamp, thread_id, thread_name, process_id,
89  logger_name, log_level, log_level_description, log_level_short_code,
90  named_args, log_message, log_statement, message_format);
91 
92  _json_message.append(std::string_view{"}\n"});
93  }
94 
95  _json_message_ready = false;
96 
97  base_type::write_log(log_metadata, log_timestamp, thread_id, thread_name, process_id, logger_name, log_level,
98  log_level_description, log_level_short_code, named_args, std::string_view{},
99  std::string_view{_json_message.data(), _json_message.size()});
100  }
101 
111  QUILL_ATTRIBUTE_HOT virtual void generate_json_message(
112  MacroMetadata const* log_metadata, uint64_t log_timestamp, std::string_view thread_id,
113  std::string_view , std::string const& ,
114  std::string_view logger_name, LogLevel ,
115  std::string_view log_level_description, std::string_view ,
116  std::vector<std::pair<std::string, std::string>> const* named_args,
117  std::string_view , std::string_view , char const* message_format)
118  {
119  _json_message.append(std::string_view{"{\"timestamp\":\""});
120  _append_json_escaped(_json_message, std::to_string(log_timestamp));
121  _json_message.append(std::string_view{"\",\"file_name\":\""});
122  _append_json_escaped(_json_message, log_metadata->file_name());
123  _json_message.append(std::string_view{"\",\"line\":\""});
124  _append_json_escaped(_json_message, log_metadata->line());
125  _json_message.append(std::string_view{"\",\"thread_id\":\""});
126  _append_json_escaped(_json_message, thread_id);
127  _json_message.append(std::string_view{"\",\"logger\":\""});
128  _append_json_escaped(_json_message, logger_name);
129  _json_message.append(std::string_view{"\",\"log_level\":\""});
130  _append_json_escaped(_json_message, log_level_description);
131  _json_message.append(std::string_view{"\",\"message\":\""});
132  _append_json_escaped(_json_message, message_format);
133  _json_message.append(std::string_view{"\""});
134 
135  // Add args as key-values
136  if (named_args)
137  {
138  for (auto const& [key, value] : *named_args)
139  {
140  _json_message.append(std::string_view{",\""});
141  _append_json_escaped(_json_message, key);
142  _json_message.append(std::string_view{"\":\""});
143  _append_json_escaped(_json_message, value);
144  _json_message.append(std::string_view{"\""});
145  }
146  }
147  }
148 
149 protected:
150  QUILL_NODISCARD size_t estimate_write_size(MacroMetadata const* log_metadata, uint64_t log_timestamp,
151  std::string_view thread_id, std::string_view thread_name,
152  std::string const& process_id, std::string_view logger_name,
153  LogLevel log_level, std::string_view log_level_description,
154  std::string_view log_level_short_code,
155  std::vector<std::pair<std::string, std::string>> const* named_args,
156  std::string_view log_message, std::string_view log_statement) override
157  {
158  _json_message.clear();
159 
160  char const* message_format = log_metadata->message_format();
161  if (strchr(message_format, '\n') != nullptr)
162  {
163  _format = message_format;
164 
165  for (size_t pos = 0; (pos = _format.find('\n', pos)) != std::string::npos; pos++)
166  {
167  _format.replace(pos, 1, " ");
168  }
169 
170  message_format = _format.data();
171  }
172 
173  generate_json_message(log_metadata, log_timestamp, thread_id, thread_name, process_id,
174  logger_name, log_level, log_level_description, log_level_short_code,
175  named_args, log_message, log_statement, message_format);
176 
177  _json_message.append(std::string_view{"}\n"});
178  _json_message_ready = true;
179  return _json_message.size();
180  }
181 
182  static void _append_json_escaped(fmtquill::memory_buffer& out, std::string_view value)
183  {
184  // Pre-computed escape table for control characters (0x00..0x1F). Each entry is the 6-byte
185  // \uXXXX form. Faster and locale-safe compared to per-byte snprintf.
186  static constexpr char control_escape_table[32][7] = {
187  "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", "\\u0007",
188  "\\u0008", "\\u0009", "\\u000A", "\\u000B", "\\u000C", "\\u000D", "\\u000E", "\\u000F",
189  "\\u0010", "\\u0011", "\\u0012", "\\u0013", "\\u0014", "\\u0015", "\\u0016", "\\u0017",
190  "\\u0018", "\\u0019", "\\u001A", "\\u001B", "\\u001C", "\\u001D", "\\u001E", "\\u001F"};
191 
192  size_t const size = value.size();
193  char const* const data = value.data();
194 
195  for (size_t i = 0; i < size; ++i)
196  {
197  unsigned char const c = static_cast<unsigned char>(data[i]);
198  switch (c)
199  {
200  case '"':
201  out.append(std::string_view{"\\\""});
202  break;
203  case '\\':
204  out.append(std::string_view{"\\\\"});
205  break;
206  case '\b':
207  out.append(std::string_view{"\\b"});
208  break;
209  case '\f':
210  out.append(std::string_view{"\\f"});
211  break;
212  case '\n':
213  out.append(std::string_view{"\\n"});
214  break;
215  case '\r':
216  out.append(std::string_view{"\\r"});
217  break;
218  case '\t':
219  out.append(std::string_view{"\\t"});
220  break;
221  // U+2028 (LINE SEPARATOR) and U+2029 (PARAGRAPH SEPARATOR) are valid JSON characters
222  // but they break some JavaScript consumers that treat JSON as JS source. Escape the
223  // 3-byte UTF-8 sequences E2 80 A8 and E2 80 A9 when we see them.
224  case 0xE2:
225  if (i + 2 < size && static_cast<unsigned char>(data[i + 1]) == 0x80 &&
226  (static_cast<unsigned char>(data[i + 2]) == 0xA8 || static_cast<unsigned char>(data[i + 2]) == 0xA9))
227  {
228  out.append(static_cast<unsigned char>(data[i + 2]) == 0xA8 ? std::string_view{"\\u2028"}
229  : std::string_view{"\\u2029"});
230  i += 2;
231  }
232  else
233  {
234  out.push_back(static_cast<char>(c));
235  }
236  break;
237  default:
238  if (c < 0x20)
239  {
240  out.append(std::string_view{control_escape_table[c], 6});
241  }
242  else
243  {
244  out.push_back(static_cast<char>(c));
245  }
246  break;
247  }
248  }
249  }
250 
251  fmtquill::memory_buffer _json_message;
252  std::string _format;
253  bool _json_message_ready{false};
254 };
255 } // namespace detail
256 
257 QUILL_END_EXPORT
258 
262 QUILL_BEGIN_EXPORT
263 class JsonFileSink : public detail::JsonSink<FileSink>
264 {
265 public:
266  JsonFileSink(fs::path const& filename, FileSinkConfig const& config,
267  FileEventNotifier file_event_notifier = FileEventNotifier{}, bool do_fopen = true,
268  std::chrono::system_clock::time_point start_time = std::chrono::system_clock::now())
269  : detail::JsonSink<FileSink>(filename, static_cast<FileSinkConfig const&>(config),
270  std::move(file_event_notifier), do_fopen, start_time)
271  {
272  }
273 
274  ~JsonFileSink() override = default;
275 };
276 
280 class JsonConsoleSink : public detail::JsonSink<StreamSink>
281 {
282 public:
283  JsonConsoleSink() : detail::JsonSink<StreamSink>("stdout", nullptr) {}
284  ~JsonConsoleSink() override = default;
285 };
286 QUILL_END_EXPORT
287 
288 QUILL_END_NAMESPACE
Captures and stores information about a logging event in compile time.
Definition: MacroMetadata.h:24
Definition: JsonSink.h:34
QUILL_ATTRIBUTE_HOT void write_log(MacroMetadata const *log_metadata, uint64_t log_timestamp, std::string_view thread_id, std::string_view thread_name, std::string const &process_id, std::string_view logger_name, LogLevel log_level, std::string_view log_level_description, std::string_view log_level_short_code, std::vector< std::pair< std::string, std::string >> const *named_args, std::string_view log_message, std::string_view log_statement) override
Logs a formatted log message to the sink.
Definition: JsonSink.h:60
JSON File Sink.
Definition: JsonSink.h:263
Setups a signal handler to handle fatal signals.
Definition: BackendManager.h:28
Definition: base.h:2200
JSON Console Sink.
Definition: JsonSink.h:280
The FileSinkConfig class holds the configuration options for the FileSink.
Definition: FileSink.h:67
Notifies on file events by calling the appropriate callback.
Definition: StreamSink.h:68
virtual QUILL_ATTRIBUTE_HOT void generate_json_message(MacroMetadata const *log_metadata, uint64_t log_timestamp, std::string_view thread_id, std::string_view, std::string const &, std::string_view logger_name, LogLevel, std::string_view log_level_description, std::string_view, std::vector< std::pair< std::string, std::string >> const *named_args, std::string_view, std::string_view, char const *message_format)
Generates a JSON-formatted log message.
Definition: JsonSink.h:111