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 <cassert>
15 #include <cstdint>
16 #include <cstdio>
17 #include <cstring>
18 #include <string>
19 #include <string_view>
20 #include <utility>
21 #include <vector>
22 
23 #if defined(_WIN32)
24  #if !defined(WIN32_LEAN_AND_MEAN)
25  #define WIN32_LEAN_AND_MEAN
26  #endif
27 
28  #if !defined(NOMINMAX)
29  // Mingw already defines this, so no need to redefine
30  #define NOMINMAX
31  #endif
32 
33  #include <io.h>
34  #include <windows.h>
35 #else
36  #include <cstdlib>
37  #include <unistd.h>
38 #endif
39 
40 QUILL_BEGIN_NAMESPACE
41 
43 class MacroMetadata;
44 
46 {
47 public:
48  enum class ColourMode
49  {
50  Always,
51  Automatic,
52  Never
53  };
54 
58  class Colours
59  {
60  public:
61  Colours() { apply_default_colours(); }
62 
63  ~Colours() = default;
64 
68  void apply_default_colours() noexcept
69  {
70  assign_colour_to_log_level(LogLevel::TraceL3, white);
71  assign_colour_to_log_level(LogLevel::TraceL2, white);
72  assign_colour_to_log_level(LogLevel::TraceL1, white);
73  assign_colour_to_log_level(LogLevel::Debug, cyan);
74  assign_colour_to_log_level(LogLevel::Info, green);
75  assign_colour_to_log_level(LogLevel::Notice, white_bold);
76  assign_colour_to_log_level(LogLevel::Warning, yellow_bold);
77  assign_colour_to_log_level(LogLevel::Error, red_bold);
78  assign_colour_to_log_level(LogLevel::Critical, bold_on_red);
79  assign_colour_to_log_level(LogLevel::Backtrace, magenta);
80  }
81 
87  void assign_colour_to_log_level(LogLevel log_level, std::string_view colour) noexcept
88  {
89  auto const log_lvl = static_cast<uint32_t>(log_level);
90  _log_level_colours[log_lvl] = colour;
91  _colours_enabled = true;
92  }
93 
94  QUILL_ATTRIBUTE_COLD void set_colours_enabled(bool value) { _colours_enabled = value; }
95 
99  QUILL_NODISCARD bool colours_enabled() const noexcept
100  {
101  return _colour_output_supported && _colours_enabled;
102  }
103 
109  QUILL_NODISCARD std::string_view log_level_colour(LogLevel log_level) const noexcept
110  {
111  auto const log_lvl = static_cast<uint32_t>(log_level);
112  return _log_level_colours[log_lvl];
113  }
114 
115  // Formatting codes
116  static constexpr std::string_view reset{"\033[0m"};
117  static constexpr std::string_view bold{"\033[1m"};
118  static constexpr std::string_view dark{"\033[2m"};
119  static constexpr std::string_view underline{"\033[4m"};
120  static constexpr std::string_view blink{"\033[5m"};
121  static constexpr std::string_view reverse{"\033[7m"};
122  static constexpr std::string_view concealed{"\033[8m"};
123  static constexpr std::string_view clear_line{"\033[K"};
124 
125  // Foreground colors
126  static constexpr std::string_view black{"\033[30m"};
127  static constexpr std::string_view red{"\033[31m"};
128  static constexpr std::string_view green{"\033[32m"};
129  static constexpr std::string_view yellow{"\033[33m"};
130  static constexpr std::string_view blue{"\033[34m"};
131  static constexpr std::string_view magenta{"\033[35m"};
132  static constexpr std::string_view cyan{"\033[36m"};
133  static constexpr std::string_view white{"\033[37m"};
134 
136  static constexpr std::string_view on_black{"\033[40m"};
137  static constexpr std::string_view on_red{"\033[41m"};
138  static constexpr std::string_view on_green{"\033[42m"};
139  static constexpr std::string_view on_yellow{"\033[43m"};
140  static constexpr std::string_view on_blue{"\033[44m"};
141  static constexpr std::string_view on_magenta{"\033[45m"};
142  static constexpr std::string_view on_cyan{"\033[46m"};
143  static constexpr std::string_view on_white{"\033[47m"};
144 
146  static constexpr std::string_view white_bold{"\033[97m\033[1m"};
147  static constexpr std::string_view yellow_bold{"\033[33m\033[1m"};
148  static constexpr std::string_view red_bold{"\033[31m\033[1m"};
149  static constexpr std::string_view bold_on_red{"\033[1m\033[41m"};
150 
151  private:
152  friend class ConsoleSink;
153 
154  /***/
155  QUILL_NODISCARD QUILL_ATTRIBUTE_COLD static bool _supports_colour_output() noexcept
156  {
157 #ifdef _WIN32
158  // On Windows 10 and later, ANSI colors are supported
159  return true;
160 #else
161  // Get term from env
162  auto* env_p = std::getenv("TERM");
163 
164  if (env_p == nullptr)
165  {
166  return false;
167  }
168 
169  static constexpr char const* terms[] = {
170  "ansi", "color", "console", "cygwin", "gnome",
171  "konsole", "kterm", "linux", "msys", "putty",
172  "rxvt", "screen", "vt100", "xterm", "tmux",
173  "terminator", "alacritty", "gnome-terminal", "xfce4-terminal", "lxterminal",
174  "mate-terminal", "uxterm", "eterm", "tilix", "rxvt-unicode",
175  "kde-konsole"};
176 
177  // Loop through each term and check if it's found in env_p
178  for (char const* term : terms)
179  {
180  if (std::strstr(env_p, term) != nullptr)
181  {
182  // term found
183  return true;
184  }
185  }
186 
187  // none of the terms are found
188  return false;
189 #endif
190  }
191 
192  /***/
193  QUILL_NODISCARD QUILL_ATTRIBUTE_COLD static bool _is_terminal_output(FILE* file) noexcept
194  {
195 #ifdef _WIN32
196  return _isatty(_fileno(file)) != 0;
197 #else
198  return ::isatty(fileno(file)) != 0;
199 #endif
200  }
201 
202 #ifdef _WIN32
203  /***/
204  QUILL_ATTRIBUTE_COLD void _activate_ansi_support(FILE* file) const
205  {
206  if (!_colour_output_supported)
207  {
208  return;
209  }
210 
211  // Try to enable ANSI support for Windows console
212  auto const out_handle = reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(file)));
213  if (out_handle == INVALID_HANDLE_VALUE)
214  {
215  return;
216  }
217 
218  DWORD dw_mode = 0;
219  if (!GetConsoleMode(out_handle, &dw_mode))
220  {
221  return;
222  }
223 
224  dw_mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
225  dw_mode |= ENABLE_PROCESSED_OUTPUT;
226 
227  SetConsoleMode(out_handle, dw_mode);
228  }
229 #endif
230 
231  /***/
232  void _configure_colour_support(FILE* file, ColourMode colour_mode) noexcept
233  {
234  if (colour_mode == ColourMode::Always)
235  {
236  _colour_output_supported = true;
237  }
238  else if (colour_mode == ColourMode::Automatic)
239  {
240  _colour_output_supported = _is_terminal_output(file) && _supports_colour_output();
241  }
242  else
243  {
244  _colour_output_supported = false;
245  }
246 
247 #ifdef _WIN32
248  // Enable ANSI color support on Windows
249  _activate_ansi_support(file);
250 #endif
251  }
252 
253  private:
254  std::array<std::string_view, 10> _log_level_colours;
255  bool _colours_enabled{true};
256  bool _colour_output_supported{false};
257  };
258 
267  QUILL_ATTRIBUTE_COLD void set_colours(Colours colours) { _colours = colours; }
268 
283  QUILL_ATTRIBUTE_COLD void set_colour_mode(ColourMode colour_mode) { _colour_mode = colour_mode; }
284 
297  QUILL_ATTRIBUTE_COLD void set_stream(std::string const& stream) { _stream = stream; }
298 
308  QUILL_ATTRIBUTE_COLD void set_override_pattern_formatter_options(std::optional<PatternFormatterOptions> const& options)
309  {
310  _override_pattern_formatter_options = options;
311  }
312 
314  QUILL_NODISCARD Colours const& colours() noexcept { return _colours; }
315  QUILL_NODISCARD ColourMode colour_mode() const noexcept { return _colour_mode; }
316  QUILL_NODISCARD std::string const& stream() const noexcept { return _stream; }
317  QUILL_NODISCARD std::optional<PatternFormatterOptions> const& override_pattern_formatter_options() const noexcept
318  {
319  return _override_pattern_formatter_options;
320  }
321 
322 private:
323  friend class ConsoleSink;
324 
325  std::optional<PatternFormatterOptions> _override_pattern_formatter_options;
326  std::string _stream = "stdout";
327  Colours _colours;
328  ColourMode _colour_mode{ColourMode::Automatic};
329 };
330 
331 /***/
332 class ConsoleSink : public StreamSink
333 {
334 public:
339  explicit ConsoleSink(ConsoleSinkConfig const& config = ConsoleSinkConfig{})
340  : StreamSink{config.stream(), nullptr, config.override_pattern_formatter_options()}, _config(config)
341  {
342  assert((_config.stream() == "stdout") || (config.stream() == "stderr"));
343 
344  if (_config.colour_mode() == ConsoleSinkConfig::ColourMode::Never)
345  {
346  _config._colours.set_colours_enabled(false);
347  }
348  else
349  {
350  _config._colours._configure_colour_support(_file, _config.colour_mode());
351  _config._colours.set_colours_enabled(true);
352  }
353  }
354 
355  ~ConsoleSink() override = default;
356 
372  QUILL_ATTRIBUTE_HOT void write_log(MacroMetadata const* log_metadata, uint64_t log_timestamp,
373  std::string_view thread_id, std::string_view thread_name,
374  std::string const& process_id, std::string_view logger_name,
375  LogLevel log_level, std::string_view log_level_description,
376  std::string_view log_level_short_code,
377  std::vector<std::pair<std::string, std::string>> const* named_args,
378  std::string_view log_message, std::string_view log_statement) override
379  {
380  if (_config.colours().colours_enabled())
381  {
382  // Write colour code
383  std::string_view const colour_code = _config.colours().log_level_colour(log_level);
384  safe_fwrite(colour_code.data(), sizeof(char), colour_code.size(), _file);
385 
386  // Write record to file
387  StreamSink::write_log(log_metadata, log_timestamp, thread_id, thread_name, process_id,
388  logger_name, log_level, log_level_description, log_level_short_code,
389  named_args, log_message, log_statement);
390 
391  // Reset colour code
392  safe_fwrite(ConsoleSinkConfig::Colours::reset.data(), sizeof(char),
393  ConsoleSinkConfig::Colours::reset.size(), _file);
394  }
395  else
396  {
397  // Write record to file
398  StreamSink::write_log(log_metadata, log_timestamp, thread_id, thread_name, process_id,
399  logger_name, log_level, log_level_description, log_level_short_code,
400  named_args, log_message, log_statement);
401  }
402  }
403 
404 protected:
405  // protected in case someone wants to derive from this class and create a custom one, e.g. for json logging to stdout
406  ConsoleSinkConfig _config;
407 };
408 
409 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:133
QUILL_ATTRIBUTE_COLD void set_stream(std::string const &stream)
Sets the output stream for console logging.
Definition: ConsoleSink.h:297
Definition: ConsoleSink.h:332
QUILL_ATTRIBUTE_COLD void set_colour_mode(ColourMode colour_mode)
Sets the colour mode for console output.
Definition: ConsoleSink.h:283
void assign_colour_to_log_level(LogLevel log_level, std::string_view colour) noexcept
Sets a custom colour per log level.
Definition: ConsoleSink.h:87
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:308
Captures and stores information about a logging event in compile time.
Definition: MacroMetadata.h:22
Definition: ConsoleSink.h:45
void apply_default_colours() noexcept
Sets some default colours for terminal.
Definition: ConsoleSink.h:68
QUILL_NODISCARD bool colours_enabled() const noexcept
Definition: ConsoleSink.h:99
ConsoleSink(ConsoleSinkConfig const &config=ConsoleSinkConfig{})
Constructor with custom ConsoleColours config.
Definition: ConsoleSink.h:339
QUILL_NODISCARD std::string_view log_level_colour(LogLevel log_level) const noexcept
The colour for the given log level.
Definition: ConsoleSink.h:109
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:372
Represents console colours.
Definition: ConsoleSink.h:58
QUILL_ATTRIBUTE_COLD void set_colours(Colours colours)
Sets custom colours for each log level.
Definition: ConsoleSink.h:267
StreamSink class for handling log messages.
Definition: StreamSink.h:48
QUILL_NODISCARD Colours const & colours() noexcept
Getters.
Definition: ConsoleSink.h:314