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