LogIt++
Header-only, macro-first C++ logging library
Loading...
Searching...
No Matches
ConsoleLogger.hpp
Go to the documentation of this file.
1#pragma once
2#ifndef LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_LOGGERS_CONSOLELOGGER_HPP_INCLUDED
3#define LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_LOGGERS_CONSOLELOGGER_HPP_INCLUDED
4
7
8#include "ILogger.hpp"
10#include <condition_variable>
11#include <iostream>
12#include <memory>
13#include <vector>
14#if defined(_WIN32)
15#include <windows.h>
16#endif
17#ifdef __EMSCRIPTEN__
18#include <emscripten/emscripten.h>
19#if defined(LOGIT_EM_BROWSER_COLORS)
20EM_JS(void, log_ansi_js, (int lvl, const char* cmsg, const char* cdefcolor), {
21 const msg = UTF8ToString(cmsg);
22 const def = UTF8ToString(cdefcolor);
23 const isNode = (typeof process !== 'undefined' && process.versions && process.versions.node);
24 const fn = lvl >= 5 ? console.error : (lvl == 4 ? console.warn : console.log);
25 if (isNode) { fn(msg); return; }
26 const map = {30:"black",31:"darkred",32:"darkgreen",33:"olive",34:"darkblue",35:"purple",36:"teal",37:"lightgray",
27 90:"gray",91:"red",92:"green",93:"yellow",94:"blue",95:"magenta",96:"cyan",97:"white"};
28 const re = /\x1b\[(\d+)m/g;
29 let last = 0, m, style = 'color:' + def;
30 const fmt = [];
31 const styles = [];
32 while ((m = re.exec(msg)) !== null) {
33 if (m.index > last) { fmt.push('%c' + msg.slice(last, m.index)); styles.push(style); }
34 style = 'color:' + (map[m[1]] || def);
35 last = re.lastIndex;
36 }
37 if (last < msg.length) { fmt.push('%c' + msg.slice(last)); styles.push(style); }
38 fn(fmt.join(''), ...styles);
39});
40#else
41EM_JS(void, log_level, (int lvl, const char* msg), {
42 const s = UTF8ToString(msg);
43 const fn = lvl >= 5 ? console.error : (lvl == 4 ? console.warn : console.log);
44 fn(s);
45});
46#endif
47#endif
48#include <mutex>
49#include <atomic>
50
51namespace logit {
52
72 class ConsoleLogger : public ILogger {
73 public:
74
77 struct Config {
79#ifdef __EMSCRIPTEN__
80 bool async = false;
81#else
82 bool async = true;
83#endif
85 std::size_t queue_capacity = 0;
86 QueuePolicy queue_policy = QueuePolicy::Block;
91 std::vector<ConsoleStreamRoute> routes;
92 };
93
95 ConsoleLogger() : m_stream(&std::cout) {
97 }
98
101 ConsoleLogger(const Config& config)
102 : ConsoleLogger(std::cout, config) {}
103
106 explicit ConsoleLogger(std::ostream& stream)
107 : m_stream(&stream) {
108 reset_color();
109 }
110
114 ConsoleLogger(std::ostream& stream, const Config& config)
115 : m_config(config), m_stream(&stream) {
116 reset_color();
120 }
121 }
122
125 ConsoleLogger(const bool async) : m_stream(&std::cout) {
126 m_config.async = async;
127 reset_color();
128 }
129
133 ConsoleLogger(std::ostream& stream, const bool async) : m_stream(&stream) {
134 m_config.async = async;
135 reset_color();
136 }
137
140 bool async,
141 bool use_dedicated_executor,
142 std::size_t queue_capacity = 0,
143 QueuePolicy queue_policy = QueuePolicy::Block)
145 async,
146 use_dedicated_executor,
147 queue_capacity,
148 queue_policy)) {}
149
152 std::ostream& stream,
153 bool async,
154 bool use_dedicated_executor,
155 std::size_t queue_capacity = 0,
156 QueuePolicy queue_policy = QueuePolicy::Block)
157 : ConsoleLogger(stream, make_config(
158 async,
159 use_dedicated_executor,
160 queue_capacity,
161 queue_policy)) {}
162
163 virtual ~ConsoleLogger() {
164 shutdown();
165 }
166
170 void set_config(const Config& config) {
171 std::shared_ptr<detail::SingleThreadExecutor> old_executor;
172 {
173 std::unique_lock<std::mutex> lock(m_mutex);
174 if (m_shutdown.load(std::memory_order_acquire)) return;
176 m_config = config;
178 if (!m_executor) {
180 }
182 } else if (m_executor) {
183 old_executor = m_executor;
184 m_executor.reset();
185 }
186 }
187 if (old_executor) {
188 old_executor->shutdown();
189 }
190 }
191
196 std::lock_guard<std::mutex> lock(m_mutex);
197 return m_config;
198 }
199
207 void log(const LogRecord& record, const std::string& message) override {
208 if (m_shutdown.load(std::memory_order_acquire)) return;
209#ifdef __EMSCRIPTEN__
210 std::unique_lock<std::mutex> lock(m_mutex);
211 if (m_shutdown.load(std::memory_order_acquire)) return;
213 const int lvl = static_cast<int>(record.log_level);
214 std::shared_ptr<detail::SingleThreadExecutor> executor = m_executor;
215 if (!m_config.async) {
216# if defined(LOGIT_EM_BROWSER_COLORS)
217 log_ansi_js(lvl, message.c_str(), text_color_to_css(m_config.default_color));
218# else
219 log_level(lvl, message.c_str());
220# endif
221 return;
222 }
223 auto msg_copy = std::string(message);
224 const auto def_color = m_config.default_color;
226 lock.unlock();
227 PendingEnqueue pending_enqueue(*this);
228 if (executor) {
229 executor->add_task([this, lvl, msg_copy, def_color]() {
230 std::lock_guard<std::mutex> inner_lock(m_mutex);
231# if defined(LOGIT_EM_BROWSER_COLORS)
232 log_ansi_js(lvl, msg_copy.c_str(), text_color_to_css(def_color));
233# else
234 log_level(lvl, msg_copy.c_str());
235# endif
236 });
237 } else {
238 detail::TaskExecutor::get_instance().add_task([this, lvl, msg_copy, def_color]() {
239 std::lock_guard<std::mutex> inner_lock(m_mutex);
240# if defined(LOGIT_EM_BROWSER_COLORS)
241 log_ansi_js(lvl, msg_copy.c_str(), text_color_to_css(def_color));
242# else
243 log_level(lvl, msg_copy.c_str());
244# endif
245 });
246 }
247 return;
248#else
249 std::unique_lock<std::mutex> lock(m_mutex);
250 if (m_shutdown.load(std::memory_order_acquire)) return;
252 std::shared_ptr<detail::SingleThreadExecutor> executor = m_executor;
253 std::ostream* stream = select_stream_for(record.log_level);
254 if (!m_config.async) {
255 write_colored_message(*stream, message);
256 return;
257 }
259 lock.unlock();
260 PendingEnqueue pending_enqueue(*this);
261 if (executor) {
262 executor->add_task([this, stream, message](){
263 std::lock_guard<std::mutex> lock(m_mutex);
264 write_colored_message(*stream, message);
265 });
266 } else {
267 detail::TaskExecutor::get_instance().add_task([this, stream, message](){
268 std::lock_guard<std::mutex> lock(m_mutex);
269 write_colored_message(*stream, message);
270 });
271 }
272#endif
273 }
274
282 std::string get_string_param(const LoggerParam& param) const override {
283 switch (param) {
284 case LoggerParam::LastLogTimestamp: return std::to_string(get_last_log_ts());
285 case LoggerParam::TimeSinceLastLog: return std::to_string(get_time_since_last_log());
286 default:
287 break;
288 };
289 return std::string();
290 }
291
295 int64_t get_int_param(const LoggerParam& param) const override {
296 switch (param) {
299 default:
300 break;
301 };
302 return 0;
303 }
304
308 double get_float_param(const LoggerParam& param) const override {
309 switch (param) {
311 return static_cast<double>(get_last_log_ts()) / 1000.0;
313 return static_cast<double>(get_time_since_last_log()) / 1000.0;
314 default:
315 break;
316 };
317 return 0.0;
318 }
319
321 void set_log_level(LogLevel level) override {
322 m_log_level = static_cast<int>(level);
323 }
324
326 LogLevel get_log_level() const override {
327 return static_cast<LogLevel>(m_log_level.load());
328 }
329
332 void wait() override {
333 std::unique_lock<std::mutex> lock(m_mutex);
335 std::shared_ptr<detail::SingleThreadExecutor> executor = m_executor;
336 if (!m_config.async) return;
337 lock.unlock();
338 if (executor) {
339 executor->wait();
340 } else {
342 }
343 }
344
346 void shutdown() override {
347 if (m_shutdown.exchange(true, std::memory_order_acq_rel)) return;
348 std::shared_ptr<detail::SingleThreadExecutor> executor;
349 bool async = false;
350 {
351 std::unique_lock<std::mutex> lock(m_mutex);
353 async = m_config.async;
354 executor = m_executor;
355 m_executor.reset();
356 }
357 if (executor) {
358 executor->shutdown();
359 } else if (async) {
361 }
362 }
363
364 private:
365 mutable std::mutex m_mutex;
367 std::ostream* m_stream;
368 std::atomic<int64_t> m_last_log_ts = ATOMIC_VAR_INIT(0);
369 std::atomic<int> m_log_level = ATOMIC_VAR_INIT(static_cast<int>(LogLevel::LOG_LVL_TRACE));
370 std::atomic<bool> m_shutdown = ATOMIC_VAR_INIT(false);
371 std::shared_ptr<detail::SingleThreadExecutor> m_executor;
372 std::condition_variable m_enqueue_cv;
373 std::size_t m_pending_enqueues = 0;
374
380 std::ostream* select_stream_for(LogLevel level) const {
381 for (const auto& route : m_config.routes) {
382 if (route.min_level > route.max_level) continue;
383 if (level < route.min_level || level > route.max_level) continue;
384 switch (route.kind) {
386 return &std::cout;
388 return &std::cerr;
390 if (route.custom_stream) return route.custom_stream;
391 break;
392 }
393 }
394 return m_stream;
395 }
396
399 static bool is_standard_console_stream(const std::ostream& stream) {
400 return &stream == &std::cout || &stream == &std::cerr;
401 }
402
405 void write_colored_message(std::ostream& stream, const std::string& message) const {
406# ifdef __EMSCRIPTEN__
407 (void)stream;
408 (void)message;
409# elif defined(_WIN32)
410 if (is_standard_console_stream(stream)) {
411 handle_ansi_colors_windows(message, stream);
412 } else {
413 stream << message << std::endl;
414 }
415# else
416 stream << message << std::endl;
417# endif
418 }
419
421 const std::shared_ptr<detail::SingleThreadExecutor>& executor,
422 const Config& config) {
423 if (!executor) return;
424 executor->set_max_queue_size(config.queue_capacity);
425 executor->set_queue_policy(config.queue_policy);
426 }
427
429 bool async,
430 bool use_dedicated_executor,
431 std::size_t queue_capacity,
432 QueuePolicy queue_policy) {
433 Config config;
434 config.async = async;
435 config.use_dedicated_executor = use_dedicated_executor;
436 config.queue_capacity = queue_capacity;
437 config.queue_policy = queue_policy;
438 return config;
439 }
440
442 public:
443 explicit PendingEnqueue(ConsoleLogger& logger) : m_logger(&logger) {}
445 if (m_logger) {
447 }
448 }
451
452 private:
454 };
455
456 void wait_for_pending_enqueues(std::unique_lock<std::mutex>& lock) {
457 m_enqueue_cv.wait(lock, [this]() { return m_pending_enqueues == 0; });
458 }
459
461 std::lock_guard<std::mutex> lock(m_mutex);
462 if (m_pending_enqueues > 0) {
464 }
465 if (m_pending_enqueues == 0) {
466 m_enqueue_cv.notify_all();
467 }
468 }
469
470# ifdef __EMSCRIPTEN__
472 const char* text_color_to_css(TextColor color) const {
473 switch (color) {
474 case TextColor::Black: return "black";
475 case TextColor::DarkRed: return "darkred";
476 case TextColor::DarkGreen: return "darkgreen";
477 case TextColor::DarkYellow: return "olive";
478 case TextColor::DarkBlue: return "darkblue";
479 case TextColor::DarkMagenta: return "purple";
480 case TextColor::DarkCyan: return "teal";
481 case TextColor::LightGray: return "lightgray";
482 case TextColor::DarkGray: return "gray";
483 case TextColor::Red: return "red";
484 case TextColor::Green: return "green";
485 case TextColor::Yellow: return "yellow";
486 case TextColor::Blue: return "blue";
487 case TextColor::Magenta: return "magenta";
488 case TextColor::Cyan: return "cyan";
489 case TextColor::White: return "white";
490 default: return "inherit";
491 }
492 }
493# endif
494
495# if defined(_WIN32)
496
497 // Windows console colors
498 enum class WinColor {
499 Black = 0,
500 DarkBlue = 1,
501 DarkGreen = 2,
502 DarkCyan = 3,
503 DarkRed = 4,
504 DarkMagenta = 5,
505 DarkYellow = 6,
506 Gray = 7,
507 DarkGray = 8,
508 Blue = 9,
509 Green = 10,
510 Cyan = 11,
511 Red = 12,
512 Magenta = 13,
513 Yellow = 14,
514 White = 15,
515 };
516
520 void handle_ansi_colors_windows(const std::string& message, std::ostream& stream) const {
521 std::string::size_type start = 0;
522 std::string::size_type pos = 0;
523
524 HANDLE handle_stdout = (&stream == &std::cerr)
525 ? GetStdHandle(STD_ERROR_HANDLE)
526 : GetStdHandle(STD_OUTPUT_HANDLE);
527
528 while ((pos = message.find("\033[", start)) != std::string::npos) {
529 // Output the part of the string before the ANSI code
530 if (pos > start) {
531 stream << message.substr(start, pos - start);
532 }
533
534 // Find the end of the ANSI code
535 std::string::size_type end_pos = message.find('m', pos);
536 if (end_pos != std::string::npos) {
537 // Extract the ANSI code
538 std::string ansi_code = message.substr(pos + 2, end_pos - pos - 2);
539 apply_color_from_ansi_code(ansi_code, handle_stdout);
540
541 // Update position
542 start = end_pos + 1;
543 } else {
544 break;
545 }
546 }
547
548 // Output any remaining part of the message
549 if (start < message.size()) {
550 stream << message.substr(start);
551 }
552 if (!message.empty()) stream << std::endl;
553
554 // Reset the console color to default
555 SetConsoleTextAttribute(handle_stdout, static_cast<WORD>(text_color_to_win_color(m_config.default_color)));
556 }
557
561 void apply_color_from_ansi_code(const std::string& ansi_code, HANDLE handle_stdout) const {
562 WORD color_value = static_cast<WORD>(text_color_to_win_color(m_config.default_color)); // Default color
563 const int code = std::stoi(ansi_code);
564 switch (code) {
565 case 30: color_value = static_cast<WORD>(WinColor::Black); break;
566 case 31: color_value = static_cast<WORD>(WinColor::DarkRed); break;
567 case 32: color_value = static_cast<WORD>(WinColor::DarkGreen); break;
568 case 33: color_value = static_cast<WORD>(WinColor::DarkYellow); break;
569 case 34: color_value = static_cast<WORD>(WinColor::DarkBlue); break;
570 case 35: color_value = static_cast<WORD>(WinColor::DarkMagenta); break;
571 case 36: color_value = static_cast<WORD>(WinColor::DarkCyan); break;
572 case 37: color_value = static_cast<WORD>(WinColor::Gray); break;
573 case 90: color_value = static_cast<WORD>(WinColor::DarkGray); break;
574 case 91: color_value = static_cast<WORD>(WinColor::Red); break;
575 case 92: color_value = static_cast<WORD>(WinColor::Green); break;
576 case 93: color_value = static_cast<WORD>(WinColor::Yellow); break;
577 case 94: color_value = static_cast<WORD>(WinColor::Blue); break;
578 case 95: color_value = static_cast<WORD>(WinColor::Magenta); break;
579 case 96: color_value = static_cast<WORD>(WinColor::Cyan); break;
580 case 97: color_value = static_cast<WORD>(WinColor::White); break;
581 default:
582 // Unknown code, use default color
583 break;
584 };
585 // Set the console text attribute to the desired color
586 SetConsoleTextAttribute(handle_stdout, color_value);
587 }
588
592 WinColor text_color_to_win_color(const TextColor& color) const {
593 switch (color) {
594 case TextColor::Black: return WinColor::Black;
595 case TextColor::DarkRed: return WinColor::DarkRed;
596 case TextColor::DarkGreen: return WinColor::DarkGreen;
597 case TextColor::DarkYellow: return WinColor::DarkYellow;
598 case TextColor::DarkBlue: return WinColor::DarkBlue;
599 case TextColor::DarkMagenta: return WinColor::DarkMagenta;
600 case TextColor::DarkCyan: return WinColor::DarkCyan;
601 case TextColor::LightGray: return WinColor::Gray;
602 case TextColor::DarkGray: return WinColor::DarkGray;
603 case TextColor::Red: return WinColor::Red;
604 case TextColor::Green: return WinColor::Green;
605 case TextColor::Yellow: return WinColor::Yellow;
606 case TextColor::Blue: return WinColor::Blue;
607 case TextColor::Magenta: return WinColor::Magenta;
608 case TextColor::Cyan: return WinColor::Cyan;
609 case TextColor::White: return WinColor::White;
610 default: return WinColor::White;
611 }
612 }
613# endif
614
615
619 void reset_color() {
621# ifdef __EMSCRIPTEN__
622 // No persistent console color in browsers
623 return;
624# elif defined(_WIN32)
625 handle_ansi_colors_windows(std::string(), *m_stream);
626# else
628# endif
629 }
630
633 int64_t get_last_log_ts() const {
634 return m_last_log_ts;
635 }
636
639 int64_t get_time_since_last_log() const {
641 }
642 }; // ConsoleLogger
643
644}; // namespace logit
645
646#endif // LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_LOGGERS_CONSOLELOGGER_HPP_INCLUDED
Level-based output stream routing for ConsoleLogger.
Defines the interface for loggers used in the logging system.
PendingEnqueue(const PendingEnqueue &)=delete
PendingEnqueue & operator=(const PendingEnqueue &)=delete
Outputs log messages to the console with optional ANSI color support.
void log(const LogRecord &record, const std::string &message) override
Logs a message to the console with thread safety.
ConsoleLogger()
Default constructor that uses default configuration and std::cout.
double get_float_param(const LoggerParam &param) const override
Retrieves a floating-point parameter from the logger.
LogLevel get_log_level() const override
Gets the minimal log level for this logger.
std::atomic< bool > m_shutdown
std::mutex m_mutex
Mutex to protect console output.
int64_t get_last_log_ts() const
Retrieves the timestamp of the last log.
int64_t get_time_since_last_log() const
Retrieves the time since the last log.
static bool is_standard_console_stream(const std::ostream &stream)
Returns true for streams whose console handle we may drive (std::cout, std::cerr).
ConsoleLogger(std::ostream &stream, const Config &config)
Constructor with a custom primary output stream and configuration.
void wait_for_pending_enqueues(std::unique_lock< std::mutex > &lock)
Config get_config() const
Gets the current logger configuration.
std::string get_string_param(const LoggerParam &param) const override
Retrieves a string parameter from the logger.
ConsoleLogger(std::ostream &stream, bool async, bool use_dedicated_executor, std::size_t queue_capacity=0, QueuePolicy queue_policy=QueuePolicy::Block)
Constructor with a custom primary output stream, async mode, and executor options.
static Config make_config(bool async, bool use_dedicated_executor, std::size_t queue_capacity, QueuePolicy queue_policy)
void set_config(const Config &config)
Sets the logger configuration.
static void configure_executor(const std::shared_ptr< detail::SingleThreadExecutor > &executor, const Config &config)
std::shared_ptr< detail::SingleThreadExecutor > m_executor
void shutdown() override
Stops the dedicated executor after draining pending messages.
std::size_t m_pending_enqueues
int64_t get_int_param(const LoggerParam &param) const override
Retrieves an integer parameter from the logger.
ConsoleLogger(const bool async)
Constructor with asynchronous flag and std::cout.
std::atomic< int > m_log_level
void set_log_level(LogLevel level) override
Sets the minimal log level for this logger.
std::ostream * m_stream
Primary output stream; lifetime owned by the caller.
std::atomic< int64_t > m_last_log_ts
ConsoleLogger(const Config &config)
Constructor with custom configuration and the default std::cout stream.
ConsoleLogger(std::ostream &stream, const bool async)
Constructor with asynchronous flag and a custom primary output stream.
void write_colored_message(std::ostream &stream, const std::string &message) const
Writes one formatted message to a stream, applying colors when supported.
std::ostream * select_stream_for(LogLevel level) const
Selects the target stream for a given log level.
ConsoleLogger(bool async, bool use_dedicated_executor, std::size_t queue_capacity=0, QueuePolicy queue_policy=QueuePolicy::Block)
Constructor with asynchronous and dedicated executor options.
Config m_config
Configuration for the console logger.
void wait() override
Waits for all asynchronous tasks to complete.
std::condition_variable m_enqueue_cv
ConsoleLogger(std::ostream &stream)
Constructor with a custom primary output stream and default config.
void reset_color()
Resets the console text color to the default.
Optional buffered snapshot access returns empty results by default.
Definition ILogger.hpp:47
Per-instance single-thread executor for isolated async logging.
void add_task(std::function< void()> task)
Enqueue a task for asynchronous execution.
void wait()
Block until the queue is empty or a shutdown is requested.
static TaskExecutor & get_instance()
Returns the global executor instance.
#define LOGIT_CURRENT_TIMESTAMP_MS()
Macro to get the current timestamp in milliseconds.
Definition config.hpp:84
#define LOGIT_DEFAULT_COLOR
Defines the default color for console output.
Definition config.hpp:22
QueuePolicy
Queue overflow handling policy used by TaskExecutor and SingleThreadExecutor.
The primary namespace for the LogIt++ library.
std::string to_string(LogLevel level, int mode=0)
Convert LogLevel to a std::string representation.
Definition enums.hpp:109
LogLevel
Logging levels.
Definition enums.hpp:15
@ LOG_LVL_TRACE
Trace level logging.
LoggerParam
Enumeration for different logger parameters that can be retrieved.
Definition enums.hpp:47
@ TimeSinceLastLog
The time elapsed since the last log in seconds.
@ LastLogTimestamp
The timestamp of the last log.
TextColor
Text colors for console output.
Definition enums.hpp:26
@ Cout
Routes records to std::cout.
@ Custom
Routes records to a caller-provided std::ostream pointer.
@ Cerr
Routes records to std::cerr.
Configuration for the console logger.
bool async
Flag indicating whether logging should be asynchronous.
QueuePolicy queue_policy
Overflow policy for the dedicated executor.
std::size_t queue_capacity
Maximum queue size for the dedicated executor (0 = unlimited).
TextColor default_color
Default text color for console output.
bool use_dedicated_executor
Use a dedicated executor instead of the global TaskExecutor; native builds create one worker thread p...
std::vector< ConsoleStreamRoute > routes
Optional level-based stream routing.
Stores log metadata and content.
Definition LogRecord.hpp:17
const LogLevel log_level
Log level (severity).
Definition LogRecord.hpp:18
const int64_t timestamp_ms
Timestamp in milliseconds.
Definition LogRecord.hpp:19