#include "logger.h" #include "color.h" #include "system.h" #include #include #if defined(CONF_FAMILY_WINDOWS) #define WIN32_LEAN_AND_MEAN #undef _WIN32_WINNT #define _WIN32_WINNT 0x0501 /* required for mingw to get getaddrinfo to work */ #include #endif extern "C" { std::atomic global_logger = nullptr; thread_local ILogger *scope_logger = nullptr; thread_local bool in_logger = false; void log_set_global_logger(ILogger *logger) { ILogger *null = nullptr; if(!global_logger.compare_exchange_strong(null, logger, std::memory_order_acq_rel)) { dbg_assert(false, "global logger has already been set and can only be set once"); } atexit(log_global_logger_finish); } void log_global_logger_finish() { global_logger.load(std::memory_order_acquire)->GlobalFinish(); } void log_set_global_logger_default() { std::unique_ptr logger; #if defined(CONF_PLATFORM_ANDROID) logger = log_logger_android(); #else logger = log_logger_stdout(); #endif log_set_global_logger(logger.release()); } ILogger *log_get_scope_logger() { if(!scope_logger) { scope_logger = global_logger.load(std::memory_order_acquire); } return scope_logger; } void log_set_scope_logger(ILogger *logger) { scope_logger = logger; if(!scope_logger) { scope_logger = global_logger.load(std::memory_order_acquire); } } void log_log_impl(LEVEL level, bool have_color, LOG_COLOR color, const char *sys, const char *fmt, va_list args) { // Make sure we're not logging recursively. if(in_logger) { return; } in_logger = true; if(!scope_logger) { scope_logger = global_logger.load(std::memory_order_acquire); } if(!scope_logger) { in_logger = false; return; } CLogMessage Msg; Msg.m_Level = level; Msg.m_HaveColor = have_color; Msg.m_Color = color; str_timestamp_format(Msg.m_aTimestamp, sizeof(Msg.m_aTimestamp), FORMAT_SPACE); Msg.m_TimestampLength = str_length(Msg.m_aTimestamp); str_copy(Msg.m_aSystem, sys, sizeof(Msg.m_aSystem)); Msg.m_SystemLength = str_length(Msg.m_aSystem); // TODO: Add level? str_format(Msg.m_aLine, sizeof(Msg.m_aLine), "[%s][%s]: ", Msg.m_aTimestamp, Msg.m_aSystem); Msg.m_LineMessageOffset = str_length(Msg.m_aLine); char *pMessage = Msg.m_aLine + Msg.m_LineMessageOffset; int MessageSize = sizeof(Msg.m_aLine) - Msg.m_LineMessageOffset; #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" #endif #if defined(CONF_FAMILY_WINDOWS) _vsnprintf(pMessage, MessageSize, fmt, args); #else vsnprintf(pMessage, MessageSize, fmt, args); #endif #ifdef __GNUC__ #pragma GCC diagnostic pop #endif Msg.m_LineLength = str_length(Msg.m_aLine); scope_logger->Log(&Msg); in_logger = false; } void log_log_v(LEVEL level, const char *sys, const char *fmt, va_list args) { log_log_impl(level, false, LOG_COLOR{0, 0, 0}, sys, fmt, args); } void log_log(LEVEL level, const char *sys, const char *fmt, ...) { va_list args; va_start(args, fmt); log_log_impl(level, false, LOG_COLOR{0, 0, 0}, sys, fmt, args); va_end(args); } void log_log_color_v(LEVEL level, LOG_COLOR color, const char *sys, const char *fmt, va_list args) { log_log_impl(level, true, color, sys, fmt, args); } void log_log_color(LEVEL level, LOG_COLOR color, const char *sys, const char *fmt, ...) { va_list args; va_start(args, fmt); log_log_impl(level, true, color, sys, fmt, args); va_end(args); } } #if defined(CONF_PLATFORM_ANDROID) class CLoggerAndroid : public ILogger { public: void Log(const CLogMessage *pMessage) override { int AndroidLevel; switch(Level) { case LEVEL_TRACE: AndroidLevel = ANDROID_LOG_VERBOSE; break; case LEVEL_DEBUG: AndroidLevel = ANDROID_LOG_DEBUG; break; case LEVEL_INFO: AndroidLevel = ANDROID_LOG_INFO; break; case LEVEL_WARN: AndroidLevel = ANDROID_LOG_WARN; break; case LEVEL_ERROR: AndroidLevel = ANDROID_LOG_ERROR; break; } __android_log_write(AndroidLevel, pMessage->m_aSystem, pMessage->Message()); } }; std::unique_ptr log_logger_android() { return std::unique_ptr(new CLoggerAndroid()); } #else std::unique_ptr log_logger_android() { dbg_assert(0, "Android logger on non-Android"); return nullptr; } #endif class CLoggerCollection : public ILogger { std::vector> m_apLoggers; public: CLoggerCollection(std::vector> &&apLoggers) : m_apLoggers(std::move(apLoggers)) { } void Log(const CLogMessage *pMessage) override { for(auto &pLogger : m_apLoggers) { pLogger->Log(pMessage); } } void GlobalFinish() override { for(auto &pLogger : m_apLoggers) { pLogger->GlobalFinish(); } } }; std::unique_ptr log_logger_collection(std::vector> &&loggers) { return std::unique_ptr(new CLoggerCollection(std::move(loggers))); } class CLoggerAsync : public ILogger { ASYNCIO *m_pAio; bool m_AnsiTruecolor; bool m_Close; public: CLoggerAsync(IOHANDLE File, bool AnsiTruecolor, bool Close) : m_pAio(aio_new(File)), m_AnsiTruecolor(AnsiTruecolor), m_Close(Close) { } void Log(const CLogMessage *pMessage) override { aio_lock(m_pAio); if(m_AnsiTruecolor) { // https://en.wikipedia.org/w/index.php?title=ANSI_escape_code&oldid=1077146479#24-bit char aAnsi[32]; if(pMessage->m_HaveColor) { str_format(aAnsi, sizeof(aAnsi), "\x1b[38;2;%d;%d;%dm", pMessage->m_Color.r, pMessage->m_Color.g, pMessage->m_Color.b); } else { str_copy(aAnsi, "\x1b[39m", sizeof(aAnsi)); } aio_write_unlocked(m_pAio, aAnsi, str_length(aAnsi)); } aio_write_unlocked(m_pAio, pMessage->m_aLine, pMessage->m_LineLength); aio_write_newline_unlocked(m_pAio); aio_unlock(m_pAio); } ~CLoggerAsync() { if(m_Close) { aio_close(m_pAio); } aio_wait(m_pAio); aio_free(m_pAio); } void GlobalFinish() override { if(m_Close) { aio_close(m_pAio); } aio_wait(m_pAio); } }; std::unique_ptr log_logger_file(IOHANDLE logfile) { return std::unique_ptr(new CLoggerAsync(logfile, false, true)); } #if defined(CONF_FAMILY_WINDOWS) static int color_hsv_to_windows_console_color(const ColorHSVA &Hsv) { int h = Hsv.h * 255.0f; int s = Hsv.s * 255.0f; int v = Hsv.v * 255.0f; if(s >= 0 && s <= 10) { if(v <= 150) return 8; return 15; } else if(h >= 0 && h < 15) return 12; else if(h >= 15 && h < 30) return 6; else if(h >= 30 && h < 60) return 14; else if(h >= 60 && h < 110) return 10; else if(h >= 110 && h < 140) return 11; else if(h >= 140 && h < 170) return 9; else if(h >= 170 && h < 195) return 5; else if(h >= 195 && h < 240) return 13; else if(h >= 240) return 12; else return 15; } class CWindowsConsoleLogger : public ILogger { HANDLE m_pConsole; public: CWindowsConsoleLogger(HANDLE pConsole) : m_pConsole(pConsole) { } void Log(const CLogMessage *pMessage) override { wchar_t *pWide = (wchar_t *)malloc((pMessage->m_LineLength + 2) * sizeof(*pWide)); const char *p = pMessage->m_aLine; int WLen = 0; mem_zero(pWide, (pMessage->m_LineLength + 2) * sizeof(*pWide)); for(int Codepoint = 0; (Codepoint = str_utf8_decode(&p)); WLen++) { char aU16[4] = {0}; if(Codepoint < 0) { free(pWide); return; } if(str_utf16le_encode(aU16, Codepoint) != 2) { free(pWide); return; } mem_copy(&pWide[WLen], aU16, 2); } pWide[WLen++] = '\r'; pWide[WLen++] = '\n'; int Color = 15; if(pMessage->m_HaveColor) { ColorRGBA Rgba(1.0, 1.0, 1.0, 1.0); Rgba.r = pMessage->m_Color.r / 255.0; Rgba.g = pMessage->m_Color.g / 255.0; Rgba.b = pMessage->m_Color.b / 255.0; Color = color_hsv_to_windows_console_color(color_cast(Rgba)); } SetConsoleTextAttribute(m_pConsole, Color); WriteConsoleW(m_pConsole, pWide, WLen, NULL, NULL); free(pWide); } }; class CWindowsFileLogger : public ILogger { HANDLE m_pFile; public: CWindowsFileLogger(HANDLE pFile) : m_pFile(pFile) { } void Log(const CLogMessage *pMessage) override { WriteFile(m_pFile, pMessage->m_aLine, pMessage->m_LineLength, NULL, NULL); WriteFile(m_pFile, "\r\n", 2, NULL, NULL); } }; #endif std::unique_ptr log_logger_stdout() { #if !defined(CONF_FAMILY_WINDOWS) // TODO: Only enable true color when COLORTERM contains "truecolor". // https://github.com/termstandard/colors/tree/65bf0cd1ece7c15fa33a17c17528b02c99f1ae0b#checking-for-colorterm return std::unique_ptr(new CLoggerAsync(io_stdout(), getenv("NO_COLOR") == nullptr, false)); #else if(GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)) == FILE_TYPE_UNKNOWN) AttachConsole(ATTACH_PARENT_PROCESS); HANDLE pOutput = GetStdHandle(STD_OUTPUT_HANDLE); switch(GetFileType(pOutput)) { case FILE_TYPE_CHAR: return std::unique_ptr(new CWindowsConsoleLogger(pOutput)); case FILE_TYPE_PIPE: // fall through, writing to pipe works the same as writing to a file case FILE_TYPE_DISK: return std::unique_ptr(new CWindowsFileLogger(pOutput)); default: return std::unique_ptr(new CLoggerAsync(io_stdout(), false, false)); } #endif } #if defined(CONF_FAMILY_WINDOWS) class CLoggerWindowsDebugger : public ILogger { public: void Log(const CLogMessage *pMessage) override { WCHAR aWBuffer[4096]; MultiByteToWideChar(CP_UTF8, 0, pMessage->m_aLine, -1, aWBuffer, sizeof(aWBuffer) / sizeof(WCHAR)); OutputDebugStringW(aWBuffer); } }; std::unique_ptr log_logger_windows_debugger() { return std::unique_ptr(new CLoggerWindowsDebugger()); } #else std::unique_ptr log_logger_windows_debugger() { dbg_assert(0, "Windows Debug logger on non-Windows"); return nullptr; } #endif void CFutureLogger::Set(std::unique_ptr &&pLogger) { ILogger *null = nullptr; m_PendingLock.lock(); ILogger *pLoggerRaw = pLogger.release(); if(!m_pLogger.compare_exchange_strong(null, pLoggerRaw, std::memory_order_acq_rel)) { dbg_assert(false, "future logger has already been set and can only be set once"); } for(const auto &Pending : m_aPending) { pLoggerRaw->Log(&Pending); } m_aPending.clear(); m_aPending.shrink_to_fit(); m_PendingLock.unlock(); } void CFutureLogger::Log(const CLogMessage *pMessage) { ILogger *pLogger = m_pLogger.load(std::memory_order_acquire); if(pLogger) { pLogger->Log(pMessage); return; } m_PendingLock.lock(); m_aPending.push_back(*pMessage); m_PendingLock.unlock(); } void CFutureLogger::GlobalFinish() { ILogger *pLogger = m_pLogger.load(std::memory_order_acquire); if(pLogger) { pLogger->GlobalFinish(); } }