1#include "logger.h"
2
3#include "color.h"
4#include "system.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(false, "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// Separate declaration, as attributes are not allowed on function definitions
76void log_log_impl(LEVEL level, bool have_color, LOG_COLOR color, const char *sys, const char *fmt, va_list args)
77 GNUC_ATTRIBUTE((format(printf, 5, 0)));
78
79void log_log_impl(LEVEL level, bool have_color, LOG_COLOR color, const char *sys, const char *fmt, va_list args)
80{
81 // Make sure we're not logging recursively.
82 if(in_logger)
83 {
84 return;
85 }
86 in_logger = true;
87 if(!scope_logger)
88 {
89 scope_logger = global_logger.load(m: std::memory_order_acquire);
90 }
91 if(!scope_logger)
92 {
93 in_logger = false;
94 return;
95 }
96
97 CLogMessage Msg;
98 Msg.m_Level = level;
99 Msg.m_HaveColor = have_color;
100 Msg.m_Color = color;
101 str_timestamp_format(buffer: Msg.m_aTimestamp, buffer_size: sizeof(Msg.m_aTimestamp), FORMAT_SPACE);
102 Msg.m_TimestampLength = str_length(str: Msg.m_aTimestamp);
103 str_copy(dst&: Msg.m_aSystem, src: sys);
104 Msg.m_SystemLength = str_length(str: Msg.m_aSystem);
105
106 // TODO: Add level?
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 __android_log_write(AndroidLevel, pMessage->m_aSystem, pMessage->Message());
169 }
170};
171std::unique_ptr<ILogger> log_logger_android()
172{
173 return std::make_unique<CLoggerAndroid>();
174}
175#else
176std::unique_ptr<ILogger> log_logger_android()
177{
178 dbg_assert(0, "Android logger on non-Android");
179 return nullptr;
180}
181#endif
182
183class CLoggerCollection : public ILogger
184{
185 std::vector<std::shared_ptr<ILogger>> m_vpLoggers;
186
187public:
188 CLoggerCollection(std::vector<std::shared_ptr<ILogger>> &&vpLoggers) :
189 m_vpLoggers(std::move(vpLoggers))
190 {
191 m_Filter.m_MaxLevel.store(i: LEVEL_TRACE, m: std::memory_order_relaxed);
192 }
193 void Log(const CLogMessage *pMessage) override
194 {
195 if(m_Filter.Filters(pMessage))
196 {
197 return;
198 }
199 for(auto &pLogger : m_vpLoggers)
200 {
201 pLogger->Log(pMessage);
202 }
203 }
204 void GlobalFinish() override
205 {
206 for(auto &pLogger : m_vpLoggers)
207 {
208 pLogger->GlobalFinish();
209 }
210 }
211};
212
213std::unique_ptr<ILogger> log_logger_collection(std::vector<std::shared_ptr<ILogger>> &&vpLoggers)
214{
215 return std::make_unique<CLoggerCollection>(args: std::move(vpLoggers));
216}
217
218class CLoggerAsync : public ILogger
219{
220 ASYNCIO *m_pAio;
221 bool m_AnsiTruecolor;
222 bool m_Close;
223
224public:
225 CLoggerAsync(IOHANDLE File, bool AnsiTruecolor, bool Close) :
226 m_pAio(aio_new(io: File)),
227 m_AnsiTruecolor(AnsiTruecolor),
228 m_Close(Close)
229 {
230 }
231 void Log(const CLogMessage *pMessage) override
232 {
233 if(m_Filter.Filters(pMessage))
234 {
235 return;
236 }
237 aio_lock(aio: m_pAio);
238 if(m_AnsiTruecolor && pMessage->m_HaveColor)
239 {
240 // https://en.wikipedia.org/w/index.php?title=ANSI_escape_code&oldid=1077146479#24-bit
241 char aAnsi[32];
242 str_format(buffer: aAnsi, buffer_size: sizeof(aAnsi),
243 format: "\x1b[38;2;%d;%d;%dm",
244 pMessage->m_Color.r,
245 pMessage->m_Color.g,
246 pMessage->m_Color.b);
247 aio_write_unlocked(aio: m_pAio, buffer: aAnsi, size: str_length(str: aAnsi));
248 }
249 aio_write_unlocked(aio: m_pAio, buffer: pMessage->m_aLine, size: pMessage->m_LineLength);
250 if(m_AnsiTruecolor && pMessage->m_HaveColor)
251 {
252 const char aResetColor[] = "\x1b[0m";
253 aio_write_unlocked(aio: m_pAio, buffer: aResetColor, size: str_length(str: aResetColor)); // reset
254 }
255 aio_write_newline_unlocked(aio: m_pAio);
256 aio_unlock(aio: m_pAio);
257 }
258 ~CLoggerAsync() override
259 {
260 if(m_Close)
261 {
262 aio_close(aio: m_pAio);
263 }
264 aio_wait(aio: m_pAio);
265 aio_free(aio: m_pAio);
266 }
267 void GlobalFinish() override
268 {
269 if(m_Close)
270 {
271 aio_close(aio: m_pAio);
272 }
273 aio_wait(aio: m_pAio);
274 }
275};
276
277std::unique_ptr<ILogger> log_logger_file(IOHANDLE logfile)
278{
279 return std::make_unique<CLoggerAsync>(args&: logfile, args: false, args: true);
280}
281
282#if defined(CONF_FAMILY_WINDOWS)
283static int color_hsv_to_windows_console_color(const ColorHSVA &Hsv)
284{
285 int h = Hsv.h * 255.0f;
286 int s = Hsv.s * 255.0f;
287 int v = Hsv.v * 255.0f;
288 if(s >= 0 && s <= 10)
289 {
290 if(v <= 150)
291 return FOREGROUND_INTENSITY;
292 return FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;
293 }
294 if(h < 0)
295 return FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;
296 else if(h < 15)
297 return FOREGROUND_RED | FOREGROUND_INTENSITY;
298 else if(h < 30)
299 return FOREGROUND_GREEN | FOREGROUND_RED;
300 else if(h < 60)
301 return FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;
302 else if(h < 110)
303 return FOREGROUND_GREEN | FOREGROUND_INTENSITY;
304 else if(h < 140)
305 return FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY;
306 else if(h < 170)
307 return FOREGROUND_BLUE | FOREGROUND_INTENSITY;
308 else if(h < 195)
309 return FOREGROUND_BLUE | FOREGROUND_RED;
310 else if(h < 240)
311 return FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_INTENSITY;
312 else
313 return FOREGROUND_RED | FOREGROUND_INTENSITY;
314}
315
316class CWindowsConsoleLogger : public ILogger
317{
318 HANDLE m_pConsole;
319 bool m_EnableColor;
320 int m_BackgroundColor;
321 int m_ForegroundColor;
322 CLock m_OutputLock;
323 bool m_Finished = false;
324
325public:
326 CWindowsConsoleLogger(HANDLE pConsole, bool EnableColor) :
327 m_pConsole(pConsole),
328 m_EnableColor(EnableColor)
329 {
330 CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo;
331 if(GetConsoleScreenBufferInfo(pConsole, &ConsoleInfo))
332 {
333 m_BackgroundColor = ConsoleInfo.wAttributes & (BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY);
334 m_ForegroundColor = ConsoleInfo.wAttributes & (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY);
335 }
336 else
337 {
338 m_BackgroundColor = 0;
339 m_ForegroundColor = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;
340 }
341 }
342 void Log(const CLogMessage *pMessage) override REQUIRES(!m_OutputLock)
343 {
344 if(m_Filter.Filters(pMessage))
345 {
346 return;
347 }
348 const std::wstring WideMessage = windows_utf8_to_wide(pMessage->m_aLine);
349
350 int Color = m_BackgroundColor;
351 if(m_EnableColor && pMessage->m_HaveColor)
352 {
353 const ColorRGBA Rgba(pMessage->m_Color.r / 255.0f, pMessage->m_Color.g / 255.0f, pMessage->m_Color.b / 255.0f);
354 Color |= color_hsv_to_windows_console_color(color_cast<ColorHSVA>(Rgba));
355 }
356 else
357 Color |= FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;
358
359 const CLockScope LockScope(m_OutputLock);
360 if(!m_Finished)
361 {
362 SetConsoleTextAttribute(m_pConsole, Color);
363 WriteConsoleW(m_pConsole, WideMessage.c_str(), WideMessage.length(), nullptr, nullptr);
364 WriteConsoleW(m_pConsole, L"\r\n", 2, nullptr, nullptr);
365 }
366 }
367 void GlobalFinish() override REQUIRES(!m_OutputLock)
368 {
369 // Restore original color
370 const CLockScope LockScope(m_OutputLock);
371 SetConsoleTextAttribute(m_pConsole, m_BackgroundColor | m_ForegroundColor);
372 m_Finished = true;
373 }
374};
375
376static IOHANDLE ConvertWindowsHandle(HANDLE pHandle, int OpenFlags)
377{
378 int FileDescriptor = _open_osfhandle(reinterpret_cast<intptr_t>(pHandle), OpenFlags);
379 dbg_assert(FileDescriptor != -1, "_open_osfhandle failure");
380 IOHANDLE FileStream = _wfdopen(FileDescriptor, L"w");
381 dbg_assert(FileStream != nullptr, "_wfdopen failure");
382 return FileStream;
383}
384#endif
385
386std::unique_ptr<ILogger> log_logger_stdout()
387{
388#if !defined(CONF_FAMILY_WINDOWS)
389 // TODO: Only enable true color when COLORTERM contains "truecolor".
390 // https://github.com/termstandard/colors/tree/65bf0cd1ece7c15fa33a17c17528b02c99f1ae0b#checking-for-colorterm
391 const bool Colors = getenv(name: "NO_COLOR") == nullptr && isatty(STDOUT_FILENO);
392 return std::make_unique<CLoggerAsync>(args: io_stdout(), args: Colors, args: false);
393#else
394 // If we currently have no stdout (console, file, pipe),
395 // try to attach to the console of the parent process.
396 if(GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)) == FILE_TYPE_UNKNOWN)
397 {
398 AttachConsole(ATTACH_PARENT_PROCESS);
399 }
400
401 HANDLE pOutput = GetStdHandle(STD_OUTPUT_HANDLE);
402 if(pOutput == nullptr)
403 {
404 // There is no console, file or pipe that we can output to.
405 return nullptr;
406 }
407 dbg_assert(pOutput != INVALID_HANDLE_VALUE, "GetStdHandle failure");
408
409 const DWORD OutputType = GetFileType(pOutput);
410 if(OutputType == FILE_TYPE_CHAR)
411 {
412 DWORD OldConsoleMode = 0;
413 dbg_assert(GetConsoleMode(pOutput, &OldConsoleMode), "GetConsoleMode failure");
414
415 const bool Colors = _wgetenv(L"NO_COLOR") == nullptr;
416
417 // Try to enable virtual terminal processing in the Windows console.
418 // See https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
419 if(!SetConsoleMode(pOutput, OldConsoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN))
420 {
421 // Try to downgrade mode gracefully when failing to set both.
422 if(!SetConsoleMode(pOutput, OldConsoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING))
423 {
424 // Fallback to old, slower Windows logging API, when failung to enable virtual terminal processing.
425 return std::make_unique<CWindowsConsoleLogger>(pOutput, Colors);
426 }
427 }
428
429 // Virtual terminal processing was enabled successfully. We can
430 // use the async logger with ANSI escape codes for colors now.
431 // We need to set the output encoding to UTF-8 manually and
432 // convert the HANDLE to an IOHANDLE to use the async logger.
433 // We assume UTF-8 is available when virtual terminal processing is.
434 dbg_assert(SetConsoleOutputCP(CP_UTF8) != 0, "SetConsoleOutputCP failure");
435 return std::make_unique<CLoggerAsync>(ConvertWindowsHandle(pOutput, _O_TEXT), Colors, false);
436 }
437 else if(OutputType == FILE_TYPE_DISK || OutputType == FILE_TYPE_PIPE)
438 {
439 // Writing to a pipe works the same as writing to a file.
440 // We can use the async logger to write to files and pipes
441 // by converting the HANDLE to an IOHANDLE.
442 // For pipes there does not seem to be any way to determine
443 // whether the console support ANSI escape codes.
444 return std::make_unique<CLoggerAsync>(ConvertWindowsHandle(pOutput, _O_APPEND), false, false);
445 }
446 else
447 {
448 dbg_assert(false, "GetFileType failure");
449 dbg_break();
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
480void CFutureLogger::Set(std::shared_ptr<ILogger> pLogger)
481{
482 const CLockScope LockScope(m_PendingLock);
483 std::shared_ptr<ILogger> pNullLogger;
484 if(!std::atomic_compare_exchange_strong_explicit(p: &m_pLogger, v: &pNullLogger, w: pLogger, std::memory_order_acq_rel, std::memory_order_acq_rel))
485 {
486 dbg_assert(false, "future logger has already been set and can only be set once");
487 }
488 m_pLogger = std::move(pLogger);
489
490 for(const auto &Pending : m_vPending)
491 {
492 m_pLogger->Log(pMessage: &Pending);
493 }
494 m_vPending.clear();
495 m_vPending.shrink_to_fit();
496}
497
498void CFutureLogger::Log(const CLogMessage *pMessage)
499{
500 auto pLogger = std::atomic_load_explicit(p: &m_pLogger, std::memory_order_acquire);
501 if(pLogger)
502 {
503 pLogger->Log(pMessage);
504 return;
505 }
506 const CLockScope LockScope(m_PendingLock);
507 pLogger = std::atomic_load_explicit(p: &m_pLogger, std::memory_order_relaxed);
508 if(pLogger)
509 {
510 pLogger->Log(pMessage);
511 return;
512 }
513 m_vPending.push_back(x: *pMessage);
514}
515
516void CFutureLogger::GlobalFinish()
517{
518 auto pLogger = std::atomic_load_explicit(p: &m_pLogger, std::memory_order_acquire);
519 if(pLogger)
520 {
521 pLogger->GlobalFinish();
522 }
523}
524
525void CFutureLogger::OnFilterChange()
526{
527 auto pLogger = std::atomic_load_explicit(p: &m_pLogger, std::memory_order_acquire);
528 if(pLogger)
529 {
530 pLogger->SetFilter(m_Filter);
531 }
532}
533