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 __android_log_write(AndroidLevel, pMessage->m_aSystem, pMessage->Message());
164 }
165};
166std::unique_ptr<ILogger> log_logger_android()
167{
168 return std::make_unique<CLoggerAndroid>();
169}
170#else
171std::unique_ptr<ILogger> log_logger_android()
172{
173 dbg_assert(0, "Android logger on non-Android");
174 return nullptr;
175}
176#endif
177
178class CLoggerCollection : public ILogger
179{
180 std::vector<std::shared_ptr<ILogger>> m_vpLoggers;
181
182public:
183 CLoggerCollection(std::vector<std::shared_ptr<ILogger>> &&vpLoggers) :
184 m_vpLoggers(std::move(vpLoggers))
185 {
186 m_Filter.m_MaxLevel.store(i: LEVEL_TRACE, m: std::memory_order_relaxed);
187 }
188 void Log(const CLogMessage *pMessage) override
189 {
190 if(m_Filter.Filters(pMessage))
191 {
192 return;
193 }
194 for(auto &pLogger : m_vpLoggers)
195 {
196 pLogger->Log(pMessage);
197 }
198 }
199 void GlobalFinish() override
200 {
201 for(auto &pLogger : m_vpLoggers)
202 {
203 pLogger->GlobalFinish();
204 }
205 }
206};
207
208std::unique_ptr<ILogger> log_logger_collection(std::vector<std::shared_ptr<ILogger>> &&vpLoggers)
209{
210 return std::make_unique<CLoggerCollection>(args: std::move(vpLoggers));
211}
212
213class CLoggerAsync : public ILogger
214{
215 ASYNCIO *m_pAio;
216 bool m_AnsiTruecolor;
217 bool m_Close;
218
219public:
220 CLoggerAsync(IOHANDLE File, bool AnsiTruecolor, bool Close) :
221 m_pAio(aio_new(io: File)),
222 m_AnsiTruecolor(AnsiTruecolor),
223 m_Close(Close)
224 {
225 }
226 void Log(const CLogMessage *pMessage) override
227 {
228 if(m_Filter.Filters(pMessage))
229 {
230 return;
231 }
232 aio_lock(aio: m_pAio);
233 if(m_AnsiTruecolor && pMessage->m_HaveColor)
234 {
235 // https://en.wikipedia.org/w/index.php?title=ANSI_escape_code&oldid=1077146479#24-bit
236 char aAnsi[32];
237 str_format(buffer: aAnsi, buffer_size: sizeof(aAnsi),
238 format: "\x1b[38;2;%d;%d;%dm",
239 pMessage->m_Color.r,
240 pMessage->m_Color.g,
241 pMessage->m_Color.b);
242 aio_write_unlocked(aio: m_pAio, buffer: aAnsi, size: str_length(str: aAnsi));
243 }
244 aio_write_unlocked(aio: m_pAio, buffer: pMessage->m_aLine, size: pMessage->m_LineLength);
245 if(m_AnsiTruecolor && pMessage->m_HaveColor)
246 {
247 const char aResetColor[] = "\x1b[0m";
248 aio_write_unlocked(aio: m_pAio, buffer: aResetColor, size: str_length(str: aResetColor)); // reset
249 }
250 aio_write_newline_unlocked(aio: m_pAio);
251 aio_unlock(aio: m_pAio);
252 }
253 ~CLoggerAsync() override
254 {
255 if(m_Close)
256 {
257 aio_close(aio: m_pAio);
258 }
259 aio_wait(aio: m_pAio);
260 aio_free(aio: m_pAio);
261 }
262 void GlobalFinish() override
263 {
264 if(m_Close)
265 {
266 aio_close(aio: m_pAio);
267 }
268 aio_wait(aio: m_pAio);
269 }
270};
271
272std::unique_ptr<ILogger> log_logger_file(IOHANDLE logfile)
273{
274 return std::make_unique<CLoggerAsync>(args&: logfile, args: false, args: true);
275}
276
277#if defined(CONF_FAMILY_WINDOWS)
278static int color_hsv_to_windows_console_color(const ColorHSVA &Hsv)
279{
280 int h = Hsv.h * 255.0f;
281 int s = Hsv.s * 255.0f;
282 int v = Hsv.v * 255.0f;
283 if(s >= 0 && s <= 10)
284 {
285 if(v <= 150)
286 return FOREGROUND_INTENSITY;
287 return FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;
288 }
289 if(h < 0)
290 return FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;
291 else if(h < 15)
292 return FOREGROUND_RED | FOREGROUND_INTENSITY;
293 else if(h < 30)
294 return FOREGROUND_GREEN | FOREGROUND_RED;
295 else if(h < 60)
296 return FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;
297 else if(h < 110)
298 return FOREGROUND_GREEN | FOREGROUND_INTENSITY;
299 else if(h < 140)
300 return FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY;
301 else if(h < 170)
302 return FOREGROUND_BLUE | FOREGROUND_INTENSITY;
303 else if(h < 195)
304 return FOREGROUND_BLUE | FOREGROUND_RED;
305 else if(h < 240)
306 return FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_INTENSITY;
307 else
308 return FOREGROUND_RED | FOREGROUND_INTENSITY;
309}
310
311class CWindowsConsoleLogger : public ILogger
312{
313 HANDLE m_pConsole;
314 bool m_EnableColor;
315 int m_BackgroundColor;
316 int m_ForegroundColor;
317 CLock m_OutputLock;
318 bool m_Finished = false;
319
320public:
321 CWindowsConsoleLogger(HANDLE pConsole, bool EnableColor) :
322 m_pConsole(pConsole),
323 m_EnableColor(EnableColor)
324 {
325 CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo;
326 if(GetConsoleScreenBufferInfo(pConsole, &ConsoleInfo))
327 {
328 m_BackgroundColor = ConsoleInfo.wAttributes & (BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY);
329 m_ForegroundColor = ConsoleInfo.wAttributes & (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY);
330 }
331 else
332 {
333 m_BackgroundColor = 0;
334 m_ForegroundColor = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;
335 }
336 }
337 void Log(const CLogMessage *pMessage) override REQUIRES(!m_OutputLock)
338 {
339 if(m_Filter.Filters(pMessage))
340 {
341 return;
342 }
343 const std::wstring WideMessage = windows_utf8_to_wide(pMessage->m_aLine);
344
345 int Color = m_BackgroundColor;
346 if(m_EnableColor && pMessage->m_HaveColor)
347 {
348 const ColorRGBA Rgba(pMessage->m_Color.r / 255.0f, pMessage->m_Color.g / 255.0f, pMessage->m_Color.b / 255.0f);
349 Color |= color_hsv_to_windows_console_color(color_cast<ColorHSVA>(Rgba));
350 }
351 else
352 Color |= FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;
353
354 const CLockScope LockScope(m_OutputLock);
355 if(!m_Finished)
356 {
357 SetConsoleTextAttribute(m_pConsole, Color);
358 WriteConsoleW(m_pConsole, WideMessage.c_str(), WideMessage.length(), nullptr, nullptr);
359 WriteConsoleW(m_pConsole, L"\r\n", 2, nullptr, nullptr);
360 }
361 }
362 void GlobalFinish() override REQUIRES(!m_OutputLock)
363 {
364 // Restore original color
365 const CLockScope LockScope(m_OutputLock);
366 SetConsoleTextAttribute(m_pConsole, m_BackgroundColor | m_ForegroundColor);
367 m_Finished = true;
368 }
369};
370
371static IOHANDLE ConvertWindowsHandle(HANDLE pHandle, int OpenFlags)
372{
373 int FileDescriptor = _open_osfhandle(reinterpret_cast<intptr_t>(pHandle), OpenFlags);
374 dbg_assert(FileDescriptor != -1, "_open_osfhandle failure");
375 IOHANDLE FileStream = _wfdopen(FileDescriptor, L"w");
376 dbg_assert(FileStream != nullptr, "_wfdopen failure");
377 return FileStream;
378}
379#endif
380
381std::unique_ptr<ILogger> log_logger_stdout()
382{
383#if !defined(CONF_FAMILY_WINDOWS)
384 // TODO: Only enable true color when COLORTERM contains "truecolor".
385 // https://github.com/termstandard/colors/tree/65bf0cd1ece7c15fa33a17c17528b02c99f1ae0b#checking-for-colorterm
386 const bool Colors = getenv(name: "NO_COLOR") == nullptr && isatty(STDOUT_FILENO);
387 return std::make_unique<CLoggerAsync>(args: io_stdout(), args: Colors, args: false);
388#else
389 // If we currently have no stdout (console, file, pipe),
390 // try to attach to the console of the parent process.
391 if(GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)) == FILE_TYPE_UNKNOWN)
392 {
393 AttachConsole(ATTACH_PARENT_PROCESS);
394 }
395
396 HANDLE pOutput = GetStdHandle(STD_OUTPUT_HANDLE);
397 if(pOutput == nullptr)
398 {
399 // There is no console, file or pipe that we can output to.
400 return nullptr;
401 }
402 dbg_assert(pOutput != INVALID_HANDLE_VALUE, "GetStdHandle failure");
403
404 const DWORD OutputType = GetFileType(pOutput);
405 if(OutputType == FILE_TYPE_CHAR)
406 {
407 DWORD OldConsoleMode = 0;
408 if(!GetConsoleMode(pOutput, &OldConsoleMode))
409 {
410 // GetConsoleMode can fail with ERROR_INVALID_HANDLE when redirecting output to "nul",
411 // which is considered a character file but cannot be used as a console.
412 dbg_assert(GetLastError() == ERROR_INVALID_HANDLE, "GetConsoleMode failure");
413 return nullptr;
414 }
415
416 const bool Colors = _wgetenv(L"NO_COLOR") == nullptr;
417
418 // Try to enable virtual terminal processing in the Windows console.
419 // See https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
420 if(!SetConsoleMode(pOutput, OldConsoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN))
421 {
422 // Try to downgrade mode gracefully when failing to set both.
423 if(!SetConsoleMode(pOutput, OldConsoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING))
424 {
425 // Fallback to old, slower Windows logging API, when failing to enable virtual terminal processing.
426 return std::make_unique<CWindowsConsoleLogger>(pOutput, Colors);
427 }
428 }
429
430 // Virtual terminal processing was enabled successfully. We can
431 // use the async logger with ANSI escape codes for colors now.
432 // We need to set the output encoding to UTF-8 manually and
433 // convert the HANDLE to an IOHANDLE to use the async logger.
434 // We assume UTF-8 is available when virtual terminal processing is.
435 dbg_assert(SetConsoleOutputCP(CP_UTF8) != 0, "SetConsoleOutputCP failure");
436 return std::make_unique<CLoggerAsync>(ConvertWindowsHandle(pOutput, _O_TEXT), Colors, false);
437 }
438 else if(OutputType == FILE_TYPE_DISK || OutputType == FILE_TYPE_PIPE)
439 {
440 // Writing to a pipe works the same as writing to a file.
441 // We can use the async logger to write to files and pipes
442 // by converting the HANDLE to an IOHANDLE.
443 // For pipes there does not seem to be any way to determine
444 // whether the console supports ANSI escape codes.
445 return std::make_unique<CLoggerAsync>(ConvertWindowsHandle(pOutput, _O_APPEND), false, false);
446 }
447 else
448 {
449 dbg_assert_failed("GetFileType failure");
450 }
451#endif
452}
453
454#if defined(CONF_FAMILY_WINDOWS)
455class CLoggerWindowsDebugger : public ILogger
456{
457public:
458 void Log(const CLogMessage *pMessage) override
459 {
460 if(m_Filter.Filters(pMessage))
461 {
462 return;
463 }
464 const std::wstring WideMessage = windows_utf8_to_wide(pMessage->m_aLine);
465 OutputDebugStringW(WideMessage.c_str());
466 }
467};
468std::unique_ptr<ILogger> log_logger_windows_debugger()
469{
470 return std::make_unique<CLoggerWindowsDebugger>();
471}
472#else
473std::unique_ptr<ILogger> log_logger_windows_debugger()
474{
475 dbg_assert(0, "Windows Debug logger on non-Windows");
476 return nullptr;
477}
478#endif
479
480class CLoggerNoOp : public ILogger
481{
482public:
483 void Log(const CLogMessage *pMessage) override
484 {
485 // no-op
486 }
487};
488std::unique_ptr<ILogger> log_logger_noop()
489{
490 return std::make_unique<CLoggerNoOp>();
491}
492
493#ifdef __GNUC__
494// atomic_compare_exchange_strong_explicit is deprecated
495#pragma GCC diagnostic push
496#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
497#endif
498void CFutureLogger::Set(std::shared_ptr<ILogger> pLogger)
499{
500 const CLockScope LockScope(m_PendingLock);
501 std::shared_ptr<ILogger> pNullLogger;
502 if(!std::atomic_compare_exchange_strong_explicit(p: &m_pLogger, v: &pNullLogger, w: pLogger, std::memory_order_acq_rel, std::memory_order_acq_rel))
503 {
504 dbg_assert_failed("future logger has already been set and can only be set once");
505 }
506 m_pLogger = std::move(pLogger);
507
508 for(const auto &Pending : m_vPending)
509 {
510 m_pLogger->Log(pMessage: &Pending);
511 }
512 m_vPending.clear();
513 m_vPending.shrink_to_fit();
514}
515
516void CFutureLogger::Log(const CLogMessage *pMessage)
517{
518 auto pLogger = std::atomic_load_explicit(p: &m_pLogger, std::memory_order_acquire);
519 if(pLogger)
520 {
521 pLogger->Log(pMessage);
522 return;
523 }
524 const CLockScope LockScope(m_PendingLock);
525 pLogger = std::atomic_load_explicit(p: &m_pLogger, std::memory_order_relaxed);
526 if(pLogger)
527 {
528 pLogger->Log(pMessage);
529 return;
530 }
531 m_vPending.push_back(x: *pMessage);
532}
533
534void CFutureLogger::GlobalFinish()
535{
536 auto pLogger = std::atomic_load_explicit(p: &m_pLogger, std::memory_order_acquire);
537 if(pLogger)
538 {
539 pLogger->GlobalFinish();
540 }
541}
542
543void CFutureLogger::OnFilterChange()
544{
545 auto pLogger = std::atomic_load_explicit(p: &m_pLogger, std::memory_order_acquire);
546 if(pLogger)
547 {
548 pLogger->SetFilter(m_Filter);
549 }
550}
551
552#ifdef __GNUC__
553#pragma GCC diagnostic pop
554#endif
555
556void CMemoryLogger::Log(const CLogMessage *pMessage)
557{
558 if(m_pParentLogger)
559 {
560 m_pParentLogger->Log(pMessage);
561 }
562 if(m_Filter.Filters(pMessage))
563 {
564 return;
565 }
566 const CLockScope LockScope(m_MessagesMutex);
567 m_vMessages.push_back(x: *pMessage);
568}
569
570std::vector<CLogMessage> CMemoryLogger::Lines()
571{
572 const CLockScope LockScope(m_MessagesMutex);
573 return m_vMessages;
574}
575
576std::string CMemoryLogger::ConcatenatedLines()
577{
578 const CLockScope LockScope(m_MessagesMutex);
579 std::string Result;
580 for(const CLogMessage &Message : m_vMessages)
581 {
582 if(!Result.empty())
583 {
584 Result += '\n';
585 }
586 Result += Message.m_aLine;
587 }
588 return Result;
589}
590