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 | |
22 | std::atomic<ILogger *> global_logger = nullptr; |
23 | thread_local ILogger *scope_logger = nullptr; |
24 | thread_local bool in_logger = false; |
25 | |
26 | void 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 | |
36 | void log_global_logger_finish() |
37 | { |
38 | ILogger *logger = global_logger.load(m: std::memory_order_acquire); |
39 | if(logger) |
40 | logger->GlobalFinish(); |
41 | } |
42 | |
43 | void 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 | |
57 | ILogger *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 | |
66 | void 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 |
76 | void 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 | |
79 | void 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 | |
118 | void 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 | |
123 | void 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 | |
131 | void 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 | |
136 | void 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 | |
144 | bool 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) |
150 | class CLoggerAndroid : public ILogger |
151 | { |
152 | public: |
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 | }; |
171 | std::unique_ptr<ILogger> log_logger_android() |
172 | { |
173 | return std::make_unique<CLoggerAndroid>(); |
174 | } |
175 | #else |
176 | std::unique_ptr<ILogger> log_logger_android() |
177 | { |
178 | dbg_assert(0, "Android logger on non-Android" ); |
179 | return nullptr; |
180 | } |
181 | #endif |
182 | |
183 | class CLoggerCollection : public ILogger |
184 | { |
185 | std::vector<std::shared_ptr<ILogger>> m_vpLoggers; |
186 | |
187 | public: |
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 | |
213 | std::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 | |
218 | class CLoggerAsync : public ILogger |
219 | { |
220 | ASYNCIO *m_pAio; |
221 | bool m_AnsiTruecolor; |
222 | bool m_Close; |
223 | |
224 | public: |
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 | |
277 | std::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) |
283 | static 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 | |
316 | class 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 | |
325 | public: |
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 | |
376 | static 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 | |
386 | std::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 | if(!GetConsoleMode(pOutput, &OldConsoleMode)) |
414 | { |
415 | // GetConsoleMode can fail with ERROR_INVALID_HANDLE when redirecting output to "nul", |
416 | // which is considered a character file but cannot be used as a console. |
417 | dbg_assert(GetLastError() == ERROR_INVALID_HANDLE, "GetConsoleMode failure" ); |
418 | return nullptr; |
419 | } |
420 | |
421 | const bool Colors = _wgetenv(L"NO_COLOR" ) == nullptr; |
422 | |
423 | // Try to enable virtual terminal processing in the Windows console. |
424 | // See https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences |
425 | if(!SetConsoleMode(pOutput, OldConsoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN)) |
426 | { |
427 | // Try to downgrade mode gracefully when failing to set both. |
428 | if(!SetConsoleMode(pOutput, OldConsoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) |
429 | { |
430 | // Fallback to old, slower Windows logging API, when failing to enable virtual terminal processing. |
431 | return std::make_unique<CWindowsConsoleLogger>(pOutput, Colors); |
432 | } |
433 | } |
434 | |
435 | // Virtual terminal processing was enabled successfully. We can |
436 | // use the async logger with ANSI escape codes for colors now. |
437 | // We need to set the output encoding to UTF-8 manually and |
438 | // convert the HANDLE to an IOHANDLE to use the async logger. |
439 | // We assume UTF-8 is available when virtual terminal processing is. |
440 | dbg_assert(SetConsoleOutputCP(CP_UTF8) != 0, "SetConsoleOutputCP failure" ); |
441 | return std::make_unique<CLoggerAsync>(ConvertWindowsHandle(pOutput, _O_TEXT), Colors, false); |
442 | } |
443 | else if(OutputType == FILE_TYPE_DISK || OutputType == FILE_TYPE_PIPE) |
444 | { |
445 | // Writing to a pipe works the same as writing to a file. |
446 | // We can use the async logger to write to files and pipes |
447 | // by converting the HANDLE to an IOHANDLE. |
448 | // For pipes there does not seem to be any way to determine |
449 | // whether the console supports ANSI escape codes. |
450 | return std::make_unique<CLoggerAsync>(ConvertWindowsHandle(pOutput, _O_APPEND), false, false); |
451 | } |
452 | else |
453 | { |
454 | dbg_assert(false, "GetFileType failure" ); |
455 | dbg_break(); |
456 | } |
457 | #endif |
458 | } |
459 | |
460 | #if defined(CONF_FAMILY_WINDOWS) |
461 | class CLoggerWindowsDebugger : public ILogger |
462 | { |
463 | public: |
464 | void Log(const CLogMessage *pMessage) override |
465 | { |
466 | if(m_Filter.Filters(pMessage)) |
467 | { |
468 | return; |
469 | } |
470 | const std::wstring WideMessage = windows_utf8_to_wide(pMessage->m_aLine); |
471 | OutputDebugStringW(WideMessage.c_str()); |
472 | } |
473 | }; |
474 | std::unique_ptr<ILogger> log_logger_windows_debugger() |
475 | { |
476 | return std::make_unique<CLoggerWindowsDebugger>(); |
477 | } |
478 | #else |
479 | std::unique_ptr<ILogger> log_logger_windows_debugger() |
480 | { |
481 | dbg_assert(0, "Windows Debug logger on non-Windows" ); |
482 | return nullptr; |
483 | } |
484 | #endif |
485 | |
486 | class CLoggerNoOp : public ILogger |
487 | { |
488 | public: |
489 | void Log(const CLogMessage *pMessage) override |
490 | { |
491 | // no-op |
492 | } |
493 | }; |
494 | std::unique_ptr<ILogger> log_logger_noop() |
495 | { |
496 | return std::make_unique<CLoggerNoOp>(); |
497 | } |
498 | |
499 | void CFutureLogger::Set(std::shared_ptr<ILogger> pLogger) |
500 | { |
501 | const CLockScope LockScope(m_PendingLock); |
502 | std::shared_ptr<ILogger> pNullLogger; |
503 | if(!std::atomic_compare_exchange_strong_explicit(p: &m_pLogger, v: &pNullLogger, w: pLogger, std::memory_order_acq_rel, std::memory_order_acq_rel)) |
504 | { |
505 | dbg_assert(false, "future logger has already been set and can only be set once" ); |
506 | } |
507 | m_pLogger = std::move(pLogger); |
508 | |
509 | for(const auto &Pending : m_vPending) |
510 | { |
511 | m_pLogger->Log(pMessage: &Pending); |
512 | } |
513 | m_vPending.clear(); |
514 | m_vPending.shrink_to_fit(); |
515 | } |
516 | |
517 | void CFutureLogger::Log(const CLogMessage *pMessage) |
518 | { |
519 | auto pLogger = std::atomic_load_explicit(p: &m_pLogger, std::memory_order_acquire); |
520 | if(pLogger) |
521 | { |
522 | pLogger->Log(pMessage); |
523 | return; |
524 | } |
525 | const CLockScope LockScope(m_PendingLock); |
526 | pLogger = std::atomic_load_explicit(p: &m_pLogger, std::memory_order_relaxed); |
527 | if(pLogger) |
528 | { |
529 | pLogger->Log(pMessage); |
530 | return; |
531 | } |
532 | m_vPending.push_back(x: *pMessage); |
533 | } |
534 | |
535 | void CFutureLogger::GlobalFinish() |
536 | { |
537 | auto pLogger = std::atomic_load_explicit(p: &m_pLogger, std::memory_order_acquire); |
538 | if(pLogger) |
539 | { |
540 | pLogger->GlobalFinish(); |
541 | } |
542 | } |
543 | |
544 | void CFutureLogger::OnFilterChange() |
545 | { |
546 | auto pLogger = std::atomic_load_explicit(p: &m_pLogger, std::memory_order_acquire); |
547 | if(pLogger) |
548 | { |
549 | pLogger->SetFilter(m_Filter); |
550 | } |
551 | } |
552 | |