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 ");