From fa4bcd5ec05c663e834a609eaf931df35cf46538 Mon Sep 17 00:00:00 2001 From: heinrich5991 Date: Sat, 23 Apr 2022 01:04:48 +0200 Subject: [PATCH] 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`. --- CMakeLists.txt | 3 + src/base/log.cpp | 418 +++++++++++++++++++++++++ src/base/log.h | 94 ++++++ src/base/logger.h | 237 ++++++++++++++ src/base/system.cpp | 271 +--------------- src/base/system.h | 22 +- src/engine/client/client.cpp | 35 ++- src/engine/console.h | 5 +- src/engine/engine.h | 7 +- src/engine/server.h | 1 - src/engine/server/server.cpp | 167 ++++++++-- src/engine/server/server.h | 12 +- src/engine/shared/assertion_logger.cpp | 55 +++- src/engine/shared/assertion_logger.h | 33 +- src/engine/shared/console.cpp | 64 ++-- src/engine/shared/console.h | 10 - src/engine/shared/econ.cpp | 19 -- src/engine/shared/econ.h | 1 - src/engine/shared/engine.cpp | 28 +- src/game/client/component.cpp | 1 + src/game/client/component.h | 1 + src/game/client/components/console.cpp | 91 ++++-- src/game/client/components/console.h | 13 +- src/game/server/gamecontext.cpp | 84 ++--- src/game/server/gamecontext.h | 2 - src/mastersrv/main_mastersrv.cpp | 3 +- src/test/test.cpp | 2 + src/tools/config_common.h | 3 +- src/tools/crapnet.cpp | 3 +- src/tools/dilate.cpp | 3 +- src/tools/dummy_map.cpp | 3 +- src/tools/map_convert_07.cpp | 3 +- src/tools/map_diff.cpp | 11 +- src/tools/map_extract.cpp | 3 +- src/tools/map_optimize.cpp | 3 +- src/tools/map_replace_image.cpp | 3 +- src/tools/unicode_confusables.cpp | 3 +- src/tools/uuid.cpp | 3 +- 38 files changed, 1180 insertions(+), 540 deletions(-) create mode 100644 src/base/log.cpp create mode 100644 src/base/log.h create mode 100644 src/base/logger.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ff81fe2c7..da8d74215 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1594,6 +1594,9 @@ set_src(BASE GLOB_RECURSE src/base hash_ctxt.h hash_libtomcrypt.cpp hash_openssl.cpp + log.cpp + log.h + logger.h math.h system.cpp system.h diff --git a/src/base/log.cpp b/src/base/log.cpp new file mode 100644 index 000000000..d71f43c2e --- /dev/null +++ b/src/base/log.cpp @@ -0,0 +1,418 @@ +#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 +{ +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(Rgba)); + } + SetConsoleTextAttribute(pConsole, Color); + WriteConsoleW(pConsole, pWide, WLen + 1, NULL, NULL); + free(pWide); + } +}; +#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 + return std::unique_ptr(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 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(); + } +} diff --git a/src/base/log.h b/src/base/log.h new file mode 100644 index 000000000..2a1cc7e86 --- /dev/null +++ b/src/base/log.h @@ -0,0 +1,94 @@ +#ifndef BASE_LOG_H +#define BASE_LOG_H + +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +#ifdef __GNUC__ +#define GNUC_ATTRIBUTE(x) __attribute__(x) +#else +#define GNUC_ATTRIBUTE(x) +#endif + +enum LEVEL : char +{ + LEVEL_ERROR, + LEVEL_WARN, + LEVEL_INFO, + LEVEL_DEBUG, + LEVEL_TRACE, +}; + +struct LOG_COLOR +{ + uint8_t r; + uint8_t g; + uint8_t b; +}; + +/** + * @defgroup Log + * + * Methods for outputting log messages and way of handling them. + */ + +/** + * @ingroup Log + * + * Prints a log message. + * + * @param level Severity of the log message. + * @param sys A string that describes what system the message belongs to. + * @param fmt A printf styled format string. + */ +void log_log(LEVEL level, const char *sys, const char *fmt, ...) + GNUC_ATTRIBUTE((format(printf, 3, 4))); + +/** + * @ingroup Log + * + * Prints a log message with a given color. + * + * @param level Severity of the log message. + * @param color Requested color for the log message output. + * @param sys A string that describes what system the message belongs to. + * @param fmt A printf styled format string. + */ +void log_log_color(LEVEL level, LOG_COLOR color, const char *sys, const char *fmt, ...) + GNUC_ATTRIBUTE((format(printf, 4, 5))); + +/** + * @ingroup Log + * + * Same as `log_log`, but takes a `va_list` instead. + * + * @param level Severity of the log message. + * @param sys A string that describes what system the message belongs to. + * @param fmt A printf styled format string. + * @param args The variable argument list. + */ +void log_log_v(LEVEL level, const char *sys, const char *fmt, va_list args) + GNUC_ATTRIBUTE((format(printf, 3, 0))); + +/** + * @ingroup Log + * + * Same as `log_log_color`, but takes a `va_list` instead. + * + * @param level Severity of the log message. + * @param color Requested color for the log message output. + * @param sys A string that describes what system the message belongs to. + * @param fmt A printf styled format string. + * @param args The variable argument list. + */ +void log_log_color_v(LEVEL level, LOG_COLOR color, const char *sys, const char *fmt, va_list args) + GNUC_ATTRIBUTE((format(printf, 4, 0))); + +#if defined(__cplusplus) +} +#endif +#endif // BASE_LOG_H diff --git a/src/base/logger.h b/src/base/logger.h new file mode 100644 index 000000000..2fe96897d --- /dev/null +++ b/src/base/logger.h @@ -0,0 +1,237 @@ +#ifndef BASE_LOGGER_H +#define BASE_LOGGER_H + +#include "log.h" +#include +#include +#include +#include + +extern "C" { + +typedef struct IOINTERNAL *IOHANDLE; + +/** + * @ingroup Log + * + * Metadata and actual content of a log message. + */ +class CLogMessage +{ +public: + /** + * Severity + */ + LEVEL m_Level; + bool m_HaveColor; + /** + * The requested color of the log message. Only useful if `m_HaveColor` + * is set. + */ + LOG_COLOR m_Color; + char m_aTimestamp[80]; + char m_aSystem[32]; + /** + * The actual log message including the timestamp and the system. + */ + char m_aLine[4096]; + int m_TimestampLength; + int m_SystemLength; + /** + * Length of the log message including timestamp and the system. + */ + int m_LineLength; + int m_LineMessageOffset; + + /** + * The actual log message excluding timestamp and the system. + */ + const char *Message() const + { + return m_aLine + m_LineMessageOffset; + } +}; + +class ILogger +{ +public: + virtual ~ILogger() {} + + /** + * Send the specified message to the logging backend. + * + * @param pMessage Struct describing the log message. + */ + virtual void Log(const CLogMessage *pMessage) = 0; + /** + * Flushes output buffers and shuts down. + * Global loggers cannot be destroyed because they might be accessed + * from multiple threads concurrently. + * + * This function is called on the global logger by + * `log_global_logger_finish` when the program is about to shut down + * and loggers are supposed to finish writing the log messages they + * have received so far. + * + * The destructor of this `ILogger` instance will not be called if this + * function is called. + * + * @see log_global_logger_finish + */ + virtual void GlobalFinish() {} +}; + +/** + * @ingroup Log + * + * Registers a logger instance as the default logger for all current and future + * threads. It will only be used if no thread-local logger is set via + * `log_set_scope_logger`. + * + * This function can only be called once. The passed logger instance will never + * be freed. + * + * @param logger The global logger default. + */ +void log_set_global_logger(ILogger *logger); + +/** + * @ingroup Log + * + * Registers a sane default as the default logger for all current and future + * threads. + * + * This is logging to stdout on most platforms and to the system log on + * Android. + * + * @see log_set_global_logger + */ +void log_set_global_logger_default(); + +/** + * @ingroup Log + * + * Notify global loggers of impending abnormal exit. + * + * This function is automatically called on normal exit. It notifies the global + * logger of the impending shutdown via `GlobalFinish`, the logger is supposed + * to flush its buffers and shut down. + * + * Don't call this except right before an abnormal exit. + */ +void log_global_logger_finish(); + +/** + * @ingroup Log + * + * Get the logger active in the current scope. This might be the global default + * logger or some other logger set via `log_set_scope_logger`. + */ +ILogger *log_get_scope_logger(); + +/** + * @ingroup Log + * + * Set the logger for the current thread. The logger isn't managed by the + * logging system, it still needs to be kept alive or freed by the caller. + * + * Consider using `CLogScope` if you only want to set the logger temporarily. + * + * @see CLogScope + */ +void log_set_scope_logger(ILogger *logger); +} + +/** + * @ingroup Log + * + * Logger for sending logs to the Android system log. + * + * Should only be called when targeting the Android platform. + */ +std::unique_ptr log_logger_android(); + +/** + * @ingroup Log + * + * Logger combining a vector of other loggers. + */ +std::unique_ptr log_logger_collection(std::vector> &&loggers); + +/** + * @ingroup Log + * + * Logger for writing logs to the given file. + * + * @param file File to write to, must be opened for writing. + */ +std::unique_ptr log_logger_file(IOHANDLE file); + +/** + * @ingroup Log + * + * Logger for writing logs to the standard output (stdout). + */ +std::unique_ptr log_logger_stdout(); + +/** + * @ingroup Log + * + * Logger for sending logs to the debugger on Windows via `OutputDebugStringW`. + * + * Should only be called when targeting the Windows platform. + */ +std::unique_ptr log_logger_windows_debugger(); + +/** + * @ingroup Log + * + * Logger that collects log messages in memory until it is replaced by another + * logger. + * + * Useful when you want to set a global logger without all logging targets + * being configured. + */ +class CFutureLogger : public ILogger +{ +private: + std::atomic m_pLogger; + std::vector m_aPending; + std::mutex m_PendingLock; + +public: + /** + * Replace the `CFutureLogger` instance with the given logger. It'll + * receive all log messages sent to the `CFutureLogger` so far. + */ + void Set(std::unique_ptr &&pLogger); + void Log(const CLogMessage *pMessage) override; + void GlobalFinish() override; +}; + +/** + * @ingroup Log + * + * RAII guard for temporarily changing the logger via `log_set_scope_logger`. + * + * @see log_set_scope_logger + */ +class CLogScope +{ + ILogger *old_scope_logger; + ILogger *new_scope_logger; + +public: + CLogScope(ILogger *logger) : + old_scope_logger(log_get_scope_logger()), + new_scope_logger(logger) + { + log_set_scope_logger(new_scope_logger); + } + ~CLogScope() + { + //dbg_assert(log_get_scope_logger() == new_scope_logger, "loggers weren't properly scoped"); + log_set_scope_logger(old_scope_logger); + } +}; +#endif // BASE_LOGGER_H diff --git a/src/base/system.cpp b/src/base/system.cpp index 2bf37d84d..510a89c84 100644 --- a/src/base/system.cpp +++ b/src/base/system.cpp @@ -1,6 +1,7 @@ /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #include // std::size +#include #include #include #include @@ -10,6 +11,9 @@ #include #include "system.h" + +#include "logger.h" + #if !defined(CONF_PLATFORM_MACOS) #include #endif @@ -133,22 +137,6 @@ IOHANDLE io_current_exe() #endif } -struct DBG_LOGGER_DATA -{ - DBG_LOGGER logger; - DBG_LOGGER_FINISH finish; - DBG_LOGGER_ASSERTION on_assert = nullptr; - void *user; -}; - -static DBG_LOGGER_DATA loggers[16]; -static int has_stdout_logger = 0; -static int num_loggers = 0; - -#ifndef CONF_FAMILY_WINDOWS -static DBG_LOGGER_DATA stdout_nonewline_logger; -#endif - static NETSTATS network_stats = {0}; #define VLEN 128 @@ -183,21 +171,20 @@ static NETSOCKET_INTERNAL invalid_socket = {NETTYPE_INVALID, -1, -1, -1}; #define AF_WEBSOCKET_INET (0xee) -static void dbg_assert_notify_loggers() +std::atomic_bool dbg_assert_failing = false; + +bool dbg_assert_has_failed() { - for(int i = 0; i < num_loggers; i++) - { - if(loggers[i].on_assert) - loggers[i].on_assert(loggers[i].user); - } + return dbg_assert_failing.load(std::memory_order_acquire); } void dbg_assert_imp(const char *filename, int line, int test, const char *msg) { if(!test) { + dbg_assert_failing.store(true, std::memory_order_release); dbg_msg("assert", "%s(%d): %s", filename, line, msg); - dbg_assert_notify_loggers(); + log_global_logger_finish(); dbg_break(); } } @@ -214,177 +201,11 @@ void dbg_break() void dbg_msg(const char *sys, const char *fmt, ...) { va_list args; - char *msg; - int len; - - char str[1024 * 4]; - int i; - - char timestr[80]; - str_timestamp_format(timestr, sizeof(timestr), FORMAT_SPACE); - - str_format(str, sizeof(str), "[%s][%s]: ", timestr, sys); - - len = str_length(str); - msg = (char *)str + len; - va_start(args, fmt); -#if defined(CONF_FAMILY_WINDOWS) - _vsnprintf(msg, sizeof(str) - len, fmt, args); -#elif defined(CONF_PLATFORM_ANDROID) - __android_log_vprint(ANDROID_LOG_DEBUG, sys, fmt, args); -#else - vsnprintf(msg, sizeof(str) - len, fmt, args); -#endif - + log_log_v(LEVEL_INFO, sys, fmt, args); va_end(args); - - for(i = 0; i < num_loggers; i++) - loggers[i].logger(str, loggers[i].user); } -#if defined(CONF_FAMILY_WINDOWS) -static void logger_win_debugger(const char *line, void *user) -{ - (void)user; - WCHAR wBuffer[512]; - MultiByteToWideChar(CP_UTF8, 0, line, -1, wBuffer, std::size(wBuffer)); - OutputDebugStringW(wBuffer); - OutputDebugStringW(L"\n"); -} -#endif - -static void logger_file(const char *line, void *user) -{ - ASYNCIO *logfile = (ASYNCIO *)user; - aio_lock(logfile); - aio_write_unlocked(logfile, line, str_length(line)); - aio_write_newline_unlocked(logfile); - aio_unlock(logfile); -} - -#if !defined(CONF_FAMILY_WINDOWS) -static void logger_file_no_newline(const char *line, void *user) -{ - ASYNCIO *logfile = (ASYNCIO *)user; - aio_lock(logfile); - aio_write_unlocked(logfile, line, str_length(line)); - aio_unlock(logfile); -} -#else -static void logger_stdout_sync(const char *line, void *user) -{ - size_t length = str_length(line); - wchar_t *wide = (wchar_t *)malloc(length * sizeof(*wide)); - const char *p = line; - int wlen = 0; - HANDLE console; - - (void)user; - mem_zero(wide, length * sizeof *wide); - - for(int codepoint = 0; (codepoint = str_utf8_decode(&p)); wlen++) - { - char u16[4] = {0}; - - if(codepoint < 0) - { - free(wide); - return; - } - - if(str_utf16le_encode(u16, codepoint) != 2) - { - free(wide); - return; - } - - mem_copy(&wide[wlen], u16, 2); - } - - console = GetStdHandle(STD_OUTPUT_HANDLE); - WriteConsoleW(console, wide, wlen, NULL, NULL); - WriteConsoleA(console, "\n", 1, NULL, NULL); - free(wide); -} -#endif - -static void logger_stdout_finish(void *user) -{ - ASYNCIO *logfile = (ASYNCIO *)user; - aio_wait(logfile); - aio_free(logfile); -} - -static void logger_file_finish(void *user) -{ - ASYNCIO *logfile = (ASYNCIO *)user; - aio_close(logfile); - logger_stdout_finish(user); -} - -static void dbg_logger_finish() -{ - int i; - for(i = 0; i < num_loggers; i++) - { - if(loggers[i].finish) - { - loggers[i].finish(loggers[i].user); - } - } -} - -void dbg_logger(DBG_LOGGER logger, DBG_LOGGER_FINISH finish, void *user) -{ - DBG_LOGGER_DATA data; - if(num_loggers == 0) - { - atexit(dbg_logger_finish); - } - data.logger = logger; - data.finish = finish; - data.user = user; - loggers[num_loggers] = data; - num_loggers++; -} - -void dbg_logger_assertion(DBG_LOGGER logger, DBG_LOGGER_FINISH finish, DBG_LOGGER_ASSERTION on_assert, void *user) -{ - dbg_logger(logger, finish, user); - - loggers[num_loggers - 1].on_assert = on_assert; -} - -void dbg_logger_stdout() -{ -#if defined(CONF_FAMILY_WINDOWS) - dbg_logger(logger_stdout_sync, 0, 0); -#else - ASYNCIO *logger_obj = aio_new(io_stdout()); - dbg_logger(logger_file, logger_stdout_finish, logger_obj); - dbg_logger(logger_file_no_newline, 0, logger_obj); - stdout_nonewline_logger = loggers[num_loggers - 1]; - --num_loggers; -#endif - has_stdout_logger = 1; -} - -void dbg_logger_debugger() -{ -#if defined(CONF_FAMILY_WINDOWS) - dbg_logger(logger_win_debugger, 0, 0); -#endif -} - -void dbg_logger_file(const char *filename) -{ - IOHANDLE logfile = io_open(filename, IOFLAG_WRITE); - if(logfile) - dbg_logger(logger_file, logger_file_finish, aio_new(logfile)); - else - dbg_msg("dbg/logger", "failed to open '%s' for logging", filename); -} /* */ void mem_copy(void *dest, const void *source, unsigned size) @@ -4035,76 +3856,6 @@ int secure_rand_below(int below) } } -#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; -} -#endif - -void set_console_msg_color(const void *rgbvoid) -{ - static const char *pNoColor = getenv("NO_COLOR"); - if(pNoColor) - return; - -#if defined(CONF_FAMILY_WINDOWS) - const ColorRGBA *rgb = (const ColorRGBA *)rgbvoid; - int color = 15; - if(rgb) - { - ColorHSVA hsv = color_cast(*rgb); - color = color_hsv_to_windows_console_color(&hsv); - } - HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(console, color); -#elif CONF_PLATFORM_LINUX - const ColorRGBA *rgb = (const ColorRGBA *)rgbvoid; - // set true color terminal escape codes refering - // https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit - int esc_seq = 0x1B; - char buff[32]; - if(rgb == NULL) - // reset foreground color - str_format(buff, sizeof(buff), "%c[39m", esc_seq); - else - // set rgb foreground color - // if not used by a true color terminal it is still converted refering - // https://wiki.archlinux.org/title/Color_output_in_console#True_color_support - str_format(buff, sizeof(buff), "%c[38;2;%d;%d;%dm", esc_seq, (int)uint8_t(rgb->r * 255.0f), (int)uint8_t(rgb->g * 255.0f), (int)uint8_t(rgb->b * 255.0f)); - if(has_stdout_logger) - stdout_nonewline_logger.logger(buff, stdout_nonewline_logger.user); -#endif -} - int os_version_str(char *version, int length) { #if defined(CONF_FAMILY_WINDOWS) diff --git a/src/base/system.h b/src/base/system.h index d17b409a1..af617d7ad 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -65,6 +65,17 @@ void dbg_assert_imp(const char *filename, int line, int test, const char *msg); #define GNUC_ATTRIBUTE(x) #endif +/** + * Checks whether the program is currently shutting down due to a failed + * assert. + * + * @ingroup Debug + * + * @return indication whether the program is currently shutting down due to a + * failed assert. + */ +bool dbg_assert_has_failed(); + /** * Breaks into the debugger. * @@ -1899,17 +1910,6 @@ int open_file(const char *path); void swap_endian(void *data, unsigned elem_size, unsigned num); -typedef void (*DBG_LOGGER)(const char *line, void *user); -typedef void (*DBG_LOGGER_FINISH)(void *user); -void dbg_logger(DBG_LOGGER logger, DBG_LOGGER_FINISH finish, void *user); - -typedef void (*DBG_LOGGER_ASSERTION)(void *user); -void dbg_logger_assertion(DBG_LOGGER logger, DBG_LOGGER_FINISH finish, DBG_LOGGER_ASSERTION on_assert, void *user); - -void dbg_logger_stdout(); -void dbg_logger_debugger(); -void dbg_logger_file(const char *filename); - typedef struct { uint64_t sent_packets; diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 21c1e2489..e54ff019b 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -35,6 +36,7 @@ #include #include +#include #include #include #include @@ -4339,6 +4341,23 @@ int main(int argc, const char **argv) init_exception_handler(); #endif + std::vector> apLoggers; +#if defined(CONF_PLATFORM_ANDROID) + apLoggers.push_back(std::shared_ptr(log_logger_android())); +#else + if(!Silent) + { + apLoggers.push_back(std::shared_ptr(log_logger_stdout())); + } +#endif + std::shared_ptr pFutureFileLogger = std::make_shared(); + apLoggers.push_back(pFutureFileLogger); + std::shared_ptr pFutureConsoleLogger = std::make_shared(); + apLoggers.push_back(pFutureConsoleLogger); + std::shared_ptr pFutureAssertionLogger = std::make_shared(); + apLoggers.push_back(pFutureAssertionLogger); + log_set_global_logger(log_logger_collection(std::move(apLoggers)).release()); + if(secure_random_init() != 0) { RandInitFailed = true; @@ -4352,7 +4371,7 @@ int main(int argc, const char **argv) pClient->RegisterInterfaces(); // create the components - IEngine *pEngine = CreateEngine(GAME_NAME, Silent, 2); + IEngine *pEngine = CreateEngine(GAME_NAME, pFutureConsoleLogger, 2); IConsole *pConsole = CreateConsole(CFGFLAG_CLIENT); IStorage *pStorage = CreateStorage(IStorage::STORAGETYPE_CLIENT, argc, (const char **)argv); IConfigManager *pConfigManager = CreateConfigManager(); @@ -4363,6 +4382,7 @@ int main(int argc, const char **argv) IDiscord *pDiscord = CreateDiscord(); ISteam *pSteam = CreateSteam(); + pFutureAssertionLogger->Set(CreateAssertionLogger(pStorage, GAME_NAME)); #if defined(CONF_EXCEPTION_HANDLING) char aBufPath[IO_MAX_PATH_LENGTH]; char aBufName[IO_MAX_PATH_LENGTH]; @@ -4484,7 +4504,18 @@ int main(int argc, const char **argv) pSteam->ClearConnectAddress(); } - pClient->Engine()->InitLogfile(); + if(g_Config.m_Logfile[0]) + { + IOHANDLE Logfile = io_open(g_Config.m_Logfile, IOFLAG_WRITE); + if(Logfile) + { + pFutureFileLogger->Set(log_logger_file(Logfile)); + } + else + { + dbg_msg("client", "failed to open '%s' for logging", g_Config.m_Logfile); + } + } // run the client dbg_msg("client", "starting..."); diff --git a/src/engine/console.h b/src/engine/console.h index 9a47fcb6b..7c8d863ff 100644 --- a/src/engine/console.h +++ b/src/engine/console.h @@ -9,6 +9,7 @@ static const ColorRGBA gs_ConsoleDefaultColor(1, 1, 1, 1); +enum LEVEL : char; struct CChecksumData; class IConsole : public IInterface @@ -104,8 +105,6 @@ public: virtual void ExecuteLineStroked(int Stroke, const char *pStr, int ClientID = -1, bool InterpretSemicolons = true) = 0; virtual void ExecuteFile(const char *pFilename, int ClientID = -1, bool LogFailure = false, int StorageType = IStorage::TYPE_ALL) = 0; - virtual int RegisterPrintCallback(int OutputLevel, FPrintCallback pfnPrintCallback, void *pUserData) = 0; - virtual void SetPrintOutputLevel(int Index, int OutputLevel) = 0; virtual char *Format(char *pBuf, int Size, const char *pFrom, const char *pStr) = 0; virtual void Print(int Level, const char *pFrom, const char *pStr, ColorRGBA PrintColor = gs_ConsoleDefaultColor) = 0; virtual void SetTeeHistorianCommandCallback(FTeeHistorianCommandCallback pfnCallback, void *pUser) = 0; @@ -115,6 +114,8 @@ public: virtual void ResetServerGameSettings() = 0; + static LEVEL ToLogLevel(int ConsoleLevel); + // DDRace bool m_Cheated; diff --git a/src/engine/engine.h b/src/engine/engine.h index 8d3e38c26..016d2f96d 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -6,6 +6,9 @@ #include "kernel.h" #include +class CFutureLogger; +class ILogger; + class CHostLookup : public IJob { private: @@ -32,12 +35,12 @@ public: virtual ~IEngine() = default; virtual void Init() = 0; - virtual void InitLogfile() = 0; virtual void AddJob(std::shared_ptr pJob) = 0; + virtual void SetAdditionalLogger(std::unique_ptr &&pLogger) = 0; static void RunJobBlocking(IJob *pJob); }; -extern IEngine *CreateEngine(const char *pAppname, bool Silent, int Jobs); +extern IEngine *CreateEngine(const char *pAppname, std::shared_ptr pFutureLogger, int Jobs); extern IEngine *CreateTestEngine(const char *pAppname, int Jobs); #endif diff --git a/src/engine/server.h b/src/engine/server.h index 4268fbab2..54695272b 100644 --- a/src/engine/server.h +++ b/src/engine/server.h @@ -60,7 +60,6 @@ public: virtual int GetClientInfo(int ClientID, CClientInfo *pInfo) const = 0; virtual void SetClientDDNetVersion(int ClientID, int DDNetVersion) = 0; virtual void GetClientAddr(int ClientID, char *pAddrStr, int Size) const = 0; - virtual void RestrictRconOutput(int ClientID) = 0; virtual int SendMsg(CMsgPacker *pMsg, int Flags, int ClientID) = 0; diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 17d473124..7e67766c1 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -5,6 +5,7 @@ #include "server.h" +#include #include #include @@ -16,6 +17,7 @@ #include #include +#include #include #include #include @@ -36,6 +38,7 @@ // DDRace #include +#include #include #include @@ -272,6 +275,77 @@ void CServerBan::ConBanRegionRange(IConsole::IResult *pResult, void *pUser) ConBanRange(pResult, static_cast(pServerBan)); } +class CServerLogger : public ILogger +{ + CServer *m_pServer; + std::mutex m_PendingLock; + std::vector m_aPending; + std::thread::id m_MainThread; + +public: + CServerLogger(CServer *pServer) : + m_pServer(pServer), + m_MainThread(std::this_thread::get_id()) + { + dbg_assert(pServer != nullptr, "server pointer must not be null"); + } + void Log(const CLogMessage *pMessage) override; + // Must be called from the main thread! + void OnServerDeletion(); +}; + +void CServerLogger::Log(const CLogMessage *pMessage) +{ + m_PendingLock.lock(); + if(m_MainThread == std::this_thread::get_id()) + { + if(!m_aPending.empty()) + { + if(m_pServer) + { + for(const auto &Message : m_aPending) + { + m_pServer->SendLogLine(&Message); + } + } + m_aPending.clear(); + } + m_PendingLock.unlock(); + m_pServer->SendLogLine(pMessage); + } + else + { + m_aPending.push_back(*pMessage); + m_PendingLock.unlock(); + } +} + +void CServerLogger::OnServerDeletion() +{ + dbg_assert(m_MainThread == std::this_thread::get_id(), "CServerLogger::OnServerDeletion not called from the main thread"); + m_pServer = nullptr; +} + +// Not thread-safe! +class CRconClientLogger : public ILogger +{ + CServer *m_pServer; + int m_ClientID; + +public: + CRconClientLogger(CServer *pServer, int ClientID) : + m_pServer(pServer), + m_ClientID(ClientID) + { + } + void Log(const CLogMessage *pMessage) override; +}; + +void CRconClientLogger::Log(const CLogMessage *pMessage) +{ + m_pServer->SendRconLogLine(m_ClientID, pMessage); +} + void CServer::CClient::Reset() { // reset input @@ -319,8 +393,6 @@ CServer::CServer() : m_RconClientID = IServer::RCON_CID_SERV; m_RconAuthLevel = AUTHED_ADMIN; - m_RconRestrict = -1; - m_ServerInfoFirstRequest = 0; m_ServerInfoNumRequests = 0; m_ServerInfoNeedsUpdate = false; @@ -539,6 +611,18 @@ int CServer::Init() return 0; } +void CServer::SendLogLine(const CLogMessage *pMessage) +{ + if(pMessage->m_Level <= IConsole::ToLogLevel(g_Config.m_ConsoleOutputLevel)) + { + SendRconLogLine(-1, pMessage); + } + if(pMessage->m_Level <= IConsole::ToLogLevel(g_Config.m_EcOutputLevel)) + { + m_Econ.Send(-1, pMessage->m_aLine); + } +} + void CServer::SetRconCID(int ClientID) { m_RconClientID = ClientID; @@ -1232,15 +1316,9 @@ void CServer::SendRconLine(int ClientID, const char *pLine) SendMsg(&Msg, MSGFLAG_VITAL, ClientID); } -void CServer::SendRconLineAuthed(const char *pLine, void *pUser, ColorRGBA PrintColor) +void CServer::SendRconLogLine(int ClientID, const CLogMessage *pMessage) { - CServer *pThis = (CServer *)pUser; - static int s_ReentryGuard = 0; - - if(s_ReentryGuard) - return; - s_ReentryGuard++; - + const char *pLine = pMessage->m_aLine; const char *pStart = str_find(pLine, "<{"); const char *pEnd = pStart == NULL ? NULL : str_find(pStart + 2, "}>"); const char *pLineWithoutIps; @@ -1267,13 +1345,19 @@ void CServer::SendRconLineAuthed(const char *pLine, void *pUser, ColorRGBA Print pLineWithoutIps = aLineWithoutIps; } - for(int i = 0; i < MAX_CLIENTS; i++) + if(ClientID == -1) { - if(pThis->m_aClients[i].m_State != CClient::STATE_EMPTY && pThis->m_aClients[i].m_Authed >= pThis->m_RconAuthLevel && (pThis->m_RconRestrict == -1 || pThis->m_RconRestrict == i)) - pThis->SendRconLine(i, pThis->m_aClients[i].m_ShowIps ? pLine : pLineWithoutIps); + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(m_aClients[i].m_State != CClient::STATE_EMPTY && m_aClients[i].m_Authed >= AUTHED_ADMIN) + SendRconLine(i, m_aClients[i].m_ShowIps ? pLine : pLineWithoutIps); + } + } + else + { + if(m_aClients[ClientID].m_State != CClient::STATE_EMPTY) + SendRconLine(ClientID, m_aClients[ClientID].m_ShowIps ? pLine : pLineWithoutIps); } - - s_ReentryGuard--; } void CServer::SendRconCmdAdd(const IConsole::CCommandInfo *pCommandInfo, int ClientID) @@ -1587,7 +1671,11 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) m_RconClientID = ClientID; m_RconAuthLevel = m_aClients[ClientID].m_Authed; Console()->SetAccessLevel(m_aClients[ClientID].m_Authed == AUTHED_ADMIN ? IConsole::ACCESS_LEVEL_ADMIN : m_aClients[ClientID].m_Authed == AUTHED_MOD ? IConsole::ACCESS_LEVEL_MOD : m_aClients[ClientID].m_Authed == AUTHED_HELPER ? IConsole::ACCESS_LEVEL_HELPER : IConsole::ACCESS_LEVEL_USER); - Console()->ExecuteLineFlag(pCmd, CFGFLAG_SERVER, ClientID); + { + CRconClientLogger Logger(this, ClientID); + CLogScope Scope(&Logger); + Console()->ExecuteLineFlag(pCmd, CFGFLAG_SERVER, ClientID); + } Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_ADMIN); m_RconClientID = IServer::RCON_CID_SERV; m_RconAuthLevel = AUTHED_ADMIN; @@ -2401,8 +2489,6 @@ int CServer::Run() g_UuidManager.DebugDump(); } - m_PrintCBIndex = Console()->RegisterPrintCallback(Config()->m_ConsoleOutputLevel, SendRconLineAuthed, this); - { int Size = GameServer()->PersistentClientDataSize(); for(auto &Client : m_aClients) @@ -3357,16 +3443,6 @@ void CServer::ConchainCommandAccessUpdate(IConsole::IResult *pResult, void *pUse pfnCallback(pResult, pCallbackUserData); } -void CServer::ConchainConsoleOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) -{ - pfnCallback(pResult, pCallbackUserData); - if(pResult->NumArguments() == 1) - { - CServer *pThis = static_cast(pUserData); - pThis->Console()->SetPrintOutputLevel(pThis->m_PrintCBIndex, pResult->GetInteger(0)); - } -} - void CServer::LogoutClient(int ClientID, const char *pReason) { if(!IsSixup(ClientID)) @@ -3549,7 +3625,6 @@ void CServer::RegisterCommands() Console()->Chain("sv_max_clients_per_ip", ConchainMaxclientsperipUpdate, this); Console()->Chain("access_level", ConchainCommandAccessUpdate, this); - Console()->Chain("console_output_level", ConchainConsoleOutputLevelUpdate, this); Console()->Chain("sv_rcon_password", ConchainRconPasswordChange, this); Console()->Chain("sv_rcon_mod_password", ConchainRconModPasswordChange, this); @@ -3619,6 +3694,23 @@ int main(int argc, const char **argv) } } + std::vector> apLoggers; +#if defined(CONF_PLATFORM_ANDROID) + apLoggers.push_back(std::shared_ptr(log_logger_android())); +#else + if(!Silent) + { + apLoggers.push_back(std::shared_ptr(log_logger_stdout())); + } +#endif + std::shared_ptr pFutureFileLogger = std::make_shared(); + apLoggers.push_back(pFutureFileLogger); + std::shared_ptr pFutureConsoleLogger = std::make_shared(); + apLoggers.push_back(pFutureConsoleLogger); + std::shared_ptr pFutureAssertionLogger = std::make_shared(); + apLoggers.push_back(pFutureAssertionLogger); + log_set_global_logger(log_logger_collection(std::move(apLoggers)).release()); + if(secure_random_init() != 0) { dbg_msg("secure", "could not initialize secure RNG"); @@ -3641,7 +3733,7 @@ int main(int argc, const char **argv) IKernel *pKernel = IKernel::Create(); // create the components - IEngine *pEngine = CreateEngine("DDNet", Silent, 2); + IEngine *pEngine = CreateEngine("DDNet", pFutureConsoleLogger, 2); IEngineMap *pEngineMap = CreateEngineMap(); IGameServer *pGameServer = CreateGameServer(); IConsole *pConsole = CreateConsole(CFGFLAG_SERVER | CFGFLAG_ECON); @@ -3650,6 +3742,7 @@ int main(int argc, const char **argv) IConfigManager *pConfigManager = CreateConfigManager(); IEngineAntibot *pEngineAntibot = CreateEngineAntibot(); + pFutureAssertionLogger->Set(CreateAssertionLogger(pStorage, GAME_NAME)); #if defined(CONF_EXCEPTION_HANDLING) char aBuf[IO_MAX_PATH_LENGTH]; char aBufName[IO_MAX_PATH_LENGTH]; @@ -3713,7 +3806,19 @@ int main(int argc, const char **argv) pConsole->Register("sv_test_cmds", "", CFGFLAG_SERVER, CServer::ConTestingCommands, pConsole, "Turns testing commands aka cheats on/off (setting only works in initial config)"); pConsole->Register("sv_rescue", "", CFGFLAG_SERVER, CServer::ConRescue, pConsole, "Allow /rescue command so players can teleport themselves out of freeze (setting only works in initial config)"); - pEngine->InitLogfile(); + if(g_Config.m_Logfile[0]) + { + IOHANDLE Logfile = io_open(g_Config.m_Logfile, IOFLAG_WRITE); + if(Logfile) + { + pFutureFileLogger->Set(log_logger_file(Logfile)); + } + else + { + dbg_msg("client", "failed to open '%s' for logging", g_Config.m_Logfile); + } + } + pEngine->SetAdditionalLogger(std::unique_ptr(new CServerLogger(pServer))); // run the server dbg_msg("server", "starting..."); diff --git a/src/engine/server/server.h b/src/engine/server/server.h index fb4710da7..95ba8cc67 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -33,6 +33,8 @@ #include "upnp.h" #endif +class CLogMessage; + class CSnapIDPool { enum @@ -88,6 +90,8 @@ public: class CServer : public IServer { + friend class CServerLogger; + class IGameServer *m_pGameServer; class CConfig *m_pConfig; class IConsole *m_pConsole; @@ -251,8 +255,6 @@ public: CRegister m_RegSixup; CAuthManager m_AuthManager; - int m_RconRestrict; - int64_t m_ServerInfoFirstRequest; int m_ServerInfoNumRequests; @@ -285,6 +287,7 @@ public: int Init(); + void SendLogLine(const CLogMessage *pMessage); void SetRconCID(int ClientID); int GetAuthedState(int ClientID) const; const char *GetAuthName(int ClientID) const; @@ -318,7 +321,8 @@ public: void SendMapData(int ClientID, int Chunk); void SendConnectionReady(int ClientID); void SendRconLine(int ClientID, const char *pLine); - static void SendRconLineAuthed(const char *pLine, void *pUser, ColorRGBA PrintColor = {1, 1, 1, 1}); + // Accepts -1 as ClientID to mean "all clients with at least auth level admin" + void SendRconLogLine(int ClientID, const CLogMessage *pMessage); void SendRconCmdAdd(const IConsole::CCommandInfo *pCommandInfo, int ClientID); void SendRconCmdRem(const IConsole::CCommandInfo *pCommandInfo, int ClientID); @@ -403,7 +407,6 @@ public: static void ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConchainMaxclientsperipUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConchainCommandAccessUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); - static void ConchainConsoleOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); void LogoutClient(int ClientID, const char *pReason); void LogoutKey(int Key, const char *pReason); @@ -432,7 +435,6 @@ public: int m_aPrevStates[MAX_CLIENTS]; const char *GetAnnouncementLine(char const *pFileName); unsigned m_AnnouncementLastLine; - void RestrictRconOutput(int ClientID) { m_RconRestrict = ClientID; } virtual int *GetIdMap(int ClientID); diff --git a/src/engine/shared/assertion_logger.cpp b/src/engine/shared/assertion_logger.cpp index 7077bbcc3..947d8271d 100644 --- a/src/engine/shared/assertion_logger.cpp +++ b/src/engine/shared/assertion_logger.cpp @@ -1,27 +1,49 @@ #include "assertion_logger.h" +#include #include +#include +#include + #include -void CAssertionLogger::DbgLogger(const char *pLine, void *pUser) +class CAssertionLogger : public ILogger { - ((CAssertionLogger *)pUser)->DbgLogger(pLine); -} + void Dump(); -void CAssertionLogger::DbgLoggerAssertion(void *pUser) -{ - ((CAssertionLogger *)pUser)->DbgLoggerAssertion(); -} + struct SDebugMessageItem + { + char m_aMessage[1024]; + }; -void CAssertionLogger::DbgLogger(const char *pLine) + std::mutex m_DbgMessageMutex; + CStaticRingBuffer m_DbgMessages; + + char m_aAssertLogPath[IO_MAX_PATH_LENGTH]; + char m_aGameName[256]; + +public: + CAssertionLogger(const char *pAssertLogPath, const char *pGameName); + void Log(const CLogMessage *pMessage) override; + void GlobalFinish() override; +}; + +void CAssertionLogger::Log(const CLogMessage *pMessage) { std::unique_lock Lock(m_DbgMessageMutex); - SDebugMessageItem *pMsgItem = (SDebugMessageItem *)m_DbgMessages.Allocate(sizeof(SDebugMessageItem)); - str_copy(pMsgItem->m_aMessage, pLine, std::size(pMsgItem->m_aMessage)); + str_copy(pMsgItem->m_aMessage, pMessage->m_aLine, std::size(pMsgItem->m_aMessage)); } -void CAssertionLogger::DbgLoggerAssertion() +void CAssertionLogger::GlobalFinish() +{ + if(dbg_assert_has_failed()) + { + Dump(); + } +} + +void CAssertionLogger::Dump() { char aAssertLogFile[IO_MAX_PATH_LENGTH]; char aDate[64]; @@ -45,10 +67,15 @@ void CAssertionLogger::DbgLoggerAssertion() } } -void CAssertionLogger::Init(const char *pAssertLogPath, const char *pGameName) +CAssertionLogger::CAssertionLogger(const char *pAssertLogPath, const char *pGameName) { str_copy(m_aAssertLogPath, pAssertLogPath, std::size(m_aAssertLogPath)); str_copy(m_aGameName, pGameName, std::size(m_aGameName)); - - dbg_logger_assertion(DbgLogger, nullptr, DbgLoggerAssertion, this); +} + +std::unique_ptr CreateAssertionLogger(IStorage *pStorage, const char *pGameName) +{ + char aAssertLogPath[IO_MAX_PATH_LENGTH]; + pStorage->GetCompletePath(IStorage::TYPE_SAVE, "dumps/", aAssertLogPath, sizeof(aAssertLogPath)); + return std::unique_ptr(new CAssertionLogger(aAssertLogPath, pGameName)); } diff --git a/src/engine/shared/assertion_logger.h b/src/engine/shared/assertion_logger.h index 72c1f68dd..45c11217e 100644 --- a/src/engine/shared/assertion_logger.h +++ b/src/engine/shared/assertion_logger.h @@ -1,36 +1,11 @@ #ifndef ENGINE_SHARED_ASSERTION_LOGGER_H #define ENGINE_SHARED_ASSERTION_LOGGER_H -#include +#include -#include -#include +class ILogger; +class IStorage; -#include - -class CAssertionLogger -{ - static void DbgLogger(const char *pLine, void *pUser); - static void DbgLoggerAssertion(void *pUser); - - static constexpr size_t s_MaxDbgMessageCount = 64; - - void DbgLogger(const char *pLine); - void DbgLoggerAssertion(); - - struct SDebugMessageItem - { - char m_aMessage[1024]; - }; - - std::mutex m_DbgMessageMutex; - CStaticRingBuffer m_DbgMessages; - - char m_aAssertLogPath[IO_MAX_PATH_LENGTH]; - char m_aGameName[256]; - -public: - void Init(const char *pAssertLogPath, const char *pGameName); -}; +std::unique_ptr CreateAssertionLogger(IStorage *pStorage, const char *pGameName); #endif diff --git a/src/engine/shared/console.cpp b/src/engine/shared/console.cpp index 22fee3915..8987587e1 100644 --- a/src/engine/shared/console.cpp +++ b/src/engine/shared/console.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -290,23 +291,6 @@ char CConsole::NextParam(const char *&pFormat) return *pFormat; } -int CConsole::RegisterPrintCallback(int OutputLevel, FPrintCallback pfnPrintCallback, void *pUserData) -{ - if(m_NumPrintCB == MAX_PRINT_CB) - return -1; - - m_aPrintCB[m_NumPrintCB].m_OutputLevel = clamp(OutputLevel, (int)(OUTPUT_LEVEL_STANDARD), (int)(OUTPUT_LEVEL_DEBUG)); - m_aPrintCB[m_NumPrintCB].m_pfnPrintCallback = pfnPrintCallback; - m_aPrintCB[m_NumPrintCB].m_pPrintCallbackUserdata = pUserData; - return m_NumPrintCB++; -} - -void CConsole::SetPrintOutputLevel(int Index, int OutputLevel) -{ - if(Index >= 0 && Index < MAX_PRINT_CB) - m_aPrintCB[Index].m_OutputLevel = clamp(OutputLevel, (int)(OUTPUT_LEVEL_STANDARD), (int)(OUTPUT_LEVEL_DEBUG)); -} - char *CConsole::Format(char *pBuf, int Size, const char *pFrom, const char *pStr) { char aTimeBuf[80]; @@ -316,26 +300,40 @@ char *CConsole::Format(char *pBuf, int Size, const char *pFrom, const char *pStr return pBuf; } +LEVEL IConsole::ToLogLevel(int Level) +{ + switch(Level) + { + case IConsole::OUTPUT_LEVEL_STANDARD: + return LEVEL_INFO; + case IConsole::OUTPUT_LEVEL_ADDINFO: + return LEVEL_DEBUG; + case IConsole::OUTPUT_LEVEL_DEBUG: + return LEVEL_TRACE; + } + dbg_assert(0, "invalid log level"); + return LEVEL_INFO; +} + +LOG_COLOR ColorToLogColor(ColorRGBA Color) +{ + return LOG_COLOR{ + (uint8_t)(Color.r * 255.0), + (uint8_t)(Color.g * 255.0), + (uint8_t)(Color.b * 255.0)}; +} + void CConsole::Print(int Level, const char *pFrom, const char *pStr, ColorRGBA PrintColor) { - if(g_Config.m_ConsoleEnableColors) + LEVEL LogLevel = IConsole::ToLogLevel(Level); + // if the color is pure white, use default terminal color + if(mem_comp(&PrintColor, &gs_ConsoleDefaultColor, sizeof(ColorRGBA)) != 0) { - // if the color is pure white, use default terminal color - if(mem_comp(&PrintColor, &gs_ConsoleDefaultColor, sizeof(ColorRGBA)) == 0) - set_console_msg_color(NULL); - else - set_console_msg_color(&PrintColor); + log_log_color(LogLevel, ColorToLogColor(PrintColor), pFrom, "%s", pStr); } - dbg_msg(pFrom, "%s", pStr); - set_console_msg_color(NULL); - char aBuf[1024]; - Format(aBuf, sizeof(aBuf), pFrom, pStr); - for(int i = 0; i < m_NumPrintCB; ++i) + else { - if(Level <= m_aPrintCB[i].m_OutputLevel && m_aPrintCB[i].m_pfnPrintCallback) - { - m_aPrintCB[i].m_pfnPrintCallback(aBuf, m_aPrintCB[i].m_pPrintCallbackUserdata, PrintColor); - } + log_log(LogLevel, pFrom, "%s", pStr); } } @@ -966,8 +964,6 @@ CConsole::CConsole(int FlagMask) m_ExecutionQueue.Reset(); m_pFirstCommand = 0; m_pFirstExec = 0; - mem_zero(m_aPrintCB, sizeof(m_aPrintCB)); - m_NumPrintCB = 0; m_pfnTeeHistorianCommandCallback = 0; m_pTeeHistorianCommandUserdata = 0; diff --git a/src/engine/shared/console.h b/src/engine/shared/console.h index 58d083143..262706596 100644 --- a/src/engine/shared/console.h +++ b/src/engine/shared/console.h @@ -63,14 +63,6 @@ class CConsole : public IConsole void ExecuteLineStroked(int Stroke, const char *pStr, int ClientID = -1, bool InterpretSemicolons = true); - struct - { - int m_OutputLevel; - FPrintCallback m_pfnPrintCallback; - void *m_pPrintCallbackUserdata; - } m_aPrintCB[MAX_PRINT_CB]; - int m_NumPrintCB; - FTeeHistorianCommandCallback m_pfnTeeHistorianCommandCallback; void *m_pTeeHistorianCommandUserdata; @@ -217,8 +209,6 @@ public: virtual void ExecuteLineFlag(const char *pStr, int FlagMask, int ClientID = -1, bool InterpretSemicolons = true); virtual void ExecuteFile(const char *pFilename, int ClientID = -1, bool LogFailure = false, int StorageType = IStorage::TYPE_ALL); - virtual int RegisterPrintCallback(int OutputLevel, FPrintCallback pfnPrintCallback, void *pUserData); - virtual void SetPrintOutputLevel(int Index, int OutputLevel); virtual char *Format(char *pBuf, int Size, const char *pFrom, const char *pStr); virtual void Print(int Level, const char *pFrom, const char *pStr, ColorRGBA PrintColor = gs_ConsoleDefaultColor); virtual void SetTeeHistorianCommandCallback(FTeeHistorianCommandCallback pfnCallback, void *pUser); diff --git a/src/engine/shared/econ.cpp b/src/engine/shared/econ.cpp index f8aaa0fad..b00e6d2ef 100644 --- a/src/engine/shared/econ.cpp +++ b/src/engine/shared/econ.cpp @@ -36,21 +36,6 @@ int CEcon::DelClientCallback(int ClientID, const char *pReason, void *pUser) return 0; } -void CEcon::SendLineCB(const char *pLine, void *pUserData, ColorRGBA PrintColor) -{ - static_cast(pUserData)->Send(-1, pLine); -} - -void CEcon::ConchainEconOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) -{ - pfnCallback(pResult, pCallbackUserData); - if(pResult->NumArguments() == 1) - { - CEcon *pThis = static_cast(pUserData); - pThis->Console()->SetPrintOutputLevel(pThis->m_PrintCBIndex, pResult->GetInteger(0)); - } -} - void CEcon::ConLogout(IConsole::IResult *pResult, void *pUserData) { CEcon *pThis = static_cast(pUserData); @@ -94,10 +79,6 @@ void CEcon::Init(CConfig *pConfig, IConsole *pConsole, CNetBan *pNetBan) char aBuf[128]; str_format(aBuf, sizeof(aBuf), "bound to %s:%d", g_Config.m_EcBindaddr, g_Config.m_EcPort); Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "econ", aBuf); - - Console()->Chain("ec_output_level", ConchainEconOutputLevelUpdate, this); - m_PrintCBIndex = Console()->RegisterPrintCallback(g_Config.m_EcOutputLevel, SendLineCB, this); - Console()->Register("logout", "", CFGFLAG_ECON, ConLogout, this, "Logout of econ"); } else diff --git a/src/engine/shared/econ.h b/src/engine/shared/econ.h index d1a18d13e..8f9a8227f 100644 --- a/src/engine/shared/econ.h +++ b/src/engine/shared/econ.h @@ -39,7 +39,6 @@ class CEcon int m_UserClientID; static void SendLineCB(const char *pLine, void *pUserData, ColorRGBA PrintColor = {1, 1, 1, 1}); - static void ConchainEconOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConLogout(IConsole::IResult *pResult, void *pUserData); static int NewClientCallback(int ClientID, void *pUser); diff --git a/src/engine/shared/engine.cpp b/src/engine/shared/engine.cpp index bcca4c19f..6fff35147 100644 --- a/src/engine/shared/engine.cpp +++ b/src/engine/shared/engine.cpp @@ -1,6 +1,7 @@ /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ +#include #include #include @@ -31,7 +32,7 @@ public: IStorage *m_pStorage; bool m_Logging; - CAssertionLogger m_AssertionLogger; + std::shared_ptr m_pFutureLogger; char m_aAppName[256]; @@ -57,15 +58,12 @@ public: } } - CEngine(bool Test, const char *pAppname, bool Silent, int Jobs) + CEngine(bool Test, const char *pAppname, std::shared_ptr pFutureLogger, int Jobs) : + m_pFutureLogger(std::move(pFutureLogger)) { str_copy(m_aAppName, pAppname, std::size(m_aAppName)); if(!Test) { - if(!Silent) - dbg_logger_stdout(); - dbg_logger_debugger(); - // dbg_msg("engine", "running on %s-%s-%s", CONF_FAMILY_STRING, CONF_PLATFORM_STRING, CONF_ARCH_STRING); #ifdef CONF_ARCH_ENDIAN_LITTLE @@ -101,24 +99,20 @@ public: char aFullPath[IO_MAX_PATH_LENGTH]; m_pStorage->GetCompletePath(IStorage::TYPE_SAVE, "dumps/", aFullPath, sizeof(aFullPath)); - m_AssertionLogger.Init(aFullPath, m_aAppName); - m_pConsole->Register("dbg_lognetwork", "", CFGFLAG_SERVER | CFGFLAG_CLIENT, Con_DbgLognetwork, this, "Log the network"); } - void InitLogfile() override - { - // open logfile if needed - if(g_Config.m_Logfile[0]) - dbg_logger_file(g_Config.m_Logfile); - } - void AddJob(std::shared_ptr pJob) override { if(g_Config.m_Debug) dbg_msg("engine", "job added"); m_JobPool.Add(std::move(pJob)); } + + void SetAdditionalLogger(std::unique_ptr &&pLogger) override + { + m_pFutureLogger->Set(std::move(pLogger)); + } }; void IEngine::RunJobBlocking(IJob *pJob) @@ -126,5 +120,5 @@ void IEngine::RunJobBlocking(IJob *pJob) CJobPool::RunBlocking(pJob); } -IEngine *CreateEngine(const char *pAppname, bool Silent, int Jobs) { return new CEngine(false, pAppname, Silent, Jobs); } -IEngine *CreateTestEngine(const char *pAppname, int Jobs) { return new CEngine(true, pAppname, true, Jobs); } +IEngine *CreateEngine(const char *pAppname, std::shared_ptr pFutureLogger, int Jobs) { return new CEngine(false, pAppname, std::move(pFutureLogger), Jobs); } +IEngine *CreateTestEngine(const char *pAppname, int Jobs) { return new CEngine(true, pAppname, nullptr, Jobs); } diff --git a/src/game/client/component.cpp b/src/game/client/component.cpp index e5aa80072..4388f0dab 100644 --- a/src/game/client/component.cpp +++ b/src/game/client/component.cpp @@ -3,6 +3,7 @@ #include "gameclient.h" class IKernel *CComponent::Kernel() const { return m_pClient->Kernel(); } +class IEngine *CComponent::Engine() const { return m_pClient->Engine(); } class IGraphics *CComponent::Graphics() const { return m_pClient->Graphics(); } class ITextRender *CComponent::TextRender() const { return m_pClient->TextRender(); } class IInput *CComponent::Input() const { return m_pClient->Input(); } diff --git a/src/game/client/component.h b/src/game/client/component.h index fb8a5441d..85d4ee2ff 100644 --- a/src/game/client/component.h +++ b/src/game/client/component.h @@ -36,6 +36,7 @@ protected: * Get the kernel interface. */ class IKernel *Kernel() const; + class IEngine *Engine() const; /** * Get the graphics interface. */ diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index 9cc8bf8cd..54107273e 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -1,6 +1,7 @@ /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ +#include #include #include @@ -38,6 +39,49 @@ #include "console.h" +class CConsoleLogger : public ILogger +{ + CGameConsole *m_pConsole; + std::mutex m_ConsoleMutex; + +public: + CConsoleLogger(CGameConsole *pConsole) : + m_pConsole(pConsole) + { + dbg_assert(pConsole != nullptr, "console pointer must not be null"); + } + + void Log(const CLogMessage *pMessage) override; + void OnConsoleDeletion(); +}; + +void CConsoleLogger::Log(const CLogMessage *pMessage) +{ + // TODO: Fix thread-unsafety of accessing `g_Config.m_ConsoleOutputLevel` + if(pMessage->m_Level > IConsole::ToLogLevel(g_Config.m_ConsoleOutputLevel)) + { + return; + } + ColorRGBA Color = gs_ConsoleDefaultColor; + if(pMessage->m_HaveColor) + { + Color.r = pMessage->m_Color.r / 255.0; + Color.g = pMessage->m_Color.g / 255.0; + Color.b = pMessage->m_Color.b / 255.0; + } + std::unique_lock Guard(m_ConsoleMutex); + if(m_pConsole) + { + m_pConsole->m_LocalConsole.PrintLine(pMessage->m_aLine, pMessage->m_LineLength, Color); + } +} + +void CConsoleLogger::OnConsoleDeletion() +{ + std::unique_lock Guard(m_ConsoleMutex); + m_pConsole = nullptr; +} + CGameConsole::CInstance::CInstance(int Type) { m_pHistoryEntry = 0x0; @@ -69,18 +113,22 @@ void CGameConsole::CInstance::Init(CGameConsole *pGameConsole) void CGameConsole::CInstance::ClearBacklog() { + m_BacklogLock.lock(); m_Backlog.Init(); m_BacklogCurPage = 0; + m_BacklogLock.unlock(); } void CGameConsole::CInstance::ClearBacklogYOffsets() { + m_BacklogLock.lock(); auto *pEntry = m_Backlog.First(); while(pEntry) { pEntry->m_YOffset = -1.0f; pEntry = m_Backlog.Next(pEntry); } + m_BacklogLock.unlock(); } void CGameConsole::CInstance::ClearHistory() @@ -374,13 +422,12 @@ void CGameConsole::CInstance::OnInput(IInput::CEvent Event) } } -void CGameConsole::CInstance::PrintLine(const char *pLine, ColorRGBA PrintColor) +void CGameConsole::CInstance::PrintLine(const char *pLine, int Len, ColorRGBA PrintColor) { - int Len = str_length(pLine); - if(Len > 255) Len = 255; + m_BacklogLock.lock(); CBacklogEntry *pEntry = m_Backlog.Allocate(sizeof(CBacklogEntry) + Len); pEntry->m_YOffset = -1.0f; pEntry->m_PrintColor = PrintColor; @@ -388,6 +435,7 @@ void CGameConsole::CInstance::PrintLine(const char *pLine, ColorRGBA PrintColor) pEntry->m_aText[Len] = 0; if(m_pGameConsole->m_ConsoleType == m_Type) m_pGameConsole->m_NewLineCounter++; + m_BacklogLock.unlock(); } CGameConsole::CGameConsole() : @@ -399,6 +447,11 @@ CGameConsole::CGameConsole() : m_StateChangeDuration = 0.1f; } +CGameConsole::~CGameConsole() +{ + m_pConsoleLogger->OnConsoleDeletion(); +} + float CGameConsole::TimeNow() { static long long s_TimeStart = time_get(); @@ -685,6 +738,8 @@ void CGameConsole::OnRender() } } + pConsole->m_BacklogLock.lock(); + // render log (current page, wrap lines) CInstance::CBacklogEntry *pEntry = pConsole->m_Backlog.Last(); float OffsetY = 0.0f; @@ -805,6 +860,8 @@ void CGameConsole::OnRender() } } + pConsole->m_BacklogLock.unlock(); + // render page char aBuf[128]; TextRender()->TextColor(1, 1, 1, 1); @@ -894,11 +951,13 @@ void CGameConsole::Dump(int Type) IOHANDLE io = Storage()->OpenFile(aFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE); if(io) { + pConsole->m_BacklogLock.lock(); for(CInstance::CBacklogEntry *pEntry = pConsole->m_Backlog.First(); pEntry; pEntry = pConsole->m_Backlog.Next(pEntry)) { io_write(io, pEntry->m_aText, str_length(pEntry->m_aText)); io_write_newline(io); } + pConsole->m_BacklogLock.unlock(); io_close(io); } } @@ -933,11 +992,6 @@ void CGameConsole::ConDumpRemoteConsole(IConsole::IResult *pResult, void *pUserD ((CGameConsole *)pUserData)->Dump(CONSOLETYPE_REMOTE); } -void CGameConsole::ClientConsolePrintCallback(const char *pStr, void *pUserData, ColorRGBA PrintColor) -{ - ((CGameConsole *)pUserData)->m_LocalConsole.PrintLine(pStr, PrintColor); -} - void CGameConsole::ConConsolePageUp(IConsole::IResult *pResult, void *pUserData) { CInstance *pConsole = ((CGameConsole *)pUserData)->CurrentConsole(); @@ -952,16 +1006,6 @@ void CGameConsole::ConConsolePageDown(IConsole::IResult *pResult, void *pUserDat pConsole->m_BacklogCurPage = 0; } -void CGameConsole::ConchainConsoleOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) -{ - pfnCallback(pResult, pCallbackUserData); - if(pResult->NumArguments() == 1) - { - CGameConsole *pThis = static_cast(pUserData); - pThis->Console()->SetPrintOutputLevel(pThis->m_PrintCBIndex, pResult->GetInteger(0)); - } -} - void CGameConsole::RequireUsername(bool UsernameReq) { if((m_RemoteConsole.m_UsernameReq = UsernameReq)) @@ -974,9 +1018,9 @@ void CGameConsole::RequireUsername(bool UsernameReq) void CGameConsole::PrintLine(int Type, const char *pLine) { if(Type == CONSOLETYPE_LOCAL) - m_LocalConsole.PrintLine(pLine); + m_LocalConsole.PrintLine(pLine, str_length(pLine), ColorRGBA{1, 1, 1, 1}); else if(Type == CONSOLETYPE_REMOTE) - m_RemoteConsole.PrintLine(pLine); + m_RemoteConsole.PrintLine(pLine, str_length(pLine), ColorRGBA{1, 1, 1, 1}); } void CGameConsole::OnConsoleInit() @@ -987,9 +1031,6 @@ void CGameConsole::OnConsoleInit() m_pConsole = Kernel()->RequestInterface(); - // - m_PrintCBIndex = Console()->RegisterPrintCallback(g_Config.m_ConsoleOutputLevel, ClientConsolePrintCallback, this); - Console()->Register("toggle_local_console", "", CFGFLAG_CLIENT, ConToggleLocalConsole, this, "Toggle local console"); Console()->Register("toggle_remote_console", "", CFGFLAG_CLIENT, ConToggleRemoteConsole, this, "Toggle remote console"); Console()->Register("clear_local_console", "", CFGFLAG_CLIENT, ConClearLocalConsole, this, "Clear local console"); @@ -999,12 +1040,12 @@ void CGameConsole::OnConsoleInit() Console()->Register("console_page_up", "", CFGFLAG_CLIENT, ConConsolePageUp, this, "Previous page in console"); Console()->Register("console_page_down", "", CFGFLAG_CLIENT, ConConsolePageDown, this, "Next page in console"); - - Console()->Chain("console_output_level", ConchainConsoleOutputLevelUpdate, this); } void CGameConsole::OnInit() { + m_pConsoleLogger = new CConsoleLogger(this); + Engine()->SetAdditionalLogger(std::unique_ptr(m_pConsoleLogger)); // add resize event Graphics()->AddWindowResizeListener([this](void *) { m_LocalConsole.ClearBacklogYOffsets(); diff --git a/src/game/client/components/console.h b/src/game/client/components/console.h index 076c87ad5..833ded971 100644 --- a/src/game/client/components/console.h +++ b/src/game/client/components/console.h @@ -8,6 +8,8 @@ #include +#include + enum { CONSOLE_CLOSED, @@ -16,8 +18,11 @@ enum CONSOLE_CLOSING, }; +class CConsoleLogger; + class CGameConsole : public CComponent { + friend class CConsoleLogger; class CInstance { public: @@ -27,6 +32,7 @@ class CGameConsole : public CComponent ColorRGBA m_PrintColor; char m_aText[1]; }; + std::mutex m_BacklogLock; CStaticRingBuffer m_Backlog; CStaticRingBuffer m_History; char *m_pHistoryEntry; @@ -64,20 +70,20 @@ class CGameConsole : public CComponent void ExecuteLine(const char *pLine); void OnInput(IInput::CEvent Event); - void PrintLine(const char *pLine, ColorRGBA PrintColor = {1, 1, 1, 1}); + void PrintLine(const char *pLine, int Len, ColorRGBA PrintColor); const char *GetString() const { return m_Input.GetString(); } static void PossibleCommandsCompleteCallback(const char *pStr, void *pUser); }; class IConsole *m_pConsole; + CConsoleLogger *m_pConsoleLogger = nullptr; CInstance m_LocalConsole; CInstance m_RemoteConsole; CInstance *CurrentConsole(); float TimeNow(); - int m_PrintCBIndex; int m_ConsoleType; int m_ConsoleState; @@ -100,7 +106,6 @@ class CGameConsole : public CComponent void Dump(int Type); static void PossibleCommandsRenderCallback(const char *pStr, void *pUser); - static void ClientConsolePrintCallback(const char *pStr, void *pUserData, ColorRGBA PrintColor = {1, 1, 1, 1}); static void ConToggleLocalConsole(IConsole::IResult *pResult, void *pUserData); static void ConToggleRemoteConsole(IConsole::IResult *pResult, void *pUserData); static void ConClearLocalConsole(IConsole::IResult *pResult, void *pUserData); @@ -109,7 +114,6 @@ class CGameConsole : public CComponent static void ConDumpRemoteConsole(IConsole::IResult *pResult, void *pUserData); static void ConConsolePageUp(IConsole::IResult *pResult, void *pUserData); static void ConConsolePageDown(IConsole::IResult *pResult, void *pUserData); - static void ConchainConsoleOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); public: enum @@ -119,6 +123,7 @@ public: }; CGameConsole(); + ~CGameConsole(); virtual int Sizeof() const override { return sizeof(*this); } void PrintLine(int Type, const char *pLine); diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index 94f686196..2073638d4 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -6,6 +6,7 @@ #include "gamecontext.h" #include "teeinfo.h" #include +#include #include #include #include @@ -29,6 +30,26 @@ #include "player.h" #include "score.h" +// Not thread-safe! +class CClientChatLogger : public ILogger +{ + CGameContext *m_pGameServer; + int m_ClientID; + +public: + CClientChatLogger(CGameContext *pGameServer, int ClientID) : + m_pGameServer(pGameServer), + m_ClientID(ClientID) + { + } + void Log(const CLogMessage *pMessage) override; +}; + +void CClientChatLogger::Log(const CLogMessage *pMessage) +{ + m_pGameServer->SendChatTarget(m_ClientID, pMessage->Message()); +} + enum { RESET, @@ -1824,7 +1845,6 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) pPlayer->m_LastCommandPos = (pPlayer->m_LastCommandPos + 1) % 4; m_ChatResponseTargetID = ClientID; - Server()->RestrictRconOutput(ClientID); Console()->SetFlagMask(CFGFLAG_CHAT); int Authed = Server()->GetAuthedState(ClientID); @@ -1832,9 +1852,12 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) Console()->SetAccessLevel(Authed == AUTHED_ADMIN ? IConsole::ACCESS_LEVEL_ADMIN : Authed == AUTHED_MOD ? IConsole::ACCESS_LEVEL_MOD : IConsole::ACCESS_LEVEL_HELPER); else Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_USER); - Console()->SetPrintOutputLevel(m_ChatPrintCBIndex, 0); - Console()->ExecuteLine(pMsg->m_pMessage + 1, ClientID, false); + { + CClientChatLogger Logger(this, ClientID); + CLogScope Scope(&Logger); + Console()->ExecuteLine(pMsg->m_pMessage + 1, ClientID, false); + } // m_apPlayers[ClientID] can be NULL, if the player used a // timeout code and replaced another client. char aBuf[256]; @@ -1844,7 +1867,6 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_ADMIN); Console()->SetFlagMask(CFGFLAG_SERVER); m_ChatResponseTargetID = -1; - Server()->RestrictRconOutput(-1); } } else @@ -3072,8 +3094,6 @@ void CGameContext::OnConsoleInit() m_pEngine = Kernel()->RequestInterface(); m_pStorage = Kernel()->RequestInterface(); - m_ChatPrintCBIndex = Console()->RegisterPrintCallback(0, SendChatResponse, this); - Console()->Register("tune", "s[tuning] i[value]", CFGFLAG_SERVER | CFGFLAG_GAME, ConTuneParam, this, "Tune variable to value"); Console()->Register("toggle_tune", "s[tuning] i[value 1] i[value 2]", CFGFLAG_SERVER | CFGFLAG_GAME, ConToggleTuneParam, this, "Toggle tune variable"); Console()->Register("tune_reset", "", CFGFLAG_SERVER, ConTuneReset, this, "Reset tuning"); @@ -3673,58 +3693,6 @@ const char *CGameContext::NetVersion() const { return GAME_NETVERSION; } IGameServer *CreateGameServer() { return new CGameContext; } -void CGameContext::SendChatResponseAll(const char *pLine, void *pUser) -{ - CGameContext *pSelf = (CGameContext *)pUser; - - static int ReentryGuard = 0; - const char *pLineOrig = pLine; - - if(ReentryGuard) - return; - ReentryGuard++; - - if(*pLine == '[') - do - pLine++; - while((pLine - 2 < pLineOrig || *(pLine - 2) != ':') && *pLine != 0); // remove the category (e.g. [Console]: No Such Command) - - pSelf->SendChat(-1, CHAT_ALL, pLine); - - ReentryGuard--; -} - -void CGameContext::SendChatResponse(const char *pLine, void *pUser, ColorRGBA PrintColor) -{ - CGameContext *pSelf = (CGameContext *)pUser; - int ClientID = pSelf->m_ChatResponseTargetID; - - if(ClientID < 0 || ClientID >= MAX_CLIENTS) - return; - - const char *pLineOrig = pLine; - - static int ReentryGuard = 0; - - if(ReentryGuard) - return; - ReentryGuard++; - - if(pLine[0] == '[') - { - // Remove time and category: [20:39:00][Console] - pLine = str_find(pLine, "]: "); - if(pLine) - pLine += 3; - else - pLine = pLineOrig; - } - - pSelf->SendChatTarget(ClientID, pLine); - - ReentryGuard--; -} - bool CGameContext::PlayerCollision() { float Temp; diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h index 8dfe39785..675861c2e 100644 --- a/src/game/server/gamecontext.h +++ b/src/game/server/gamecontext.h @@ -456,8 +456,6 @@ public: inline bool IsSpecVote() const { return m_VoteType == VOTE_TYPE_SPECTATE; } void SendRecord(int ClientID); - static void SendChatResponse(const char *pLine, void *pUser, ColorRGBA PrintColor = {1, 1, 1, 1}); - static void SendChatResponseAll(const char *pLine, void *pUser); virtual void OnSetAuthed(int ClientID, int Level); virtual bool PlayerCollision(); virtual bool PlayerHooking(); diff --git a/src/mastersrv/main_mastersrv.cpp b/src/mastersrv/main_mastersrv.cpp index 87db7f5a4..37c362728 100644 --- a/src/mastersrv/main_mastersrv.cpp +++ b/src/mastersrv/main_mastersrv.cpp @@ -1,5 +1,6 @@ /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ +#include #include #include @@ -326,7 +327,7 @@ int main(int argc, const char **argv) cmdline_fix(&argc, &argv); - dbg_logger_stdout(); + log_set_global_logger_default(); net_init(); mem_copy(m_CountData.m_Header, SERVERBROWSE_COUNT, sizeof(SERVERBROWSE_COUNT)); diff --git a/src/test/test.cpp b/src/test/test.cpp index 37781f6fc..6f015ce58 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -1,6 +1,7 @@ #include "test.h" #include +#include #include #include @@ -114,6 +115,7 @@ CTestInfo::~CTestInfo() int main(int argc, const char **argv) { cmdline_fix(&argc, &argv); + log_set_global_logger_default(); ::testing::InitGoogleTest(&argc, const_cast(argv)); net_init(); if(secure_random_init()) diff --git a/src/tools/config_common.h b/src/tools/config_common.h index 0660bce88..ce3ec2b83 100644 --- a/src/tools/config_common.h +++ b/src/tools/config_common.h @@ -1,3 +1,4 @@ +#include #include #include @@ -47,7 +48,7 @@ static int ListdirCallback(const char *pItemName, int IsDir, int StorageType, vo int main(int argc, const char **argv) // NOLINT(misc-definitions-in-headers) { cmdline_fix(&argc, &argv); - dbg_logger_stdout(); + log_set_global_logger_default(); IStorage *pStorage = CreateLocalStorage(); if(argc == 1) { diff --git a/src/tools/crapnet.cpp b/src/tools/crapnet.cpp index 311697895..0a5d9a1a6 100644 --- a/src/tools/crapnet.cpp +++ b/src/tools/crapnet.cpp @@ -1,5 +1,6 @@ /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ +#include #include #include // std::size @@ -209,8 +210,8 @@ void Run(unsigned short Port, NETADDR Dest) int main(int argc, const char **argv) { cmdline_fix(&argc, &argv); + log_set_global_logger_default(); NETADDR Addr = {NETTYPE_IPV4, {127, 0, 0, 1}, 8303}; - dbg_logger_stdout(); Run(8302, Addr); cmdline_free(argc, argv); return 0; diff --git a/src/tools/dilate.cpp b/src/tools/dilate.cpp index af69e498c..96cd80a62 100644 --- a/src/tools/dilate.cpp +++ b/src/tools/dilate.cpp @@ -1,5 +1,6 @@ /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ +#include #include #include #include @@ -74,7 +75,7 @@ int DilateFile(const char *pFilename) int main(int argc, const char **argv) { cmdline_fix(&argc, &argv); - dbg_logger_stdout(); + log_set_global_logger_default(); if(argc == 1) { dbg_msg("usage", "%s FILE1 [ FILE2... ]", argv[0]); diff --git a/src/tools/dummy_map.cpp b/src/tools/dummy_map.cpp index 50d53a424..8e2fb755f 100644 --- a/src/tools/dummy_map.cpp +++ b/src/tools/dummy_map.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -74,7 +75,7 @@ void CreateEmptyMap(IStorage *pStorage) int main(int argc, const char **argv) { cmdline_fix(&argc, &argv); - dbg_logger_stdout(); + log_set_global_logger_default(); IStorage *pStorage = CreateStorage(IStorage::STORAGETYPE_SERVER, argc, argv); CreateEmptyMap(pStorage); cmdline_free(argc, argv); diff --git a/src/tools/map_convert_07.cpp b/src/tools/map_convert_07.cpp index e45e4fc0b..83b76b2e8 100644 --- a/src/tools/map_convert_07.cpp +++ b/src/tools/map_convert_07.cpp @@ -1,6 +1,7 @@ /* (c) DDNet developers. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ +#include #include #include #include @@ -136,7 +137,7 @@ void *ReplaceImageItem(void *pItem, int Type, CMapItemImage *pNewImgItem) int main(int argc, const char **argv) { cmdline_fix(&argc, &argv); - dbg_logger_stdout(); + log_set_global_logger_default(); IStorage *pStorage = CreateStorage(IStorage::STORAGETYPE_BASIC, argc, argv); diff --git a/src/tools/map_diff.cpp b/src/tools/map_diff.cpp index e1f5e2e67..e4086c482 100644 --- a/src/tools/map_diff.cpp +++ b/src/tools/map_diff.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -101,8 +102,14 @@ bool Process(IStorage *pStorage, const char **pMapNames) int main(int argc, const char *argv[]) { cmdline_fix(&argc, &argv); - dbg_logger_stdout(); - dbg_logger_file("map_diff.txt"); + std::vector> apLoggers; + apLoggers.push_back(std::shared_ptr(log_logger_stdout())); + IOHANDLE LogFile = io_open("map_diff.txt", IOFLAG_WRITE); + if(LogFile) + { + apLoggers.push_back(std::shared_ptr(log_logger_file(LogFile))); + } + log_set_global_logger(log_logger_collection(std::move(apLoggers)).release()); IStorage *pStorage = CreateLocalStorage(); diff --git a/src/tools/map_extract.cpp b/src/tools/map_extract.cpp index 9492bdbd7..664442404 100644 --- a/src/tools/map_extract.cpp +++ b/src/tools/map_extract.cpp @@ -1,4 +1,5 @@ // Adapted from TWMapImagesRecovery by Tardo: https://github.com/Tardo/TWMapImagesRecovery +#include #include #include #include @@ -96,7 +97,7 @@ bool Process(IStorage *pStorage, const char *pMapName, const char *pPathSave) int main(int argc, const char *argv[]) { cmdline_fix(&argc, &argv); - dbg_logger_stdout(); + log_set_global_logger_default(); char aMap[512]; char aDir[512]; diff --git a/src/tools/map_optimize.cpp b/src/tools/map_optimize.cpp index d862bafc0..8b156ee64 100644 --- a/src/tools/map_optimize.cpp +++ b/src/tools/map_optimize.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -79,7 +80,7 @@ void GetImageSHA256(uint8_t *pImgBuff, int ImgSize, int Width, int Height, char int main(int argc, const char **argv) { cmdline_fix(&argc, &argv); - dbg_logger_stdout(); + log_set_global_logger_default(); IStorage *pStorage = CreateStorage(IStorage::STORAGETYPE_BASIC, argc, argv); int ID = 0, Type = 0, Size; diff --git a/src/tools/map_replace_image.cpp b/src/tools/map_replace_image.cpp index f4082d234..51130dc0d 100644 --- a/src/tools/map_replace_image.cpp +++ b/src/tools/map_replace_image.cpp @@ -1,6 +1,7 @@ /* (c) DDNet developers. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ +#include #include #include #include @@ -111,7 +112,7 @@ void *ReplaceImageItem(void *pItem, int Type, const char *pImgName, const char * int main(int argc, const char **argv) { cmdline_fix(&argc, &argv); - dbg_logger_stdout(); + log_set_global_logger_default(); IStorage *pStorage = CreateStorage(IStorage::STORAGETYPE_BASIC, argc, argv); diff --git a/src/tools/unicode_confusables.cpp b/src/tools/unicode_confusables.cpp index 2affe9995..abf2efa34 100644 --- a/src/tools/unicode_confusables.cpp +++ b/src/tools/unicode_confusables.cpp @@ -1,9 +1,10 @@ +#include #include int main(int argc, const char **argv) { cmdline_fix(&argc, &argv); - dbg_logger_stdout(); + log_set_global_logger_default(); if(argc < 1 + 2) { dbg_msg("usage", "%s STR1 STR2", argv[0] ? argv[0] : "unicode_confusables"); diff --git a/src/tools/uuid.cpp b/src/tools/uuid.cpp index 0854c7eab..8cc1f6373 100644 --- a/src/tools/uuid.cpp +++ b/src/tools/uuid.cpp @@ -1,8 +1,9 @@ +#include #include int main(int argc, const char **argv) { cmdline_fix(&argc, &argv); - dbg_logger_stdout(); + log_set_global_logger_default(); if(argc != 2) { dbg_msg("usage", "uuid ");