1#include "color.h"
2#include "logger.h"
3#include "system.h"
4#include "windows.h"
5
6#include <atomic>
7#include <cstdio>
8#include <memory>
9
10#if defined(CONF_FAMILY_WINDOWS)
11#include <fcntl.h>
12#include <io.h>
13#include <windows.h>
14#else
15#include <unistd.h>
16#endif
17
18#if defined(CONF_PLATFORM_ANDROID)
19#include <android/log.h>
20#endif
21
22std::atomic<ILogger *> global_logger = nullptr;
23thread_local ILogger *scope_logger = nullptr;
24thread_local bool in_logger = false;
25
26void log_set_global_logger(ILogger *logger)
27{
28 ILogger *null = nullptr;
29 if(!global_logger.compare_exchange_strong(p1&: null, p2: logger, m: std::memory_order_acq_rel))
30 {
31 dbg_assert_failed("global logger has already been set and can only be set once");
32 }
33 atexit(func: log_global_logger_finish);
34}
35
36void log_global_logger_finish()
37{
38 ILogger *logger = global_logger.load(m: std::memory_order_acquire);
39 if(logger)
40 logger->GlobalFinish();
41}
42
43void log_set_global_logger_default()
44{
45 std::unique_ptr<ILogger> logger;
46#if defined(CONF_PLATFORM_ANDROID)
47 logger = log_logger_android();
48#else
49 logger = log_logger_stdout();
50#endif
51 if(logger)
52 {
53 log_set_global_logger(logger: logger.release());
54 }
55}
56
57ILogger *log_get_scope_logger()
58{
59 if(!scope_logger)
60 {
61 scope_logger = global_logger.load(m: std::memory_order_acquire);
62 }
63 return scope_logger;
64}
65
66void log_set_scope_logger(ILogger *logger)
67{
68 scope_logger = logger;
69 if(!scope_logger)
70 {
71 scope_logger = global_logger.load(m: std::memory_order_acquire);
72 }
73}
74
75[[gnu::format(printf, 5, 0)]] static void log_log_impl(LEVEL level, bool have_color, LOG_COLOR color, const char *sys, const char *fmt, va_list args)
76{
77 // Make sure we're not logging recursively.
78 if(in_logger)
79 {
80 return;
81 }
82 in_logger = true;
83 if(!scope_logger)
84 {
85 scope_logger = global_logger.load(m: std::memory_order_acquire);
86 }
87 if(!scope_logger)
88 {
89 in_logger = false;
90 return;
91 }
92
93 CLogMessage Msg;
94 Msg.m_Level = level;
95 Msg.m_HaveColor = have_color;
96 Msg.m_Color = color;
97 str_timestamp_format(buffer: Msg.m_aTimestamp, buffer_size: sizeof(Msg.m_aTimestamp), FORMAT_SPACE);
98 Msg.m_TimestampLength = str_length(str: Msg.m_aTimestamp);
99 str_copy(dst&: Msg.m_aSystem, src: sys);
100 Msg.m_SystemLength = str_length(str: Msg.m_aSystem);
101
102 str_format(buffer: Msg.m_aLine, buffer_size: sizeof(Msg.m_aLine), format: "%s %c %s: ", Msg.m_aTimestamp, "EWIDT"[level], Msg.m_aSystem);
103 Msg.m_LineMessageOffset = str_length(str: Msg.m_aLine);
104
105 char *pMessage = Msg.m_aLine + Msg.m_LineMessageOffset;
106 int MessageSize = sizeof(Msg.m_aLine) - Msg.m_LineMessageOffset;
107 str_format_v(buffer: pMessage, buffer_size: MessageSize, format: fmt, args);
108 Msg.m_LineLength = str_length(str: Msg.m_aLine);
109 scope_logger->Log(pMessage: &Msg);
110 in_logger = false;
111}
112
113void log_log_v(LEVEL level, const char *sys, const char *fmt, va_list args)
114{
115 log_log_impl(level, have_color: false, color: LOG_COLOR{.r: 0, .g: 0, .b: 0}, sys, fmt, args);
116}
117
118void log_log(LEVEL level, const char *sys, const char *fmt, ...)
119{
120 va_list args;
121 va_start(args, fmt);
122 log_log_impl(level, have_color: false, color: LOG_COLOR{.r: 0, .g: 0, .b: 0}, sys, fmt, args);
123 va_end(args);
124}
125
126void log_log_color_v(LEVEL level, LOG_COLOR color, const char *sys, const char *fmt, va_list args)
127{
128 log_log_impl(level, have_color: true, color, sys, fmt, args);
129}
130
131void log_log_color(LEVEL level, LOG_COLOR color, const char *sys, const char *fmt, ...)
132{
133 va_list args;
134 va_start(args, fmt);
135 log_log_impl(level, have_color: true, color, sys, fmt, args);
136 va_end(args);
137}
138
139bool CLogFilter::Filters(const CLogMessage *pMessage)
140{
141 return pMessage->m_Level > m_MaxLevel.load(m: std::memory_order_relaxed);
142}
143
144#if defined(CONF_PLATFORM_ANDROID)
145class CLoggerAndroid : public ILogger
146{
147public:
148 void Log(const CLogMessage *pMessage) override
149 {
150 if(m_Filter.Filters(pMessage))
151 {
152 return;
153 }
154 int AndroidLevel;
155 switch(pMessage->m_Level)
156 {
157 case LEVEL_TRACE: AndroidLevel = ANDROID_LOG_VERBOSE; break;
158 case LEVEL_DEBUG: AndroidLevel = ANDROID_LOG_DEBUG; break;
159 case LEVEL_INFO: AndroidLevel = ANDROID_LOG_INFO; break;
160 case LEVEL_WARN: AndroidLevel = ANDROID_LOG_WARN; break;
161 case LEVEL_ERROR: AndroidLevel = ANDROID_LOG_ERROR; break;
162 }
163 char aTag[64];
164 str_copy(aTag, ANDROID_PACKAGE_NAME "/");
165 str_append(aTag, pMessage->m_aSystem);
166 __android_log_write(AndroidLevel, aTag, pMessage->Message());
167 }
168};
169std::unique_ptr<ILogger> log_logger_android()
170{
171 return std::make_unique<CLoggerAndroid>();
172}
173#else
174std::unique_ptr<ILogger> log_logger_android()
175{
176 dbg_assert(0, "Android logger on non-Android");
177 return nullptr;
178}
179#endif
180
181class CLoggerCollection : public ILogger
182{
183 std::vector<std::shared_ptr<ILogger>> m_vpLoggers;
184
185public:
186 CLoggerCollection(std::vector<std::shared_ptr<ILogger>> &&vpLoggers) :
187 m_vpLoggers(std::move(vpLoggers))
188 {
189 m_Filter.m_MaxLevel.store(i: LEVEL_TRACE, m: std::memory_order_relaxed);
190 }
191 void Log(const CLogMessage *pMessage) override
192 {
193 if(m_Filter.Filters(pMessage))
194 {
195 return;
196 }
197 for(auto &pLogger : m_vpLoggers)
198 {
199 pLogger->Log(pMessage);
200 }
201 }
202 void GlobalFinish() override
203 {
204 for(auto &pLogger : m_vpLoggers)
205 {
206 pLogger->GlobalFinish();
207 }
208 }
209};
210
211std::unique_ptr<ILogger> log_logger_collection(std::vector<std::shared_ptr<ILogger>> &&vpLoggers)
212{
213 return std::make_unique<CLoggerCollection>(args: std::move(vpLoggers));
214}
215
216class CLoggerAsync : public ILogger
217{
218 ASYNCIO *m_pAio;
219 bool m_AnsiTruecolor;
220 bool m_Close;
221
222public:
223 CLoggerAsync(IOHANDLE File, bool AnsiTruecolor, bool Close) :
224 m_pAio(aio_new(io: File)),
225 m_AnsiTruecolor(AnsiTruecolor),
226 m_Close(Close)
227 {
228 }
229 void Log(const CLogMessage *pMessage) override
230 {
231 if(m_Filter.Filters(pMessage))
232 {
233 return;
234 }
235 aio_lock(aio: m_pAio);
236 if(m_AnsiTruecolor && pMessage->m_HaveColor)
237 {
238 // https://en.wikipedia.org/w/index.php?title=ANSI_escape_code&oldid=1077146479#24-bit
239 char aAnsi[32];
240 str_format(buffer: aAnsi, buffer_size: sizeof(aAnsi),
241 format: "\x1b[38;2;%d;%d;%dm",
242 pMessage->m_Color.r,
243 pMessage->m_Color.g,
244 pMessage->m_Color.b);
245 aio_write_unlocked(aio: m_pAio, buffer: aAnsi, size: str_length(str: aAnsi));
246 }
247 aio_write_unlocked(aio: m_pAio, buffer: pMessage->m_aLine, size: pMessage->m_LineLength);
248 if(m_AnsiTruecolor && pMessage->m_HaveColor)
249 {
250 const char aResetColor[] = "\x1b[0m";
251 aio_write_unlocked(aio: m_pAio, buffer: aResetColor, size: str_length(str: aResetColor)); // reset
252 }
253 aio_write_newline_unlocked(aio: m_pAio);
254 aio_unlock(aio: m_pAio);
255 }
256 ~CLoggerAsync() override
257 {
258 if(m_Close)
259 {
260 aio_close(aio: m_pAio);
261 }
262 aio_wait(aio: m_pAio);
263 aio_free(aio: m_pAio);
264 }
265 void GlobalFinish() override
266 {
267 if(m_Close)
268 {
269 aio_close(aio: m_pAio);
270 }
271 aio_wait(aio: m_pAio);
272 }
273};
274
275std::unique_ptr<ILogger> log_logger_file(IOHANDLE logfile)
276{
277 return std::make_unique<CLoggerAsync>(args&: logfile, args: false, args: true);
278}
279
280#if defined(CONF_FAMILY_WINDOWS)
281static int color_hsv_to_windows_console_color(const ColorHSVA &Hsv)
282{
283 int h = Hsv.h * 255.0f;
284 int s = Hsv.s * 255.0f;
285 int v = Hsv.v * 255.0f;
286 if(s >= 0 && s <= 10)
287 {
288 if(v <= 150)
289 return FOREGROUND_INTENSITY;
290 return FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;
291 }
292 if(h < 0)
293 return FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;
294 else if(h < 15)
295 return FOREGROUND_RED | FOREGROUND_INTENSITY;
296 else if(h < 30)
297 return FOREGROUND_GREEN | FOREGROUND_RED;
298 else if(h < 60)
299 return FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;
300 else if(h < 110)
301 return FOREGROUND_GREEN | FOREGROUND_INTENSITY;
302 else if(h < 140)
303 return FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY;
304 else if(h < 170)
305 return FOREGROUND_BLUE | FOREGROUND_INTENSITY;
306 else if(h < 195)
307 return FOREGROUND_BLUE | FOREGROUND_RED;
308 else if(h < 240)
309 return FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_INTENSITY;
310 else
311 return FOREGROUND_RED | FOREGROUND_INTENSITY;
312}
313
314class CWindowsConsoleLogger : public ILogger
315{
316 HANDLE m_pConsole;
317 bool m_EnableColor;
318 int m_BackgroundColor;
319 int m_ForegroundColor;
320 CLock m_OutputLock;
321 bool m_Finished = false;
322
323public:
324 CWindowsConsoleLogger(HANDLE pConsole, bool EnableColor) :
325 m_pConsole(pConsole),
326 m_EnableColor(EnableColor)
327 {
328 CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo;
329 if(GetConsoleScreenBufferInfo(pConsole, &ConsoleInfo))
330 {
331 m_BackgroundColor = ConsoleInfo.wAttributes & (BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY);
332 m_ForegroundColor = ConsoleInfo.wAttributes & (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY);
333 }
334 else
335 {
336 m_BackgroundColor = 0;
337 m_ForegroundColor = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;
338 }
339 }
340 void Log(const CLogMessage *pMessage) override REQUIRES(!m_OutputLock)
341 {
342 if(m_Filter.Filters(pMessage))
343 {
344 return;
345 }
346 const std::wstring WideMessage = windows_utf8_to_wide(pMessage->m_aLine);
347
348 int Color = m_BackgroundColor;
349 if(m_EnableColor && pMessage->m_HaveColor)
350 {
351 const ColorRGBA Rgba(pMessage->m_Color.r / 255.0f, pMessage->m_Color.g / 255.0f, pMessage->m_Color.b / 255.0f);
352 Color |= color_hsv_to_windows_console_color(color_cast<ColorHSVA>(Rgba));
353 }
354 else
355 Color |= FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;
356
357 const CLockScope LockScope(m_OutputLock);
358 if(!m_Finished)
359 {
360 SetConsoleTextAttribute(m_pConsole, Color);
361 WriteConsoleW(m_pConsole, WideMessage.c_str(), WideMessage.length(), nullptr, nullptr);
362 WriteConsoleW(m_pConsole, L"\r\n", 2, nullptr, nullptr);
363 }
364 }
365 void GlobalFinish() override REQUIRES(!m_OutputLock)
366 {
367 // Restore original color
368 const CLockScope LockScope(m_OutputLock);
369 SetConsoleTextAttribute(m_pConsole, m_BackgroundColor | m_ForegroundColor);
370 m_Finished = true;
371 }
372};
373
374static IOHANDLE ConvertWindowsHandle(HANDLE pHandle, int OpenFlags)
375{
376 int FileDescriptor = _open_osfhandle(reinterpret_cast<intptr_t>(pHandle), OpenFlags);
377 dbg_assert(FileDescriptor != -1, "_open_osfhandle failure");
378 IOHANDLE FileStream = _wfdopen(FileDescriptor, L"w");
379 dbg_assert(FileStream != nullptr, "_wfdopen failure");
380 return FileStream;
381}
382#endif
383
384std::unique_ptr<ILogger> log_logger_stdout()
385{
386#if !defined(CONF_FAMILY_WINDOWS)
387 // TODO: Only enable true color when COLORTERM contains "truecolor".
388 // https://github.com/termstandard/colors/tree/65bf0cd1ece7c15fa33a17c17528b02c99f1ae0b#checking-for-colorterm
389 const bool Colors = getenv(name: "NO_COLOR") == nullptr && isatty(STDOUT_FILENO);
390 return std::make_unique<CLoggerAsync>(args: io_stdout(), args: Colors, args: false);
391#else
392 // If we currently have no stdout (console, file, pipe),
393 // try to attach to the console of the parent process.
394 if(GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)) == FILE_TYPE_UNKNOWN)
395 {
396 AttachConsole(ATTACH_PARENT_PROCESS);
397 }
398
399 HANDLE pOutput = GetStdHandle(STD_OUTPUT_HANDLE);
400 if(pOutput == nullptr)
401 {
402 // There is no console, file or pipe that we can output to.
403 return nullptr;
404 }
405 dbg_assert(pOutput != INVALID_HANDLE_VALUE, "GetStdHandle failure");
406
407 const DWORD OutputType = GetFileType(pOutput);
408 if(OutputType == FILE_TYPE_CHAR)
409 {
410 DWORD OldConsoleMode = 0;
411 if(!GetConsoleMode(pOutput, &OldConsoleMode))
412 {
413 // GetConsoleMode can fail with ERROR_INVALID_HANDLE when redirecting output to "nul",
414 // which is considered a character file but cannot be used as a console.
415 dbg_assert(GetLastError() == ERROR_INVALID_HANDLE, "GetConsoleMode failure");
416 return nullptr;
417 }
418
419 const bool Colors = _wgetenv(L"NO_COLOR") == nullptr;
420
421 // Try to enable virtual terminal processing in the Windows console.
422 // See https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
423 if(!SetConsoleMode(pOutput, OldConsoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN))
424 {
425 // Try to downgrade mode gracefully when failing to set both.
426 if(!SetConsoleMode(pOutput, OldConsoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING))
427 {
428 // Fallback to old, slower Windows logging API, when failing to enable virtual terminal processing.
429 return std::make_unique<CWindowsConsoleLogger>(pOutput, Colors);
430 }
431 }
432
433 // Virtual terminal processing was enabled successfully. We can
434 // use the async logger with ANSI escape codes for colors now.
435 // We need to set the output encoding to UTF-8 manually and
436 // convert the HANDLE to an IOHANDLE to use the async logger.
437 // We assume UTF-8 is available when virtual terminal processing is.
438 dbg_assert(SetConsoleOutputCP(CP_UTF8) != 0, "SetConsoleOutputCP failure");
439 return std::make_unique<CLoggerAsync>(ConvertWindowsHandle(pOutput, _O_TEXT), Colors, false);
440 }
441 else if(OutputType == FILE_TYPE_DISK || OutputType == FILE_TYPE_PIPE)
442 {
443 // Writing to a pipe works the same as writing to a file.
444 // We can use the async logger to write to files and pipes
445 // by converting the HANDLE to an IOHANDLE.
446 // For pipes there does not seem to be any way to determine
447 // whether the console supports ANSI escape codes.
448 return std::make_unique<CLoggerAsync>(ConvertWindowsHandle(pOutput, _O_APPEND), false, false);
449 }
450 else
451 {
452 dbg_assert_failed("GetFileType failure");
453 }
454#endif
455}
456
457#if defined(CONF_FAMILY_WINDOWS)
458class CLoggerWindowsDebugger : public ILogger
459{
460public:
461 void Log(const CLogMessage *pMessage) override
462 {
463 if(m_Filter.Filters(pMessage))
464 {
465 return;
466 }
467 const std::wstring WideMessage = windows_utf8_to_wide(pMessage->m_aLine);
468 OutputDebugStringW(WideMessage.c_str());
469 }
470};
471std::unique_ptr<ILogger> log_logger_windows_debugger()
472{
473 return std::make_unique<CLoggerWindowsDebugger>();
474}
475#else
476std::unique_ptr<ILogger> log_logger_windows_debugger()
477{
478 dbg_assert(0, "Windows Debug logger on non-Windows");
479 return nullptr;
480}
481#endif
482
483class CLoggerNoOp : public ILogger
484{
485public:
486 void Log(const CLogMessage *pMessage) override
487 {
488 // no-op
489 }
490};
491std::unique_ptr<ILogger> log_logger_noop()
492{
493 return std::make_unique<CLoggerNoOp>();
494}
495
496#ifdef __GNUC__
497// atomic_compare_exchange_strong_explicit is deprecated
498#pragma GCC diagnostic push
499#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
500#endif
501void CFutureLogger::Set(std::shared_ptr<ILogger> pLogger)
502{
503 const CLockScope LockScope(m_PendingLock);
504 std::shared_ptr<ILogger> pNullLogger;
505 if(!std::atomic_compare_exchange_strong_explicit(p: &m_pLogger, v: &pNullLogger, w: pLogger, std::memory_order_acq_rel, std::memory_order_acq_rel))
506 {
507 dbg_assert_failed("future logger has already been set and can only be set once");
508 }
509 m_pLogger = std::move(pLogger);
510
511 for(const auto &Pending : m_vPending)
512 {
513 m_pLogger->Log(pMessage: &Pending);
514 }
515 m_vPending.clear();
516 m_vPending.shrink_to_fit();
517}
518
519void CFutureLogger::Log(const CLogMessage *pMessage)
520{
521 auto pLogger = std::atomic_load_explicit(p: &m_pLogger, std::memory_order_acquire);
522 if(pLogger)
523 {
524 pLogger->Log(pMessage);
525 return;
526 }
527 const CLockScope LockScope(m_PendingLock);
528 pLogger = std::atomic_load_explicit(p: &m_pLogger, std::memory_order_relaxed);
529 if(pLogger)
530 {
531 pLogger->Log(pMessage);
532 return;
533 }
534 m_vPending.push_back(x: *pMessage);
535}
536
537void CFutureLogger::GlobalFinish()
538{
539 auto pLogger = std::atomic_load_explicit(p: &m_pLogger, std::memory_order_acquire);
540 if(pLogger)
541 {
542 pLogger->GlobalFinish();
543 }
544}
545
546void CFutureLogger::OnFilterChange()
547{
548 auto pLogger = std::atomic_load_explicit(p: &m_pLogger, std::memory_order_acquire);
549 if(pLogger)
550 {
551 pLogger->SetFilter(m_Filter);
552 }
553}
554
555#ifdef __GNUC__
556#pragma GCC diagnostic pop
557#endif
558
559void CMemoryLogger::Log(const CLogMessage *pMessage)
560{
561 if(m_pParentLogger)
562 {
563 m_pParentLogger->Log(pMessage);
564 }
565 if(m_Filter.Filters(pMessage))
566 {
567 return;
568 }
569 const CLockScope LockScope(m_MessagesMutex);
570 m_vMessages.push_back(x: *pMessage);
571}
572
573std::vector<CLogMessage> CMemoryLogger::Lines()
574{
575 const CLockScope LockScope(m_MessagesMutex);
576 return m_vMessages;
577}
578
579std::string CMemoryLogger::ConcatenatedLines()
580{
581 const CLockScope LockScope(m_MessagesMutex);
582 std::string Result;
583 for(const CLogMessage &Message : m_vMessages)
584 {
585 if(!Result.empty())
586 {
587 Result += '\n';
588 }
589 Result += Message.m_aLine;
590 }
591 return Result;
592}
593