mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-14 20:18:19 +00:00
fa4bcd5ec0
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`.
419 lines
9.5 KiB
C++
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();
|
|
}
|
|
}
|