diff --git a/managed/src/SwiftlyS2.Core/Misc/SwiftlyLogger.cs b/managed/src/SwiftlyS2.Core/Misc/SwiftlyLogger.cs index 71b086b86..dec704989 100644 --- a/managed/src/SwiftlyS2.Core/Misc/SwiftlyLogger.cs +++ b/managed/src/SwiftlyS2.Core/Misc/SwiftlyLogger.cs @@ -3,109 +3,86 @@ namespace SwiftlyS2.Core.Misc; -internal class SwiftlyLoggerProvider : ILoggerProvider +internal class SwiftlyLoggerProvider( string contextName ) : ILoggerProvider { - private readonly string _contextName; - - public SwiftlyLoggerProvider(string contextName) - { - _contextName = contextName; - } - - public ILogger CreateLogger(string categoryName) - { - return new SwiftlyLogger(categoryName, _contextName); - } - - public void Dispose() - { - } + public ILogger CreateLogger( string categoryName ) => new SwiftlyLogger(categoryName, contextName); + public void Dispose() { } } -internal class SwiftlyLogger : ILogger +internal class SwiftlyLogger( string categoryName, string contextName ) : ILogger { - private readonly string _categoryName; - private readonly string _contextName; - - public SwiftlyLogger(string categoryName, string contextName) - { - _categoryName = categoryName; - _contextName = contextName; - } - - public IDisposable? BeginScope(TState state) where TState : notnull - { - return NullScope.Instance; - } - - public bool IsEnabled(LogLevel logLevel) - { - return logLevel != LogLevel.None; - } - - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) - { - if (!IsEnabled(logLevel)) return; + private static readonly LogLevel MinLogLevel = GetMinLogLevelFromEnv(); + private static readonly Dictionary LogLevelConfig = new() { + [LogLevel.Trace] = ("Debug", "grey42"), + [LogLevel.Debug] = ("Debug", "grey42"), + [LogLevel.Information] = ("Information", "silver"), + [LogLevel.Warning] = ("Warning", "yellow1"), + [LogLevel.Error] = ("Error", "red3"), + [LogLevel.Critical] = ("Critical", "red3") + }; - var timestamp = DateTime.Now.ToString("MM/dd HH:mm:ss"); - var level = GetLogLevelString(logLevel); - var id = $"[{eventId.ToString()}]"; - var color = GetLogLevelColor(logLevel); + public IDisposable BeginScope( TState state ) where TState : notnull => NullScope.Instance; - AnsiConsole.MarkupLineInterpolated($"[lightsteelblue1 bold]{_contextName}[/] [lightsteelblue]|[/] [grey42]{timestamp}[/] [lightsteelblue]|[/] [{color}]{level}[/] [lightsteelblue]|[/] [lightsteelblue]{_categoryName}{id}[/]"); + public bool IsEnabled( LogLevel logLevel ) => logLevel != LogLevel.None && logLevel >= MinLogLevel; - string? message = formatter != null ? formatter(state, exception) : state?.ToString(); - if (!string.IsNullOrEmpty(message)) + public void Log( LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter ) { - FileLogger.Log($"{_contextName} | {timestamp} | {level} | {_categoryName}{id} | {message}"); - var lines = message.Split('\n'); - for (int i = 0; i < lines.Length; i++) - { - var line = lines[i]; - if (i == lines.Length - 1 && line == "") break; - AnsiConsole.MarkupLineInterpolated($"[lightsteelblue1 bold]{_contextName}[/] [lightsteelblue]|[/] [grey85]{line}[/]"); - } + if (!IsEnabled(logLevel)) + { + return; + } + + var timestamp = DateTime.Now.ToString("MM/dd HH:mm:ss"); + var (levelText, color) = LogLevelConfig.TryGetValue(logLevel, out var config) ? config : ("Unknown", "grey42"); + var eventIdText = eventId.Id != 0 ? $"[{eventId.Id}]" : string.Empty; + AnsiConsole.Profile.Width = int.MaxValue; + + // Console output + AnsiConsole.MarkupLineInterpolated($"[lightsteelblue1 bold]{contextName}[/] [lightsteelblue]|[/] [grey42]{timestamp}[/] [lightsteelblue]|[/] [{color}]{levelText}[/] [lightsteelblue]|[/] [lightsteelblue]{categoryName}{eventIdText}[/]"); + + // Message output + var message = formatter?.Invoke(state, exception) ?? state?.ToString(); + if (!string.IsNullOrEmpty(message)) + { + FileLogger.Log($"{contextName} | {timestamp} | {levelText} | {categoryName}{eventIdText} | {message}"); + OutputMessageLines(message); + } + + // Exception output + if (exception != null) + { + FileLogger.LogException(exception, exception.Message); + AnsiConsole.WriteException(exception); + } + + AnsiConsole.Reset(); } - if (exception != null) + private void OutputMessageLines( string message ) { - FileLogger.LogException(exception, $"{exception.Message}"); - AnsiConsole.WriteException(exception); + var lines = message.Split('\n', StringSplitOptions.RemoveEmptyEntries); + foreach (var line in lines) + { + AnsiConsole.MarkupLineInterpolated($"[lightsteelblue1 bold]{contextName}[/] [lightsteelblue]|[/] [grey85]{line}[/]"); + } } - AnsiConsole.Reset(); - } - private static string GetLogLevelString(LogLevel logLevel) - { - return logLevel switch + private static LogLevel GetMinLogLevelFromEnv() { - LogLevel.Trace => "Trace ", - LogLevel.Debug => "Debug ", - LogLevel.Information => "Information", - LogLevel.Warning => "Warning ", - LogLevel.Error => "Error ", - LogLevel.Critical => "Critical ", - _ => "Unknown " - }; - } + var level = Environment.GetEnvironmentVariable("SWIFTLY_LOG_LEVEL")?.ToUpperInvariant(); + return level switch { + "DEBUG" => LogLevel.Debug, + "INFO" => LogLevel.Information, + "WARNING" => LogLevel.Warning, + "ERROR" => LogLevel.Error, + "OFF" => LogLevel.None, + _ => LogLevel.Information + }; + } - private static string GetLogLevelColor(LogLevel logLevel) - { - return logLevel switch + private sealed class NullScope : IDisposable { - LogLevel.Trace => "grey42", - LogLevel.Debug => "grey42", - LogLevel.Information => "silver", - LogLevel.Warning => "yellow1", - LogLevel.Error => "red3", - LogLevel.Critical => "red3", - _ => "grey42" - }; - } - - private sealed class NullScope : IDisposable - { - public static readonly NullScope Instance = new NullScope(); - public void Dispose() { } - } + public static readonly NullScope Instance = new(); + public void Dispose() { } + } } \ No newline at end of file diff --git a/src/api/monitor/logger/logger.h b/src/api/monitor/logger/logger.h index 3788a9ff5..5a4572622 100644 --- a/src/api/monitor/logger/logger.h +++ b/src/api/monitor/logger/logger.h @@ -23,10 +23,13 @@ enum class LogType { + TRACE, + DEBUG, INFO, WARNING, ERR, - DEBUG + CRITICAL, + NONE }; class ILogger @@ -35,15 +38,19 @@ class ILogger virtual void Log(LogType type, const std::string& message) = 0; virtual void Log(LogType type, const std::string& category, const std::string& message) = 0; + virtual void Trace(const std::string& message) = 0; + virtual void Debug(const std::string& message) = 0; virtual void Info(const std::string& message) = 0; virtual void Warning(const std::string& message) = 0; virtual void Error(const std::string& message) = 0; - virtual void Debug(const std::string& message) = 0; + virtual void Critical(const std::string& message) = 0; + virtual void Trace(const std::string& category, const std::string& message) = 0; + virtual void Debug(const std::string& category, const std::string& message) = 0; virtual void Info(const std::string& category, const std::string& message) = 0; virtual void Warning(const std::string& category, const std::string& message) = 0; virtual void Error(const std::string& category, const std::string& message) = 0; - virtual void Debug(const std::string& category, const std::string& message) = 0; + virtual void Critical(const std::string& category, const std::string& message) = 0; virtual void SetLogFile(LogType type, const std::string& path) = 0; virtual void ShouldOutputToFile(LogType type, bool enabled) = 0; diff --git a/src/core/entrypoint.cpp b/src/core/entrypoint.cpp index 783f2b0e2..201847860 100644 --- a/src/core/entrypoint.cpp +++ b/src/core/entrypoint.cpp @@ -24,7 +24,6 @@ #include #include - #include "managed/host/dynlib.h" #include "managed/host/strconv.h" @@ -33,7 +32,6 @@ #include #include - #include #include @@ -41,17 +39,14 @@ #include #include - #include #include - #include #include #include - #include #include @@ -94,9 +89,7 @@ bool SwiftlyCore::Load(BridgeKind_t kind) #ifdef _WIN32 void* libServer = load_library((const char_t*)WIN_LINUX(StringWide((Plat_GetGameDirectory() + std::string("\\csgo\\bin\\win64\\server.dll"))).c_str(), (Plat_GetGameDirectory() + std::string("/csgo/bin/linuxsteamrt64/libserver.so")).c_str())); - void* libEngine = load_library((const char_t*)WIN_LINUX(StringWide(Plat_GetGameDirectory() + std::string("\\bin\\win64\\engine2.dll")).c_str(), (Plat_GetGameDirectory() + std::string("/bin/linuxsteamrt64/libengine2.so")).c_str())); - s2binlib_set_module_base_from_pointer("server", libServer); s2binlib_set_module_base_from_pointer("engine2", libEngine); #endif @@ -109,29 +102,43 @@ bool SwiftlyCore::Load(BridgeKind_t kind) auto logger = g_ifaceService.FetchInterface(LOGGER_INTERFACE_VERSION); + const char* logLevel = CommandLine()->ParmValue(CUtlStringToken("-sw_loglevel")); + if (logLevel) + { +#ifdef _WIN32 + _putenv_s("SWIFTLY_LOG_LEVEL", logLevel); +#else + setenv("SWIFTLY_LOG_LEVEL", logLevel, 1); +#endif + } + if (GetCurrentGame() == "unknown") { auto engine = g_ifaceService.FetchInterface(INTERFACEVERSION_VENGINESERVER); - if (engine) + { logger->Error("Entrypoint", fmt::format("Unknown game detected. App ID: {}", engine->GetAppID())); + } else + { logger->Error("Entrypoint", "Unknown game detected. No engine interface available."); - + } return false; } m_sCorePath = CommandLine()->ParmValue(CUtlStringToken("-sw_path"), WIN_LINUX("addons\\swiftlys2", "addons/swiftlys2")); if (!ends_with(m_sCorePath, WIN_LINUX("\\", "/"))) + { m_sCorePath += WIN_LINUX("\\", "/"); - + } m_sLogPath = CommandLine()->ParmValue(CUtlStringToken("-sw_logpath"), WIN_LINUX("addons\\swiftlys2\\logs", "addons/swiftlys2/logs")); if (!ends_with(m_sLogPath, WIN_LINUX("\\", "/"))) + { m_sLogPath += WIN_LINUX("\\", "/"); + } auto configuration = g_ifaceService.FetchInterface(CONFIGURATION_INTERFACE_VERSION); configuration->InitializeExamples(); - if (!configuration->Load()) { logger->Error("Entrypoint", "Couldn't load the core configuration."); @@ -142,7 +149,6 @@ bool SwiftlyCore::Load(BridgeKind_t kind) sdkclass->Load(); auto gamedata = g_ifaceService.FetchInterface(GAMEDATA_INTERFACE_VERSION); - gamedata->GetOffsets()->Load(GetCurrentGame()); gamedata->GetSignatures()->Load(GetCurrentGame()); gamedata->GetPatches()->Load(GetCurrentGame()); @@ -167,8 +173,12 @@ bool SwiftlyCore::Load(BridgeKind_t kind) auto consoleoutput = g_ifaceService.FetchInterface(CONSOLEOUTPUT_INTERFACE_VERSION); consoleoutput->Initialize(); if (bool* b = std::get_if(&configuration->GetValue("core.ConsoleFilter"))) + { if (*b) + { consoleoutput->ToggleFilter(); + } + } auto entsystem = g_ifaceService.FetchInterface(ENTITYSYSTEM_INTERFACE_VERSION); entsystem->Initialize(); @@ -292,7 +302,9 @@ bool SwiftlyCore::Unload() void GameServerSteamAPIActivatedHook(void* _this) { if (!CommandLine()->HasParm("-dedicated")) + { return; + } static auto playermanager = g_ifaceService.FetchInterface(PLAYERMANAGER_INTERFACE_VERSION); playermanager->SteamAPIServerActivated(); @@ -312,10 +324,14 @@ bool LoopInitHook(void* _this, KeyValues* pKeyValues, void* pRegistry) bool ret = reinterpret_cast(g_pLoopInitHook->GetOriginal())(_this, pKeyValues, pRegistry); g_SwiftlyCore.OnMapLoad(pKeyValues->GetString("levelname")); - if (pKeyValues->FindKey("customgamemode")) { + if (pKeyValues->FindKey("customgamemode")) + { workshop_map = pKeyValues->GetString("customgamemode"); } - else workshop_map = ""; + else + { + workshop_map = ""; + } return ret; } @@ -336,13 +352,17 @@ void SwiftlyCore::OnMapLoad(std::string map_name) current_map = map_name; if (g_pOnMapLoadCallback) + { reinterpret_cast(g_pOnMapLoadCallback)(map_name.c_str()); + } } void SwiftlyCore::OnMapUnload() { if (g_pOnMapUnloadCallback) + { reinterpret_cast(g_pOnMapUnloadCallback)(current_map.c_str()); + } current_map = ""; } @@ -353,7 +373,9 @@ void* SwiftlyCore::GetInterface(const std::string& interface_name) { auto it = g_mInterfacesCache.find(interface_name); if (it != g_mInterfacesCache.end()) + { return it->second; + } void* ifaceptr = nullptr; void* ifaceCreate = nullptr; @@ -406,7 +428,9 @@ void* SwiftlyCore::GetInterface(const std::string& interface_name) } if (ifaceptr != nullptr) - g_mInterfacesCache.insert({ interface_name, ifaceptr }); + { + g_mInterfacesCache.insert({interface_name, ifaceptr}); + } return ifaceptr; } @@ -420,7 +444,9 @@ std::string SwiftlyCore::GetCurrentGame() { auto engine = g_ifaceService.FetchInterface(INTERFACEVERSION_VENGINESERVER); if (!engine) + { return "unknown"; + } switch (engine->GetAppID()) { @@ -435,7 +461,9 @@ int SwiftlyCore::GetMaxGameClients() { auto engine = g_ifaceService.FetchInterface(INTERFACEVERSION_VENGINESERVER); if (!engine) + { return 0; + } switch (engine->GetAppID()) { diff --git a/src/monitor/logger/logger.cpp b/src/monitor/logger/logger.cpp index 16db81ed6..ebca64bf8 100644 --- a/src/monitor/logger/logger.cpp +++ b/src/monitor/logger/logger.cpp @@ -21,38 +21,42 @@ #include #include -#include #include +#include +#include #include +#include +#include +#include -#define PREFIX "[Swiftly]" +constexpr const char* PREFIX = "[Swiftly]"; +static const std::unordered_map logTypeToString = {{LogType::TRACE, "TRACE"}, {LogType::DEBUG, "DEBUG"}, {LogType::INFO, "INFO"}, {LogType::WARNING, "WARNING"}, + {LogType::ERR, "ERROR"}, {LogType::CRITICAL, "CRITICAL"}, {LogType::NONE, "NONE"}}; +static const std::unordered_map stringToLogType = {{"TRACE", LogType::TRACE}, {"DEBUG", LogType::DEBUG}, {"INFO", LogType::INFO}, {"WARNING", LogType::WARNING}, + {"ERROR", LogType::ERR}, {"CRITICAL", LogType::CRITICAL}, {"NONE", LogType::NONE}}; std::string GetLogTypeString(LogType type) { - switch (type) - { - case LogType::INFO: - return "INFO"; - case LogType::WARNING: - return "WARNING"; - case LogType::ERR: - return "ERROR"; - case LogType::DEBUG: - return "DEBUG"; - default: - return "UNKNOWN"; - } + auto it = logTypeToString.find(type); + return (it != logTypeToString.end()) ? it->second : "UNKNOWN"; } void Logger::Log(LogType type, const std::string& message) { + if (!ShouldLog(type)) + { + return; + } + std::string final_output = fmt::format("{} [{}{}{}] {}", PREFIX, GetTerminalStringColor(GetLogTypeString(type)), GetLogTypeString(type), "[/]", message); std::string color_processed = TerminalProcessColor(final_output); std::string without_colors = ClearTerminalColors(final_output); - if (m_bShouldOutputToConsole[(int)type]) g_SwiftlyCore.SendConsoleMessage(color_processed); - + if (m_bShouldOutputToConsole[(int)type]) + { + g_SwiftlyCore.SendConsoleMessage(color_processed); + } if (m_bShouldOutputToFile[(int)type] && !m_sLogFilePaths[(int)type].empty()) { Files::Append(m_sLogFilePaths[(int)type], without_colors); @@ -61,10 +65,29 @@ void Logger::Log(LogType type, const std::string& message) void Logger::Log(LogType type, const std::string& category, const std::string& message) { + if (!ShouldLog(type)) + { + return; + } + if (m_bShouldOutputToConsole[(int)type] && !m_sNonColoredCategories.contains(category)) + { Log(type, fmt::format("[{}{}{}] {}", GetTerminalStringColor(category), category, "[/]", message)); + } else + { Log(type, fmt::format("[{}] {}", category, message)); + } +} + +void Logger::Trace(const std::string& message) +{ + Log(LogType::TRACE, message); +} + +void Logger::Debug(const std::string& message) +{ + Log(LogType::DEBUG, message); } void Logger::Info(const std::string& message) @@ -82,9 +105,19 @@ void Logger::Error(const std::string& message) Log(LogType::ERR, message); } -void Logger::Debug(const std::string& message) +void Logger::Critical(const std::string& message) { - Log(LogType::DEBUG, message); + Log(LogType::CRITICAL, message); +} + +void Logger::Trace(const std::string& category, const std::string& message) +{ + Log(LogType::TRACE, category, message); +} + +void Logger::Debug(const std::string& category, const std::string& message) +{ + Log(LogType::DEBUG, category, message); } void Logger::Info(const std::string& category, const std::string& message) @@ -102,9 +135,9 @@ void Logger::Error(const std::string& category, const std::string& message) Log(LogType::ERR, category, message); } -void Logger::Debug(const std::string& category, const std::string& message) +void Logger::Critical(const std::string& category, const std::string& message) { - Log(LogType::DEBUG, category, message); + Log(LogType::CRITICAL, category, message); } void Logger::SetLogFile(LogType type, const std::string& path) @@ -120,12 +153,43 @@ void Logger::ShouldOutputToFile(LogType type, bool enabled) void Logger::ShouldColorCategoryInConsole(const std::string& category, bool enabled) { if (!enabled) + { m_sNonColoredCategories.insert(category); + } else + { + m_sNonColoredCategories.erase(category); + } } void Logger::ShouldOutputToConsole(LogType type, bool enabled) { m_bShouldOutputToConsole[static_cast(type)] = enabled; } + +bool Logger::ShouldLog(LogType type) +{ + if (type == LogType::NONE) + { + return false; + } + + static LogType minLevel = GetMinLogLevelFromEnv(); + return static_cast(type) >= static_cast(minLevel); +} + +LogType Logger::GetMinLogLevelFromEnv() +{ + const char* level = std::getenv("SWIFTLY_LOG_LEVEL"); + if (!level) + { + return LogType::INFO; + } + + std::string levelStr = level; + std::transform(levelStr.begin(), levelStr.end(), levelStr.begin(), ::toupper); + + auto it = stringToLogType.find(levelStr); + return (it != stringToLogType.end()) ? it->second : LogType::INFO; +} \ No newline at end of file diff --git a/src/monitor/logger/logger.h b/src/monitor/logger/logger.h index b20339e20..49fc9ddce 100644 --- a/src/monitor/logger/logger.h +++ b/src/monitor/logger/logger.h @@ -19,8 +19,8 @@ #ifndef src_monitor_logger_logger_h #define src_monitor_logger_logger_h -#include #include +#include #include @@ -30,25 +30,33 @@ class Logger : public ILogger virtual void Log(LogType type, const std::string& message) override; virtual void Log(LogType type, const std::string& category, const std::string& message) override; + virtual void Trace(const std::string& message) override; + virtual void Debug(const std::string& message) override; virtual void Info(const std::string& message) override; virtual void Warning(const std::string& message) override; virtual void Error(const std::string& message) override; - virtual void Debug(const std::string& message) override; + virtual void Critical(const std::string& message) override; + virtual void Trace(const std::string& category, const std::string& message) override; + virtual void Debug(const std::string& category, const std::string& message) override; virtual void Info(const std::string& category, const std::string& message) override; virtual void Warning(const std::string& category, const std::string& message) override; virtual void Error(const std::string& category, const std::string& message) override; - virtual void Debug(const std::string& category, const std::string& message) override; + virtual void Critical(const std::string& category, const std::string& message) override; virtual void SetLogFile(LogType type, const std::string& path) override; virtual void ShouldOutputToFile(LogType type, bool enabled) override; virtual void ShouldColorCategoryInConsole(const std::string& category, bool enabled) override; virtual void ShouldOutputToConsole(LogType type, bool enabled) override; + private: - bool m_bShouldOutputToConsole[4] = { true, true, true, true }; - bool m_bShouldOutputToFile[4] = { false, false, false, false }; - std::string m_sLogFilePaths[4] = { "", "", "", "" }; + bool ShouldLog(LogType type); + LogType GetMinLogLevelFromEnv(); + + bool m_bShouldOutputToConsole[7] = {true, true, true, true, true, true, false}; + bool m_bShouldOutputToFile[7] = {false, false, false, false, false, false, false}; + std::string m_sLogFilePaths[7] = {"", "", "", "", "", "", ""}; std::set m_sNonColoredCategories; };