quill
ConsoleSink.h
1 
7 #pragma once
8 
9 #include "quill/core/Attributes.h"
10 #include "quill/core/LogLevel.h"
11 #include "quill/sinks/StreamSink.h"
12 
13 #include <array>
14 #include <cstdint>
15 #include <cstdio>
16 #include <cstring>
17 #include <string>
18 #include <string_view>
19 #include <utility>
20 #include <vector>
21 
22 #if defined(_WIN32)
23  #if !defined(WIN32_LEAN_AND_MEAN)
24  #define WIN32_LEAN_AND_MEAN
25  #endif
26 
27  #if !defined(NOMINMAX)
28  // Mingw already defines this, so no need to redefine
29  #define NOMINMAX
30  #endif
31 
32  #include <io.h>
33  #include <windows.h>
34 #else
35  #include <cstdlib>
36  #include <unistd.h>
37 #endif
38 
39 QUILL_BEGIN_NAMESPACE
40 
41 QUILL_BEGIN_EXPORT
42 
44 {
45 public:
46  enum class ColourMode
47  {
48  Always,
49  Automatic,
50  Never
51  };
52 
56  class Colours
57  {
58  public:
59  Colours() { apply_default_colours(); }
60 
61  ~Colours() = default;
62 
66  void apply_default_colours() noexcept
67  {
68  assign_colour_to_log_level(LogLevel::TraceL3, white);
69  assign_colour_to_log_level(LogLevel::TraceL2, white);
70  assign_colour_to_log_level(LogLevel::TraceL1, white);
71  assign_colour_to_log_level(LogLevel::Debug, cyan);
72  assign_colour_to_log_level(LogLevel::Info, green);
73  assign_colour_to_log_level(LogLevel::Notice, white_bold);
74  assign_colour_to_log_level(LogLevel::Warning, yellow_bold);
75  assign_colour_to_log_level(LogLevel::Error, red_bold);
76  assign_colour_to_log_level(LogLevel::Critical, bold_on_red);
77  assign_colour_to_log_level(LogLevel::Backtrace, magenta);
78  }
79 
85  void assign_colour_to_log_level(LogLevel log_level, std::string_view colour) noexcept
86  {
87  auto const log_lvl = static_cast<uint32_t>(log_level);
88  _log_level_colours[log_lvl] = colour;
89  _colours_enabled = true;
90  }
91 
92  QUILL_ATTRIBUTE_COLD void set_colours_enabled(bool value) { _colours_enabled = value; }
93 
97  QUILL_NODISCARD bool colours_enabled() const noexcept
98  {
99  return _colour_output_supported && _colours_enabled;
100  }
101 
107  QUILL_NODISCARD std::string_view log_level_colour(LogLevel log_level) const noexcept
108  {
109  auto const log_lvl = static_cast<uint32_t>(log_level);
110  return _log_level_colours[log_lvl];
111  }
112 
113  // Formatting codes
114  static constexpr std::string_view reset{"\033[0m"};
115  static constexpr std::string_view bold{"\033[1m"};
116  static constexpr std::string_view dark{"\033[2m"};
117  static constexpr std::string_view underline{"\033[4m"};
118  static constexpr std::string_view blink{"\033[5m"};
119  static constexpr std::string_view reverse{"\033[7m"};
120  static constexpr std::string_view concealed{"\033[8m"};
121  static constexpr std::string_view clear_line{"\033[K"};
122 
123  // Foreground colors
124  static constexpr std::string_view black{"\033[30m"};
125  static constexpr std::string_view red{"\033[31m"};
126  static constexpr std::string_view green{"\033[32m"};
127  static constexpr std::string_view yellow{"\033[33m"};
128  static constexpr std::string_view blue{"\033[34m"};
129  static constexpr std::string_view magenta{"\033[35m"};
130  static constexpr std::string_view cyan{"\033[36m"};
131  static constexpr std::string_view white{"\033[37m"};
132 
134  static constexpr std::string_view on_black{"\033[40m"};
135  static constexpr std::string_view on_red{"\033[41m"};
136  static constexpr std::string_view on_green{"\033[42m"};
137  static constexpr std::string_view on_yellow{"\033[43m"};
138  static constexpr std::string_view on_blue{"\033[44m"};
139  static constexpr std::string_view on_magenta{"\033[45m"};
140  static constexpr std::string_view on_cyan{"\033[46m"};
141  static constexpr std::string_view on_white{"\033[47m"};
142 
144  static constexpr std::string_view white_bold{"\033[97m\033[1m"};
145  static constexpr std::string_view yellow_bold{"\033[33m\033[1m"};
146  static constexpr std::string_view red_bold{"\033[31m\033[1m"};
147  static constexpr std::string_view bold_on_red{"\033[1m\033[41m"};
148 
149  private:
150  friend class ConsoleSink;
151 
152  /***/
153  QUILL_NODISCARD QUILL_ATTRIBUTE_COLD static bool _supports_colour_output() noexcept
154  {
155 #ifdef _WIN32
156  // On Windows 10 and later, ANSI colors are supported
157  return true;
158 #else
159  // Get term from env
160  auto* env_p = std::getenv("TERM");
161 
162  if (env_p == nullptr)
163  {
164  return false;
165  }
166 
167  static constexpr char const* terms[] = {
168  "ansi", "color", "console", "cygwin", "gnome",
169  "konsole", "kterm", "linux", "msys", "putty",
170  "rxvt", "screen", "vt100", "xterm", "tmux",
171  "terminator", "alacritty", "gnome-terminal", "xfce4-terminal", "lxterminal",
172  "mate-terminal", "uxterm", "eterm", "tilix", "rxvt-unicode",
173  "kde-konsole"};
174 
175  // Loop through each term and check if it's found in env_p
176  for (char const* term : terms)
177  {
178  if (std::strstr(env_p, term) != nullptr)
179  {
180  // term found
181  return true;
182  }
183  }
184 
185  // none of the terms are found
186  return false;
187 #endif
188  }
189 
190  /***/
191  QUILL_NODISCARD QUILL_ATTRIBUTE_COLD static bool _is_terminal_output(FILE* file) noexcept
192  {
193 #ifdef _WIN32
194  return _isatty(_fileno(file)) != 0;
195 #else
196  return ::isatty(fileno(file)) != 0;
197 #endif
198  }
199 
200 #ifdef _WIN32
201  /***/
202  QUILL_ATTRIBUTE_COLD bool _activate_ansi_support(FILE* file) const noexcept
203  {
204  if (!_colour_output_supported)
205  {
206  return false;
207  }
208 
209  // Try to enable ANSI support for Windows console
210  auto const out_handle = reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(file)));
211  if (out_handle == INVALID_HANDLE_VALUE)
212  {
213  return false;
214  }
215 
216  DWORD dw_mode = 0;
217  if (!GetConsoleMode(out_handle, &dw_mode))
218  {
219  return false;
220  }
221 
222  dw_mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
223  dw_mode |= ENABLE_PROCESSED_OUTPUT;
224 
225  return SetConsoleMode(out_handle, dw_mode) != 0;
226  }
227 #endif
228 
229  /***/
230  void _configure_colour_support(FILE* file, ColourMode colour_mode) noexcept
231  {
232  if (colour_mode == ColourMode::Always)
233  {
234  _colour_output_supported = true;
235  }
236  else if (colour_mode == ColourMode::Automatic)
237  {
238  _colour_output_supported = _is_terminal_output(file) && _supports_colour_output();
239  }
240  else
241  {
242  _colour_output_supported = false;
243  }
244 
245 #ifdef _WIN32
246  // Enable ANSI color support on Windows
247  if ((colour_mode == ColourMode::Automatic) && !_activate_ansi_support(file))
248  {
249  _colour_output_supported = false;
250  }
251  else if (colour_mode == ColourMode::Always)
252  {
253  static_cast<void>(_activate_ansi_support(file));
254  }
255 #endif
256  }
257 
258  private:
259  static_assert(static_cast<size_t>(LogLevel::None) < LogLevelCount,
260  "_log_level_colours must be large enough to be indexed by every LogLevel value");
261 
262  std::array<std::string_view, LogLevelCount> _log_level_colours;
263  bool _colours_enabled{true};
264  bool _colour_output_supported{false};
265  };
266 
275  QUILL_ATTRIBUTE_COLD void set_colours(Colours colours) { _colours = colours; }
276 
291  QUILL_ATTRIBUTE_COLD void set_colour_mode(ColourMode colour_mode) { _colour_mode = colour_mode; }
292 
305  QUILL_ATTRIBUTE_COLD void set_stream(std::string const& stream) { _stream = stream; }
306 
316  QUILL_ATTRIBUTE_COLD void set_override_pattern_formatter_options(std::optional<PatternFormatterOptions> const& options)
317  {
318  _override_pattern_formatter_options = options;
319  }
320 
322  QUILL_NODISCARD Colours const& colours() const noexcept { return _colours; }
323  QUILL_NODISCARD ColourMode colour_mode() const noexcept { return _colour_mode; }
324  QUILL_NODISCARD std::string const& stream() const noexcept { return _stream; }
325  QUILL_NODISCARD std::optional<PatternFormatterOptions> const& override_pattern_formatter_options() const noexcept
326  {
327  return _override_pattern_formatter_options;
328  }
329 
330 private:
331  friend class ConsoleSink;
332 
333  std::optional<PatternFormatterOptions> _override_pattern_formatter_options;
334  std::string _stream = "stdout";
335  Colours _colours;
336  ColourMode _colour_mode{ColourMode::Automatic};
337 };
338 
339 /***/
340 class ConsoleSink : public StreamSink
341 {
342 public:
347  explicit ConsoleSink(ConsoleSinkConfig const& config = ConsoleSinkConfig{},
348  FileEventNotifier file_event_notifier = FileEventNotifier{})
349  : StreamSink{config.stream(), nullptr, config.override_pattern_formatter_options(),
350  std::move(file_event_notifier)},
351  _config(config)
352  {
353  if (QUILL_UNLIKELY(_config.stream() != "stdout" && _config.stream() != "stderr"))
354  {
355  QUILL_THROW(
356  QuillError{"Invalid stream name in ConsoleSink constructor, must be 'stdout' or 'stderr'"});
357  }
358 
359  if (_config.colour_mode() == ConsoleSinkConfig::ColourMode::Never)
360  {
361  _config._colours.set_colours_enabled(false);
362  }
363  else
364  {
365  _config._colours._configure_colour_support(_file, _config.colour_mode());
366  _config._colours.set_colours_enabled(true);
367  }
368  }
369 
370  ~ConsoleSink() override = default;
371 
387  QUILL_ATTRIBUTE_HOT void write_log(MacroMetadata const* log_metadata, uint64_t log_timestamp,
388  std::string_view thread_id, std::string_view thread_name,
389  std::string const& process_id, std::string_view logger_name,
390  LogLevel log_level, std::string_view log_level_description,
391  std::string_view log_level_short_code,
392  std::vector<std::pair<std::string, std::string>> const* named_args,
393  std::string_view log_message, std::string_view log_statement) override
394  {
395  if (_config.colours().colours_enabled())
396  {
397  // Write colour code
398  std::string_view const colour_code = _config.colours().log_level_colour(log_level);
399  safe_fwrite(colour_code.data(), sizeof(char), colour_code.size(), _file);
400 
401  // Write record to file
402  StreamSink::write_log(log_metadata, log_timestamp, thread_id, thread_name, process_id,
403  logger_name, log_level, log_level_description, log_level_short_code,
404  named_args, log_message, log_statement);
405 
406  // Reset colour code
407  safe_fwrite(ConsoleSinkConfig::Colours::reset.data(), sizeof(char),
408  ConsoleSinkConfig::Colours::reset.size(), _file);
409  }
410  else
411  {
412  // Write record to file
413  StreamSink::write_log(log_metadata, log_timestamp, thread_id, thread_name, process_id,
414  logger_name, log_level, log_level_description, log_level_short_code,
415  named_args, log_message, log_statement);
416  }
417  }
418 
419 protected:
420  // protected in case someone wants to derive from this class and create a custom one, e.g. for json logging to stdout
421  ConsoleSinkConfig _config;
422 };
423 
424 QUILL_END_EXPORT
425 
426 QUILL_END_NAMESPACE
QUILL_ATTRIBUTE_HOT void write_log(MacroMetadata const *, uint64_t, std::string_view, std::string_view, std::string const &, std::string_view, LogLevel, std::string_view, std::string_view, std::vector< std::pair< std::string, std::string >> const *, std::string_view, std::string_view log_statement) override
Writes a formatted log message to the stream.
Definition: StreamSink.h:123
QUILL_ATTRIBUTE_COLD void set_stream(std::string const &stream)
Sets the output stream for console logging.
Definition: ConsoleSink.h:305
Definition: ConsoleSink.h:340
QUILL_ATTRIBUTE_COLD void set_colour_mode(ColourMode colour_mode)
Sets the colour mode for console output.
Definition: ConsoleSink.h:291
QUILL_NODISCARD Colours const & colours() const noexcept
Getters.
Definition: ConsoleSink.h:322
void assign_colour_to_log_level(LogLevel log_level, std::string_view colour) noexcept
Sets a custom colour per log level.
Definition: ConsoleSink.h:85
QUILL_ATTRIBUTE_COLD void set_override_pattern_formatter_options(std::optional< PatternFormatterOptions > const &options)
Sets custom pattern formatter options for this sink.
Definition: ConsoleSink.h:316
Captures and stores information about a logging event in compile time.
Definition: MacroMetadata.h:24
Definition: ConsoleSink.h:43
void apply_default_colours() noexcept
Sets some default colours for terminal.
Definition: ConsoleSink.h:66
QUILL_NODISCARD bool colours_enabled() const noexcept
Definition: ConsoleSink.h:97
QUILL_NODISCARD std::string_view log_level_colour(LogLevel log_level) const noexcept
The colour for the given log level.
Definition: ConsoleSink.h:107
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
Write a formatted log message to the stream.
Definition: ConsoleSink.h:387
Represents console colours.
Definition: ConsoleSink.h:56
custom exception
Definition: QuillError.h:47
QUILL_ATTRIBUTE_COLD void set_colours(Colours colours)
Sets custom colours for each log level.
Definition: ConsoleSink.h:275
Notifies on file events by calling the appropriate callback.
Definition: StreamSink.h:68
ConsoleSink(ConsoleSinkConfig const &config=ConsoleSinkConfig{}, FileEventNotifier file_event_notifier=FileEventNotifier{})
Constructor with custom ConsoleColours config.
Definition: ConsoleSink.h:347
StreamSink class for handling log messages.
Definition: StreamSink.h:80