From 27d95c37d5cd30acb6ae2a911857760b983540db Mon Sep 17 00:00:00 2001 From: Luke Street Date: Tue, 14 Apr 2026 01:32:58 -0600 Subject: [PATCH] Create log files under configDir/logs; seed initial pipeline cache --- include/dusk/logging.h | 3 + src/dusk/crash_reporting.cpp | 16 ++++ src/dusk/logging.cpp | 140 +++++++++++++++++++++++++++++------ src/m_Do/m_Do_main.cpp | 50 ++++++++++++- 4 files changed, 186 insertions(+), 23 deletions(-) diff --git a/include/dusk/logging.h b/include/dusk/logging.h index 7f239d8b45..0a9cbf238d 100644 --- a/include/dusk/logging.h +++ b/include/dusk/logging.h @@ -7,6 +7,9 @@ void aurora_log_callback(AuroraLogLevel level, const char* module, const char* message, unsigned int len); namespace dusk { + void InitializeFileLogging(const char* configDir, AuroraLogLevel logLevel); + void ShutdownFileLogging(); + const char* GetLogFilePath(); void SendToStubLog(AuroraLogLevel level, const char* module, const char* message); } diff --git a/src/dusk/crash_reporting.cpp b/src/dusk/crash_reporting.cpp index 2d7534965e..73f432e418 100644 --- a/src/dusk/crash_reporting.cpp +++ b/src/dusk/crash_reporting.cpp @@ -69,6 +69,13 @@ std::filesystem::path GetSentryDatabasePath() { return std::filesystem::path(configPath) / "sentry"; } +std::filesystem::path GetLogAttachmentPath() { + if (const char* logPath = GetLogFilePath()) { + return logPath; + } + return {}; +} + std::filesystem::path GetCrashpadHandlerPath() { const char* basePath = SDL_GetBasePath(); if (!basePath) { @@ -111,6 +118,15 @@ void ConfigurePathOptions(sentry_options_t* options) { sentry_options_set_handler_path(options, handlerPathUtf8.c_str()); } #endif + + const auto logPath = GetLogAttachmentPath(); + if (!logPath.empty()) { +#if _WIN32 + sentry_options_add_attachmentw(options, logPath.wstring().c_str()); +#else + sentry_options_add_attachment(options, logPath.string().c_str()); +#endif + } } #endif diff --git a/src/dusk/logging.cpp b/src/dusk/logging.cpp index 37d1d4f55f..323945dd8b 100644 --- a/src/dusk/logging.cpp +++ b/src/dusk/logging.cpp @@ -1,6 +1,11 @@ #include "dusk/logging.h" +#include +#include #include #include +#include +#include +#include #include "tracy/Tracy.hpp" @@ -20,6 +25,60 @@ static constexpr std::string_view StubFragments[] = { "but selective updates are not implemented"sv, }; +namespace { +std::mutex g_logMutex; +FILE* g_logFile = nullptr; +std::string g_logFilePath; + +const char* LogLevelString(AuroraLogLevel level) { + switch (level) { + case LOG_DEBUG: + return "DEBUG"; + case LOG_INFO: + return "INFO"; + case LOG_WARNING: + return "WARNING"; + case LOG_ERROR: + return "ERROR"; + case LOG_FATAL: + return "FATAL"; + } + + return "??"; +} + +FILE* LogStreamForLevel(AuroraLogLevel level) { + return level >= LOG_ERROR ? stderr : stdout; +} + +std::string MakeTimestampedLogName() { + const auto now = std::chrono::system_clock::now(); + const std::time_t nowTime = std::chrono::system_clock::to_time_t(now); + + std::tm localTime{}; +#if _WIN32 + localtime_s(&localTime, &nowTime); +#else + localtime_r(&nowTime, &localTime); +#endif + + std::array buffer{}; + std::strftime(buffer.data(), buffer.size(), "dusk-%Y%m%d-%H%M%S.log", &localTime); + return buffer.data(); +} + +void WriteLogLine(FILE* out, const char* levelStr, const char* module, const char* message, unsigned int len) { + if (out == nullptr) { + return; + } + + std::fprintf(out, "[%s | %s] ", levelStr, module); + std::fwrite(message, 1, len, out); + std::fputc('\n', out); + std::fflush(out); +} +} // namespace + static bool IsForStubLog(const char* message) { std::string_view msg_view(message); @@ -40,32 +99,69 @@ void aurora_log_callback(AuroraLogLevel level, const char* module, const char* m return; } - const char* levelStr = "??"; - FILE* out = stdout; - switch (level) { - case LOG_DEBUG: - levelStr = "DEBUG"; - break; - case LOG_INFO: - levelStr = "INFO"; - break; - case LOG_WARNING: - levelStr = "WARNING"; - break; - case LOG_ERROR: - levelStr = "ERROR"; - out = stderr; - break; - case LOG_FATAL: - levelStr = "FATAL"; - out = stderr; - break; + if (module == nullptr) { + module = ""; } - fprintf(out, "[%s | %s] %s\n", levelStr, module, message); + + const char* levelStr = LogLevelString(level); + FILE* out = LogStreamForLevel(level); + WriteLogLine(out, levelStr, module, message, len); + + { + std::lock_guard lock(g_logMutex); + if (g_logFile != nullptr) { + WriteLogLine(g_logFile, levelStr, module, message, len); + } + } + if (level == LOG_FATAL) { - fflush(out); abort(); } } aurora::Module DuskLog("dusk"); + +void dusk::InitializeFileLogging(const char* configDir, AuroraLogLevel logLevel) { + std::lock_guard lock(g_logMutex); + if (g_logFile != nullptr || configDir == nullptr) { + return; + } + + std::error_code ec; + const std::filesystem::path logsDir = std::filesystem::path(configDir) / "logs"; + std::filesystem::create_directories(logsDir, ec); + if (ec) { + std::fprintf(stderr, "[WARNING | dusk] Failed to create log directory '%s': %s\n", + logsDir.string().c_str(), ec.message().c_str()); + return; + } + + const std::filesystem::path logPath = logsDir / MakeTimestampedLogName(); + g_logFile = std::fopen(logPath.string().c_str(), "wb"); + if (g_logFile == nullptr) { + std::fprintf(stderr, "[WARNING | dusk] Failed to open log file '%s'\n", + logPath.string().c_str()); + return; + } + + g_logFilePath = logPath.string(); + aurora::g_config.logCallback = &aurora_log_callback; + aurora::g_config.logLevel = logLevel; + WriteLogLine(g_logFile, "INFO", "dusk", "File logging initialized", 24); +} + +void dusk::ShutdownFileLogging() { + std::lock_guard lock(g_logMutex); + if (g_logFile == nullptr) { + return; + } + + std::fflush(g_logFile); + std::fclose(g_logFile); + g_logFile = nullptr; +} + +const char* dusk::GetLogFilePath() { + std::lock_guard lock(g_logMutex); + return g_logFilePath.empty() ? nullptr : g_logFilePath.c_str(); +} diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 2c6278e202..fbe0bce3cc 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -43,6 +43,8 @@ #include #include +#include +#include #include #include "SSystem/SComponent/c_API.h" #include "dusk/app_info.hpp" @@ -366,6 +368,48 @@ static const char* CalculateConfigPath() { return result; } +static void EnsureInitialPipelineCache(const char* configDir) { + if (configDir == nullptr) { + return; + } + + const std::filesystem::path configPathFs(configDir); + const std::filesystem::path pipelineCachePath = configPathFs / "pipeline_cache.db"; + if (std::filesystem::exists(pipelineCachePath)) { + return; + } + + const char* basePath = SDL_GetBasePath(); + if (basePath == nullptr) { + DuskLog.warn("Unable to resolve base path while seeding pipeline cache: {}", SDL_GetError()); + return; + } + + const std::filesystem::path initialPipelineCachePath = + std::filesystem::path(basePath) / "initial_pipeline_cache.db"; + if (!std::filesystem::exists(initialPipelineCachePath)) { + DuskLog.info("No bundled initial pipeline cache found at '{}'", initialPipelineCachePath.string()); + return; + } + + std::error_code ec; + std::filesystem::create_directories(configPathFs, ec); + if (ec) { + DuskLog.warn("Failed to create config directory '{}' for pipeline cache: {}", + configPathFs.string(), ec.message()); + return; + } + + std::filesystem::copy_file(initialPipelineCachePath, pipelineCachePath, std::filesystem::copy_options::none, ec); + if (ec) { + DuskLog.warn("Failed to seed pipeline cache from '{}' to '{}': {}", + initialPipelineCachePath.string(), pipelineCachePath.string(), ec.message()); + return; + } + + DuskLog.info("Seeded pipeline cache from '{}'", initialPipelineCachePath.string()); +} + static constexpr PADDefaultMapping defaultPadMapping = { .buttons = { {SDL_GAMEPAD_BUTTON_SOUTH, PAD_BUTTON_A}, @@ -444,10 +488,13 @@ int game_main(int argc, char* argv[]) { } configPath = CalculateConfigPath(); + const auto startupLogLevel = static_cast(parsed_arg_options["log-level"].as()); + dusk::InitializeFileLogging(configPath, startupLogLevel); dusk::config::LoadFromUserPreferences(); ApplyCVarOverrides(parsed_arg_options["cvar"]); dusk::InitializeCrashReporting(); + EnsureInitialPipelineCache(configPath); AuroraConfig config{}; config.appName = dusk::AppName; @@ -460,7 +507,7 @@ int game_main(int argc, char* argv[]) { config.windowHeight = defaultWindowHeight * 2; config.desiredBackend = ResolveDesiredBackend(parsed_arg_options); config.logCallback = &aurora_log_callback; - config.logLevel = (AuroraLogLevel)parsed_arg_options["log-level"].as(); + config.logLevel = startupLogLevel; config.mem1Size = 256 * 1024 * 1024; config.mem2Size = 24 * 1024 * 1024; config.allowJoystickBackgroundEvents = true; @@ -542,6 +589,7 @@ int game_main(int argc, char* argv[]) { main01(); dusk::ShutdownCrashReporting(); + dusk::ShutdownFileLogging(); fflush(stdout); fflush(stderr);