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