diff --git a/.gitmodules b/.gitmodules index dac510bef..d84699c85 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,3 +25,8 @@ [submodule "externals/MoltenVK"] path = externals/MoltenVK url = https://github.com/shadPS4-emu/ext-MoltenVK.git +[submodule "externals/spdlog"] + path = externals/spdlog + url = https://github.com/gabime/spdlog + shallow = true + branch = v1.x diff --git a/CMakeLists.txt b/CMakeLists.txt index 51aad0ff3..60c800e26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -258,16 +258,9 @@ file(APPEND ${TRANSLATIONS_QRC} "") qt_add_resources(TRANSLATIONS ${TRANSLATIONS_QRC}) -set(COMMON src/common/logging/backend.cpp - src/common/logging/backend.h - src/common/logging/filter.cpp - src/common/logging/filter.h - src/common/logging/formatter.h - src/common/logging/log_entry.h +set(COMMON src/common/logging/classes.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/log_analyzer.cpp src/common/log_analyzer.h src/common/aes.h @@ -399,7 +392,7 @@ qt_add_executable(shadPS4QtLauncher create_target_directory_groups(shadPS4QtLauncher) -target_link_libraries(shadPS4QtLauncher PRIVATE Vulkan::Headers fmt::fmt toml11::toml11 SDL3::SDL3 volk_headers) +target_link_libraries(shadPS4QtLauncher PRIVATE Vulkan::Headers fmt::fmt toml11::toml11 SDL3::SDL3 volk_headers spdlog::spdlog) if (APPLE) # Include MoltenVK, along with an ICD file so it can be found by the system Vulkan loader if used for loading layers. diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index d293d95f3..b9988233f 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -62,3 +62,9 @@ add_subdirectory(volk) #nlohmann json set(JSON_BuildTests OFF CACHE INTERNAL "") add_subdirectory(json) + +# spdlog +set(SPDLOG_NO_EXCEPTIONS ON) +set(SPDLOG_FMT_EXTERNAL ON) +set(SPDLOG_DISABLE_DEFAULT_LOGGER ON) +add_subdirectory(spdlog EXCLUDE_FROM_ALL SYSTEM) diff --git a/externals/spdlog b/externals/spdlog new file mode 160000 index 000000000..0f7562a0f --- /dev/null +++ b/externals/spdlog @@ -0,0 +1 @@ +Subproject commit 0f7562a0f9273cfc71fddc6ae52ebff7a490fa04 diff --git a/src/common/assert.cpp b/src/common/assert.cpp index 88591bfee..8411e1f4a 100644 --- a/src/common/assert.cpp +++ b/src/common/assert.cpp @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/assert.h" -#include "common/logging/backend.h" +#include "common/logging/log.h" #if defined(__x86_64__) || defined(_M_X64) #define Crash() __asm__ __volatile__("int $3") @@ -13,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 181c61a30..82aca133d 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -1,7 +1,8 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include #include @@ -10,7 +11,6 @@ #include "common/assert.h" #include "common/config.h" -#include "common/logging/formatter.h" #include "common/path_util.h" #include "common/scm_rev.h" @@ -52,6 +52,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); @@ -136,14 +140,26 @@ 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 userName("shadPS4"); static ConfigEntry isShowSplash(false); static ConfigEntry isSideTrophy("right"); static ConfigEntry isConnectedToNetwork(false); 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); @@ -197,9 +213,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 = {}; @@ -234,6 +248,17 @@ void setSysModulesPath(const std::filesystem::path& path) { sys_modules_path = path; } +std::filesystem::path getFontsPath() { + if (fonts_path.empty()) { + return Common::FS::GetUserPath(Common::FS::PathType::FontsDir); + } + return fonts_path; +} + +void setFontsPath(const std::filesystem::path& path) { + fonts_path = path; +} + int getVolumeSlider() { return volumeSlider.get(); } @@ -262,7 +287,7 @@ int* GetControllerCustomColor() { } bool getLoggingEnabled() { - return logEnabled.get(); + return logEnable.get(); } void SetControllerCustomColor(int r, int b, int g) { @@ -361,7 +386,7 @@ u32 getWindowHeight() { } u32 getInternalScreenWidth() { - return internalScreenHeight.get(); + return internalScreenWidth.get(); } u32 getInternalScreenHeight() { @@ -376,9 +401,31 @@ string getLogFilter() { return logFilter.get(); } +bool isLogSync() { + return logSync.get(); +} + +#ifdef _WIN32 string getLogType() { return logType.get(); } +#endif + +bool getLogSkipDuplicate() { + return logSkipDuplicate.get(); +} + +bool isLogAppend() { + return logAppend.get(); +} + +u32 getMaxSkipDuration() { + return logMaxSkipDuration.get(); +} + +unsigned long long getLogSizeLimit() { + return logSizeLimit.get(); +} string getUserName() { return userName.get(); @@ -461,7 +508,7 @@ void setShowFpsCounter(bool enable, bool is_game_specific) { } bool isLoggingEnabled() { - return logEnabled.get(); + return logEnable.get(); } u32 vblankFreq() { @@ -544,7 +591,7 @@ void setDebugDump(bool enable, bool is_game_specific) { } void setLoggingEnabled(bool enable, bool is_game_specific) { - logEnabled.set(enable, is_game_specific); + logEnable.set(enable, is_game_specific); } void setCollectShaderForDebug(bool enable, bool is_game_specific) { @@ -675,16 +722,38 @@ void setDevKitConsole(bool enable, bool is_game_specific) { isDevKit.set(enable, is_game_specific); } +void setLogSync(bool sync, bool is_game_specific) { + logSync.set(sync, is_game_specific); +} + +#ifdef _WIN32 void setLogType(const string& type, bool is_game_specific) { logType.set(type, is_game_specific); } +#endif + +void setLogSkipDuplicate(bool enable, bool is_game_specific) { + logSkipDuplicate.set(enable, is_game_specific); +} + +void setMaxSkipDuration(u32 duration, bool is_game_specific) { + logMaxSkipDuration.set(duration, is_game_specific); +} + +void setLogSizeLimit(unsigned long long size, bool is_game_specific) { + logSizeLimit.set(size, is_game_specific); +} + +void setLogAppend(bool enable, bool is_game_specific) { + logAppend.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); + logSeparate.set(enabled, is_game_specific); } void setUserName(const string& name, bool is_game_specific) { @@ -781,7 +850,7 @@ u32 GetLanguage() { } bool getSeparateLogFilesEnabled() { - return isSeparateLogFilesEnabled.get(); + return logSeparate.get(); } bool getPSNSignedIn() { @@ -856,7 +925,7 @@ void load(const std::filesystem::path& path, bool is_game_specific) { std::ifstream ifs; ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); ifs.open(path, std::ios_base::binary); - data = toml::parse(ifs, string{fmt::UTF(path.filename().u8string()).data}); + data = toml::parse(ifs, path.filename().string()); } catch (std::exception& ex) { fmt::print("Got exception trying to load config file. Exception: {}\n", ex.what()); return; @@ -876,8 +945,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); userName.setFromToml(general, "userName", is_game_specific); isShowSplash.setFromToml(general, "showSplash", is_game_specific); isSideTrophy.setFromToml(general, "sideTrophy", is_game_specific); @@ -885,6 +952,23 @@ void load(const std::filesystem::path& path, bool is_game_specific) { isConnectedToNetwork.setFromToml(general, "isConnectedToNetwork", is_game_specific); defaultControllerID.setFromToml(general, "defaultControllerID", is_game_specific); sys_modules_path = toml::find_fs_path_or(general, "sysModulesPath", sys_modules_path); + fonts_path = toml::find_fs_path_or(general, "fontsPath", fonts_path); + } + + if (data.contains("Log")) { + const toml::value& log = data.at("Log"); + + logFilter.setFromToml(log, "filter", is_game_specific); + logSkipDuplicate.setFromToml(log, "skipDuplicate", is_game_specific); + logSync.setFromToml(log, "sync", is_game_specific); +#ifdef _WIN32 + logType.setFromToml(log, "type", is_game_specific); +#endif + logAppend.setFromToml(log, "append", is_game_specific); + logSeparate.setFromToml(log, "separate", is_game_specific); + logEnable.setFromToml(log, "enable", is_game_specific); + logMaxSkipDuration.setFromToml(log, "maxSkipDuration", is_game_specific); + logSizeLimit.setFromToml(log, "sizeLimit", is_game_specific); } if (data.contains("Input")) { @@ -953,10 +1037,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); } @@ -1007,8 +1089,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)) { @@ -1045,8 +1127,7 @@ void save(const std::filesystem::path& path, bool is_game_specific) { std::ifstream ifs; ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); ifs.open(path, std::ios_base::binary); - data = toml::parse( - ifs, string{fmt::UTF(path.filename().u8string()).data}); + data = toml::parse(ifs, path.filename().string()); } catch (const std::exception& ex) { fmt::print("Exception trying to parse config file. Exception: {}\n", ex.what()); return; @@ -1055,7 +1136,7 @@ void save(const std::filesystem::path& path, bool is_game_specific) { if (error) { fmt::print("Filesystem error: {}\n", error.message()); } - fmt::print("Saving new configuration file {}\n", fmt::UTF(path.u8string())); + fmt::print("Saving new configuration file {}\n", path.string()); } // Entries saved by the game-specific settings GUI @@ -1063,8 +1144,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); userName.setTomlValue(data, "General", "userName", is_game_specific); isShowSplash.setTomlValue(data, "General", "showSplash", is_game_specific); isSideTrophy.setTomlValue(data, "General", "sideTrophy", is_game_specific); @@ -1076,6 +1155,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); + logFilter.setTomlValue(data, "Log", "filter", is_game_specific); + logSync.setTomlValue(data, "Log", "sync", is_game_specific); +#ifdef _WIN32 + logType.setTomlValue(data, "Log", "type", is_game_specific); +#endif + logSkipDuplicate.setTomlValue(data, "Log", "skipDuplicate", is_game_specific); + logAppend.setTomlValue(data, "Log", "append", is_game_specific); + logSeparate.setTomlValue(data, "Log", "separate", is_game_specific); + logEnable.setTomlValue(data, "Log", "enable", is_game_specific); + logMaxSkipDuration.setTomlValue(data, "Log", "maxSkipDuration", is_game_specific); + logSizeLimit.setTomlValue(data, "Log", "sizeLimit", is_game_specific); + cursorState.setTomlValue(data, "Input", "cursorState", is_game_specific); cursorHideTimeout.setTomlValue(data, "Input", "cursorHideTimeout", is_game_specific); isMotionControlsEnabled.setTomlValue(data, "Input", "isMotionControlsEnabled", @@ -1119,9 +1210,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); @@ -1137,8 +1225,7 @@ void save(const std::filesystem::path& path, bool is_game_specific) { std::vector sorted_dirs; for (const auto& dirInfo : settings_install_dirs) { - sorted_dirs.push_back( - {string{fmt::UTF(dirInfo.path.u8string()).data}, dirInfo.enabled}); + sorted_dirs.push_back({dirInfo.path.string(), dirInfo.enabled}); } // Sort directories alphabetically @@ -1157,12 +1244,12 @@ void save(const std::filesystem::path& path, bool is_game_specific) { // Non game-specific entries data["General"]["enableDiscordRPC"] = enableDiscordRPC; - data["General"]["sysModulesPath"] = string{fmt::UTF(sys_modules_path.u8string()).data}; + data["General"]["sysModulesPath"] = sys_modules_path.string(); + data["General"]["fontsPath"] = fonts_path.u8string(); data["GUI"]["installDirs"] = install_dirs; data["GUI"]["installDirsEnabled"] = install_dirs_enabled; - data["GUI"]["saveDataPath"] = string{fmt::UTF(save_data_path.u8string()).data}; - data["GUI"]["addonInstallDir"] = - string{fmt::UTF(settings_addon_install_dir.u8string()).data}; + data["GUI"]["saveDataPath"] = save_data_path.u8string(); + data["GUI"]["addonInstallDir"] = settings_addon_install_dir.u8string(); data["Debug"]["ConfigVersion"] = config_version; data["Keys"]["TrophyKey"] = trophyKey; @@ -1205,12 +1292,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); userName.set("shadPS4", is_game_specific); isShowSplash.set(false, is_game_specific); isSideTrophy.set("right", is_game_specific); + // GS - Log + logFilter.set("", is_game_specific); + logSync.set(true, is_game_specific); +#ifdef _WIN32 + logType.set("wincolor", is_game_specific); +#endif + logSkipDuplicate.set(true, is_game_specific); + logAppend.set(false, is_game_specific); + logSeparate.set(false, is_game_specific); + logEnable.set(true, is_game_specific); + logMaxSkipDuration.set(5'000, is_game_specific); + logSizeLimit.set(100_MB, is_game_specific); + // GS - Input cursorState.set(HideCursorState::Idle, is_game_specific); cursorHideTimeout.set(5, is_game_specific); @@ -1252,8 +1350,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); @@ -1289,16 +1385,6 @@ void setDefaultValues(bool is_game_specific) { constexpr std::string_view GetDefaultGlobalConfig() { return R"(# Anything put here will be loaded for all games, # alongside the game's config or default.ini depending on your preference. - -hotkey_renderdoc_capture = f12 -hotkey_fullscreen = f11 -hotkey_show_fps = f10 -hotkey_pause = f9 -hotkey_reload_inputs = f8 -hotkey_toggle_mouse_to_joystick = f7 -hotkey_toggle_mouse_to_gyro = f6 -hotkey_toggle_mouse_to_touchpad = delete -hotkey_quit = lctrl, lshift, end )"; } @@ -1376,7 +1462,7 @@ analog_deadzone = rightjoystick, 2, 127 override_controller_color = false, 0, 0, 255 )"; } -std::filesystem::path GetFoolproofInputConfigFile(const string& game_id) { +std::filesystem::path GetInputConfigFile(const string& game_id) { // Read configuration file of the game, and if it doesn't exist, generate it from default // If that doesn't exist either, generate that from getDefaultConfig() and try again // If even the folder is missing, we start with that. @@ -1415,6 +1501,39 @@ std::filesystem::path GetFoolproofInputConfigFile(const string& game_id) { } } } + if (game_id == "global") { + std::map default_bindings_to_add = { + {"hotkey_renderdoc_capture", "f12"}, + {"hotkey_fullscreen", "f11"}, + {"hotkey_show_fps", "f10"}, + {"hotkey_pause", "f9"}, + {"hotkey_reload_inputs", "f8"}, + {"hotkey_toggle_mouse_to_joystick", "f7"}, + {"hotkey_toggle_mouse_to_gyro", "f6"}, + {"hotkey_toggle_mouse_to_touchpad", "delete"}, + {"hotkey_quit", "lctrl, lshift, end"}, + {"hotkey_volume_up", "kpplus"}, + {"hotkey_volume_down", "kpminus"}, + }; + std::ifstream global_in(config_file); + string line; + while (std::getline(global_in, line)) { + line.erase(std::remove_if(line.begin(), line.end(), + [](unsigned char c) { return std::isspace(c); }), + line.end()); + std::size_t equal_pos = line.find('='); + if (equal_pos == std::string::npos) { + continue; + } + std::string output_string = line.substr(0, equal_pos); + default_bindings_to_add.erase(output_string); + } + global_in.close(); + std::ofstream global_out(config_file, std::ios::app); + for (auto const& b : default_bindings_to_add) { + global_out << b.first << " = " << b.second << "\n"; + } + } // If game-specific config doesn't exist, create it from the default config if (!std::filesystem::exists(config_file)) { diff --git a/src/common/config.h b/src/common/config.h index 1e065106a..41bd37326 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -25,8 +25,8 @@ enum HideCursorState : int { Never, Idle, Always }; enum GpuReadbacksMode : int { Disabled, - Low, - High, + Relaxed, + Precise, }; void load(const std::filesystem::path& path, bool is_game_specific = false); @@ -105,8 +105,20 @@ 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); +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 +bool getLogSkipDuplicate(); +void setLogSkipDuplicate(bool enable, bool is_game_specific = false); +u32 getMaxSkipDuration(); +void setMaxSkipDuration(u32 duration, bool is_game_specific = false); +unsigned long long getLogSizeLimit(); +void setLogSizeLimit(unsigned long long size, bool is_game_specific = false); +bool isLogAppend(); +void setLogAppend(bool enable, bool is_game_specific = false); std::string getLogFilter(); void setLogFilter(const std::string& type, bool is_game_specific = false); double getTrophyNotificationDuration(); @@ -159,6 +171,8 @@ void setConnectedToNetwork(bool enable, bool is_game_specific = false); void setUserName(const std::string& name, bool is_game_specific = false); std::filesystem::path getSysModulesPath(); void setSysModulesPath(const std::filesystem::path& path); +std::filesystem::path getFontsPath(); +void setFontsPath(const std::filesystem::path& path); enum UsbBackendType : int { Real, SkylandersPortal, InfinityBase, DimensionsToypad }; int getUsbDeviceBackend(); @@ -189,6 +203,6 @@ std::filesystem::path getAddonInstallDir(); void setDefaultValues(bool is_game_specific = false); constexpr std::string_view GetDefaultGlobalConfig(); -std::filesystem::path GetFoolproofInputConfigFile(const std::string& game_id = ""); +std::filesystem::path GetInputConfigFile(const std::string& game_id = ""); }; // namespace Config diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp deleted file mode 100644 index 3e1ef6e31..000000000 --- a/src/common/logging/backend.cpp +++ /dev/null @@ -1,262 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include -#include - -#include "common/bounded_threadsafe_queue.h" -#include "common/config.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" - -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) - : file{filename, FS::FileAccessMode::Write, FS::FileType::TextFile} {} - - ~FileBackend() = default; - - void Write(const Entry& entry) { - if (!enabled) { - 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; -}; - -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(Config::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 bool IsActive() { - return instance != nullptr; - } - - static void Start() { - instance->StartBackendThread(); - } - - static void Stop() { - instance->StopBackendThread(); - } - - 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, std::string message) { - if (!filter.CheckMessage(log_class, log_level) || !Config::getLoggingEnabled()) { - return; - } - - using std::chrono::duration_cast; - using std::chrono::microseconds; - using std::chrono::steady_clock; - - 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 = std::move(message), - }; - if (Config::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} {} - - ~Impl() = default; - - void StartBackendThread() { - backend_thread = std::jthread([this](std::stop_token stop_token) { - 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() { - backend_thread.request_stop(); - if (backend_thread.joinable()) { - backend_thread.join(); - } - - ForEachBackend([](auto& backend) { backend.Flush(); }); - } - - void ForEachBackend(auto lambda) { - lambda(color_console_backend); - lambda(file_backend); - } - - static void Deleter(Impl* ptr) { - delete ptr; - } - - static inline std::unique_ptr instance{nullptr, Deleter}; - - Filter filter; - 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; -}; -} // 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 SetGlobalFilter(const Filter& filter) { - Impl::Instance().SetGlobalFilter(filter); -} - -void SetColorConsoleBackendEnabled(bool enabled) { - Impl::Instance().SetColorConsoleBackendEnabled(enabled); -} - -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, - fmt::vformat(format, args)); - } -} -} // namespace Common::Log diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h deleted file mode 100644 index a1ad66369..000000000 --- a/src/common/logging/backend.h +++ /dev/null @@ -1,29 +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(); - -/// The global filter will prevent any messages from even being processed if they are filtered. -void SetGlobalFilter(const Filter& filter); - -void SetColorConsoleBackendEnabled(bool enabled); - -} // namespace Common::Log diff --git a/src/common/logging/classes.h b/src/common/logging/classes.h new file mode 100644 index 000000000..c068a50a6 --- /dev/null +++ b/src/common/logging/classes.h @@ -0,0 +1,219 @@ +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +namespace Common::Log { +// clang-format off +/// Llisting all log classes, if you add here, dont forget ALL_LOG_CLASSES +constexpr auto Log = "Log"; +constexpr auto Common = "Common"; +constexpr auto Common_Filesystem = "Common.Filesystem"; +constexpr auto Common_Memory = "Common.Memory"; +constexpr auto Core = "Core"; +constexpr auto Core_Linker = "Core.Linker"; +constexpr auto Core_Devices = "Core.Devices"; +constexpr auto Config = "Config"; +constexpr auto Debug = "Debug"; +constexpr auto Kernel = "Kernel"; +constexpr auto Kernel_Pthread = "Kernel.Pthread"; +constexpr auto Kernel_Vmm = "Kernel.Vmm"; +constexpr auto Kernel_Fs = "Kernel.Fs"; +constexpr auto Kernel_Event = "Kernel.Event"; +constexpr auto Kernel_Sce = "Kernel.Sce"; +constexpr auto Lib = "Lib"; +constexpr auto Lib_LibC = "Lib.LibC"; +constexpr auto Lib_LibcInternal = "Lib.LibcInternal"; +constexpr auto Lib_Kernel = "Lib.Kernel"; +constexpr auto Lib_Pad = "Lib.Pad"; +constexpr auto Lib_SystemGesture = "Lib.SystemGesture"; +constexpr auto Lib_GnmDriver = "Lib.GnmDriver"; +constexpr auto Lib_SystemService = "Lib.SystemService"; +constexpr auto Lib_UserService = "Lib.UserService"; +constexpr auto Lib_VideoOut = "Lib.VideoOut"; +constexpr auto Lib_CommonDlg = "Lib.CommonDlg"; +constexpr auto Lib_MsgDlg = "Lib.MsgDlg"; +constexpr auto Lib_AudioOut = "Lib.AudioOut"; +constexpr auto Lib_AudioIn = "Lib.AudioIn"; +constexpr auto Lib_Net = "Lib.Net"; +constexpr auto Lib_NetCtl = "Lib.NetCtl"; +constexpr auto Lib_SaveData = "Lib.SaveData"; +constexpr auto Lib_SaveDataDialog = "Lib.SaveDataDialog"; +constexpr auto Lib_Http = "Lib.Http"; +constexpr auto Lib_Http2 = "Lib.Http2"; +constexpr auto Lib_Ssl = "Lib.Ssl"; +constexpr auto Lib_Ssl2 = "Lib.Ssl2"; +constexpr auto Lib_SysModule = "Lib.SysModule"; +constexpr auto Lib_Move = "Lib.Move"; +constexpr auto Lib_NpAuth = "Lib.NpAuth"; +constexpr auto Lib_NpCommon = "Lib.NpCommon"; +constexpr auto Lib_NpCommerce = "Lib.NpCommerce"; +constexpr auto Lib_NpManager = "Lib.NpManager"; +constexpr auto Lib_NpMatching2 = "Lib.NpMatching2"; +constexpr auto Lib_NpScore = "Lib.NpScore"; +constexpr auto Lib_NpTrophy = "Lib.NpTrophy"; +constexpr auto Lib_NpTus = "Lib.NpTus"; +constexpr auto Lib_NpWebApi = "Lib.NpWebApi"; +constexpr auto Lib_NpWebApi2 = "Lib.NpWebApi2"; +constexpr auto Lib_NpProfileDialog = "Lib.NpProfileDialog"; +constexpr auto Lib_NpSnsFacebookDialog = "Lib.NpSnsFacebookDialog"; +constexpr auto Lib_NpPartner = "Lib.NpPartner"; +constexpr auto Lib_Screenshot = "Lib.Screenshot"; +constexpr auto Lib_LibCInternal = "Lib.LibCInternal"; +constexpr auto Lib_AppContent = "Lib.AppContent"; +constexpr auto Lib_Rtc = "Lib.Rtc"; +constexpr auto Lib_Rudp = "Lib.Rudp"; +constexpr auto Lib_DiscMap = "Lib.DiscMap"; +constexpr auto Lib_Png = "Lib.Png"; +constexpr auto Lib_Jpeg = "Lib.Jpeg"; +constexpr auto Lib_PlayGo = "Lib.PlayGo"; +constexpr auto Lib_PlayGoDialog = "Lib.PlayGoDialog"; +constexpr auto Lib_Random = "Lib.Random"; +constexpr auto Lib_Usbd = "Lib.Usbd"; +constexpr auto Lib_Ajm = "Lib.Ajm"; +constexpr auto Lib_ErrorDialog = "Lib.ErrorDialog"; +constexpr auto Lib_ImeDialog = "Lib.ImeDialog"; +constexpr auto Lib_AvPlayer = "Lib.AvPlayer"; +constexpr auto Lib_Ngs2 = "Lib.Ngs2"; +constexpr auto Lib_Audio3d = "Lib.Audio3d"; +constexpr auto Lib_Ime = "Lib.Ime"; +constexpr auto Lib_GameLiveStreaming = "Lib.GameLiveStreaming"; +constexpr auto Lib_Remoteplay = "Lib.Remoteplay"; +constexpr auto Lib_SharePlay = "Lib.SharePlay"; +constexpr auto Lib_Fiber = "Lib.Fiber"; +constexpr auto Lib_Vdec2 = "Lib.Vdec2"; +constexpr auto Lib_Videodec = "Lib.Videodec"; +constexpr auto Lib_RazorCpu = "Lib.RazorCpu"; +constexpr auto Lib_Mouse = "Lib.Mouse"; +constexpr auto Lib_WebBrowserDialog = "Lib.WebBrowserDialog"; +constexpr auto Lib_NpParty = "Lib.NpParty"; +constexpr auto Lib_Zlib = "Lib.Zlib"; +constexpr auto Lib_Hmd = "Lib.Hmd"; +constexpr auto Lib_Font = "Lib.Font"; +constexpr auto Lib_FontFt = "Lib.FontFt"; +constexpr auto Lib_HmdSetupDialog = "Lib.HmdSetupDialog"; +constexpr auto Lib_SigninDialog = "Lib.SigninDialog"; +constexpr auto Lib_Camera = "Lib.Camera"; +constexpr auto Lib_CompanionHttpd = "Lib.CompanionHttpd"; +constexpr auto Lib_CompanionUtil = "Lib.CompanionUtil"; +constexpr auto Lib_Voice = "Lib.Voice"; +constexpr auto Lib_VrTracker = "Lib.VrTracker"; +constexpr auto Frontend = "Frontend"; +constexpr auto Render = "Render"; +constexpr auto Render_Vulkan = "Render.Vulkan"; +constexpr auto Render_Recompiler = "Render.Recompiler"; +constexpr auto ImGui = "ImGui"; +constexpr auto Input = "Input"; +constexpr auto Tty = "Tty"; +constexpr auto IPC = "IPC"; +constexpr auto KeyManager = "KeyManager"; +constexpr auto Loader = "Loader"; +// clang-format on + +constexpr std::array ALL_LOG_CLASSES{ + Log, + Common, + Common_Filesystem, + Common_Memory, + Core, + Core_Linker, + Core_Devices, + Config, + Debug, + Kernel, + Kernel_Pthread, + Kernel_Vmm, + Kernel_Fs, + Kernel_Event, + Kernel_Sce, + Lib, + Lib_LibC, + Lib_LibcInternal, + Lib_Kernel, + Lib_Pad, + Lib_SystemGesture, + Lib_GnmDriver, + Lib_SystemService, + Lib_UserService, + Lib_VideoOut, + Lib_CommonDlg, + Lib_MsgDlg, + Lib_AudioOut, + Lib_AudioIn, + Lib_Net, + Lib_NetCtl, + Lib_SaveData, + Lib_SaveDataDialog, + Lib_Http, + Lib_Http2, + Lib_Ssl, + Lib_Ssl2, + Lib_SysModule, + Lib_Move, + Lib_NpAuth, + Lib_NpCommon, + Lib_NpCommerce, + Lib_NpManager, + Lib_NpMatching2, + Lib_NpScore, + Lib_NpTrophy, + Lib_NpTus, + Lib_NpWebApi, + Lib_NpWebApi2, + Lib_NpProfileDialog, + Lib_NpSnsFacebookDialog, + Lib_NpPartner, + Lib_Screenshot, + Lib_LibCInternal, + Lib_AppContent, + Lib_Rtc, + Lib_Rudp, + Lib_DiscMap, + Lib_Png, + Lib_Jpeg, + Lib_PlayGo, + Lib_PlayGoDialog, + Lib_Random, + Lib_Usbd, + Lib_Ajm, + Lib_ErrorDialog, + Lib_ImeDialog, + Lib_AvPlayer, + Lib_Ngs2, + Lib_Audio3d, + Lib_Ime, + Lib_GameLiveStreaming, + Lib_Remoteplay, + Lib_SharePlay, + Lib_Fiber, + Lib_Vdec2, + Lib_Videodec, + Lib_RazorCpu, + Lib_Mouse, + Lib_WebBrowserDialog, + Lib_NpParty, + Lib_Zlib, + Lib_Hmd, + Lib_Font, + Lib_FontFt, + Lib_HmdSetupDialog, + Lib_SigninDialog, + Lib_Camera, + Lib_CompanionHttpd, + Lib_CompanionUtil, + Lib_Voice, + Lib_VrTracker, + Frontend, + Render, + Render_Vulkan, + Render_Recompiler, + ImGui, + Input, + Tty, + IPC, + KeyManager, + Loader, +}; +} // namespace Common::Log diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp deleted file mode 100644 index eba08d4ce..000000000 --- a/src/common/logging/filter.cpp +++ /dev/null @@ -1,237 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2014 Citra 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) { - 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(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, LibC) \ - 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, NpManager) \ - SUB(Lib, NpScore) \ - SUB(Lib, NpTrophy) \ - SUB(Lib, NpWebApi) \ - SUB(Lib, NpProfileDialog) \ - SUB(Lib, Screenshot) \ - SUB(Lib, LibCInternal) \ - SUB(Lib, AppContent) \ - SUB(Lib, Rtc) \ - 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, 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) \ - CLS(IPC) - -// 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/formatter.h b/src/common/logging/formatter.h deleted file mode 100644 index fad8451e9..000000000 --- a/src/common/logging/formatter.h +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -// Adapted from https://github.com/fmtlib/fmt/issues/2704 -// a generic formatter for enum classes -#if FMT_VERSION >= 80100 -template -struct fmt::formatter, char>> - : formatter> { - template - auto format(const T& value, FormatContext& ctx) -> decltype(ctx.out()) { - return fmt::formatter>::format( - static_cast>(value), ctx); - } -}; -#endif - -namespace fmt { -template -struct UTF { - T data; - - explicit UTF(const std::u8string_view view) { - data = view.empty() ? T{} : T{(const char*)&view.front(), (const char*)&view.back() + 1}; - } - - explicit UTF(const std::u8string& str) : UTF(std::u8string_view{str}) {} -}; -} // namespace fmt - -template <> -struct fmt::formatter, char> : formatter { - template - auto format(const UTF& wrapper, FormatContext& ctx) const { - return formatter::format(wrapper.data, ctx); - } -}; \ No newline at end of file diff --git a/src/common/logging/log.cpp b/src/common/logging/log.cpp new file mode 100644 index 000000000..370df4152 --- /dev/null +++ b/src/common/logging/log.cpp @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "log.h" + +namespace Common::Log { +std::shared_ptr g_console_sink; +std::shared_ptr g_shad_file_sink; +std::shared_ptr g_game_file_sink; +bool g_should_append = false; +} // namespace Common::Log diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 5fa430348..cbbe3db1d 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -1,70 +1,232 @@ -// 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 +#include +#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/formatter.h" -#include "common/logging/types.h" +#include "common/config.h" +#include "common/logging/classes.h" +#include "common/path_util.h" +#include "common/types.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::level::to_string_view(msg.level), + dest); + dest.push_back('>'); + dest.push_back(' '); + + spdlog::details::fmt_helper::append_string_view( + std::filesystem::path(msg.source.filename).filename().string(), dest); + dest.push_back(':'); + spdlog::details::fmt_helper::append_int(msg.source.line, dest); + dest.push_back(' '); + spdlog::details::fmt_helper::append_string_view(msg.source.funcname, dest); + 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; +}; + +static constexpr auto POSITON_CONSOLE_LOG = 0; +static constexpr auto POSITON_SHAD_LOG = 1; +static constexpr auto POSITON_GAME_LOG = 2; + +static constexpr auto POSITON_DUPLICATE_SINK = 0; + +extern std::shared_ptr g_console_sink; +extern std::shared_ptr g_shad_file_sink; +extern std::shared_ptr g_game_file_sink; +extern bool g_should_append; + +static void Shutdown() { + g_game_file_sink.reset(); + g_shad_file_sink.reset(); + g_console_sink.reset(); + spdlog::shutdown(); +} + +static void Setup(int argc, char* argv[]) { + if (Config::getLoggingEnabled()) { + spdlog::cfg::load_env_levels(); + spdlog::cfg::helpers::load_levels(Config::getLogFilter()); + spdlog::cfg::load_argv_levels(argc, argv); + } else { + spdlog::cfg::helpers::load_levels("off"); + } + + if (!Config::isLogSync()) { + spdlog::init_thread_pool(8192, 1); + } + + std::at_quick_exit(Shutdown); + + spdlog::set_formatter(std::make_unique(UNLIMITED_SIZE)); + +#ifdef _WIN32 + if (Config::getLogType() == "wincolor") { + g_console_sink = std::make_shared(); + } else { + g_console_sink = std::make_shared(); + } +#else + g_console_sink = std::make_shared(); +#endif -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; + g_shad_file_sink = std::make_shared( + (GetUserPath(Common::FS::PathType::LogDir) / "shadPS4Launcher.log").string()); + g_shad_file_sink->set_formatter( + std::make_unique(Config::getLogSizeLimit())); + + std::shared_ptr dup_filter; + + if (Config::getLogSkipDuplicate()) { + dup_filter = std::make_shared( + std::chrono::milliseconds(Config::getMaxSkipDuration())); + dup_filter->set_sinks( + {g_console_sink, g_shad_file_sink, + std::make_shared< + spdlog::sinks::null_sink_mt>() /*for POSITON_GAME_LOG id + ".log" */}); + } + + for (const auto& name : Common::Log::ALL_LOG_CLASSES) { + if (Config::getLogSkipDuplicate()) { + spdlog::initialize_logger(Config::isLogSync() + ? std::make_shared(name, dup_filter) + : std::make_shared( + name, dup_filter, spdlog::thread_pool())); + } else { + spdlog::initialize_logger( + Config::isLogSync() + ? std::make_shared( + name, + std::initializer_list{ + g_console_sink, g_shad_file_sink, + std::make_shared< + spdlog::sinks:: + null_sink_mt>() /*for POSITON_GAME_LOG id + ".log" */}) + : std::make_shared( + name, + std::initializer_list{ + g_console_sink, g_shad_file_sink, + std::make_shared< + spdlog::sinks:: + null_sink_mt>() /*for POSITON_GAME_LOG id + ".log" */}, + spdlog::thread_pool())); + } + } } -/// 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); +static void Redirect(const std::string& name) { + g_game_file_sink = std::make_shared( + (GetUserPath(Common::FS::PathType::LogDir) / name).string(), !g_should_append); + g_game_file_sink->set_formatter( + std::make_unique(Config::getLogSizeLimit())); + + for (const auto& name : Common::Log::ALL_LOG_CLASSES) { + auto l = spdlog::get(name); + auto& sinks = Config::getLogSkipDuplicate() + ? std::dynamic_pointer_cast( + l->sinks()[POSITON_DUPLICATE_SINK]) + ->sinks() + : l->sinks(); -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...)); + if (sinks.size() == 3) { + sinks[POSITON_GAME_LOG] = g_game_file_sink; + } + } + + g_shad_file_sink->set_level(spdlog::level::off); +} + +static void StopRedirection() { + g_game_file_sink->flush(); + g_game_file_sink->set_level(spdlog::level::off); + g_shad_file_sink->set_level(spdlog::level::trace); } +static void Truncate() { + if (g_game_file_sink != nullptr) { + g_game_file_sink->truncate(); + } + if (g_shad_file_sink != nullptr) { + g_shad_file_sink->truncate(); + } +} } // 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(spdlog::get(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__) + SPDLOG_LOGGER_TRACE(spdlog::get(Common::Log::log_class), __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__) + SPDLOG_LOGGER_DEBUG(spdlog::get(Common::Log::log_class), __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__) + SPDLOG_LOGGER_INFO(spdlog::get(Common::Log::log_class), __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__) + SPDLOG_LOGGER_WARN(spdlog::get(Common::Log::log_class), __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__) + SPDLOG_LOGGER_ERROR(spdlog::get(Common::Log::log_class), __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__) + SPDLOG_LOGGER_CRITICAL(spdlog::get(Common::Log::log_class), __VA_ARGS__) diff --git a/src/common/logging/log_entry.h b/src/common/logging/log_entry.h deleted file mode 100644 index cd4ae9355..000000000 --- a/src/common/logging/log_entry.h +++ /dev/null @@ -1,26 +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; -}; - -} // namespace Common::Log diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp deleted file mode 100644 index b4fa204bc..000000000 --- a/src/common/logging/text_formatter.cpp +++ /dev/null @@ -1,109 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2014 Citra 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.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/types.h b/src/common/logging/types.h deleted file mode 100644 index c5e497a30..000000000 --- a/src/common/logging/types.h +++ /dev/null @@ -1,128 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2023 Citra 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 - 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_LibC, ///< The LibC implementation. - 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_NpAuth, ///< The LibSceNpAuth implementation - Lib_NpManager, ///< The LibSceNpManager implementation - Lib_NpScore, ///< The LibSceNpScore implementation - Lib_NpTrophy, ///< The LibSceNpTrophy implementation - Lib_NpWebApi, ///< The LibSceWebApi implementation - Lib_NpProfileDialog, ///< The LibSceNpProfileDialog implementation - Lib_Screenshot, ///< The LibSceScreenshot implementation - Lib_LibCInternal, ///< The LibCInternal implementation. - Lib_AppContent, ///< The LibSceAppContent implementation. - Lib_Rtc, ///< The LibSceRtc 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_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. - 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 - IPC, ///< IPC - Count ///< Total number of logging classes -}; - -} // namespace Common::Log diff --git a/src/common/path_util.h b/src/common/path_util.h index e4bc40e9b..1b5962bd0 100644 --- a/src/common/path_util.h +++ b/src/common/path_util.h @@ -30,6 +30,7 @@ enum class PathType { LauncherDir, // Where launcher stores its data. LauncherMetaData, // Where launcher stores its game metadata. CacheDir, // Where pipeline and shader cache is stored. + FontsDir, // Where dumped system fonts are stored. }; constexpr auto PORTABLE_DIR = "user"; diff --git a/src/core/file_format/trp.cpp b/src/core/file_format/trp.cpp index 75d79d82c..ebbc9dc8b 100644 --- a/src/core/file_format/trp.cpp +++ b/src/core/file_format/trp.cpp @@ -142,7 +142,7 @@ bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string tit LOG_CRITICAL( Common_Filesystem, "Trophy XML {} write failed, wanted to write {} bytes, wrote {}", - fmt::UTF(path.u8string()), XML.size(), written); + path.string(), XML.size(), written); } } } diff --git a/src/ipc/ipc_client.cpp b/src/ipc/ipc_client.cpp index 06564b355..5896c6fe7 100644 --- a/src/ipc/ipc_client.cpp +++ b/src/ipc/ipc_client.cpp @@ -232,15 +232,15 @@ void IpcClient::onStdout() { QStringList entries = dataString.split('\n'); for (QString& entry : entries) { - if (entry.contains("")) { + if (entry.contains("")) { color = Qt::yellow; - } else if (entry.contains("")) { + } else if (entry.contains("")) { color = Qt::red; - } else if (entry.contains("")) { + } else if (entry.contains("")) { color = Qt::magenta; - } else if (entry.contains("")) { + } else if (entry.contains("")) { color = Qt::gray; - } else if (entry.contains("")) { + } else if (entry.contains("")) { color = Qt::cyan; } else { color = Qt::white; diff --git a/src/main.cpp b/src/main.cpp index 13b381e06..2020ab797 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,7 +6,7 @@ #include "unordered_map" #include "common/config.h" -#include "common/logging/backend.h" +#include "common/logging/log.h" #include "common/versions.h" #include "qt_gui/game_install_dialog.h" #include "qt_gui/main_window.h" @@ -27,16 +27,18 @@ int main(int argc, char* argv[]) { SetConsoleOutputCP(CP_UTF8); #endif + // Load configurations and initialize Qt application + const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); + Config::load(user_dir / "config.toml"); + + Common::Log::Setup(argc, argv); + QApplication a(argc, argv); QApplication::setDesktopFileName("net.shadps4.qtlauncher"); std::shared_ptr m_emu_state = std::make_shared(); EmulatorState::SetInstance(m_emu_state); - // Load configurations and initialize Qt application - const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); - Config::load(user_dir / "config.toml"); - const bool has_command_line_argument = argc > 1; bool has_emulator_argument = false; bool show_gui = false, no_ipc = false; @@ -138,8 +140,6 @@ int main(int argc, char* argv[]) { dlg.exec(); } - Common::Log::Initialize("shadPS4Launcher.log"); - if (has_command_line_argument && !has_emulator_argument) { std::cerr << "Error: Please provide a name or path for the emulator core.\n"; exit(1); @@ -179,5 +179,10 @@ int main(int argc, char* argv[]) { if (!has_emulator_argument || show_gui) { m_main_window->show(); } - return a.exec(); + + const auto status = a.exec(); + + Common::Log::Shutdown(); + + return status; } diff --git a/src/qt_gui/control_settings.cpp b/src/qt_gui/control_settings.cpp index 3c3bca332..2a3d5cccf 100644 --- a/src/qt_gui/control_settings.cpp +++ b/src/qt_gui/control_settings.cpp @@ -185,7 +185,7 @@ void ControlSettings::SaveControllerConfig(bool CloseOnSave) { config_id = (ui->ProfileComboBox->currentText() == tr("Common Config")) ? "default" : ui->ProfileComboBox->currentText().toStdString(); - const auto config_file = Config::GetFoolproofInputConfigFile(config_id); + const auto config_file = Config::GetInputConfigFile(config_id); int lineCount = 0; std::string line; @@ -426,7 +426,7 @@ void ControlSettings::SetUIValuestoMappings() { ? "default" : ui->ProfileComboBox->currentText().toStdString(); - const auto config_file = Config::GetFoolproofInputConfigFile(config_id); + const auto config_file = Config::GetInputConfigFile(config_id); std::ifstream file(config_file); bool CrossExists = false, CircleExists = false, SquareExists = false, TriangleExists = false, diff --git a/src/qt_gui/hotkeys.cpp b/src/qt_gui/hotkeys.cpp index 5f1c04829..1a5501ee2 100644 --- a/src/qt_gui/hotkeys.cpp +++ b/src/qt_gui/hotkeys.cpp @@ -171,7 +171,7 @@ void Hotkeys::SaveHotkeys(bool CloseOnSave) { add_mapping(ui->mouseJoystickButton->text(), "hotkey_toggle_mouse_to_joystick"); add_mapping(ui->mouseGyroButton->text(), "hotkey_toggle_mouse_to_gyro"); - auto hotkey_file = Config::GetFoolproofInputConfigFile("global"); + auto hotkey_file = Config::GetInputConfigFile("global"); std::fstream file(hotkey_file); int lineCount = 0; std::string line; @@ -254,7 +254,7 @@ void Hotkeys::SaveHotkeys(bool CloseOnSave) { } void Hotkeys::LoadHotkeys() { - auto hotkey_file = Config::GetFoolproofInputConfigFile("global"); + auto hotkey_file = Config::GetInputConfigFile("global"); std::ifstream file(hotkey_file); int lineCount = 0; std::string line = ""; diff --git a/src/qt_gui/kbm_config_dialog.cpp b/src/qt_gui/kbm_config_dialog.cpp index aefd0afb3..d4231c49d 100644 --- a/src/qt_gui/kbm_config_dialog.cpp +++ b/src/qt_gui/kbm_config_dialog.cpp @@ -84,7 +84,7 @@ EditorDialog::EditorDialog(QWidget* parent) : QDialog(parent) { void EditorDialog::loadFile(QString game) { - const auto config_file = Config::GetFoolproofInputConfigFile(game.toStdString()); + const auto config_file = Config::GetInputConfigFile(game.toStdString()); QFile file(config_file); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { @@ -99,7 +99,7 @@ void EditorDialog::loadFile(QString game) { void EditorDialog::saveFile(QString game) { - const auto config_file = Config::GetFoolproofInputConfigFile(game.toStdString()); + const auto config_file = Config::GetInputConfigFile(game.toStdString()); QFile file(config_file); if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { @@ -194,10 +194,10 @@ void EditorDialog::onResetToDefaultClicked() { if (reply == QMessageBox::Yes) { if (default_default) { - const auto default_file = Config::GetFoolproofInputConfigFile("default"); + const auto default_file = Config::GetInputConfigFile("default"); std::filesystem::remove(default_file); } - const auto config_file = Config::GetFoolproofInputConfigFile("default"); + const auto config_file = Config::GetInputConfigFile("default"); QFile file(config_file); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { diff --git a/src/qt_gui/kbm_gui.cpp b/src/qt_gui/kbm_gui.cpp index 1e76df28e..f89e76881 100644 --- a/src/qt_gui/kbm_gui.cpp +++ b/src/qt_gui/kbm_gui.cpp @@ -254,7 +254,7 @@ void KBMSettings::SaveKBMConfig(bool close_on_save) { lines.push_back(output_string + " = " + input_string); lines.push_back(""); - const auto config_file = Config::GetFoolproofInputConfigFile(config_id); + const auto config_file = Config::GetInputConfigFile(config_id); std::fstream file(config_file); int lineCount = 0; std::string line; @@ -396,7 +396,7 @@ void KBMSettings::SetDefault() { } void KBMSettings::SetUIValuestoMappings(std::string config_id) { - const auto config_file = Config::GetFoolproofInputConfigFile(config_id); + const auto config_file = Config::GetInputConfigFile(config_id); std::ifstream file(config_file); int lineCount = 0; diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 58b29faa9..f68357720 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -20,8 +20,7 @@ #include "check_update.h" #endif #include "background_music_player.h" -#include "common/logging/backend.h" -#include "common/logging/filter.h" +#include "common/logging/log.h" #include "core/emulator_state.h" #include "log_presets_dialog.h" #include "sdl_event_wrapper.h" @@ -120,7 +119,7 @@ SettingsDialog::SettingsDialog(std::shared_ptr gui_settings, ui->buttonBox->button(QDialogButtonBox::StandardButton::Close)->setFocus(); - logTypeMap = {{tr("async"), "async"}, {tr("sync"), "sync"}}; + logTypeMap = {{tr("wincolor"), "wincolor"}, {tr("msvc"), "msvc"}}; screenModeMap = {{tr("Fullscreen (Borderless)"), "Fullscreen (Borderless)"}, {tr("Windowed"), "Windowed"}, {tr("Fullscreen"), "Fullscreen"}}; @@ -224,11 +223,7 @@ SettingsDialog::SettingsDialog(std::shared_ptr gui_settings, BackgroundMusicPlayer::getInstance().setVolume(bgm_volume_backup); SyncRealTimeWidgetstoConfig(); } - if (Common::Log::IsActive()) { - Common::Log::Filter filter; - filter.ParseFilterString(Config::getLogFilter()); - Common::Log::SetGlobalFilter(filter); - } + spdlog::cfg::helpers::load_levels(Config::getLogFilter()); }); ui->buttonBox->button(QDialogButtonBox::Save)->setText(tr("Save")); @@ -410,9 +405,9 @@ SettingsDialog::SettingsDialog(std::shared_ptr gui_settings, }); } - // DEBUG TAB + // LOG TAB { - connect(ui->OpenLogLocationButton, &QPushButton::clicked, this, []() { + connect(ui->logOpenLocationButton, &QPushButton::clicked, this, []() { QString userPath; Common::FS::PathToQString(userPath, Common::FS::GetUserPath(Common::FS::PathType::LogDir)); @@ -427,6 +422,14 @@ SettingsDialog::SettingsDialog(std::shared_ptr gui_settings, dlg->exec(); }); + connect(ui->logSkipDuplicateCheckBox, &QPushButton::clicked, this, [this]() { + ui->logMaxSkipDurationGroupBox->setVisible( + !ui->logMaxSkipDurationGroupBox->isVisible()); + }); + } + + // DEBUG TAB + { connect(ui->vkValidationCheckBox, &QCheckBox::checkStateChanged, this, [this](Qt::CheckState state) { state ? ui->vkLayersGroupBox->setVisible(true) @@ -517,11 +520,16 @@ SettingsDialog::SettingsDialog(std::shared_ptr gui_settings, ui->folderButton->installEventFilter(this); // Log - ui->logTypeGroupBox->installEventFilter(this); + ui->logAppendCheckBox->installEventFilter(this); + ui->logEnableCheckBox->installEventFilter(this); ui->logFilter->installEventFilter(this); - ui->enableLoggingCheckBox->installEventFilter(this); - ui->separateLogFilesCheckbox->installEventFilter(this); - ui->OpenLogLocationButton->installEventFilter(this); + ui->logMaxSkipDurationLineEdit->installEventFilter(this); + ui->logOpenLocationButton->installEventFilter(this); + ui->logSeparateCheckBox->installEventFilter(this); + ui->logSizeLimitLineEdit->installEventFilter(this); + ui->logSkipDuplicateCheckBox->installEventFilter(this); + ui->logSyncCheckBox->installEventFilter(this); + ui->logTypeGroupBox->installEventFilter(this); // Debug ui->debugDump->installEventFilter(this); @@ -741,19 +749,35 @@ void SettingsDialog::LoadValuesFromConfig() { QString translatedText_PresentMode = presentModeMap.key(QString::fromStdString(presentMode)); ui->presentModeComboBox->setCurrentText(translatedText_PresentMode); - std::string logType = toml::find_or(data, "General", "logType", "sync"); + // Log + ui->logAppendCheckBox->setChecked(toml::find_or(data, "Log", "append", false)); + ui->logEnableCheckBox->setChecked(toml::find_or(data, "Log", "enable", true)); + ui->logFilterLineEdit->setText( + QString::fromStdString(toml::find_or(data, "Log", "filter", ""))); + ui->logMaxSkipDurationLineEdit->setValue( + toml::find_or(data, "Log", "maxSkipDuration", 5'000)); + ui->logSeparateCheckBox->setChecked(toml::find_or(data, "Log", "separate", false)); + ui->logSizeLimitLineEdit->setValue( + toml::find_or(data, "Log", "sizeLimit", 100_MB)); + ui->logSkipDuplicateCheckBox->setChecked( + toml::find_or(data, "Log", "skipDuplicate", true)); + ui->logMaxSkipDurationGroupBox->setVisible(ui->logSkipDuplicateCheckBox->isChecked()); + ui->logSyncCheckBox->setChecked(toml::find_or(data, "Log", "sync", true)); +#ifdef _WIN32 + std::string logType = toml::find_or(data, "Log", "type", "wincolor"); QString translatedText_logType = logTypeMap.key(QString::fromStdString(logType)); if (!translatedText_logType.isEmpty()) { ui->logTypeComboBox->setCurrentText(translatedText_logType); } - ui->logFilterLineEdit->setText( - QString::fromStdString(toml::find_or(data, "General", "logFilter", ""))); +#else + ui->logTypeGroupBox->setVisible(false); +#endif + ui->userNameLineEdit->setText( QString::fromStdString(toml::find_or(data, "General", "userName", "shadPS4"))); ui->debugDump->setChecked(toml::find_or(data, "Debug", "DebugDump", false)); - ui->separateLogFilesCheckbox->setChecked( - toml::find_or(data, "Debug", "isSeparateLogFilesEnabled", false)); + ui->vkValidationCheckBox->setChecked(toml::find_or(data, "Vulkan", "validation", false)); ui->vkSyncValidationCheckBox->setChecked( toml::find_or(data, "Vulkan", "validation_sync", false)); @@ -776,7 +800,6 @@ void SettingsDialog::LoadValuesFromConfig() { toml::find_or(data, "GPU", "copyGPUBuffers", false)); ui->collectShaderCheckBox->setChecked( toml::find_or(data, "Debug", "CollectShader", false)); - ui->enableLoggingCheckBox->setChecked(toml::find_or(data, "Debug", "logEnabled", true)); ui->GenAudioComboBox->setCurrentText( QString::fromStdString(toml::find_or(data, "Audio", "mainOutputDevice", ""))); @@ -882,10 +905,6 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { text = tr("Username:\\nSets the PS4's account username, which may be displayed by some games."); } else if (elementName == "label_Trophy" || elementName == "trophyKeyLineEdit") { text = tr("Trophy Key:\\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\\nMust contain only hex characters."); - } else if (elementName == "logTypeGroupBox") { - text = tr("Log Type:\\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation."); - } else if (elementName == "logFilter") { - text = tr("Log Filter:\\nFilters the log to only print specific information.\\nExamples: \"Core:Trace\" \"Lib.Pad:Debug Common.Filesystem:Error\" \"*:Critical\"\\nLevels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it."); #ifdef ENABLE_UPDATER } else if (elementName == "updaterGroupBox") { text = tr("GUI Updates:\\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable.\\n\\n*This update applies only to the Qt user interface. To update the emulator core, please use the 'Version Manager' menu."); @@ -906,6 +925,29 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { text = tr("Update Compatibility Database:\\nImmediately update the compatibility database."); } + // Log + if (elementName == "logAppendCheckBox") { + text = tr("Log Append:\\nAppend to existing logs."); + } else if (elementName == "logEnableCheckBox") { + text = tr("Enable Logging:\\nEnables logging.\\nDo not change this if you do not know what you're doing!\\nWhen asking for help, make sure this setting is ENABLED."); + } else if (elementName == "logFilter") { + text = tr("Log Filter:\\nFilters the log to only print specific information.\\nExamples: \"Core=trace\" \"Lib.Pad=debug,Common.Filesystem=error\" \"critical\"\\nLevels: trace, debug, info, warning, error, critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it."); + } else if (elementName == "logMaxSkipDurationLineEdit") { + text = tr("Log Max Skip Duration:\\nInterval without writing same lines (ms) - only if 'Log Skip Duplicate' enabled."); //TODO grey out if disabled + } else if (elementName == "logOpenLocationButton") { + text = tr("Open Log Location:\\nOpen the folder where the log file is saved."); + } else if (elementName == "logSeparateCheckBox") { + text = tr("Separate Log Files:\\nWrites a separate logfile for each game."); + } else if (elementName == "logSizeLimitLineEdit") { + text = tr("Log Size Limit:\\nMaximum size of log files (bytes)."); + } else if (elementName == "logSkipDuplicateCheckBox") { + text = tr("Log Skip Duplicate:\\nSave storage by avoiding writing log that is identical."); + } else if (elementName == "logSyncCheckBox") { + text = tr("Log Sync:\\nSwitch between sync (order) or async (performance)."); + } else if (elementName == "logTypeGroupBox") { + text = tr("Log Type:\\nChoose between wincolor or msvc log types.\\nwincolor: Default logging for Windows\\nmsvc: Logging for debugging"); + } + //User if (elementName == "OpenCustomTrophyLocationButton") { text = tr("Open the custom trophy images/sounds folder:\\nYou can add custom images to the trophies and an audio.\\nAdd the files to custom_trophy with the following names:\\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\\nNote: The sound will only work in QT versions."); @@ -990,12 +1032,6 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { text = tr("Copy GPU Buffers:\\nGets around race conditions involving GPU submits.\\nMay or may not help with PM4 type 0 crashes."); } else if (elementName == "collectShaderCheckBox") { text = tr("Collect Shaders:\\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10)."); - } else if (elementName == "separateLogFilesCheckbox") { - text = tr("Separate Log Files:\\nWrites a separate logfile for each game."); - } else if (elementName == "enableLoggingCheckBox") { - text = tr("Enable Logging:\\nEnables logging.\\nDo not change this if you do not know what you're doing!\\nWhen asking for help, make sure this setting is ENABLED."); - } else if (elementName == "OpenLogLocationButton") { - text = tr("Open Log Location:\\nOpen the folder where the log file is saved."); } else if (elementName == "micComboBox") { text = tr("Microphone:\\nNone: Does not use the microphone.\\nDefault Device: Will use the default device defined in the system.\\nOr manually choose the microphone to be used from the list."); } else if (elementName == "volumeSliderElement") { @@ -1087,12 +1123,22 @@ void SettingsDialog::UpdateSettings(bool is_specific) { Config::setSideTrophy("bottom", is_specific); } - Config::setLoggingEnabled(ui->enableLoggingCheckBox->isChecked(), is_specific); + // Log + Config::setLogAppend(ui->logAppendCheckBox->isChecked(), is_specific); + Config::setLoggingEnabled(ui->logEnableCheckBox->isChecked(), is_specific); + Config::setLogFilter(ui->logFilterLineEdit->text().toStdString(), is_specific); + Config::setSeparateLogFilesEnabled(ui->logSeparateCheckBox->isChecked(), is_specific); + Config::setMaxSkipDuration(ui->logMaxSkipDurationLineEdit->value(), is_specific); + Config::setLogSizeLimit(ui->logSizeLimitLineEdit->value(), is_specific); + Config::setLogSkipDuplicate(ui->logSkipDuplicateCheckBox->isChecked(), is_specific); + Config::setLogSync(ui->logSyncCheckBox->isChecked(), is_specific); + Config::setAllowHDR(ui->enableHDRCheckBox->isChecked(), is_specific); +#ifdef _WIN32 Config::setLogType(logTypeMap.value(ui->logTypeComboBox->currentText()).toStdString(), is_specific); +#endif Config::setMicDevice(ui->micComboBox->currentData().toString().toStdString(), is_specific); - Config::setLogFilter(ui->logFilterLineEdit->text().toStdString(), is_specific); Config::setUserName(ui->userNameLineEdit->text().toStdString(), is_specific); Config::setCursorState(ui->hideCursorComboBox->currentIndex(), is_specific); Config::setCursorHideTimeout(ui->hideCursorComboBox->currentIndex(), is_specific); @@ -1109,7 +1155,6 @@ void SettingsDialog::UpdateSettings(bool is_specific) { Config::setRcasAttenuation(ui->RCASSlider->value(), is_specific); Config::setShowSplash(ui->showSplashCheckBox->isChecked(), is_specific); Config::setDebugDump(ui->debugDump->isChecked(), is_specific); - Config::setSeparateLogFilesEnabled(ui->separateLogFilesCheckbox->isChecked(), is_specific); Config::setVkValidation(ui->vkValidationCheckBox->isChecked(), is_specific); Config::setVkSyncValidation(ui->vkSyncValidationCheckBox->isChecked(), is_specific); Config::setVkCoreValidation(ui->vkCoreValidationCheckBox->isChecked(), is_specific); diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index a8e3c5501..130230ab7 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -2048,23 +2048,82 @@ - + Enable Logging - + + + Open Log Location + + + + + Separate Log Files - + - Open Log Location + Log Sync + + + + + + + Log Skip Duplicate + + + + + + + Log Max Skip Duration + + + + + + ms + + + 9999999 + + + + + + + + + + Log Size Limit + + + + + + B + + + 999999999 + + + + + + + + + + Log Append @@ -2087,12 +2146,12 @@ - async + wincolor - sync + msvc