// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #include #include #include #ifdef _WIN32 #include #endif #include "common/assert.h" #include "common/logging/log.h" #include "common/logging/log_file_sink.h" #include "common/types.h" #include "core/emulator_settings.h" // return codes above 'standard' // https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes enum class ShadPs4ReturnCode : u32 { TERMINATE_WITHOUT_EXCEPTION = 20'000, TERMINATE_WITH_EXCEPTION = 20'001, TERMINATE_WITH_UNKNOWN_EXCEPTION = 20'002, }; 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_ContentExport, 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_NpSignaling, 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_VideoRecording, nullptr}, {Class::Lib_Voice, nullptr}, {Class::Lib_VrTracker, nullptr}, {Class::Lib_WebBrowserDialog, nullptr}, {Class::Lib_Zlib, nullptr}, {Class::Loader, nullptr}, {Class::Log, nullptr}, {Class::NpHandler, nullptr}, {Class::Render, nullptr}, {Class::Render_Recompiler, nullptr}, {Class::Render_Vulkan, nullptr}, {Class::ShadNet, nullptr}, {Class::Tty, nullptr}, }; template static auto UpdateColorLevels(T sink) { #ifdef _WIN32 using LogColor = std::uint16_t; const auto Grey = FOREGROUND_INTENSITY; const auto Cyan = FOREGROUND_GREEN | FOREGROUND_BLUE; const auto Bright_gray = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; const auto Bright_yellow = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; const auto Bright_red = FOREGROUND_RED | FOREGROUND_INTENSITY; const auto Bright_magenta = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY; #else using LogColor = std::string_view; #define ESC "\x1b" const auto Grey = ESC "[1;30m"; const auto Cyan = ESC "[0;36m"; const auto Bright_gray = ESC "[0;37m"; const auto Bright_yellow = ESC "[1;33m"; const auto Bright_red = ESC "[1;31m"; const auto Bright_magenta = ESC "[1;35m"; #undef ESC #endif const std::unordered_map colors{ {spdlog::level::trace, Grey}, {spdlog::level::debug, Cyan}, {spdlog::level::info, Bright_gray}, {spdlog::level::warn, Bright_yellow}, {spdlog::level::err, Bright_red}, {spdlog::level::critical, Bright_magenta}}; for (const auto& [level, color] : colors) { sink->set_color(level, color); } return sink; } void Setup(std::string_view shadps4_filename) { static std::once_flag already_registered; std::call_once(already_registered, []() { std::atexit(Shutdown); std::at_quick_exit(Flush); std::set_terminate(Terminate); }); for (auto& [name, logger] : ALL_LOGGERS) { logger = std::make_shared(std::string(name)); logger->set_level(spdlog::level::trace); } // Setup console #ifdef _WIN32 if (EmulatorSettings.GetLogType() == "wincolor") { g_console_sink = std::make_shared(); } else { g_console_sink = std::make_shared(); } #else g_console_sink = UpdateColorLevels(std::make_shared(spdlog::color_mode::always)); #endif g_console_sink->set_pattern("%^%v%$"); // Setup file g_shad_file_sink = std::make_shared( (GetUserPath(Common::FS::PathType::LogDir) / shadps4_filename).string(), false, EmulatorSettings.GetLogSizeLimit()); g_shad_file_sink->set_pattern("%^%v%$"); UpdateSinks(); } void Switch(std::string_view game_filename) { UpdateSinks(); UpdateLogLevels(EmulatorSettings.GetLogFilter()); g_shad_file_sink->_size_limit = EmulatorSettings.GetLogSizeLimit(); g_shad_file_sink->session_file_helper_.open( (GetUserPath(Common::FS::PathType::LogDir) / game_filename).string(), !g_should_append); } void Shutdown() { for (auto& logger : ALL_LOGGERS | std::views::values) { logger.reset(); } g_shad_file_sink.reset(); g_console_sink.reset(); } void Flush() { if (g_shad_file_sink != nullptr) { g_shad_file_sink->flush(); } if (g_console_sink != nullptr) { g_console_sink->flush(); } } void Terminate() { try { if (std::exception_ptr eptr{std::current_exception()}) { std::rethrow_exception(eptr); } LOG_CRITICAL(Debug, "Exiting without exception"); std::quick_exit(std::to_underlying(ShadPs4ReturnCode::TERMINATE_WITHOUT_EXCEPTION)); } catch (const std::exception& exception) { LOG_CRITICAL(Debug, "Exception: {}", exception); std::quick_exit(std::to_underlying(ShadPs4ReturnCode::TERMINATE_WITH_EXCEPTION)); } catch (...) { LOG_CRITICAL(Debug, "Unknown exception caught"); std::quick_exit(std::to_underlying(ShadPs4ReturnCode::TERMINATE_WITH_UNKNOWN_EXCEPTION)); } } void UpdateSinks() { 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)}; for (auto& [name, logger] : ALL_LOGGERS) { logger->sinks() = EmulatorSettings.IsLogSkipDuplicate() ? dup_filter : (EmulatorSettings.IsLogSync() ? sinks : async_sink); } } void UpdateLogLevels(std::string_view log_filter) { 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(log_filter, ' ')) { const auto class_level_pair = std::views::split(class_level, ':') | std::ranges::to>(); if (class_level_pair.size() != 2) { LOG_ERROR(Config, "bad log filter provided"); continue; } if (class_level_pair.front()[0] == '*') { default_log_level = spdlog::level_from_str(class_level_pair.back() | 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) { 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); } } } } // namespace Common::Log