ddnet/src/base/log.cpp

493 lines
12 KiB
C++
Raw Normal View History

#include "logger.h"
#include "color.h"
#include "system.h"
#include <engine/shared/config.h>
#include <atomic>
#include <cstdio>
#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 <windows.h>
#else
#include <unistd.h>
#endif
extern "C" {
std::atomic<ILogger *> 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<ILogger> 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)
{
if(level > g_Config.m_Loglevel)
return;
// 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<ILogger> log_logger_android()
{
return std::make_unique<CLoggerAndroid>();
}
#else
std::unique_ptr<ILogger> log_logger_android()
{
dbg_assert(0, "Android logger on non-Android");
return nullptr;
}
#endif
class CLoggerCollection : public ILogger
{
std::vector<std::shared_ptr<ILogger>> m_apLoggers;
public:
CLoggerCollection(std::vector<std::shared_ptr<ILogger>> &&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<ILogger> log_logger_collection(std::vector<std::shared_ptr<ILogger>> &&loggers)
{
return std::make_unique<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);
aio_write_unlocked(m_pAio, aAnsi, str_length(aAnsi));
}
}
aio_write_unlocked(m_pAio, pMessage->m_aLine, pMessage->m_LineLength);
if(m_AnsiTruecolor && pMessage->m_HaveColor)
{
const char aResetColor[] = "\x1b[0m";
aio_write_unlocked(m_pAio, aResetColor, sizeof(aResetColor)); // reset
}
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<ILogger> log_logger_file(IOHANDLE logfile)
{
return std::make_unique<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 FOREGROUND_INTENSITY;
return FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;
}
if(h < 0)
return FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;
else if(h < 15)
return FOREGROUND_RED | FOREGROUND_INTENSITY;
else if(h < 30)
return FOREGROUND_GREEN | FOREGROUND_RED;
else if(h < 60)
return FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;
else if(h < 110)
return FOREGROUND_GREEN | FOREGROUND_INTENSITY;
else if(h < 140)
return FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY;
else if(h < 170)
return FOREGROUND_BLUE | FOREGROUND_INTENSITY;
else if(h < 195)
return FOREGROUND_BLUE | FOREGROUND_RED;
else if(h < 240)
return FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_INTENSITY;
else
return FOREGROUND_RED | FOREGROUND_INTENSITY;
}
class CWindowsConsoleLogger : public ILogger
{
HANDLE m_pConsole;
int m_BackgroundColor;
int m_ForegroundColor;
2022-05-31 17:21:40 +00:00
std::mutex m_OutputLock;
bool m_Finished = false;
public:
CWindowsConsoleLogger(HANDLE pConsole) :
m_pConsole(pConsole)
{
CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo;
if(GetConsoleScreenBufferInfo(pConsole, &ConsoleInfo))
{
m_BackgroundColor = ConsoleInfo.wAttributes & (BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY);
m_ForegroundColor = ConsoleInfo.wAttributes & (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY);
}
else
{
m_BackgroundColor = 0;
m_ForegroundColor = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;
}
}
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 = m_BackgroundColor;
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<ColorHSVA>(Rgba));
}
else
Color |= FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;
2022-05-31 17:21:40 +00:00
m_OutputLock.lock();
if(!m_Finished)
{
SetConsoleTextAttribute(m_pConsole, Color);
WriteConsoleW(m_pConsole, pWide, WLen, NULL, NULL);
}
m_OutputLock.unlock();
free(pWide);
}
void GlobalFinish() override
{
// Restore original color
2022-05-31 17:21:40 +00:00
m_OutputLock.lock();
SetConsoleTextAttribute(m_pConsole, m_BackgroundColor | m_ForegroundColor);
2022-05-31 17:21:40 +00:00
m_Finished = true;
m_OutputLock.unlock();
}
};
class CWindowsFileLogger : public ILogger
{
HANDLE m_pFile;
2022-05-31 17:21:40 +00:00
std::mutex m_OutputLock;
public:
CWindowsFileLogger(HANDLE pFile) :
m_pFile(pFile)
{
}
void Log(const CLogMessage *pMessage) override
{
2022-05-31 17:21:40 +00:00
m_OutputLock.lock();
DWORD Written; // we don't care about the value, but Windows 7 crashes if we pass NULL
WriteFile(m_pFile, pMessage->m_aLine, pMessage->m_LineLength, &Written, NULL);
WriteFile(m_pFile, "\r\n", 2, &Written, NULL);
2022-05-31 17:21:40 +00:00
m_OutputLock.unlock();
}
};
#endif
std::unique_ptr<ILogger> 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
const bool colors = getenv("NO_COLOR") == nullptr && isatty(STDOUT_FILENO);
return std::make_unique<CLoggerAsync>(io_stdout(), colors, false);
#else
2022-05-19 19:24:46 +00:00
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::make_unique<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::make_unique<CWindowsFileLogger>(pOutput);
default: return std::make_unique<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<ILogger> log_logger_windows_debugger()
{
return std::make_unique<CLoggerWindowsDebugger>();
}
#else
std::unique_ptr<ILogger> log_logger_windows_debugger()
{
dbg_assert(0, "Windows Debug logger on non-Windows");
return nullptr;
}
#endif
void CFutureLogger::Set(std::unique_ptr<ILogger> &&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();
}
}