ddnet/src/base/log.cpp
heinrich5991 fa4bcd5ec0 Unify logging infrastructure between IConsole and dbg_msg
This makes the "black console window" less important on Windows (or
anywhere else, for that matter), lets you see logs from other threads in
the f1 console, and removes the distinction between `IConsole::Print`
and `dbg_msg`.
2022-04-29 15:21:26 +02:00

419 lines
9.5 KiB
C++

#include "logger.h"
#include "color.h"
#include "system.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>
#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)
{
// 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::unique_ptr<ILogger>(new 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::unique_ptr<ILogger>(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<ILogger> log_logger_file(IOHANDLE logfile)
{
return std::unique_ptr<ILogger>(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
{
public:
void Log(const CLogMessage *pMessage) override
{
wchar_t *pWide = (wchar_t *)malloc((pMessage->m_LineLength + 1) * sizeof(*pWide));
const char *p = pMessage->m_aLine;
int WLen = 0;
mem_zero(pWide, pMessage->m_LineLength * 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] = '\n';
HANDLE pConsole = GetStdHandle(STD_OUTPUT_HANDLE);
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<ColorHSVA>(Rgba));
}
SetConsoleTextAttribute(pConsole, Color);
WriteConsoleW(pConsole, pWide, WLen + 1, NULL, NULL);
free(pWide);
}
};
#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
return std::unique_ptr<ILogger>(new CLoggerAsync(io_stdout(), getenv("NO_COLOR") == nullptr, false));
#else
return std::unique_ptr<ILogger>(new CWindowsConsoleLogger());
#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::unique_ptr<ILogger>(new 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();
}
}