From 854b291c63360ff24e590809aff54cf5ab2dd4c9 Mon Sep 17 00:00:00 2001 From: Niram7777 Date: Sat, 18 Apr 2026 23:57:05 +0200 Subject: [PATCH] Spdlog migration (#4069) * spdlog migration * gitmodule instead of cmake --- .gitmodules | 5 + CMakeLists.txt | 15 +- documents/Debugging/Debugging.md | 40 +- externals/CMakeLists.txt | 5 + externals/spdlog | 1 + src/common/assert.cpp | 7 +- src/common/config.cpp | 186 +++++--- src/common/config.h | 29 +- src/common/logging/backend.cpp | 396 ------------------ src/common/logging/backend.h | 34 -- src/common/logging/classes.h | 112 +++++ src/common/logging/filter.cpp | 246 ----------- src/common/logging/filter.h | 66 --- src/common/logging/log.cpp | 203 +++++++++ src/common/logging/log.h | 78 ++-- src/common/logging/log_entry.h | 28 -- src/common/logging/text_formatter.cpp | 110 ----- src/common/logging/text_formatter.h | 21 - src/common/logging/thread_name_formatter.h | 68 +++ src/common/logging/types.h | 136 ------ src/common/path_util.cpp | 1 + src/core/devtools/widget/text_editor.cpp | 1 + src/core/emulator_settings.cpp | 40 +- src/core/emulator_settings.h | 92 ++-- src/core/libraries/ajm/ajm_batch.cpp | 4 +- src/core/libraries/ime/ime.cpp | 26 +- src/core/libraries/np/trophy_ui.cpp | 1 + src/core/libraries/save_data/save_backup.cpp | 2 +- src/emulator.cpp | 19 +- src/imgui/settings_dialog_imgui.cpp | 27 +- src/main.cpp | 156 +++---- src/video_core/renderdoc.cpp | 1 + .../renderer_vulkan/vk_platform.cpp | 10 +- .../texture_cache/texture_cache.cpp | 2 +- tests/CMakeLists.txt | 8 +- tests/stubs/common_stub.cpp | 10 + tests/stubs/log_stub.cpp | 27 -- tests/test_emulator_settings.cpp | 12 +- 38 files changed, 853 insertions(+), 1372 deletions(-) create mode 160000 externals/spdlog delete mode 100644 src/common/logging/backend.cpp delete mode 100644 src/common/logging/backend.h create mode 100644 src/common/logging/classes.h delete mode 100644 src/common/logging/filter.cpp delete mode 100644 src/common/logging/filter.h create mode 100644 src/common/logging/log.cpp delete mode 100644 src/common/logging/log_entry.h delete mode 100644 src/common/logging/text_formatter.cpp delete mode 100644 src/common/logging/text_formatter.h create mode 100644 src/common/logging/thread_name_formatter.h delete mode 100644 src/common/logging/types.h create mode 100644 tests/stubs/common_stub.cpp delete mode 100644 tests/stubs/log_stub.cpp diff --git a/.gitmodules b/.gitmodules index 25eb43286..5211eee3b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -136,3 +136,8 @@ [submodule "externals/ImGuiFileDialog"] path = externals/ImGuiFileDialog url = https://github.com/shadexternals/ImGuiFileDialog.git +[submodule "externals/spdlog"] + path = externals/spdlog + url = https://github.com/gabime/spdlog.git + shallow = true + branch = v2.x diff --git a/CMakeLists.txt b/CMakeLists.txt index f4bed51c6..21ae48a22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,8 @@ endif() project(shadPS4 CXX C ASM ${ADDITIONAL_LANGUAGES}) +include(FetchContent) + # Forcing PIE makes sure that the base address is high enough so that it doesn't clash with the PS4 memory. if(UNIX AND NOT APPLE) set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) @@ -701,16 +703,11 @@ set(DEV_TOOLS src/core/devtools/layer.cpp src/core/devtools/widget/text_editor.h ) -set(COMMON src/common/logging/backend.cpp - src/common/logging/backend.h - src/common/logging/filter.cpp - src/common/logging/filter.h +set(COMMON src/common/logging/classes.h src/common/logging/formatter.h - src/common/logging/log_entry.h + src/common/logging/log.cpp src/common/logging/log.h - src/common/logging/text_formatter.cpp - src/common/logging/text_formatter.h - src/common/logging/types.h + src/common/logging/thread_name_formatter.h src/common/aes.h src/common/alignment.h src/common/arch.h @@ -1149,7 +1146,7 @@ create_target_directory_groups(shadps4) target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui ImGuiFileDialog gcn half::half ZLIB::ZLIB PNG::PNG minimp3) target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 pugixml::pugixml) -target_link_libraries(shadps4 PRIVATE stb::headers lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz::miniz fdk-aac CLI11::CLI11 OpenAL::OpenAL Cpp_Httplib) +target_link_libraries(shadps4 PRIVATE stb::headers lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz::miniz fdk-aac CLI11::CLI11 OpenAL::OpenAL Cpp_Httplib spdlog::spdlog) if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") target_link_libraries(shadps4 PRIVATE "/usr/lib/libusb.so") diff --git a/documents/Debugging/Debugging.md b/documents/Debugging/Debugging.md index 013ca15fb..82a93e670 100644 --- a/documents/Debugging/Debugging.md +++ b/documents/Debugging/Debugging.md @@ -58,30 +58,38 @@ You can configure the emulator by editing the `config.toml` file found in the `u
Some configuration entries worth changing -- `[General]` - - - `logType`: Configures logging synchronization (`sync`/`async`) - - By default, the emulator logs messages asynchronously for better performance. Some log messages may end up being received out-of-order. - - It can be beneficial to set this to `sync` in order for the log to accurately maintain message order, at the cost of performance. - - When communicating about issues with games and the log messages aren't clear due to potentially confusing order, set this to `sync` and send that log as well. - - `logFilter`: Sets the logging category for various logging classes. - - Format: `: ...` - - Multiple classes can be set by separating them with a space. (example: `Render:Warning Debug:Critical Lib.Pad:Error`) +- `[Log]` + - `sync`: Log synchronously (`true`/`false`) + - By default `true`, the emulator logs messages synchronously to respect the order. + - It can be beneficial to set this to `false` for better performance. + - When communicating about issues with games and the log messages aren't clear due to potentially confusing order, set this to `true` and send that log instead. + - `filter`: Sets the logging category for various logging classes. + - Format: `=,...` + - Multiple classes can be set by separating them with a comma. (example: `Render=warning,Debug=critical,Lib.Pad=error`) - Sub-classes can be specified in the same format as seen in the console/log (such as `Core.Linker`). - - All classes and sub-classes can be set by specifying a `*` symbol. (example: `Kernel.*:Critical`) - - Valid log levels: `Trace, Debug, Info, Warning, Error, Critical` - in this order, setting a level silences all levels preceding it and logs every level after it. + - Valid log levels: `trace, debug, info, warning, error, critical` - in this order, setting a level silences all levels preceding it and logs every level after it. - Examples: - - If the log is being spammed with messages coming from Lib.Pad, you can use `Lib.Pad:Critical` to only log critical-level messages. - - If you'd like to mute everything, but still want to receive messages from Vulkan rendering: `*:Critical Render.Vulkan:Info` - - `isIdenticalLogGrouped`: Group same logs in one line with a counter (`true`/`false`) - - By default, the emulator will not rewrite the same line, and instead add a counter. + - If the log is being spammed with messages coming from Lib.Pad, you can use `Lib.Pad=critical` to only log critical-level messages. + - If you'd like to mute everything, but still want to receive messages from Vulkan rendering: `off,Render.Vulkan=info` (if you want critical at least `critical,Render.Vulkan=info`) + - `skipDuplicate`: Skip same lines with a `Skipped N duplicate messages..` message (`true`/`false`) + - By default, the emulator will skip same lines for `maxSkipDuration` milliseconds. + - `append`: Append log to the existing file (`true`/`false`) + - By default, the emulator will overwrite the log file. (it can also be by-passed with CLI `--log-append`) + - `separate`: Write log to `log/{GAME ID}.log` instead of `log/shad_log.txt` (`true`/`false`) + - By default, the emulator use `log/shad_log.txt`. + - `maxSkipDuration`: Amount of time in which identical lines will not be logged (milliseconds). + - By default, 5'000 milliseconds. + - `sizeLimit`: Size limit for log files (bytes). + - By default, 100 MB. + - `type`: Choose between `wincolor` (WriteConsole*) and `msvc` (OutputDebugString*) - only for Windows. + - By default, `wincolor`. - - `Fullscreen`: Display the game in a full screen borderless window. - `[GPU]` - `dumpShaders`: Dump shaders that are loaded by the emulator. Dump path: `../user/shader/dumps` - `nullGpu`: Disables rendering. - `screenWidth` and `screenHeight`: Configures the game window width and height. + - `Fullscreen`: Display the game in a full screen borderless window. - `[Vulkan]` - `validation`-related settings: Use when debugging Vulkan. diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 9161a3d16..7560b66f3 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -21,6 +21,11 @@ if (NOT TARGET Boost::headers) add_subdirectory(ext-boost) endif() +# spdlog +set(SPDLOG_NO_EXCEPTIONS ON) +set(SPDLOG_DISABLE_GLOBAL_LOGGER ON) +add_subdirectory(spdlog) + # fmtlib if (NOT TARGET fmt::fmt) add_subdirectory(fmt) diff --git a/externals/spdlog b/externals/spdlog new file mode 160000 index 000000000..b8944a4bc --- /dev/null +++ b/externals/spdlog @@ -0,0 +1 @@ +Subproject commit b8944a4bcd478ee03375c9c50dc8d6c741f43f7b diff --git a/src/common/assert.cpp b/src/common/assert.cpp index be0feb71d..b80191211 100644 --- a/src/common/assert.cpp +++ b/src/common/assert.cpp @@ -3,7 +3,6 @@ #include "common/arch.h" #include "common/assert.h" -#include "common/logging/backend.h" #if defined(ARCH_X86_64) #define Crash() __asm__ __volatile__("int $3") @@ -14,14 +13,12 @@ #endif void assert_fail_impl() { - Common::Log::Stop(); - std::fflush(stdout); + Common::Log::Shutdown(); Crash(); } [[noreturn]] void unreachable_impl() { - Common::Log::Stop(); - std::fflush(stdout); + Common::Log::Shutdown(); Crash(); throw std::runtime_error("Unreachable code"); } diff --git a/src/common/config.cpp b/src/common/config.cpp index 60efdc656..c8f5aa4b9 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -55,6 +55,10 @@ std::optional get_optional(const toml::value& v, const std::string& key) { if (it->second.is_integer()) { return static_cast(toml::get(it->second)); } + } else if constexpr (std::is_same_v) { + if (it->second.is_integer()) { + return static_cast(toml::get(it->second)); + } } else if constexpr (std::is_same_v) { if (it->second.is_floating()) { return toml::get(it->second); @@ -139,9 +143,6 @@ static ConfigEntry extraDmemInMbytes(0); static ConfigEntry isPSNSignedIn(false); static ConfigEntry isTrophyPopupDisabled(false); static ConfigEntry trophyNotificationDuration(6.0); -static ConfigEntry logFilter(""); -static ConfigEntry logType("sync"); -static ConfigEntry isIdenticalLogGrouped(true); static ConfigEntry userName("shadPS4"); static ConfigEntry isShowSplash(false); static ConfigEntry isSideTrophy("right"); @@ -150,6 +151,19 @@ static bool enableDiscordRPC = false; static std::filesystem::path sys_modules_path = {}; static std::filesystem::path fonts_path = {}; +// Log +static ConfigEntry logAppend(false); +static ConfigEntry logEnable(true); +static ConfigEntry logFilter(""); +static ConfigEntry logMaxSkipDuration(5'000); +static ConfigEntry logSeparate(false); +static ConfigEntry logSizeLimit(100_MB); +static ConfigEntry logSkipDuplicate(true); +static ConfigEntry logSync(true); +#ifdef _WIN32 +static ConfigEntry logType("wincolor"); +#endif + // Input static ConfigEntry cursorState(HideCursorState::Idle); static ConfigEntry cursorHideTimeout(5); // 5 seconds (default) @@ -202,9 +216,7 @@ static ConfigEntry pipelineCacheArchive(false); // Debug static ConfigEntry isDebugDump(false); static ConfigEntry isShaderDebug(false); -static ConfigEntry isSeparateLogFilesEnabled(false); static ConfigEntry showFpsCounter(false); -static ConfigEntry logEnabled(true); // GUI static std::vector settings_install_dirs = {}; @@ -277,10 +289,69 @@ int* GetControllerCustomColor() { return controllerCustomColorRGB; } -bool getLoggingEnabled() { - return logEnabled.get(); +// Log +bool isLogAppend() { + return logAppend.get(); } +bool isLogEnable() { + return logEnable.get(); +} + +void setLogEnable(bool enable, bool is_game_specific) { + logEnable.set(enable, is_game_specific); +} + +bool getLogEnable() { + return logEnable.get(); +} + +string getLogFilter() { + return logFilter.get(); +} + +void setLogFilter(const string& type, bool is_game_specific) { + logFilter.set(type, is_game_specific); +} + +u32 getLogMaxSkipDuration() { + return logMaxSkipDuration.get(); +} + +bool getLogSeparate() { + return logSeparate.get(); +} + +void setLogSeparate(bool enabled, bool is_game_specific) { + logSeparate.set(enabled, is_game_specific); +} + +unsigned long long getLogSizeLimit() { + return logSizeLimit.get(); +} + +bool getLogSkipDuplicate() { + return logSkipDuplicate.get(); +} + +void setLogSkipDuplicate(bool enable, bool is_game_specific) { + logSkipDuplicate.set(enable, is_game_specific); +} + +bool isLogSync() { + return logSync.get(); +} + +#ifdef _WIN32 +string getLogType() { + return logType.get(); +} + +void setLogType(const string& type, bool is_game_specific) { + logType.set(type, is_game_specific); +} +#endif + void SetControllerCustomColor(int r, int b, int g) { controllerCustomColorRGB[0] = r; controllerCustomColorRGB[1] = b; @@ -388,18 +459,6 @@ s32 getGpuId() { return gpuId.get(); } -string getLogFilter() { - return logFilter.get(); -} - -string getLogType() { - return logType.get(); -} - -bool groupIdenticalLogs() { - return isIdenticalLogGrouped.get(); -} - string getUserName() { return userName.get(); } @@ -480,10 +539,6 @@ void setShowFpsCounter(bool enable, bool is_game_specific) { showFpsCounter.set(enable, is_game_specific); } -bool isLoggingEnabled() { - return logEnabled.get(); -} - u32 vblankFreq() { if (vblankFrequency.get() < 60) { vblankFrequency = 60; @@ -563,10 +618,6 @@ void setDebugDump(bool enable, bool is_game_specific) { isDebugDump.set(enable, is_game_specific); } -void setLoggingEnabled(bool enable, bool is_game_specific) { - logEnabled.set(enable, is_game_specific); -} - void setCollectShaderForDebug(bool enable, bool is_game_specific) { isShaderDebug.set(enable, is_game_specific); } @@ -695,22 +746,6 @@ void setDevKitConsole(bool enable, bool is_game_specific) { isDevKit.set(enable, is_game_specific); } -void setLogType(const string& type, bool is_game_specific) { - logType.set(type, is_game_specific); -} - -void setIdenticalLogGrouped(bool enable, bool is_game_specific) { - isIdenticalLogGrouped.set(enable, is_game_specific); -} - -void setLogFilter(const string& type, bool is_game_specific) { - logFilter.set(type, is_game_specific); -} - -void setSeparateLogFilesEnabled(bool enabled, bool is_game_specific) { - isSeparateLogFilesEnabled.set(enabled, is_game_specific); -} - void setUserName(const string& name, bool is_game_specific) { userName.set(name, is_game_specific); } @@ -804,10 +839,6 @@ u32 GetLanguage() { return m_language.get(); } -bool getSeparateLogFilesEnabled() { - return isSeparateLogFilesEnabled.get(); -} - bool getPSNSignedIn() { return isPSNSignedIn.get(); } @@ -900,9 +931,6 @@ void load(const std::filesystem::path& path, bool is_game_specific) { trophyNotificationDuration.setFromToml(general, "trophyNotificationDuration", is_game_specific); enableDiscordRPC = toml::find_or(general, "enableDiscordRPC", enableDiscordRPC); - logFilter.setFromToml(general, "logFilter", is_game_specific); - logType.setFromToml(general, "logType", is_game_specific); - isIdenticalLogGrouped.setFromToml(general, "isIdenticalLogGrouped", is_game_specific); userName.setFromToml(general, "userName", is_game_specific); isShowSplash.setFromToml(general, "showSplash", is_game_specific); isSideTrophy.setFromToml(general, "sideTrophy", is_game_specific); @@ -913,6 +941,22 @@ void load(const std::filesystem::path& path, bool is_game_specific) { fonts_path = toml::find_fs_path_or(general, "fontsPath", fonts_path); } + if (data.contains("Log")) { + const toml::value& log = data.at("Log"); + + logAppend.setFromToml(log, "append", is_game_specific); + logEnable.setFromToml(log, "enable", is_game_specific); + logFilter.setFromToml(log, "filter", is_game_specific); + logMaxSkipDuration.setFromToml(log, "maxSkipDuration", is_game_specific); + logSeparate.setFromToml(log, "separate", is_game_specific); + logSkipDuplicate.setFromToml(log, "skipDuplicate", is_game_specific); + logSizeLimit.setFromToml(log, "sizeLimit", is_game_specific); + logSync.setFromToml(log, "sync", is_game_specific); +#ifdef _WIN32 + logType.setFromToml(log, "type", is_game_specific); +#endif + } + if (data.contains("Input")) { const toml::value& input = data.at("Input"); @@ -979,10 +1023,8 @@ void load(const std::filesystem::path& path, bool is_game_specific) { const toml::value& debug = data.at("Debug"); isDebugDump.setFromToml(debug, "DebugDump", is_game_specific); - isSeparateLogFilesEnabled.setFromToml(debug, "isSeparateLogFilesEnabled", is_game_specific); isShaderDebug.setFromToml(debug, "CollectShader", is_game_specific); showFpsCounter.setFromToml(debug, "showFpsCounter", is_game_specific); - logEnabled.setFromToml(debug, "logEnabled", is_game_specific); current_version = toml::find_or(debug, "ConfigVersion", current_version); } @@ -1033,8 +1075,8 @@ void load(const std::filesystem::path& path, bool is_game_specific) { void sortTomlSections(toml::ordered_value& data) { toml::ordered_value ordered_data; - std::vector section_order = {"General", "Input", "Audio", "GPU", "Vulkan", - "Debug", "Keys", "GUI", "Settings"}; + std::vector section_order = {"General", "Log", "Input", "Audio", "GPU", + "Vulkan", "Debug", "Keys", "GUI", "Settings"}; for (const auto& section : section_order) { if (data.contains(section)) { @@ -1089,9 +1131,6 @@ void save(const std::filesystem::path& path, bool is_game_specific) { isTrophyPopupDisabled.setTomlValue(data, "General", "isTrophyPopupDisabled", is_game_specific); trophyNotificationDuration.setTomlValue(data, "General", "trophyNotificationDuration", is_game_specific); - logFilter.setTomlValue(data, "General", "logFilter", is_game_specific); - logType.setTomlValue(data, "General", "logType", is_game_specific); - isIdenticalLogGrouped.setTomlValue(data, "General", "isIdenticalLogGrouped", is_game_specific); userName.setTomlValue(data, "General", "userName", is_game_specific); isShowSplash.setTomlValue(data, "General", "showSplash", is_game_specific); isSideTrophy.setTomlValue(data, "General", "sideTrophy", is_game_specific); @@ -1103,6 +1142,18 @@ void save(const std::filesystem::path& path, bool is_game_specific) { isPSNSignedIn.setTomlValue(data, "General", "isPSNSignedIn", is_game_specific); isConnectedToNetwork.setTomlValue(data, "General", "isConnectedToNetwork", is_game_specific); + logAppend.setTomlValue(data, "Log", "append", is_game_specific); + logEnable.setTomlValue(data, "Log", "enable", is_game_specific); + logFilter.setTomlValue(data, "Log", "filter", is_game_specific); + logMaxSkipDuration.setTomlValue(data, "Log", "maxSkipDuration", is_game_specific); + logSeparate.setTomlValue(data, "Log", "separate", is_game_specific); + logSizeLimit.setTomlValue(data, "Log", "sizeLimit", is_game_specific); + logSkipDuplicate.setTomlValue(data, "Log", "skipDuplicate", is_game_specific); + logSync.setTomlValue(data, "Log", "sync", is_game_specific); +#ifdef _WIN32 + logType.setTomlValue(data, "Log", "type", is_game_specific); +#endif + cursorState.setTomlValue(data, "Input", "cursorState", is_game_specific); cursorHideTimeout.setTomlValue(data, "Input", "cursorHideTimeout", is_game_specific); isMotionControlsEnabled.setTomlValue(data, "Input", "isMotionControlsEnabled", @@ -1146,9 +1197,6 @@ void save(const std::filesystem::path& path, bool is_game_specific) { isDebugDump.setTomlValue(data, "Debug", "DebugDump", is_game_specific); isShaderDebug.setTomlValue(data, "Debug", "CollectShader", is_game_specific); - isSeparateLogFilesEnabled.setTomlValue(data, "Debug", "isSeparateLogFilesEnabled", - is_game_specific); - logEnabled.setTomlValue(data, "Debug", "logEnabled", is_game_specific); m_language.setTomlValue(data, "Settings", "consoleLanguage", is_game_specific); @@ -1233,13 +1281,23 @@ void setDefaultValues(bool is_game_specific) { volumeSlider.set(100, is_game_specific); isTrophyPopupDisabled.set(false, is_game_specific); trophyNotificationDuration.set(6.0, is_game_specific); - logFilter.set("", is_game_specific); - logType.set("sync", is_game_specific); - isIdenticalLogGrouped.set("isIdenticalLogGrouped", is_game_specific); userName.set("shadPS4", is_game_specific); isShowSplash.set(false, is_game_specific); isSideTrophy.set("right", is_game_specific); + // GS - Log + logAppend.set(false, is_game_specific); + logEnable.set(true, is_game_specific); + logFilter.set("", is_game_specific); + logMaxSkipDuration.set(5'000, is_game_specific); + logSeparate.set(false, is_game_specific); + logSkipDuplicate.set(true, is_game_specific); + logSync.set(true, is_game_specific); + logSizeLimit.set(100_MB, is_game_specific); +#ifdef _WIN32 + logType.set("wincolor", is_game_specific); +#endif + // GS - Input cursorState.set(HideCursorState::Idle, is_game_specific); cursorHideTimeout.set(5, is_game_specific); @@ -1281,8 +1339,6 @@ void setDefaultValues(bool is_game_specific) { // GS - Debug isDebugDump.set(false, is_game_specific); isShaderDebug.set(false, is_game_specific); - isSeparateLogFilesEnabled.set(false, is_game_specific); - logEnabled.set(true, is_game_specific); // GS - Settings m_language.set(1, is_game_specific); diff --git a/src/common/config.h b/src/common/config.h index b341030e0..e8f66ebde 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -105,12 +105,29 @@ bool isPipelineCacheArchived(); void setRdocEnabled(bool enable, bool is_game_specific = false); void setPipelineCacheEnabled(bool enable, bool is_game_specific = false); void setPipelineCacheArchived(bool enable, bool is_game_specific = false); -std::string getLogType(); -void setLogType(const std::string& type, bool is_game_specific = false); -bool groupIdenticalLogs(); -void setGroupIdenticalLogs(bool enable, bool is_game_specific = false); + +// Log +bool isLogAppend(); +void setLogAppend(bool enable, bool is_game_specific = false); +bool getLogEnable(); +void setLogEnable(bool enable, bool is_game_specific = false); std::string getLogFilter(); void setLogFilter(const std::string& type, bool is_game_specific = false); +u32 getLogMaxSkipDuration(); +void setLogMaxSkipDuration(u32 duration, bool is_game_specific = false); +bool getLogSeparateLogFilesEnabled(); +void setLogSeparateLogFilesEnabled(bool enabled, bool is_game_specific = false); +unsigned long long getLogSizeLimit(); +void setLogSizeLimit(unsigned long long size, bool is_game_specific = false); +bool getLogSkipDuplicate(); +void setLogSkipDuplicate(bool enable, bool is_game_specific = false); +bool isLogSync(); +void setLogSync(bool sync, bool is_game_specific = false); +#ifdef _WIN32 +std::string getLogType(); +void setLogType(const std::string& type, bool is_game_specific = false); +#endif + double getTrophyNotificationDuration(); void setTrophyNotificationDuration(double newTrophyNotificationDuration, bool is_game_specific = false); @@ -122,8 +139,6 @@ void setPadSpkOutputDevice(std::string device, bool is_game_specific = false); std::string getMicDevice(); void setCursorHideTimeout(int newcursorHideTimeout, bool is_game_specific = false); void setMicDevice(std::string device, bool is_game_specific = false); -void setSeparateLogFilesEnabled(bool enabled, bool is_game_specific = false); -bool getSeparateLogFilesEnabled(); u32 GetLanguage(); void setLanguage(u32 language, bool is_game_specific = false); void setUseSpecialPad(bool use); @@ -148,8 +163,6 @@ std::string getDefaultControllerID(); void setDefaultControllerID(std::string id); bool getBackgroundControllerInput(); void setBackgroundControllerInput(bool enable, bool is_game_specific = false); -bool getLoggingEnabled(); -void setLoggingEnabled(bool enable, bool is_game_specific = false); bool getFsrEnabled(); void setFsrEnabled(bool enable, bool is_game_specific = false); bool getRcasEnabled(); diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp deleted file mode 100644 index 5de4f64a0..000000000 --- a/src/common/logging/backend.cpp +++ /dev/null @@ -1,396 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project -// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include -#include - -#include - -#ifdef _WIN32 -#include // For OutputDebugStringW -#endif - -#include "common/bounded_threadsafe_queue.h" -#include "common/debug.h" -#include "common/io_file.h" -#include "common/logging/backend.h" -#include "common/logging/log.h" -#include "common/logging/log_entry.h" -#include "common/logging/text_formatter.h" -#include "common/path_util.h" -#include "common/string_util.h" -#include "common/thread.h" -#include "core/emulator_settings.h" - -namespace Common::Log { - -using namespace Common::FS; - -namespace { - -/** - * Backend that writes to stderr and with color - */ -class ColorConsoleBackend { -public: - explicit ColorConsoleBackend() = default; - - ~ColorConsoleBackend() = default; - - void Write(const Entry& entry) { - if (enabled.load(std::memory_order_relaxed)) { - PrintColoredMessage(entry); - } - } - - void Flush() { - // stderr shouldn't be buffered - } - - void SetEnabled(bool enabled_) { - enabled = enabled_; - } - -private: - std::atomic_bool enabled{true}; -}; - -/** - * Backend that writes to a file passed into the constructor - */ -class FileBackend { -public: - explicit FileBackend(const std::filesystem::path& filename, bool should_append = false) - : file{filename, should_append ? FS::FileAccessMode::Append : FS::FileAccessMode::Create, - FS::FileType::TextFile} {} - - ~FileBackend() = default; - - void Write(const Entry& entry) { - if (!enabled && entry.log_level != Level::Critical) { - return; - } - - bytes_written += file.WriteString(FormatLogMessage(entry).append(1, '\n')); - - // Prevent logs from exceeding a set maximum size in the event that log entries are spammed. - const auto write_limit = 100_MB; - const bool write_limit_exceeded = bytes_written > write_limit; - if (entry.log_level >= Level::Error || write_limit_exceeded) { - if (write_limit_exceeded) { - // Stop writing after the write limit is exceeded. - // Don't close the file so we can print a stacktrace if necessary - enabled = false; - } - file.Flush(); - } - } - - void Flush() { - file.Flush(); - } - -private: - Common::FS::IOFile file; - bool enabled = true; - std::size_t bytes_written = 0; -}; - -#ifdef _WIN32 -/** - * Backend that writes to Visual Studio's output window - */ -class DebuggerBackend { -public: - explicit DebuggerBackend() = default; - - ~DebuggerBackend() = default; - - void Write(const Entry& entry) { - ::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str()); - } - - void Flush() {} - - void EnableForStacktrace() {} -}; -#endif - -bool initialization_in_progress_suppress_logging = true; - -/** - * Static state as a singleton. - */ -class Impl { -public: - static Impl& Instance() { - if (!instance) { - throw std::runtime_error("Using Logging instance before its initialization"); - } - return *instance; - } - - static void Initialize(std::string_view log_file) { - if (instance) { - LOG_WARNING(Log, "Reinitializing logging backend"); - return; - } - const auto& log_dir = GetUserPath(PathType::LogDir); - std::filesystem::create_directory(log_dir); - Filter filter; - filter.ParseFilterString(EmulatorSettings.GetLogFilter()); - const auto& log_file_path = log_file.empty() ? LOG_FILE : log_file; - instance = std::unique_ptr( - new Impl(log_dir / log_file_path, filter), Deleter); - initialization_in_progress_suppress_logging = false; - } - - static void ResetInstance() { - initialization_in_progress_suppress_logging = true; - instance.reset(); - } - - static bool IsActive() { - return instance != nullptr; - } - - static void Start() { - instance->StartBackendThread(); - } - - static void Stop() { - instance->StopBackendThread(); - } - - static void SetAppend() { - should_append = true; - } - - Impl(const Impl&) = delete; - Impl& operator=(const Impl&) = delete; - - Impl(Impl&&) = delete; - Impl& operator=(Impl&&) = delete; - - void SetGlobalFilter(const Filter& f) { - filter = f; - } - - void SetColorConsoleBackendEnabled(bool enabled) { - color_console_backend.SetEnabled(enabled); - } - - void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num, - const char* function, const char* format, const fmt::format_args& args) { - if (!filter.CheckMessage(log_class, log_level) || !EmulatorSettings.IsLogEnabled()) { - return; - } - - const auto message = fmt::vformat(format, args); - - // Propagate important log messages to the profiler - if (IsProfilerConnected()) { - const auto& msg_str = fmt::format("[{}] {}", GetLogClassName(log_class), message); - switch (log_level) { - case Level::Warning: - TRACE_WARN(msg_str); - break; - case Level::Error: - TRACE_ERROR(msg_str); - break; - case Level::Critical: - TRACE_CRIT(msg_str); - break; - default: - break; - } - } - - using std::chrono::duration_cast; - using std::chrono::microseconds; - using std::chrono::steady_clock; - - if (EmulatorSettings.IsIdenticalLogGrouped()) { - std::unique_lock entry_loc(_mutex); - - if (_last_entry.message == message) { - ++_last_entry.counter; - return; - } - - if (_last_entry.counter >= 2) { - _last_entry.message += " x" + std::to_string(_last_entry.counter); - } - - if (_last_entry.counter >= 1) { - if (EmulatorSettings.GetLogType() == "async") { - message_queue.EmplaceWait(_last_entry); - } else { - ForEachBackend([this](auto& backend) { backend.Write(this->_last_entry); }); - std::fflush(stdout); - } - } - - this->_last_entry = { - .timestamp = duration_cast(steady_clock::now() - time_origin), - .log_class = log_class, - .log_level = log_level, - .filename = filename, - .line_num = line_num, - .function = function, - .message = message, - .thread = Common::GetCurrentThreadName(), - .counter = 1, - }; - } else { - const Entry entry = { - .timestamp = duration_cast(steady_clock::now() - time_origin), - .log_class = log_class, - .log_level = log_level, - .filename = filename, - .line_num = line_num, - .function = function, - .message = message, - .thread = Common::GetCurrentThreadName(), - .counter = 1, - }; - - if (EmulatorSettings.GetLogType() == "async") { - message_queue.EmplaceWait(entry); - } else { - ForEachBackend([&entry](auto& backend) { backend.Write(entry); }); - std::fflush(stdout); - } - } - } - -private: - Impl(const std::filesystem::path& file_backend_filename, const Filter& filter_) - : filter{filter_}, file_backend{file_backend_filename, should_append} {} - - ~Impl() = default; - - void StartBackendThread() { - backend_thread = std::jthread([this](std::stop_token stop_token) { - Common::SetCurrentThreadName("shadPS4:Log"); - Entry entry; - const auto write_logs = [this, &entry]() { - ForEachBackend([&entry](auto& backend) { backend.Write(entry); }); - }; - while (!stop_token.stop_requested()) { - message_queue.PopWait(entry, stop_token); - if (entry.filename != nullptr) { - write_logs(); - } - } - // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a - // case where a system is repeatedly spamming logs even on close. - int max_logs_to_write = filter.IsDebug() ? std::numeric_limits::max() : 100; - while (max_logs_to_write-- && message_queue.TryPop(entry)) { - write_logs(); - } - }); - } - - void StopBackendThread() { - if (EmulatorSettings.IsIdenticalLogGrouped()) { - // log last message - if (_last_entry.counter >= 2) { - _last_entry.message += " x" + std::to_string(_last_entry.counter); - } - - if (_last_entry.counter >= 1) { - if (EmulatorSettings.GetLogType() == "async") { - message_queue.EmplaceWait(_last_entry); - } else { - ForEachBackend([this](auto& backend) { backend.Write(this->_last_entry); }); - std::fflush(stdout); - } - } - - this->_last_entry = {}; - } - - backend_thread.request_stop(); - if (backend_thread.joinable()) { - backend_thread.join(); - } - - ForEachBackend([](auto& backend) { backend.Flush(); }); - } - - void ForEachBackend(auto lambda) { -#ifdef _WIN32 - lambda(debugger_backend); -#endif - lambda(color_console_backend); - lambda(file_backend); - } - - static void Deleter(Impl* ptr) { - delete ptr; - } - - static inline std::unique_ptr instance{nullptr, Deleter}; - static inline bool should_append{false}; - - Filter filter; -#ifdef _WIN32 - DebuggerBackend debugger_backend{}; -#endif - ColorConsoleBackend color_console_backend{}; - FileBackend file_backend; - - MPSCQueue message_queue{}; - std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()}; - std::jthread backend_thread; - Entry _last_entry; - std::mutex _mutex; -}; -} // namespace - -void Initialize(std::string_view log_file) { - Impl::Initialize(log_file.empty() ? LOG_FILE : log_file); -} - -bool IsActive() { - return Impl::IsActive(); -} - -void Start() { - Impl::Start(); -} - -void Stop() { - Impl::Stop(); -} - -void Denitializer() { - Impl::Stop(); - Impl::ResetInstance(); -} - -void SetGlobalFilter(const Filter& filter) { - Impl::Instance().SetGlobalFilter(filter); -} - -void SetColorConsoleBackendEnabled(bool enabled) { - Impl::Instance().SetColorConsoleBackendEnabled(enabled); -} - -void SetAppend() { - Impl::SetAppend(); -} - -void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, - unsigned int line_num, const char* function, const char* format, - const fmt::format_args& args) { - if (!initialization_in_progress_suppress_logging) [[likely]] { - Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function, format, - args); - } -} -} // namespace Common::Log diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h deleted file mode 100644 index 3003d1eae..000000000 --- a/src/common/logging/backend.h +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include "common/logging/filter.h" - -namespace Common::Log { - -class Filter; - -/// Initializes the logging system. This should be the first thing called in main. -void Initialize(std::string_view log_file = ""); - -bool IsActive(); - -/// Starts the logging threads. -void Start(); - -/// Explictily stops the logger thread and flushes the buffers -void Stop(); - -/// Closes log files and stops the logger -void Denitializer(); - -/// The global filter will prevent any messages from even being processed if they are filtered. -void SetGlobalFilter(const Filter& filter); - -void SetColorConsoleBackendEnabled(bool enabled); - -void SetAppend(); - -} // namespace Common::Log diff --git a/src/common/logging/classes.h b/src/common/logging/classes.h new file mode 100644 index 000000000..eab6dfe27 --- /dev/null +++ b/src/common/logging/classes.h @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +namespace Common::Log::Class { +// clang-format off +/// Listing all log classes, if you add here, dont forget ALL_LOGGERS +constexpr auto Common = "Common"; ///< Library routines +constexpr auto Common_Filesystem = "Common.Filesystem"; ///< Filesystem interface library +constexpr auto Common_Memory = "Common.Memory"; ///< Memory mapping and management functions +constexpr auto Config = "Config"; ///< Emulator configuration (including commandline) +constexpr auto Core = "Core"; ///< LLE emulation core +constexpr auto Core_Devices = "Core.Devices"; ///< Devices emulation +constexpr auto Core_Linker = "Core.Linker"; ///< The module linker +constexpr auto Debug = "Debug"; ///< Debugging tools +constexpr auto Frontend = "Frontend"; ///< Emulator UI +constexpr auto IPC = "IPC"; ///< IPC +constexpr auto ImGui = "ImGui"; ///< ImGui +constexpr auto Input = "Input"; ///< Input emulation +constexpr auto Kernel = "Kernel"; ///< The HLE implementation of the PS4 kernel. +constexpr auto Kernel_Event = "Kernel.Event"; ///< The event management implementation of the kernel. +constexpr auto Kernel_Fs = "Kernel.Fs"; ///< The filesystem implementation of the kernel. +constexpr auto Kernel_Pthread = "Kernel.Pthread"; ///< The pthread implementation of the kernel. +constexpr auto Kernel_Sce = "Kernel.Sce"; ///< The Sony-specific interfaces provided by the kernel. +constexpr auto Kernel_Vmm = "Kernel.Vmm"; ///< The virtual memory implementation of the kernel. +constexpr auto KeyManager = "KeyManager"; ///< Key management system +constexpr auto Lib = "Lib"; ///< HLE implementation of system library. Each major library should have its own subclass. +constexpr auto Lib_Ajm = "Lib.Ajm"; ///< The LibSceAjm implementation. +constexpr auto Lib_AppContent = "Lib.AppContent"; ///< The LibSceAppContent implementation. +constexpr auto Lib_Audio3d = "Lib.Audio3d"; ///< The LibSceAudio3d implementation. +constexpr auto Lib_AudioIn = "Lib.AudioIn"; ///< The LibSceAudioIn implementation. +constexpr auto Lib_AudioOut = "Lib.AudioOut"; ///< The LibSceAudioOut implementation. +constexpr auto Lib_AvPlayer = "Lib.AvPlayer"; ///< The LibSceAvPlayer implementation. +constexpr auto Lib_Camera = "Lib.Camera"; ///< The LibCamera implementation. +constexpr auto Lib_CommonDlg = "Lib.CommonDlg"; ///< The LibSceCommonDialog implementation. +constexpr auto Lib_CompanionHttpd = "Lib.CompanionHttpd"; ///< The LibCompanionHttpd implementation. +constexpr auto Lib_CompanionUtil = "Lib.CompanionUtil"; ///< The LibCompanionUtil implementation. +constexpr auto Lib_DiscMap = "Lib.DiscMap"; ///< The LibSceDiscMap implementation. +constexpr auto Lib_ErrorDialog = "Lib.ErrorDialog"; ///< The LibSceErrorDialog implementation. +constexpr auto Lib_Fiber = "Lib.Fiber"; ///< The LibSceFiber implementation. +constexpr auto Lib_Font = "Lib.Font"; ///< The libSceFont implementation. +constexpr auto Lib_FontFt = "Lib.FontFt"; ///< The libSceFontFt implementation. +constexpr auto Lib_GameLiveStreaming = "Lib.GameLiveStreaming"; ///< The LibSceGameLiveStreaming implementation +constexpr auto Lib_GnmDriver = "Lib.GnmDriver"; ///< The LibSceGnmDriver implementation. +constexpr auto Lib_Hmd = "Lib.Hmd"; ///< The LibSceHmd implementation. +constexpr auto Lib_HmdSetupDialog = "Lib.HmdSetupDialog"; ///< The LibSceHmdSetupDialog implementation. +constexpr auto Lib_Http = "Lib.Http"; ///< The LibSceHttp implementation. +constexpr auto Lib_Http2 = "Lib.Http2"; ///< The LibSceHttp2 implementation. +constexpr auto Lib_Ime = "Lib.Ime"; ///< The LibSceIme implementation +constexpr auto Lib_ImeDialog = "Lib.ImeDialog"; ///< The LibSceImeDialog implementation. +constexpr auto Lib_Jpeg = "Lib.Jpeg"; ///< The LibSceJpeg implementation. +constexpr auto Lib_Kernel = "Lib.Kernel"; ///< The LibKernel implementation. +constexpr auto Lib_LibcInternal = "Lib.LibcInternal"; ///< The LibcInternal implementation. +constexpr auto Lib_Mouse = "Lib.Mouse"; ///< The LibSceMouse implementation +constexpr auto Lib_Move = "Lib.Move"; ///< The LibSceMove implementation. +constexpr auto Lib_MsgDlg = "Lib.MsgDlg"; ///< The LibSceMsgDialog implementation. +constexpr auto Lib_Net = "Lib.Net"; ///< The LibSceNet implementation. +constexpr auto Lib_NetCtl = "Lib.NetCtl"; ///< The LibSceNetCtl implementation. +constexpr auto Lib_Ngs2 = "Lib.Ngs2"; ///< The LibSceNgs2 implementation. +constexpr auto Lib_NpAuth = "Lib.NpAuth"; ///< The LibSceNpAuth implementation +constexpr auto Lib_NpCommerce = "Lib.NpCommerce"; ///< The LibSceNpCommerce implementation +constexpr auto Lib_NpCommon = "Lib.NpCommon"; ///< The LibSceNpCommon implementation +constexpr auto Lib_NpManager = "Lib.NpManager"; ///< The LibSceNpManager implementation +constexpr auto Lib_NpMatching2 = "Lib.NpMatching2"; ///< The LibSceNpMatching2 implementation +constexpr auto Lib_NpPartner = "Lib.NpPartner"; ///< The LibSceNpPartner implementation +constexpr auto Lib_NpParty = "Lib.NpParty"; ///< The LibSceNpParty implementation +constexpr auto Lib_NpProfileDialog = "Lib.NpProfileDialog"; ///< The LibSceNpProfileDialog implementation +constexpr auto Lib_NpScore = "Lib.NpScore"; ///< The LibSceNpScore implementation +constexpr auto Lib_NpSnsFacebookDialog = "Lib.NpSnsFacebookDialog"; ///< The LibSceNpSnsFacebookDialog implementation +constexpr auto Lib_NpTrophy = "Lib.NpTrophy"; ///< The LibSceNpTrophy implementation +constexpr auto Lib_NpTus = "Lib.NpTus"; ///< The LibSceNpTus implementation +constexpr auto Lib_NpWebApi = "Lib.NpWebApi"; ///< The LibSceWebApi implementation +constexpr auto Lib_NpWebApi2 = "Lib.NpWebApi2"; ///< The LibSceWebApi2 implementation +constexpr auto Lib_Pad = "Lib.Pad"; ///< The LibScePad implementation. +constexpr auto Lib_PlayGo = "Lib.PlayGo"; ///< The LibScePlayGo implementation. +constexpr auto Lib_PlayGoDialog = "Lib.PlayGoDialog"; ///< The LibScePlayGoDialog implementation. +constexpr auto Lib_Png = "Lib.Png"; ///< The LibScePng implementation. +constexpr auto Lib_Random = "Lib.Random"; ///< The LibSceRandom implementation. +constexpr auto Lib_RazorCpu = "Lib.RazorCpu"; ///< The LibRazorCpu implementation. +constexpr auto Lib_Remoteplay = "Lib.Remoteplay"; ///< The LibSceRemotePlay implementation +constexpr auto Lib_Rtc = "Lib.Rtc"; ///< The LibSceRtc implementation. +constexpr auto Lib_Rudp = "Lib.Rudp"; ///< The LibSceRudp implementation. +constexpr auto Lib_SaveData = "Lib.SaveData"; ///< The LibSceSaveData implementation. +constexpr auto Lib_SaveDataDialog = "Lib.SaveDataDialog"; ///< The LibSceSaveDataDialog implementation. +constexpr auto Lib_Screenshot = "Lib.Screenshot"; ///< The LibSceScreenshot implementation +constexpr auto Lib_SharePlay = "Lib.SharePlay"; ///< The LibSceSharePlay implemenation +constexpr auto Lib_SigninDialog = "Lib.SigninDialog"; ///< The LibSigninDialog implementation. +constexpr auto Lib_Ssl = "Lib.Ssl"; ///< The LibSceSsl implementation. +constexpr auto Lib_Ssl2 = "Lib.Ssl2"; ///< The LibSceSsl2 implementation. +constexpr auto Lib_SysModule = "Lib.SysModule"; ///< The LibSceSysModule implementation +constexpr auto Lib_SystemGesture = "Lib.SystemGesture"; ///< The LibSceSystemGesture implementation. +constexpr auto Lib_SystemService = "Lib.SystemService"; ///< The LibSceSystemService implementation. +constexpr auto Lib_Usbd = "Lib.Usbd"; ///< The LibSceUsbd implementation. +constexpr auto Lib_UserService = "Lib.UserService"; ///< The LibSceUserService implementation. +constexpr auto Lib_Vdec2 = "Lib.Vdec2"; ///< The LibSceVideodec2 implementation. +constexpr auto Lib_VideoOut = "Lib.VideoOut"; ///< The LibSceVideoOut implementation. +constexpr auto Lib_Videodec = "Lib.Videodec"; ///< The LibSceVideodec implementation. +constexpr auto Lib_Voice = "Lib.Voice"; ///< The LibSceVoice implementation. +constexpr auto Lib_VrTracker = "Lib.VrTracker"; ///< The LibSceVrTracker implementation. +constexpr auto Lib_WebBrowserDialog = "Lib.WebBrowserDialog"; ///< The LibSceWebBrowserDialog implementation +constexpr auto Lib_Zlib = "Lib.Zlib"; ///< The LibSceZlib implementation. +constexpr auto Loader = "Loader"; ///< ROM loader +constexpr auto Log = "Log"; ///< Messages about the log system itself +constexpr auto Render = "Render"; ///< Video Core +constexpr auto Render_Recompiler = "Render.Recompiler"; ///< Shader recompiler +constexpr auto Render_Vulkan = "Render.Vulkan"; ///< Vulkan backend +constexpr auto Tty = "Tty"; ///< Debug output from emu +// clang-format on +} // namespace Common::Log::Class diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp deleted file mode 100644 index fd48faf72..000000000 --- a/src/common/logging/filter.cpp +++ /dev/null @@ -1,246 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project -// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include - -#include "common/assert.h" -#include "common/logging/filter.h" - -namespace Common::Log { -namespace { -template -Level GetLevelByName(const It begin, const It end) { - for (u8 i = 0; i < static_cast(Level::Count); ++i) { - const char* level_name = GetLevelName(static_cast(i)); - if (std::string_view(begin, end).compare(level_name) == 0) { - return static_cast(i); - } - } - return Level::Count; -} - -template -Class GetClassByName(const It begin, const It end) { - for (u8 i = 0; i < static_cast(Class::Count); ++i) { - const char* level_name = GetLogClassName(static_cast(i)); - if (std::string_view(begin, end).compare(level_name) == 0) { - return static_cast(i); - } - } - return Class::Count; -} - -template -bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { - auto level_separator = std::find(begin, end, ':'); - if (level_separator == end) { - LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: {}", - std::string_view(begin, end)); - return false; - } - - const Level level = GetLevelByName(level_separator + 1, end); - if (level == Level::Count) { - LOG_ERROR(Log, "Unknown log level in filter: {}", std::string_view(begin, end)); - return false; - } - - if (std::string_view(begin, level_separator).compare("*") == 0) { - instance.ResetAll(level); - return true; - } - - const Class log_class = GetClassByName(begin, level_separator); - if (log_class == Class::Count) { - LOG_ERROR(Log, "Unknown log class in filter: {}", std::string(begin, end)); - return false; - } - - instance.SetClassLevel(log_class, level); - return true; -} -} // Anonymous namespace - -/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this. -#define ALL_LOG_CLASSES() \ - CLS(Log) \ - CLS(Common) \ - SUB(Common, Filesystem) \ - SUB(Common, Memory) \ - CLS(KeyManager) \ - CLS(Core) \ - SUB(Core, Linker) \ - SUB(Core, Devices) \ - CLS(Config) \ - CLS(Debug) \ - CLS(Kernel) \ - SUB(Kernel, Pthread) \ - SUB(Kernel, Vmm) \ - SUB(Kernel, Fs) \ - SUB(Kernel, Event) \ - SUB(Kernel, Sce) \ - CLS(Lib) \ - SUB(Lib, LibcInternal) \ - SUB(Lib, Kernel) \ - SUB(Lib, Pad) \ - SUB(Lib, SystemGesture) \ - SUB(Lib, GnmDriver) \ - SUB(Lib, SystemService) \ - SUB(Lib, UserService) \ - SUB(Lib, VideoOut) \ - SUB(Lib, CommonDlg) \ - SUB(Lib, MsgDlg) \ - SUB(Lib, AudioOut) \ - SUB(Lib, AudioIn) \ - SUB(Lib, Net) \ - SUB(Lib, NetCtl) \ - SUB(Lib, SaveData) \ - SUB(Lib, SaveDataDialog) \ - SUB(Lib, Http) \ - SUB(Lib, Http2) \ - SUB(Lib, Ssl) \ - SUB(Lib, Ssl2) \ - SUB(Lib, SysModule) \ - SUB(Lib, Move) \ - SUB(Lib, NpAuth) \ - SUB(Lib, NpCommon) \ - SUB(Lib, NpCommerce) \ - SUB(Lib, NpManager) \ - SUB(Lib, NpMatching2) \ - SUB(Lib, NpScore) \ - SUB(Lib, NpTrophy) \ - SUB(Lib, NpTus) \ - SUB(Lib, NpWebApi) \ - SUB(Lib, NpWebApi2) \ - SUB(Lib, NpProfileDialog) \ - SUB(Lib, NpSnsFacebookDialog) \ - SUB(Lib, NpPartner) \ - SUB(Lib, Screenshot) \ - SUB(Lib, AppContent) \ - SUB(Lib, Rtc) \ - SUB(Lib, Rudp) \ - SUB(Lib, DiscMap) \ - SUB(Lib, Png) \ - SUB(Lib, Jpeg) \ - SUB(Lib, PlayGo) \ - SUB(Lib, PlayGoDialog) \ - SUB(Lib, Random) \ - SUB(Lib, Usbd) \ - SUB(Lib, Ajm) \ - SUB(Lib, ErrorDialog) \ - SUB(Lib, ImeDialog) \ - SUB(Lib, AvPlayer) \ - SUB(Lib, Ngs2) \ - SUB(Lib, Audio3d) \ - SUB(Lib, Ime) \ - SUB(Lib, GameLiveStreaming) \ - SUB(Lib, Remoteplay) \ - SUB(Lib, SharePlay) \ - SUB(Lib, Fiber) \ - SUB(Lib, Vdec2) \ - SUB(Lib, Videodec) \ - SUB(Lib, RazorCpu) \ - SUB(Lib, Mouse) \ - SUB(Lib, WebBrowserDialog) \ - SUB(Lib, NpParty) \ - SUB(Lib, Zlib) \ - SUB(Lib, Hmd) \ - SUB(Lib, Font) \ - SUB(Lib, FontFt) \ - SUB(Lib, HmdSetupDialog) \ - SUB(Lib, SigninDialog) \ - SUB(Lib, Camera) \ - SUB(Lib, CompanionHttpd) \ - SUB(Lib, CompanionUtil) \ - SUB(Lib, Voice) \ - SUB(Lib, VrTracker) \ - CLS(Frontend) \ - CLS(Render) \ - SUB(Render, Vulkan) \ - SUB(Render, Recompiler) \ - CLS(ImGui) \ - CLS(Input) \ - CLS(Tty) \ - CLS(Loader) - -// GetClassName is a macro defined by Windows.h, grrr... -const char* GetLogClassName(Class log_class) { - switch (log_class) { -#define CLS(x) \ - case Class::x: \ - return #x; -#define SUB(x, y) \ - case Class::x##_##y: \ - return #x "." #y; - ALL_LOG_CLASSES() -#undef CLS -#undef SUB - case Class::Count: - default: - break; - } - UNREACHABLE(); -} - -const char* GetLevelName(Level log_level) { -#define LVL(x) \ - case Level::x: \ - return #x - switch (log_level) { - LVL(Trace); - LVL(Debug); - LVL(Info); - LVL(Warning); - LVL(Error); - LVL(Critical); - case Level::Count: - default: - break; - } -#undef LVL - UNREACHABLE(); -} - -Filter::Filter(Level default_level) { - ResetAll(default_level); -} - -void Filter::ResetAll(Level level) { - class_levels.fill(level); -} - -void Filter::SetClassLevel(Class log_class, Level level) { - class_levels[static_cast(log_class)] = level; -} - -void Filter::ParseFilterString(std::string_view filter_view) { - auto clause_begin = filter_view.cbegin(); - while (clause_begin != filter_view.cend()) { - auto clause_end = std::find(clause_begin, filter_view.cend(), ' '); - - // If clause isn't empty - if (clause_end != clause_begin) { - ParseFilterRule(*this, clause_begin, clause_end); - } - - if (clause_end != filter_view.cend()) { - // Skip over the whitespace - ++clause_end; - } - clause_begin = clause_end; - } -} - -bool Filter::CheckMessage(Class log_class, Level level) const { - return static_cast(level) >= - static_cast(class_levels[static_cast(log_class)]); -} - -bool Filter::IsDebug() const { - return std::any_of(class_levels.begin(), class_levels.end(), [](const Level& l) { - return static_cast(l) <= static_cast(Level::Debug); - }); -} - -} // namespace Common::Log diff --git a/src/common/logging/filter.h b/src/common/logging/filter.h deleted file mode 100644 index 7c46fd822..000000000 --- a/src/common/logging/filter.h +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include "common/logging/types.h" - -namespace Common::Log { - -/** - * Returns the name of the passed log class as a C-string. Subclasses are separated by periods - * instead of underscores as in the enumeration. - */ -const char* GetLogClassName(Class log_class); - -/** - * Returns the name of the passed log level as a C-string. - */ -const char* GetLevelName(Level log_level); - -/** - * Implements a log message filter which allows different log classes to have different minimum - * severity levels. The filter can be changed at runtime and can be parsed from a string to allow - * editing via the interface or loading from a configuration file. - */ -class Filter { -public: - /// Initializes the filter with all classes having `default_level` as the minimum level. - explicit Filter(Level default_level = Level::Info); - - /// Resets the filter so that all classes have `level` as the minimum displayed level. - void ResetAll(Level level); - - /// Sets the minimum level of `log_class` (and not of its subclasses) to `level`. - void SetClassLevel(Class log_class, Level level); - - /** - * Parses a filter string and applies it to this filter. - * - * A filter string consists of a space-separated list of filter rules, each of the format - * `:`. `` is a log class name, with subclasses separated using periods. - * `*` is allowed as a class name and will reset all filters to the specified level. `` - * a severity level name which will be set as the minimum logging level of the matched classes. - * Rules are applied left to right, with each rule overriding previous ones in the sequence. - * - * A few examples of filter rules: - * - `*:Info` -- Resets the level of all classes to Info. - * - `Service:Info` -- Sets the level of Service to Info. - * - `Service.FS:Trace` -- Sets the level of the Service.FS class to Trace. - */ - void ParseFilterString(std::string_view filter_view); - - /// Matches class/level combination against the filter, returning true if it passed. - bool CheckMessage(Class log_class, Level level) const; - - /// Returns true if any logging classes are set to debug - bool IsDebug() const; - -private: - std::array(Class::Count)> class_levels; -}; - -} // namespace Common::Log diff --git a/src/common/logging/log.cpp b/src/common/logging/log.cpp new file mode 100644 index 000000000..cc63f4c10 --- /dev/null +++ b/src/common/logging/log.cpp @@ -0,0 +1,203 @@ +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include "common/assert.h" +#include "common/config.h" +#include "common/logging/log.h" +#include "common/logging/thread_name_formatter.h" +#include "common/types.h" +#include "core/emulator_settings.h" + +namespace Common::Log { +bool g_should_append = false; + +static std::shared_ptr g_console_sink; +static std::shared_ptr g_shad_file_sink; + +std::unordered_map> ALL_LOGGERS{ + {Class::Common, nullptr}, + {Class::Common_Filesystem, nullptr}, + {Class::Common_Memory, nullptr}, + {Class::Config, nullptr}, + {Class::Core, nullptr}, + {Class::Core_Devices, nullptr}, + {Class::Core_Linker, nullptr}, + {Class::Debug, nullptr}, + {Class::Frontend, nullptr}, + {Class::IPC, nullptr}, + {Class::ImGui, nullptr}, + {Class::Input, nullptr}, + {Class::Kernel, nullptr}, + {Class::Kernel_Event, nullptr}, + {Class::Kernel_Fs, nullptr}, + {Class::Kernel_Pthread, nullptr}, + {Class::Kernel_Sce, nullptr}, + {Class::Kernel_Vmm, nullptr}, + {Class::KeyManager, nullptr}, + {Class::Lib, nullptr}, + {Class::Lib_Ajm, nullptr}, + {Class::Lib_AppContent, nullptr}, + {Class::Lib_Audio3d, nullptr}, + {Class::Lib_AudioIn, nullptr}, + {Class::Lib_AudioOut, nullptr}, + {Class::Lib_AvPlayer, nullptr}, + {Class::Lib_Camera, nullptr}, + {Class::Lib_CommonDlg, nullptr}, + {Class::Lib_CompanionHttpd, nullptr}, + {Class::Lib_CompanionUtil, nullptr}, + {Class::Lib_DiscMap, nullptr}, + {Class::Lib_ErrorDialog, nullptr}, + {Class::Lib_Fiber, nullptr}, + {Class::Lib_Font, nullptr}, + {Class::Lib_FontFt, nullptr}, + {Class::Lib_GameLiveStreaming, nullptr}, + {Class::Lib_GnmDriver, nullptr}, + {Class::Lib_Hmd, nullptr}, + {Class::Lib_HmdSetupDialog, nullptr}, + {Class::Lib_Http, nullptr}, + {Class::Lib_Http2, nullptr}, + {Class::Lib_Ime, nullptr}, + {Class::Lib_ImeDialog, nullptr}, + {Class::Lib_Jpeg, nullptr}, + {Class::Lib_Kernel, nullptr}, + {Class::Lib_LibcInternal, nullptr}, + {Class::Lib_Mouse, nullptr}, + {Class::Lib_Move, nullptr}, + {Class::Lib_MsgDlg, nullptr}, + {Class::Lib_Net, nullptr}, + {Class::Lib_NetCtl, nullptr}, + {Class::Lib_Ngs2, nullptr}, + {Class::Lib_NpAuth, nullptr}, + {Class::Lib_NpCommerce, nullptr}, + {Class::Lib_NpCommon, nullptr}, + {Class::Lib_NpManager, nullptr}, + {Class::Lib_NpMatching2, nullptr}, + {Class::Lib_NpPartner, nullptr}, + {Class::Lib_NpParty, nullptr}, + {Class::Lib_NpProfileDialog, nullptr}, + {Class::Lib_NpScore, nullptr}, + {Class::Lib_NpSnsFacebookDialog, nullptr}, + {Class::Lib_NpTrophy, nullptr}, + {Class::Lib_NpTus, nullptr}, + {Class::Lib_NpWebApi, nullptr}, + {Class::Lib_NpWebApi2, nullptr}, + {Class::Lib_Pad, nullptr}, + {Class::Lib_PlayGo, nullptr}, + {Class::Lib_PlayGoDialog, nullptr}, + {Class::Lib_Png, nullptr}, + {Class::Lib_Random, nullptr}, + {Class::Lib_RazorCpu, nullptr}, + {Class::Lib_Remoteplay, nullptr}, + {Class::Lib_Rtc, nullptr}, + {Class::Lib_Rudp, nullptr}, + {Class::Lib_SaveData, nullptr}, + {Class::Lib_SaveDataDialog, nullptr}, + {Class::Lib_Screenshot, nullptr}, + {Class::Lib_SharePlay, nullptr}, + {Class::Lib_SigninDialog, nullptr}, + {Class::Lib_Ssl, nullptr}, + {Class::Lib_Ssl2, nullptr}, + {Class::Lib_SysModule, nullptr}, + {Class::Lib_SystemGesture, nullptr}, + {Class::Lib_SystemService, nullptr}, + {Class::Lib_Usbd, nullptr}, + {Class::Lib_UserService, nullptr}, + {Class::Lib_Vdec2, nullptr}, + {Class::Lib_VideoOut, nullptr}, + {Class::Lib_Videodec, nullptr}, + {Class::Lib_Voice, nullptr}, + {Class::Lib_VrTracker, nullptr}, + {Class::Lib_WebBrowserDialog, nullptr}, + {Class::Lib_Zlib, nullptr}, + {Class::Loader, nullptr}, + {Class::Log, nullptr}, + {Class::Render, nullptr}, + {Class::Render_Recompiler, nullptr}, + {Class::Render_Vulkan, nullptr}, + {Class::Tty, nullptr}, +}; + +void Setup(std::string_view log_filename) { + static bool already_registered = false; + + if (!already_registered) { + already_registered = true; + std::atexit(Shutdown); + std::at_quick_exit(Shutdown); + } + +#ifdef _WIN32 + if (EmulatorSettings.GetLogType() == "wincolor") { + g_console_sink = std::make_shared(); + } else { + g_console_sink = std::make_shared(); + } +#else + g_console_sink = std::make_shared(); +#endif + + g_console_sink->set_formatter(std::make_unique(UNLIMITED_SIZE)); + + g_shad_file_sink = std::make_shared( + (GetUserPath(Common::FS::PathType::LogDir) / log_filename).string(), !g_should_append); + g_shad_file_sink->set_formatter( + std::make_unique(EmulatorSettings.GetLogSizeLimit())); + + std::initializer_list sinks{g_console_sink, g_shad_file_sink}; + + std::initializer_list async_sink{std::make_shared( + spdlog::sinks::async_sink::config{.sinks = sinks})}; + + std::initializer_list dup_filter{ + std::make_shared( + std::chrono::milliseconds(EmulatorSettings.GetLogMaxSkipDuration()), + EmulatorSettings.IsLogSync() ? sinks : async_sink)}; + + spdlog::level default_log_level = spdlog::level::info; + std::unordered_map log_level_per_class; + + if (EmulatorSettings.IsLogEnable()) { + for (const auto class_level : std::views::split(EmulatorSettings.GetLogFilter(), ',')) { + const auto class_level_pair = + std::views::split(class_level, '=') | std::ranges::to>(); + + if (class_level_pair.size() == 1) { + default_log_level = spdlog::level_from_str(class_level_pair.front() | + std::ranges::to()); + } else { + log_level_per_class[class_level_pair.front() | std::ranges::to()] = + spdlog::level_from_str(class_level_pair.back() | + std::ranges::to()); + } + } + } + + for (auto& [name, logger] : ALL_LOGGERS) { + logger = std::make_shared( + std::string(name), EmulatorSettings.IsLogSkipDuplicate() + ? dup_filter + : (EmulatorSettings.IsLogSync() ? sinks : async_sink)); + + if (EmulatorSettings.IsLogEnable()) { + const auto level_it = log_level_per_class.find(std::string(name)); + + logger->set_level(level_it != log_level_per_class.end() ? level_it->second + : default_log_level); + } else { + logger->set_level(spdlog::level::off); + } + } +} + +void Shutdown() { + for (auto& logger : ALL_LOGGERS | std::views::values) { + logger.reset(); + } + + g_shad_file_sink.reset(); + g_console_sink.reset(); +} +} // namespace Common::Log diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 5fa430348..b10fedc9e 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -1,70 +1,58 @@ -// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include -#include "common/logging/formatter.h" -#include "common/logging/types.h" +#ifdef _WIN32 +#include +#include +using spdlog_stdout = spdlog::sinks::sink; +#else +using spdlog_stdout = spdlog::sinks::stdout_color_sink_mt; +#endif +#include + +#include "common/logging/classes.h" +#include "common/path_util.h" namespace Common::Log { +extern bool g_should_append; +extern std::unordered_map> ALL_LOGGERS; -constexpr const char* TrimSourcePath(std::string_view source) { - const auto rfind = [source](const std::string_view match) { - return source.rfind(match) == source.npos ? 0 : (source.rfind(match) + match.size()); - }; - auto idx = std::max({rfind("/"), rfind("\\")}); - return source.data() + idx; -} +void Setup(std::string_view log_filename); -/// Logs a message to the global logger, using fmt -void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, - unsigned int line_num, const char* function, const char* format, - const fmt::format_args& args); - -template -void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num, - const char* function, const char* format, const Args&... args) { - FmtLogMessageImpl(log_class, log_level, filename, line_num, function, format, - fmt::make_format_args(args...)); -} +void Shutdown(); +void Redirect(const std::string& name); } // namespace Common::Log // Define the fmt lib macros #define LOG_GENERIC(log_class, log_level, ...) \ - Common::Log::FmtLogMessage(log_class, log_level, Common::Log::TrimSourcePath(__FILE__), \ - __LINE__, __func__, __VA_ARGS__) + SPDLOG_LOGGER_CALL(Common::Log::ALL_LOGGERS[log_class], log_level, __VA_ARGS__) #ifdef _DEBUG #define LOG_TRACE(log_class, ...) \ - Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Trace, \ - Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \ - __VA_ARGS__) + LOG_GENERIC(Common::Log::Class::log_class, spdlog::level::trace, __VA_ARGS__) #else -#define LOG_TRACE(log_class, fmt, ...) (void(0)) +#define LOG_TRACE(log_class, ...) (void(0)) #endif #define LOG_DEBUG(log_class, ...) \ - Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Debug, \ - Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \ - __VA_ARGS__) + LOG_GENERIC(Common::Log::Class::log_class, spdlog::level::debug, __VA_ARGS__) #define LOG_INFO(log_class, ...) \ - Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Info, \ - Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \ - __VA_ARGS__) + LOG_GENERIC(Common::Log::Class::log_class, spdlog::level::info, __VA_ARGS__) #define LOG_WARNING(log_class, ...) \ - Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Warning, \ - Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \ - __VA_ARGS__) + LOG_GENERIC(Common::Log::Class::log_class, spdlog::level::warn, __VA_ARGS__) #define LOG_ERROR(log_class, ...) \ - Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Error, \ - Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \ - __VA_ARGS__) + LOG_GENERIC(Common::Log::Class::log_class, spdlog::level::err, __VA_ARGS__) #define LOG_CRITICAL(log_class, ...) \ - Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Critical, \ - Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \ - __VA_ARGS__) + LOG_GENERIC(Common::Log::Class::log_class, spdlog::level::critical, __VA_ARGS__) diff --git a/src/common/logging/log_entry.h b/src/common/logging/log_entry.h deleted file mode 100644 index 7b52ad7e1..000000000 --- a/src/common/logging/log_entry.h +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -#include "common/logging/types.h" - -namespace Common::Log { - -/** - * A log entry. Log entries are store in a structured format to permit more varied output - * formatting on different frontends, as well as facilitating filtering and aggregation. - */ -struct Entry { - std::chrono::microseconds timestamp; - Class log_class{}; - Level log_level{}; - const char* filename = nullptr; - u32 line_num = 0; - std::string function; - std::string message; - std::string thread; - u32 counter = 0; -}; - -} // namespace Common::Log diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp deleted file mode 100644 index e8c5f4979..000000000 --- a/src/common/logging/text_formatter.cpp +++ /dev/null @@ -1,110 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project -// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include - -#ifdef _WIN32 -#include -#endif - -#include "common/assert.h" -#include "common/logging/filter.h" -#include "common/logging/log.h" -#include "common/logging/log_entry.h" -#include "common/logging/text_formatter.h" - -namespace Common::Log { - -std::string FormatLogMessage(const Entry& entry) { - const u32 time_seconds = static_cast(entry.timestamp.count() / 1000000); - const u32 time_fractional = static_cast(entry.timestamp.count() % 1000000); - - const char* class_name = GetLogClassName(entry.log_class); - const char* level_name = GetLevelName(entry.log_level); - - return fmt::format("[{}] <{}> ({}) {}:{} {}: {}", class_name, level_name, entry.thread, - entry.filename, entry.line_num, entry.function, entry.message); -} - -void PrintMessage(const Entry& entry) { - const auto str = FormatLogMessage(entry).append(1, '\n'); - fputs(str.c_str(), stdout); -} - -void PrintColoredMessage(const Entry& entry) { -#ifdef _WIN32 - HANDLE console_handle = GetStdHandle(STD_ERROR_HANDLE); - if (console_handle == INVALID_HANDLE_VALUE) { - return; - } - - CONSOLE_SCREEN_BUFFER_INFO original_info{}; - GetConsoleScreenBufferInfo(console_handle, &original_info); - - WORD color = 0; - switch (entry.log_level) { - case Level::Trace: // Grey - color = FOREGROUND_INTENSITY; - break; - case Level::Debug: // Cyan - color = FOREGROUND_GREEN | FOREGROUND_BLUE; - break; - case Level::Info: // Bright gray - color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; - break; - case Level::Warning: // Bright yellow - color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; - break; - case Level::Error: // Bright red - color = FOREGROUND_RED | FOREGROUND_INTENSITY; - break; - case Level::Critical: // Bright magenta - color = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY; - break; - case Level::Count: - UNREACHABLE(); - } - - SetConsoleTextAttribute(console_handle, color); -#else -#define ESC "\x1b" - const char* color = ""; - switch (entry.log_level) { - case Level::Trace: // Grey - color = ESC "[1;30m"; - break; - case Level::Debug: // Cyan - color = ESC "[0;36m"; - break; - case Level::Info: // Bright gray - color = ESC "[0;37m"; - break; - case Level::Warning: // Bright yellow - color = ESC "[1;33m"; - break; - case Level::Error: // Bright red - color = ESC "[1;31m"; - break; - case Level::Critical: // Bright magenta - color = ESC "[1;35m"; - break; - case Level::Count: - UNREACHABLE(); - } - - fputs(color, stdout); -#endif - - PrintMessage(entry); - -#ifdef _WIN32 - SetConsoleTextAttribute(console_handle, original_info.wAttributes); -#else - fputs(ESC "[0m", stdout); -#undef ESC -#endif -} - -} // namespace Common::Log diff --git a/src/common/logging/text_formatter.h b/src/common/logging/text_formatter.h deleted file mode 100644 index 504d8639b..000000000 --- a/src/common/logging/text_formatter.h +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -namespace Common::Log { - -struct Entry; - -/// Formats a log entry into the provided text buffer. -std::string FormatLogMessage(const Entry& entry); - -/// Formats and prints a log entry to stderr. -void PrintMessage(const Entry& entry); - -/// Prints the same message as `PrintMessage`, but colored according to the severity level. -void PrintColoredMessage(const Entry& entry); - -} // namespace Common::Log diff --git a/src/common/logging/thread_name_formatter.h b/src/common/logging/thread_name_formatter.h new file mode 100644 index 000000000..5ddb85b19 --- /dev/null +++ b/src/common/logging/thread_name_formatter.h @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "common/thread.h" + +namespace Common::Log { +static constexpr unsigned long long UNLIMITED_SIZE = 0; + +struct thread_name_formatter : spdlog::formatter { + ~thread_name_formatter() override = default; + + thread_name_formatter(unsigned long long size_limit) : _size_limit(size_limit) {} + + void format(const spdlog::details::log_msg& msg, spdlog::memory_buf_t& dest) override { + if (_size_limit != UNLIMITED_SIZE && _current_size >= _size_limit) { + return; + } + + msg.color_range_start = dest.size(); + + dest.push_back('['); + spdlog::details::fmt_helper::append_string_view(msg.logger_name, dest); + dest.push_back(']'); + dest.push_back(' '); + dest.push_back('<'); + spdlog::details::fmt_helper::append_string_view(spdlog::to_string_view(msg.log_level), + dest); + dest.push_back('>'); + dest.push_back(' '); + dest.push_back('('); + spdlog::details::fmt_helper::append_string_view(GetCurrentThreadName(), dest); + dest.push_back(')'); + dest.push_back(' '); + spdlog::details::fmt_helper::append_string_view(msg.source.short_filename, dest); + dest.push_back(':'); + spdlog::details::fmt_helper::append_int(msg.source.line, dest); + dest.push_back(' '); + spdlog::details::fmt_helper::append_string_view( + std::string_view(msg.source.funcname).contains("(anonymous class)::operator()") + ? "lambda" + : msg.source.funcname, + dest); + dest.push_back(':'); + dest.push_back(' '); + spdlog::details::fmt_helper::append_string_view(msg.payload, dest); + spdlog::details::fmt_helper::append_string_view(spdlog::details::os::default_eol, dest); + + msg.color_range_end = dest.size(); + + _current_size += dest.size(); + } + + std::unique_ptr clone() const override { + return std::make_unique(_size_limit); + } + + const unsigned long long _size_limit; + unsigned long long _current_size = 0; +}; +} // namespace Common::Log diff --git a/src/common/logging/types.h b/src/common/logging/types.h deleted file mode 100644 index 2c6edef3b..000000000 --- a/src/common/logging/types.h +++ /dev/null @@ -1,136 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2023 Citra Emulator Project -// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/types.h" - -namespace Common::Log { - -/// Specifies the severity or level of detail of the log message. -enum class Level : u8 { - Trace, ///< Extremely detailed and repetitive debugging information that is likely to - ///< pollute logs. - Debug, ///< Less detailed debugging information. - Info, ///< Status information from important points during execution. - Warning, ///< Minor or potential problems found during execution of a task. - Error, ///< Major problems found during execution of a task that prevent it from being - ///< completed. - Critical, ///< Major problems during execution that threaten the stability of the entire - ///< application. - - Count, ///< Total number of logging levels -}; - -/** - * Specifies the sub-system that generated the log message. - * - * @note If you add a new entry here, also add a corresponding one to `ALL_LOG_CLASSES` in - * filter.cpp. - */ -enum class Class : u8 { - Log, ///< Messages about the log system itself - Common, ///< Library routines - Common_Filesystem, ///< Filesystem interface library - Common_Memory, ///< Memory mapping and management functions - KeyManager, ///< Key management system - Core, ///< LLE emulation core - Core_Linker, ///< The module linker - Core_Devices, ///< Devices emulation - Config, ///< Emulator configuration (including commandline) - Debug, ///< Debugging tools - Kernel, ///< The HLE implementation of the PS4 kernel. - Kernel_Pthread, ///< The pthread implementation of the kernel. - Kernel_Fs, ///< The filesystem implementation of the kernel. - Kernel_Vmm, ///< The virtual memory implementation of the kernel. - Kernel_Event, ///< The event management implementation of the kernel. - Kernel_Sce, ///< The Sony-specific interfaces provided by the kernel. - Lib, ///< HLE implementation of system library. Each major library - ///< should have its own subclass. - Lib_LibcInternal, ///< The LibcInternal implementation. - Lib_Kernel, ///< The LibKernel implementation. - Lib_Pad, ///< The LibScePad implementation. - Lib_SystemGesture, ///< The LibSceSystemGesture implementation. - Lib_GnmDriver, ///< The LibSceGnmDriver implementation. - Lib_SystemService, ///< The LibSceSystemService implementation. - Lib_UserService, ///< The LibSceUserService implementation. - Lib_VideoOut, ///< The LibSceVideoOut implementation. - Lib_CommonDlg, ///< The LibSceCommonDialog implementation. - Lib_MsgDlg, ///< The LibSceMsgDialog implementation. - Lib_AudioOut, ///< The LibSceAudioOut implementation. - Lib_AudioIn, ///< The LibSceAudioIn implementation. - Lib_Move, ///< The LibSceMove implementation. - Lib_Net, ///< The LibSceNet implementation. - Lib_NetCtl, ///< The LibSceNetCtl implementation. - Lib_SaveData, ///< The LibSceSaveData implementation. - Lib_SaveDataDialog, ///< The LibSceSaveDataDialog implementation. - Lib_Ssl, ///< The LibSceSsl implementation. - Lib_Ssl2, ///< The LibSceSsl2 implementation. - Lib_Http, ///< The LibSceHttp implementation. - Lib_Http2, ///< The LibSceHttp2 implementation. - Lib_SysModule, ///< The LibSceSysModule implementation - Lib_NpCommon, ///< The LibSceNpCommon implementation - Lib_NpCommerce, ///< The LibSceNpCommerce implementation - Lib_NpAuth, ///< The LibSceNpAuth implementation - Lib_NpManager, ///< The LibSceNpManager implementation - Lib_NpMatching2, ///< The LibSceNpMatching2 implementation - Lib_NpScore, ///< The LibSceNpScore implementation - Lib_NpTrophy, ///< The LibSceNpTrophy implementation - Lib_NpTus, ///< The LibSceNpTus implementation - Lib_NpWebApi, ///< The LibSceWebApi implementation - Lib_NpWebApi2, ///< The LibSceWebApi2 implementation - Lib_NpProfileDialog, ///< The LibSceNpProfileDialog implementation - Lib_NpSnsFacebookDialog, ///< The LibSceNpSnsFacebookDialog implementation - Lib_Screenshot, ///< The LibSceScreenshot implementation - Lib_AppContent, ///< The LibSceAppContent implementation. - Lib_Rtc, ///< The LibSceRtc implementation. - Lib_Rudp, ///< The LibSceRudp implementation. - Lib_DiscMap, ///< The LibSceDiscMap implementation. - Lib_Png, ///< The LibScePng implementation. - Lib_Jpeg, ///< The LibSceJpeg implementation. - Lib_PlayGo, ///< The LibScePlayGo implementation. - Lib_PlayGoDialog, ///< The LibScePlayGoDialog implementation. - Lib_Random, ///< The LibSceRandom implementation. - Lib_Usbd, ///< The LibSceUsbd implementation. - Lib_Ajm, ///< The LibSceAjm implementation. - Lib_ErrorDialog, ///< The LibSceErrorDialog implementation. - Lib_ImeDialog, ///< The LibSceImeDialog implementation. - Lib_AvPlayer, ///< The LibSceAvPlayer implementation. - Lib_Ngs2, ///< The LibSceNgs2 implementation. - Lib_Audio3d, ///< The LibSceAudio3d implementation. - Lib_Ime, ///< The LibSceIme implementation - Lib_GameLiveStreaming, ///< The LibSceGameLiveStreaming implementation - Lib_Remoteplay, ///< The LibSceRemotePlay implementation - Lib_SharePlay, ///< The LibSceSharePlay implemenation - Lib_Fiber, ///< The LibSceFiber implementation. - Lib_Vdec2, ///< The LibSceVideodec2 implementation. - Lib_Videodec, ///< The LibSceVideodec implementation. - Lib_Voice, ///< The LibSceVoice implementation. - Lib_RazorCpu, ///< The LibRazorCpu implementation. - Lib_Mouse, ///< The LibSceMouse implementation - Lib_WebBrowserDialog, ///< The LibSceWebBrowserDialog implementation - Lib_NpParty, ///< The LibSceNpParty implementation - Lib_NpPartner, ///< The LibSceNpPartner implementation - Lib_Zlib, ///< The LibSceZlib implementation. - Lib_Hmd, ///< The LibSceHmd implementation. - Lib_HmdSetupDialog, ///< The LibSceHmdSetupDialog implementation. - Lib_SigninDialog, ///< The LibSigninDialog implementation. - Lib_Camera, ///< The LibCamera implementation. - Lib_CompanionHttpd, ///< The LibCompanionHttpd implementation. - Lib_CompanionUtil, ///< The LibCompanionUtil implementation. - Lib_VrTracker, ///< The LibSceVrTracker implementation. - Lib_Font, ///< The libSceFont implementation. - Lib_FontFt, ///< The libSceFontFt implementation. - Frontend, ///< Emulator UI - Render, ///< Video Core - Render_Vulkan, ///< Vulkan backend - Render_Recompiler, ///< Shader recompiler - ImGui, ///< ImGui - Loader, ///< ROM loader - Input, ///< Input emulation - Tty, ///< Debug output from emu - Count ///< Total number of logging classes -}; - -} // namespace Common::Log diff --git a/src/common/path_util.cpp b/src/common/path_util.cpp index 103f17d29..6e70aa202 100644 --- a/src/common/path_util.cpp +++ b/src/common/path_util.cpp @@ -6,6 +6,7 @@ #include "common/logging/log.h" #include "common/path_util.h" #include "common/scope_exit.h" +#include "common/types.h" #ifdef __APPLE__ #include diff --git a/src/core/devtools/widget/text_editor.cpp b/src/core/devtools/widget/text_editor.cpp index 12031d1ef..460b4bfd2 100644 --- a/src/core/devtools/widget/text_editor.cpp +++ b/src/core/devtools/widget/text_editor.cpp @@ -12,6 +12,7 @@ #include "text_editor.h" #define IMGUI_DEFINE_MATH_OPERATORS +#include "common/types.h" #include "imgui.h" // for imGui::GetCurrentWindow() // TODO diff --git a/src/core/emulator_settings.cpp b/src/core/emulator_settings.cpp index 5388995d9..1d371e662 100644 --- a/src/core/emulator_settings.cpp +++ b/src/core/emulator_settings.cpp @@ -8,6 +8,7 @@ #include #include #include +#include "common/logging/formatter.h" #include "common/logging/log.h" #include "emulator_settings.h" #include "emulator_state.h" @@ -55,6 +56,10 @@ std::optional get_optional(const toml::value& v, const std::string& key) { if (it->second.is_integer()) { return static_cast(toml::get(it->second)); } + } else if constexpr (std::is_same_v) { + if (it->second.is_integer()) { + return static_cast(toml::get(it->second)); + } } else if constexpr (std::is_same_v) { if (it->second.is_floating()) { return toml::get(it->second); @@ -221,6 +226,7 @@ void EmulatorSettingsImpl::SetAddonInstallDir(const std::filesystem::path& dir) // ── Game-specific override management ──────────────────────────────── void EmulatorSettingsImpl::ClearGameSpecificOverrides() { ClearGroupOverrides(m_general); + ClearGroupOverrides(m_log); ClearGroupOverrides(m_debug); ClearGroupOverrides(m_input); ClearGroupOverrides(m_audio); @@ -242,6 +248,8 @@ void EmulatorSettingsImpl::ResetGameSpecificValue(const std::string& key) { }; if (tryGroup(m_general)) return; + if (tryGroup(m_log)) + return; if (tryGroup(m_debug)) return; if (tryGroup(m_input)) @@ -268,6 +276,10 @@ bool EmulatorSettingsImpl::Save(const std::string& serial) { SaveGroupGameSpecific(m_general, generalObj); j["General"] = generalObj; + json logObj = json::object(); + SaveGroupGameSpecific(m_log, logObj); + j["Log"] = logObj; + json debugObj = json::object(); SaveGroupGameSpecific(m_debug, debugObj); j["Debug"] = debugObj; @@ -305,6 +317,7 @@ bool EmulatorSettingsImpl::Save(const std::string& serial) { json j; j["General"] = m_general; + j["Log"] = m_log; j["Debug"] = m_debug; j["Input"] = m_input; j["Audio"] = m_audio; @@ -366,6 +379,7 @@ bool EmulatorSettingsImpl::Load(const std::string& serial) { }; mergeGroup(m_general, "General"); + mergeGroup(m_log, "Log"); mergeGroup(m_debug, "Debug"); mergeGroup(m_input, "Input"); mergeGroup(m_audio, "Audio"); @@ -446,6 +460,8 @@ bool EmulatorSettingsImpl::Load(const std::string& serial) { // time without ever touching the base values. if (gj.contains("General")) ApplyGroupOverrides(m_general, gj.at("General"), changed); + if (gj.contains("Log")) + ApplyGroupOverrides(m_log, gj.at("Log"), changed); if (gj.contains("Debug")) ApplyGroupOverrides(m_debug, gj.at("Debug"), changed); if (gj.contains("Input")) @@ -469,6 +485,7 @@ bool EmulatorSettingsImpl::Load(const std::string& serial) { void EmulatorSettingsImpl::SetDefaultValues() { m_general = GeneralSettings{}; + m_log = LogSettings{}; m_debug = DebugSettings{}; m_input = InputSettings{}; m_audio = AudioSettings{}; @@ -502,9 +519,6 @@ bool EmulatorSettingsImpl::TransferSettings() { setFromToml(s.trophy_popup_disabled, general, "isTrophyPopupDisabled"); setFromToml(s.trophy_notification_duration, general, "trophyNotificationDuration"); setFromToml(s.discord_rpc_enabled, general, "enableDiscordRPC"); - setFromToml(s.log_filter, general, "logFilter"); - setFromToml(s.log_type, general, "logType"); - setFromToml(s.identical_log_grouped, general, "isIdenticalLogGrouped"); setFromToml(s.show_splash, general, "showSplash"); setFromToml(s.trophy_notification_side, general, "sideTrophy"); setFromToml(s.connected_to_network, general, "isConnectedToNetwork"); @@ -514,6 +528,23 @@ bool EmulatorSettingsImpl::TransferSettings() { // setFromToml(s.defaultControllerID, general, "defaultControllerID"); } + if (og_data.contains("Log")) { + const toml::value& log = og_data.at("Log"); + auto& s = m_log; + + setFromToml(s.append, log, "append"); + setFromToml(s.enable, log, "enable"); + setFromToml(s.filter, log, "filter"); + setFromToml(s.max_skip_duration, log, "maxSkipDuration"); + setFromToml(s.separate, log, "separate"); + setFromToml(s.size_limit, log, "sizeLimit"); + setFromToml(s.skip_duplicate, log, "skipDuplicate"); + setFromToml(s.sync, log, "sync"); +#ifdef _WIN32 + setFromToml(s.type, log, "type"); +#endif + } + if (og_data.contains("Input")) { const toml::value& input = og_data.at("Input"); auto& s = m_input; @@ -584,9 +615,7 @@ bool EmulatorSettingsImpl::TransferSettings() { auto& s = m_debug; setFromToml(s.debug_dump, debug, "DebugDump"); - setFromToml(s.separate_logging_enabled, debug, "isSeparateLogFilesEnabled"); setFromToml(s.shader_collect, debug, "CollectShader"); - setFromToml(s.log_enabled, debug, "logEnabled"); setFromToml(m_general.show_fps_counter, debug, "showFpsCounter"); } @@ -689,6 +718,7 @@ std::vector EmulatorSettingsImpl::GetAllOverrideableKeys() const { keys.push_back(item.key); }; addGroup(m_general.GetOverrideableFields()); + addGroup(m_log.GetOverrideableFields()); addGroup(m_debug.GetOverrideableFields()); addGroup(m_input.GetOverrideableFields()); addGroup(m_audio.GetOverrideableFields()); diff --git a/src/core/emulator_settings.h b/src/core/emulator_settings.h index e5ee81a32..74fc1b59a 100644 --- a/src/core/emulator_settings.h +++ b/src/core/emulator_settings.h @@ -183,10 +183,7 @@ struct GeneralSettings { Setting trophy_popup_disabled{false}; Setting trophy_notification_duration{6.0}; Setting trophy_notification_side{"right"}; - Setting log_filter{""}; - Setting log_type{"sync"}; Setting show_splash{false}; - Setting identical_log_grouped{true}; Setting connected_to_network{false}; Setting discord_rpc_enabled{false}; Setting show_fps_counter{false}; @@ -207,10 +204,6 @@ struct GeneralSettings { &GeneralSettings::trophy_popup_disabled), make_override("trophy_notification_duration", &GeneralSettings::trophy_notification_duration), - make_override("log_filter", &GeneralSettings::log_filter), - make_override("log_type", &GeneralSettings::log_type), - make_override("identical_log_grouped", - &GeneralSettings::identical_log_grouped), make_override("show_splash", &GeneralSettings::show_splash), make_override("trophy_notification_side", &GeneralSettings::trophy_notification_side), @@ -218,35 +211,71 @@ struct GeneralSettings { &GeneralSettings::connected_to_network)}; } }; + NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GeneralSettings, install_dirs, addon_install_dir, home_dir, sys_modules_dir, font_dir, volume_slider, neo_mode, dev_kit_mode, extra_dmem_in_mbytes, shad_net_enabled, trophy_popup_disabled, - trophy_notification_duration, log_filter, log_type, show_splash, - identical_log_grouped, trophy_notification_side, - connected_to_network, discord_rpc_enabled, show_fps_counter, - console_language, big_picture_scale, shadnet_server) + trophy_notification_duration, show_splash, + trophy_notification_side, connected_to_network, + discord_rpc_enabled, show_fps_counter, console_language, + big_picture_scale, shadnet_server) + +// ------------------------------- +// Log settings +// ------------------------------- +struct LogSettings { + Setting append{false}; // specific + Setting enable{true}; // specific + Setting filter{""}; + Setting max_skip_duration{5'000}; + Setting separate{false}; // specific + Setting size_limit{100_MB}; + Setting skip_duplicate{true}; + Setting sync{true}; +#ifdef _WIN32 + Setting type{"wincolor"}; +#endif + + // return a vector of override descriptors (runtime, but tiny) + std::vector GetOverrideableFields() const { + return std::vector{ + make_override("append", &LogSettings::append), + make_override("enable", &LogSettings::enable), + make_override("filter", &LogSettings::filter), + make_override("max_skip_duration", &LogSettings::max_skip_duration), + make_override("separate", &LogSettings::separate), + make_override("size_limit", &LogSettings::size_limit), + make_override("skip_duplicate", &LogSettings::skip_duplicate), + make_override("sync", &LogSettings::sync), +#ifdef _WIN32 + make_override("type", &LogSettings::type), +#endif + }; + } +}; +#ifdef _WIN32 +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(LogSettings, append, enable, filter, max_skip_duration, separate, + size_limit, skip_duplicate, sync, type) +#else +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(LogSettings, append, enable, filter, max_skip_duration, separate, + size_limit, skip_duplicate, sync) +#endif // ------------------------------- // Debug settings // ------------------------------- struct DebugSettings { - Setting separate_logging_enabled{false}; // specific - Setting debug_dump{false}; // specific - Setting shader_collect{false}; // specific - Setting log_enabled{true}; // specific - Setting config_version{""}; // specific + Setting debug_dump{false}; // specific + Setting shader_collect{false}; // specific + Setting config_version{""}; // specific std::vector GetOverrideableFields() const { return std::vector{ make_override("debug_dump", &DebugSettings::debug_dump), - make_override("shader_collect", &DebugSettings::shader_collect), - make_override("separate_logging_enabled", - &DebugSettings::separate_logging_enabled), - make_override("log_enabled", &DebugSettings::log_enabled)}; + make_override("shader_collect", &DebugSettings::shader_collect)}; } }; -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(DebugSettings, separate_logging_enabled, debug_dump, - shader_collect, log_enabled, config_version) +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(DebugSettings, debug_dump, shader_collect, config_version) // ------------------------------- // Input settings @@ -466,6 +495,7 @@ public: private: GeneralSettings m_general{}; + LogSettings m_log{}; DebugSettings m_debug{}; InputSettings m_input{}; AudioSettings m_audio{}; @@ -556,9 +586,6 @@ public: SETTING_FORWARD(m_general, TrophyNotificationDuration, trophy_notification_duration) SETTING_FORWARD(m_general, TrophyNotificationSide, trophy_notification_side) SETTING_FORWARD_BOOL(m_general, ShowSplash, show_splash) - SETTING_FORWARD_BOOL(m_general, IdenticalLogGrouped, identical_log_grouped) - SETTING_FORWARD(m_general, LogFilter, log_filter) - SETTING_FORWARD(m_general, LogType, log_type) SETTING_FORWARD_BOOL(m_general, ConnectedToNetwork, connected_to_network) SETTING_FORWARD_BOOL(m_general, DiscordRPCEnabled, discord_rpc_enabled) SETTING_FORWARD_BOOL(m_general, ShowFpsCounter, show_fps_counter) @@ -566,6 +593,19 @@ public: SETTING_FORWARD(m_general, BigPictureScale, big_picture_scale) SETTING_FORWARD(m_general, ShadNetServer, shadnet_server) + // Log settings + SETTING_FORWARD_BOOL(m_log, LogAppend, append) + SETTING_FORWARD_BOOL(m_log, LogEnable, enable) + SETTING_FORWARD(m_log, LogFilter, filter) + SETTING_FORWARD(m_log, LogMaxSkipDuration, max_skip_duration) + SETTING_FORWARD_BOOL(m_log, LogSeparate, separate) + SETTING_FORWARD(m_log, LogSizeLimit, size_limit) + SETTING_FORWARD_BOOL(m_log, LogSkipDuplicate, skip_duplicate) + SETTING_FORWARD_BOOL(m_log, LogSync, sync) +#ifdef _WIN32 + SETTING_FORWARD(m_log, LogType, type) +#endif + // Audio settings SETTING_FORWARD(m_audio, AudioBackend, audio_backend) SETTING_FORWARD(m_audio, SDLMicDevice, sdl_mic_device) @@ -576,10 +616,8 @@ public: SETTING_FORWARD(m_audio, OpenALPadSpkOutputDevice, openal_padSpk_output_device) // Debug settings - SETTING_FORWARD_BOOL(m_debug, SeparateLoggingEnabled, separate_logging_enabled) SETTING_FORWARD_BOOL(m_debug, DebugDump, debug_dump) SETTING_FORWARD_BOOL(m_debug, ShaderCollect, shader_collect) - SETTING_FORWARD_BOOL(m_debug, LogEnabled, log_enabled) SETTING_FORWARD(m_debug, ConfigVersion, config_version) // GPU Settings diff --git a/src/core/libraries/ajm/ajm_batch.cpp b/src/core/libraries/ajm/ajm_batch.cpp index 3ab2ed4ab..5a4d4ec17 100644 --- a/src/core/libraries/ajm/ajm_batch.cpp +++ b/src/core/libraries/ajm/ajm_batch.cpp @@ -158,7 +158,7 @@ AjmJob AjmStatisticsJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buf break; } default: - UNREACHABLE_MSG("Unknown chunk: {}", header.ident); + UNREACHABLE_MSG("Unknown chunk: {}", +header.ident); } } @@ -257,7 +257,7 @@ AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) { break; } default: - UNREACHABLE_MSG("Unknown chunk: {}", header.ident); + UNREACHABLE_MSG("Unknown chunk: {}", +header.ident); } } diff --git a/src/core/libraries/ime/ime.cpp b/src/core/libraries/ime/ime.cpp index 96ae446fa..cc6ab609c 100644 --- a/src/core/libraries/ime/ime.cpp +++ b/src/core/libraries/ime/ime.cpp @@ -12,8 +12,8 @@ namespace Libraries::Ime { static std::queue g_ime_events; -static ImeState g_ime_state{}; -static ImeUi g_ime_ui; +static std::unique_ptr g_ime_state; +static std::unique_ptr g_ime_ui; class ImeHandler { public: @@ -67,14 +67,14 @@ public: }*/ if (ime_mode) { - g_ime_state = ImeState(&m_param.ime, &m_param.ime_ext); - g_ime_ui = ImeUi(&g_ime_state, &m_param.ime, &m_param.ime_ext); + g_ime_state = std::make_unique(&m_param.ime, &m_param.ime_ext); + g_ime_ui = std::make_unique(g_ime_state.get(), &m_param.ime, &m_param.ime_ext); // Queue the Open event so it is delivered on next sceImeUpdate LOG_DEBUG(Lib_Ime, "IME Event queued: Open rect x={}, y={}, w={}, h={}", openEvent.param.rect.x, openEvent.param.rect.y, openEvent.param.rect.width, openEvent.param.rect.height); - g_ime_state.SendEvent(&openEvent); + g_ime_state->SendEvent(&openEvent); } } @@ -84,11 +84,11 @@ public: return Error::OK; } - std::unique_lock lock{g_ime_state.queue_mutex}; + std::unique_lock lock{g_ime_state->queue_mutex}; - while (!g_ime_state.event_queue.empty()) { - OrbisImeEvent event = g_ime_state.event_queue.front(); - g_ime_state.event_queue.pop(); + while (!g_ime_state->event_queue.empty()) { + OrbisImeEvent event = g_ime_state->event_queue.front(); + g_ime_state->event_queue.pop(); Execute(handler, &event, false); } @@ -118,7 +118,7 @@ public: LOG_WARNING(Lib_Ime, "ImeHandler::SetText received null text pointer"); return Error::INVALID_ADDRESS; } - g_ime_state.SetText(text, length); + g_ime_state->SetText(text, length); return Error::OK; } @@ -127,7 +127,7 @@ public: LOG_WARNING(Lib_Ime, "ImeHandler::SetCaret received null caret pointer"); return Error::INVALID_ADDRESS; } - g_ime_state.SetCaret(caret->index); + g_ime_state->SetCaret(caret->index); return Error::OK; } @@ -185,8 +185,8 @@ Error PS4_SYSV_ABI sceImeClose() { LOG_ERROR(Lib_Ime, "Failed to close IME handler, it is still open"); return Error::INTERNAL; } - g_ime_ui = ImeUi(); - g_ime_state = ImeState(); + g_ime_ui = std::make_unique(); + g_ime_state = std::make_unique(); LOG_DEBUG(Lib_Ime, "IME closed successfully"); return Error::OK; diff --git a/src/core/libraries/np/trophy_ui.cpp b/src/core/libraries/np/trophy_ui.cpp index d277a6e3c..74a7317b6 100644 --- a/src/core/libraries/np/trophy_ui.cpp +++ b/src/core/libraries/np/trophy_ui.cpp @@ -12,6 +12,7 @@ #define MINIMP3_IMPLEMENTATION #include +#include "common/logging/formatter.h" #include "common/path_util.h" #include "core/emulator_settings.h" #include "core/libraries/np/trophy_ui.h" diff --git a/src/core/libraries/save_data/save_backup.cpp b/src/core/libraries/save_data/save_backup.cpp index c5f66d883..c14cd9ec1 100644 --- a/src/core/libraries/save_data/save_backup.cpp +++ b/src/core/libraries/save_data/save_backup.cpp @@ -10,8 +10,8 @@ #include "save_backup.h" #include "save_instance.h" +#include "common/logging/formatter.h" #include "common/logging/log.h" -#include "common/logging/log_entry.h" #include "common/polyfill_thread.h" #include "common/thread.h" diff --git a/src/emulator.cpp b/src/emulator.cpp index ac21c9759..6a4980c41 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -11,7 +11,6 @@ #include #include "common/debug.h" -#include "common/logging/backend.h" #include "common/logging/log.h" #include "common/thread.h" #include "core/emulator_settings.h" @@ -245,12 +244,11 @@ void Emulator::Run(std::filesystem::path file, std::vector args, EmulatorSettings.Load(id); // Initialize logging as soon as possible - if (!id.empty() && EmulatorSettings.IsSeparateLoggingEnabled()) { - Common::Log::Initialize(id + ".log"); - } else { - Common::Log::Initialize(); + if (!id.empty() && EmulatorSettings.IsLogSeparate()) { + Common::Log::Shutdown(); + Common::Log::Setup(id + ".log"); } - Common::Log::Start(); + if (!std::filesystem::exists(file)) { LOG_CRITICAL(Loader, "eboot.bin does not exist: {}", std::filesystem::absolute(file).string()); @@ -266,12 +264,15 @@ void Emulator::Run(std::filesystem::path file, std::vector args, LOG_INFO(Config, "Game-specific config used: {}", EmulatorState::GetInstance()->IsGameSpecifigConfigUsed()); - LOG_INFO(Config, "General LogType: {}", EmulatorSettings.GetLogType()); - LOG_INFO(Config, "General isIdenticalLogGrouped: {}", EmulatorSettings.IsIdenticalLogGrouped()); LOG_INFO(Config, "General isNeo: {}", EmulatorSettings.IsNeo()); LOG_INFO(Config, "General isDevKit: {}", EmulatorSettings.IsDevKit()); LOG_INFO(Config, "General isConnectedToNetwork: {}", EmulatorSettings.IsConnectedToNetwork()); LOG_INFO(Config, "General isShadNetEnabled: {}", EmulatorSettings.IsShadNetEnabled()); + LOG_INFO(Config, "Log sync: {}", EmulatorSettings.IsLogSync()); + LOG_INFO(Config, "Log skipDuplicate: {}", EmulatorSettings.IsLogSkipDuplicate()); +#ifdef _WIN32 + LOG_INFO(Config, "Log type: {}", EmulatorSettings.GetLogType()); +#endif LOG_INFO(Config, "GPU isNullGpu: {}", EmulatorSettings.IsNullGPU()); LOG_INFO(Config, "GPU readbacksMode: {}", EmulatorSettings.GetReadbacksMode()); LOG_INFO(Config, "GPU readbackLinearImages: {}", @@ -535,7 +536,7 @@ void Emulator::Restart(std::filesystem::path eboot_path, LOG_INFO(Common, "Restarting the emulator with args: {}", fmt::join(args, " ")); Libraries::SaveData::Backup::StopThread(); - Common::Log::Denitializer(); + Common::Log::Shutdown(); auto& ipc = IPC::Instance(); diff --git a/src/imgui/settings_dialog_imgui.cpp b/src/imgui/settings_dialog_imgui.cpp index 4a5377352..7d637a6af 100644 --- a/src/imgui/settings_dialog_imgui.cpp +++ b/src/imgui/settings_dialog_imgui.cpp @@ -48,7 +48,6 @@ const std::map languageMap = {{"Arabic", 21}, {"Ukrainian", 30}, {"Vietnamese", 28}}; std::vector languageOptions; // assigned from keys above -const std::vector logTypeOptions = {"sync", "async"}; const std::vector fullscreenModeOptions = {"Windowed", "Fullscreen", "Fullscreen (Borderless)"}; const std::vector audioBackendOptions = {"SDL", "OpenAL"}; @@ -88,9 +87,9 @@ int trophySideSetting; float trophyDurationSetting; // Log tab -bool logEnabledSetting; -bool separateLogSetting; -int logTypeSetting; +bool logEnableSetting; +bool logSeparateSetting; +bool logSyncSetting; // Experimental tab int readbacksModeSetting; @@ -420,10 +419,10 @@ void LoadCategory(SettingsCategory category) { ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale); ImGui::TableSetupColumn("Value"); - AddSettingBool("Enable Logging", logEnabledSetting); - if (logEnabledSetting) { - AddSettingBool("Separate Log Files", separateLogSetting); - AddSettingCombo("Log Type", logTypeSetting, logTypeOptions); + AddSettingBool("Enable Logging", logEnableSetting); + if (logEnableSetting) { + AddSettingBool("Separate Log Files", logSeparateSetting); + AddSettingBool("Log Sync", logSyncSetting); } ImGui::EndTable(); @@ -538,9 +537,9 @@ void SaveSettings(std::string profile) { EmulatorSettings.SetTrophyNotificationDuration(static_cast(trophyDurationSetting)); /////////// Log Tab - EmulatorSettings.SetLogEnabled(logEnabledSetting, isSpecific); - EmulatorSettings.SetLogType(logTypeOptions.at(logTypeSetting), isSpecific); - EmulatorSettings.SetSeparateLoggingEnabled(separateLogSetting, isSpecific); + EmulatorSettings.SetLogEnable(logEnableSetting, isSpecific); + EmulatorSettings.SetLogSeparate(logSeparateSetting, isSpecific); + EmulatorSettings.SetLogSync(logSyncSetting, isSpecific); /////////// Experimental Tab if (isSpecific) { @@ -609,9 +608,9 @@ void LoadSettings(std::string profile) { trophyDurationSetting = static_cast(EmulatorSettings.GetTrophyNotificationDuration()); /////////// Log Tab - logEnabledSetting = EmulatorSettings.IsLogEnabled(); - logTypeSetting = GetComboIndex(EmulatorSettings.GetLogType(), logTypeOptions); - separateLogSetting = EmulatorSettings.IsSeparateLoggingEnabled(); + logEnableSetting = EmulatorSettings.IsLogEnable(); + logSeparateSetting = EmulatorSettings.IsLogSeparate(); + logSyncSetting = EmulatorSettings.IsLogSync(); /////////// Experimental Tab if (isSpecific) { diff --git a/src/main.cpp b/src/main.cpp index 826c8ab03..919673a8d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,7 +13,7 @@ #include #include "common/config.h" #include "common/key_manager.h" -#include "common/logging/backend.h" +#include "common/logging/log.h" #include "common/memory_patcher.h" #include "common/path_util.h" #include "core/debugger.h" @@ -32,6 +32,81 @@ int main(int argc, char* argv[]) { SetConsoleOutputCP(CP_UTF8); #endif + CLI::App app{"shadPS4 Emulator CLI"}; + + // ---- CLI state ---- + std::optional gamePath; + std::vector gameArgs; + std::optional overrideRoot; + std::optional waitPid; + bool waitForDebugger = false; + + std::optional fullscreenStr; + bool ignoreGamePatch = false; + bool showFps = false; + bool configClean = false; + bool configGlobal = false; + bool bigPicture = false; + + std::optional addGameFolder; + std::optional setAddonFolder; + std::optional patchFile; + + // ---- Options ---- + app.add_option("-g,--game", gamePath, "Game path or ID"); + app.add_option("-p,--patch", patchFile, "Patch file to apply"); + app.add_flag("-i,--ignore-game-patch", ignoreGamePatch, + "Disable automatic loading of game patches"); + + app.add_flag("-b,--big-picture", bigPicture, "Start in Big Picture Mode"); + + // FULLSCREEN: behavior-identical + app.add_option("-f,--fullscreen", fullscreenStr, "Fullscreen mode (true|false)"); + + app.add_option("--override-root", overrideRoot)->check(CLI::ExistingDirectory); + + app.add_flag("--wait-for-debugger", waitForDebugger); + app.add_option("--wait-for-pid", waitPid); + + app.add_flag("--show-fps", showFps); + app.add_flag("--config-clean", configClean); + app.add_flag("--config-global", configGlobal); + app.add_flag("--log-append", Common::Log::g_should_append); + + app.add_option("--add-game-folder", addGameFolder)->check(CLI::ExistingDirectory); + app.add_option("--set-addon-folder", setAddonFolder)->check(CLI::ExistingDirectory); + + // ---- Capture args after `--` verbatim ---- + app.allow_extras(); + app.parse_complete_callback([&]() { + const auto& extras = app.remaining(); + if (!extras.empty()) { + gameArgs = extras; + } + }); + + // ---- No-args behavior ---- + if (argc == 1) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "shadPS4", + "This is a CLI application. Please use the QTLauncher for a GUI:\n" + "https://github.com/shadps4-emu/shadps4-qtlauncher/releases", + nullptr); + std::cout << app.help(); + return -1; + } + + try { + app.parse(argc, argv); + } catch (const CLI::ParseError& e) { + return app.exit(e); + } + + if (waitPid) + Core::Debugger::WaitForPid(*waitPid); + + // Start default log + Common::Log::Setup("shad_log.txt"); + IPC::Instance().Init(); auto emu_state = std::make_shared(); @@ -60,75 +135,10 @@ int main(int argc, char* argv[]) { EmulatorSettingsImpl::SetInstance(emu_settings); emu_settings->Load(); - CLI::App app{"shadPS4 Emulator CLI"}; - - // ---- CLI state ---- - std::optional gamePath; - std::vector gameArgs; - std::optional overrideRoot; - std::optional waitPid; - bool waitForDebugger = false; - - std::optional fullscreenStr; - bool ignoreGamePatch = false; - bool showFps = false; - bool configClean = false; - bool configGlobal = false; - bool logAppend = false; - bool bigPicture = false; - - std::optional addGameFolder; - std::optional setAddonFolder; - std::optional patchFile; - - // ---- Options ---- - app.add_option("-g,--game", gamePath, "Game path or ID"); - app.add_option("-p,--patch", patchFile, "Patch file to apply"); - app.add_flag("-i,--ignore-game-patch", ignoreGamePatch, - "Disable automatic loading of game patches"); - - app.add_flag("-b,--big-picture", bigPicture, "Start in Big Picture Mode"); - - // FULLSCREEN: behavior-identical - app.add_option("-f,--fullscreen", fullscreenStr, "Fullscreen mode (true|false)"); - - app.add_option("--override-root", overrideRoot)->check(CLI::ExistingDirectory); - - app.add_flag("--wait-for-debugger", waitForDebugger); - app.add_option("--wait-for-pid", waitPid); - - app.add_flag("--show-fps", showFps); - app.add_flag("--config-clean", configClean); - app.add_flag("--config-global", configGlobal); - app.add_flag("--log-append", logAppend); - - app.add_option("--add-game-folder", addGameFolder)->check(CLI::ExistingDirectory); - app.add_option("--set-addon-folder", setAddonFolder)->check(CLI::ExistingDirectory); - - // ---- Capture args after `--` verbatim ---- - app.allow_extras(); - app.parse_complete_callback([&]() { - const auto& extras = app.remaining(); - if (!extras.empty()) { - gameArgs = extras; - } - }); - - // ---- No-args behavior ---- - if (argc == 1) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "shadPS4", - "This is a CLI application. Please use the QTLauncher for a GUI:\n" - "https://github.com/shadps4-emu/shadps4-qtlauncher/releases", - nullptr); - std::cout << app.help(); - return -1; - } - - try { - app.parse(argc, argv); - } catch (const CLI::ParseError& e) { - return app.exit(e); - } + Common::Log::Shutdown(); + // Start configured log + Common::Log::g_should_append |= EmulatorSettings.IsLogAppend(); + Common::Log::Setup("shad_log.txt"); // ---- Utility commands ---- if (addGameFolder) { @@ -190,9 +200,6 @@ int main(int argc, char* argv[]) { if (configGlobal) EmulatorSettings.SetConfigMode(ConfigMode::Global); - if (logAppend) - Common::Log::SetAppend(); - // ---- Resolve game path or ID ---- std::filesystem::path ebootPath(*gamePath); if (!std::filesystem::exists(ebootPath)) { @@ -211,9 +218,6 @@ int main(int argc, char* argv[]) { } } - if (waitPid) - Core::Debugger::WaitForPid(*waitPid); - if (bigPicture) { BigPictureMode::Launch(); } else { diff --git a/src/video_core/renderdoc.cpp b/src/video_core/renderdoc.cpp index 3f5a5b09f..0fe7e9fc8 100644 --- a/src/video_core/renderdoc.cpp +++ b/src/video_core/renderdoc.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/assert.h" +#include "common/logging/formatter.h" #include "core/emulator_settings.h" #include "video_core/renderdoc.h" diff --git a/src/video_core/renderer_vulkan/vk_platform.cpp b/src/video_core/renderer_vulkan/vk_platform.cpp index c77c80223..f1612000d 100644 --- a/src/video_core/renderer_vulkan/vk_platform.cpp +++ b/src/video_core/renderer_vulkan/vk_platform.cpp @@ -36,20 +36,20 @@ static VKAPI_ATTR VkBool32 VKAPI_CALL DebugUtilsCallback( vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* callback_data, void* user_data) { - Common::Log::Level level{}; + spdlog::level level{}; switch (severity) { case vk::DebugUtilsMessageSeverityFlagBitsEXT::eError: - level = Common::Log::Level::Error; + level = spdlog::level::err; break; case vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning: - level = Common::Log::Level::Info; + level = spdlog::level::info; break; case vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo: case vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose: - level = Common::Log::Level::Debug; + level = spdlog::level::debug; break; default: - level = Common::Log::Level::Info; + level = spdlog::level::info; } LOG_GENERIC(Common::Log::Class::Render_Vulkan, level, "{}: {}", diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 163712756..507238d7c 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -415,7 +415,7 @@ std::tuple TextureCache::ResolveOverlap(const ImageInfo& imag cache_image.info.size.height, cache_image.info.size.depth, cache_image.info.pitch, cache_image.info.resources.levels, cache_image.info.resources.layers, cache_image.info.num_samples, static_cast(cache_image.info.tile_mode), - cache_image.info.num_bits, cache_image.info.props.is_block, + cache_image.info.num_bits, +cache_image.info.props.is_block, cache_image.info.guest_size, cache_image.tick_accessed_last, safe_to_delete, // New image details diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6b656762a..cf5e02936 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -19,11 +19,10 @@ set(SETTINGS_TEST_SOURCES ${CMAKE_SOURCE_DIR}/src/common/assert.cpp ${CMAKE_SOURCE_DIR}/src/common/error.cpp ${CMAKE_SOURCE_DIR}/src/common/string_util.cpp - ${CMAKE_SOURCE_DIR}/src/common/logging/filter.cpp - ${CMAKE_SOURCE_DIR}/src/common/logging/text_formatter.cpp + ${CMAKE_SOURCE_DIR}/src/common/logging/log.cpp # Stubs that replace dependencies - stubs/log_stub.cpp + stubs/common_stub.cpp stubs/scm_rev_stub.cpp stubs/sdl_stub.cpp @@ -43,7 +42,8 @@ target_link_libraries(shadps4_settings_test PRIVATE fmt::fmt nlohmann_json::nlohmann_json toml11::toml11 - SDL3::SDL3 + SDL3::SDL3 + spdlog::spdlog ) target_compile_features(shadps4_settings_test PRIVATE cxx_std_23) diff --git a/tests/stubs/common_stub.cpp b/tests/stubs/common_stub.cpp new file mode 100644 index 000000000..cc665adcc --- /dev/null +++ b/tests/stubs/common_stub.cpp @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +namespace Common { + +std::string GetCurrentThreadName() { return "shadPS4::Test"; } + +} // namespace Common diff --git a/tests/stubs/log_stub.cpp b/tests/stubs/log_stub.cpp deleted file mode 100644 index 64d6df644..000000000 --- a/tests/stubs/log_stub.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include -#include "common/logging/backend.h" -#include "common/logging/filter.h" -#include "common/logging/log.h" - -namespace Common::Log { - - void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, - unsigned int line_num, const char* function, const char* format, - const fmt::format_args& args) { - } - - void Initialize(std::string_view) {} - bool IsActive() { return false; } - void SetGlobalFilter(const Filter&) {} - void SetColorConsoleBackendEnabled(bool) {} - void Start() {} - void Stop() {} - void Denitializer() {} - void SetAppend() {} - -} // namespace Common::Log diff --git a/tests/test_emulator_settings.cpp b/tests/test_emulator_settings.cpp index 1a4a667b3..620ea6b73 100644 --- a/tests/test_emulator_settings.cpp +++ b/tests/test_emulator_settings.cpp @@ -54,7 +54,14 @@ static json ReadJson(const fs::path& p) { return j; } -class EmulatorSettingsTest : public ::testing::Test { +class TestWrapper : public ::testing::Test { +protected: + TestWrapper() { + Common::Log::Setup("shad_test.log"); + } +}; + +class EmulatorSettingsTest : public TestWrapper { protected: void SetUp() override { temp_dir = std::make_unique(); @@ -286,7 +293,7 @@ TEST_F(EmulatorSettingsTest, SaveCreatesConfigJson) { TEST_F(EmulatorSettingsTest, SaveWritesAllExpectedSections) { ASSERT_TRUE(temp_settings->Save()); json j = ReadJson(ConfigJson()); - for (const char* section : {"General", "Debug", "Input", "Audio", "GPU", "Vulkan"}) + for (const char* section : {"General", "Log", "Debug", "Input", "Audio", "GPU", "Vulkan"}) EXPECT_TRUE(j.contains(section)) << "Missing section: " << section; } @@ -699,7 +706,6 @@ TEST_F(EmulatorSettingsTest, GetAllOverrideableKeysContainsRepresentativeKeys) { EXPECT_TRUE(has("pipeline_cache_enabled")); // Debug EXPECT_TRUE(has("debug_dump")); - EXPECT_TRUE(has("log_enabled")); // Input EXPECT_TRUE(has("cursor_state")); // Audio