diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp index 7fc8188c8..aade3ee9c 100644 --- a/src/citra/citra.cpp +++ b/src/citra/citra.cpp @@ -174,25 +174,10 @@ static void OnStatusMessageReceived(const Network::StatusMessageEntry& msg) { std::cout << std::endl << "* " << message << std::endl << std::endl; } -static void InitializeLogging() { - using namespace Common; - - Log::Filter log_filter(Log::Level::Debug); - log_filter.ParseFilterString(Settings::values.log_filter.GetValue()); - Log::SetGlobalFilter(log_filter); - - Log::AddBackend(std::make_unique()); - - const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); - FileUtil::CreateFullPath(log_dir); - Log::AddBackend(std::make_unique(log_dir + LOG_FILE)); -#ifdef _WIN32 - Log::AddBackend(std::make_unique()); -#endif -} - /// Application entry point int main(int argc, char** argv) { + Common::Log::Initialize(); + Common::Log::SetColorConsoleBackendEnabled(true); Common::DetachedTasks detached_tasks; Config config; int option_index = 0; @@ -203,8 +188,6 @@ int main(int argc, char** argv) { std::string movie_play; std::string dump_video; - InitializeLogging(); - char* endarg; #ifdef _WIN32 int argc_w; @@ -346,6 +329,7 @@ int main(int argc, char** argv) { return -1; } + Core::System::InitializeGlobalInstance(); auto& system = Core::System::GetInstance(); auto& movie = Core::Movie::GetInstance(); diff --git a/src/citra_qt/debugger/console.cpp b/src/citra_qt/debugger/console.cpp index 4fb8d83db..8187b0b1d 100644 --- a/src/citra_qt/debugger/console.cpp +++ b/src/citra_qt/debugger/console.cpp @@ -21,6 +21,7 @@ void ToggleConsole() { console_shown = UISettings::values.show_console.GetValue(); } + using namespace Common::Log; #ifdef _WIN32 FILE* temp; if (UISettings::values.show_console) { @@ -29,24 +30,20 @@ void ToggleConsole() { freopen_s(&temp, "CONIN$", "r", stdin); freopen_s(&temp, "CONOUT$", "w", stdout); freopen_s(&temp, "CONOUT$", "w", stderr); - Common::Log::AddBackend(std::make_unique()); + SetColorConsoleBackendEnabled(true); } } else { if (FreeConsole()) { // In order to close the console, we have to also detach the streams on it. // Just redirect them to NUL if there is no console window - Common::Log::RemoveBackend(Common::Log::ColorConsoleBackend::Name()); + SetColorConsoleBackendEnabled(false); freopen_s(&temp, "NUL", "r", stdin); freopen_s(&temp, "NUL", "w", stdout); freopen_s(&temp, "NUL", "w", stderr); } } #else - if (UISettings::values.show_console) { - Log::AddBackend(std::make_unique()); - } else { - Log::RemoveBackend(Log::ColorConsoleBackend::Name()); - } + SetColorConsoleBackendEnabled(UISettings::values.show_console.GetValue()); #endif } } // namespace Debugger diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index b69bcf3d0..71f293785 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -146,21 +146,6 @@ void GMainWindow::ShowTelemetryCallout() { const int GMainWindow::max_recent_files_item; -static void InitializeLogging() { - using namespace Common; - - Log::Filter log_filter; - log_filter.ParseFilterString(Settings::values.log_filter.GetValue()); - Log::SetGlobalFilter(log_filter); - - const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); - FileUtil::CreateFullPath(log_dir); - Log::AddBackend(std::make_unique(log_dir + LOG_FILE)); -#ifdef _WIN32 - Log::AddBackend(std::make_unique()); -#endif -} - static QString PrettyProductName() { #ifdef _WIN32 // After Windows 10 Version 2004, Microsoft decided to switch to a different notation: 20H2 @@ -186,7 +171,6 @@ static QString PrettyProductName() { GMainWindow::GMainWindow(Core::System& system_) : ui{std::make_unique()}, system{system_}, movie{Core::Movie::GetInstance()}, config{std::make_unique()}, emu_thread{nullptr} { - InitializeLogging(); Debugger::ToggleConsole(); Settings::LogSettings(); @@ -2869,6 +2853,7 @@ static Qt::HighDpiScaleFactorRoundingPolicy GetHighDpiRoundingPolicy() { } int main(int argc, char* argv[]) { + Common::Log::Initialize(); Common::DetachedTasks detached_tasks; MicroProfileOnThreadCreate("Frontend"); SCOPE_EXIT({ MicroProfileShutdown(); }); @@ -2893,7 +2878,8 @@ int main(int argc, char* argv[]) { // generating shaders setlocale(LC_ALL, "C"); - Core::System& system = Core::System::GetInstance(); + Core::System::InitializeGlobalInstance(); + auto& system{Core::System::GetInstance()}; GMainWindow main_window(system); // Register frontend applets diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 3c8f8f54d..60d87b1b8 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -2,13 +2,10 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include #include -#include -#include -#include #include #include +#include "common/common_paths.h" #ifdef _WIN32 #include // For _SH_DENYWR #include // For OutputDebugStringW @@ -16,6 +13,9 @@ #define _SH_DENYWR 0 #endif #include "common/file_util.h" +#include "common/literals.h" +#include "common/settings.h" +#include "common/thread.h" #include "common/logging/backend.h" #include "common/logging/log.h" #include "common/logging/text_formatter.h" @@ -24,14 +24,176 @@ namespace Common::Log { +namespace { + +/** + * Interface for logging backends. + */ +class Backend { +public: + virtual ~Backend() = default; + + virtual void Write(const Entry& entry) = 0; + + virtual void EnableForStacktrace() = 0; + + virtual void Flush() = 0; +}; + +/** + * Backend that writes to stderr and with color + */ +class ColorConsoleBackend final : public Backend { +public: + explicit ColorConsoleBackend() = default; + + ~ColorConsoleBackend() override = default; + + void Write(const Entry& entry) override { + if (enabled.load(std::memory_order_relaxed)) { + PrintColoredMessage(entry); + } + } + + void Flush() override { + // stderr shouldn't be buffered + } + + void EnableForStacktrace() override { + enabled = true; + } + + void SetEnabled(bool enabled_) { + enabled = enabled_; + } + +private: + std::atomic_bool enabled{false}; +}; + +/** + * Backend that writes to a file passed into the constructor + */ +class FileBackend final : public Backend { +public: + explicit FileBackend(const std::string& filename) { + auto old_filename = filename; + old_filename += ".old.txt"; + + // Existence checks are done within the functions themselves. + // We don't particularly care if these succeed or not. + static_cast(FileUtil::Delete(old_filename)); + static_cast(FileUtil::Rename(filename, old_filename)); + + // _SH_DENYWR allows read only access to the file for other programs. + // It is #defined to 0 on other platforms + file = std::make_unique(filename, "w", _SH_DENYWR); + } + + ~FileBackend() override = default; + + void Write(const Entry& entry) override { + if (!enabled) { + return; + } + + bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n')); + + using namespace Common::Literals; + // Prevent logs from exceeding a set maximum size in the event that log entries are spammed. + const auto write_limit = 100_MiB; + 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() override { + file->Flush(); + } + + void EnableForStacktrace() override { + enabled = true; + bytes_written = 0; + } + +private: + std::unique_ptr file; + bool enabled = true; + std::size_t bytes_written = 0; +}; + +/** + * Backend that writes to Visual Studio's output window + */ +class DebuggerBackend final : public Backend { +public: + explicit DebuggerBackend() = default; + + ~DebuggerBackend() override = default; + + void Write(const Entry& entry) override { +#ifdef _WIN32 + ::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str()); +#endif + } + + void Flush() override {} + + void EnableForStacktrace() override {} +}; + +#ifdef ANDROID +/** + * Backend that writes to the Android logcat + */ +class LogcatBackend : public Backend { +public: + explicit LogcatBackend() = default; + + ~LogcatBackend() override = default; + + void Write(const Entry& entry) override { + PrintMessageToLogcat(entry); + } + + void Flush() override {} + + void EnableForStacktrace() override {} +}; +#endif + +bool initialization_in_progress_suppress_logging = false; + /** * Static state as a singleton. */ class Impl { public: static Impl& Instance() { - static Impl backend; - return backend; + if (!instance) { + abort(); + } + return *instance; + } + + static void Initialize() { + if (instance) { + abort(); + } + initialization_in_progress_suppress_logging = true; + const auto& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); + void(FileUtil::CreateDir(log_dir)); + Filter filter; + filter.ParseFilterString(Settings::values.log_filter.GetValue()); + instance = std::unique_ptr(new Impl(log_dir + LOG_FILE, filter), + Deleter); + initialization_in_progress_suppress_logging = false; } Impl(const Impl&) = delete; @@ -40,74 +202,54 @@ public: Impl(Impl&&) = delete; Impl& operator=(Impl&&) = delete; - void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num, - const char* function, std::string message) { - message_queue.Push( - CreateEntry(log_class, log_level, filename, line_num, function, std::move(message))); - } - - void AddBackend(std::unique_ptr backend) { - std::lock_guard lock{writing_mutex}; - backends.push_back(std::move(backend)); - } - - void RemoveBackend(std::string_view backend_name) { - std::lock_guard lock{writing_mutex}; - - std::erase_if(backends, [&backend_name](const auto& backend) { - return backend_name == backend->GetName(); - }); - } - - const Filter& GetGlobalFilter() const { - return filter; - } - void SetGlobalFilter(const Filter& f) { filter = f; } - Backend* GetBackend(std::string_view backend_name) { - const auto it = - std::find_if(backends.begin(), backends.end(), - [&backend_name](const auto& i) { return backend_name == i->GetName(); }); - if (it == backends.end()) - return nullptr; - return it->get(); + 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)) + return; + const Entry& entry = + CreateEntry(log_class, log_level, filename, line_num, function, std::move(message)); + message_queue.Push(entry); } private: - Impl() { - backend_thread = std::thread([&] { - Entry entry; - auto write_logs = [&](Entry& e) { - std::lock_guard lock{writing_mutex}; - for (const auto& backend : backends) { - backend->Write(e); - } - }; - while (true) { - entry = message_queue.PopWait(); - if (entry.final_entry) { - break; - } - write_logs(entry); - } - - // 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. - constexpr int MAX_LOGS_TO_WRITE = 100; - int logs_written = 0; - while (logs_written++ < MAX_LOGS_TO_WRITE && message_queue.Pop(entry)) { - write_logs(entry); - } - }); - } + Impl(const std::string& file_backend_filename, const Filter& filter_) + : filter{filter_}, file_backend{file_backend_filename}, backend_thread{std::thread([this] { + Common::SetCurrentThreadName("citra:Log"); + Entry entry; + const auto write_logs = [this, &entry]() { + ForEachBackend([&entry](Backend& backend) { backend.Write(entry); }); + }; + while (true) { + entry = message_queue.PopWait(); + if (entry.final_entry) { + break; + } + 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() ? INT_MAX : 100; + while (max_logs_to_write-- && message_queue.Pop(entry)) { + write_logs(); + } + })} {} ~Impl() { - Entry entry; - entry.final_entry = true; - message_queue.Push(entry); + StopBackendThread(); + } + + void StopBackendThread() { + Entry stop_entry{}; + stop_entry.final_entry = true; + message_queue.Push(stop_entry); backend_thread.join(); } @@ -129,102 +271,51 @@ private: }; } - std::mutex writing_mutex; - std::thread backend_thread; - std::vector> backends; - Common::MPSCQueue message_queue; + void ForEachBackend(auto lambda) { + lambda(static_cast(debugger_backend)); + lambda(static_cast(color_console_backend)); + lambda(static_cast(file_backend)); + } + + static void Deleter(Impl* ptr) { + delete ptr; + } + + static inline std::unique_ptr instance{nullptr, Deleter}; + Filter filter; + DebuggerBackend debugger_backend{}; + ColorConsoleBackend color_console_backend{}; + FileBackend file_backend; + + std::thread backend_thread; + MPSCQueue message_queue{}; std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()}; }; +} // namespace -ConsoleBackend::~ConsoleBackend() = default; - -void ConsoleBackend::Write(const Entry& entry) { - PrintMessage(entry); +void Initialize() { + Impl::Initialize(); } -ColorConsoleBackend::~ColorConsoleBackend() = default; - -void ColorConsoleBackend::Write(const Entry& entry) { - PrintColoredMessage(entry); -} - -LogcatBackend::~LogcatBackend() = default; - -void LogcatBackend::Write(const Entry& entry) { - PrintMessageToLogcat(entry); -} - -FileBackend::FileBackend(const std::string& filename) { - const auto old_filename = filename + ".old.txt"; - - if (FileUtil::Exists(old_filename)) { - FileUtil::Delete(old_filename); - } - if (FileUtil::Exists(filename)) { - FileUtil::Rename(filename, old_filename); - } - - // _SH_DENYWR allows read only access to the file for other programs. - // It is #defined to 0 on other platforms - file = std::make_unique(filename, "w", _SH_DENYWR); -} - -FileBackend::~FileBackend() = default; - -void FileBackend::Write(const Entry& entry) { - if (!file->IsOpen()) { - return; - } - - // Prevent logs from exceeding a set maximum size in the event that log entries are spammed. - constexpr std::size_t MAX_BYTES_WRITTEN = 50 * 1024L * 1024L; - - // Close the file after the write limit is exceeded. - if (bytes_written > MAX_BYTES_WRITTEN) { - file->Close(); - return; - } - - bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n')); - if (entry.log_level >= Level::Error) { - file->Flush(); - } -} - -DebuggerBackend::~DebuggerBackend() = default; - -void DebuggerBackend::Write(const Entry& entry) { -#ifdef _WIN32 - ::OutputDebugStringW(Common::UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str()); -#endif +void DisableLoggingInTests() { + initialization_in_progress_suppress_logging = true; } void SetGlobalFilter(const Filter& filter) { Impl::Instance().SetGlobalFilter(filter); } -void AddBackend(std::unique_ptr backend) { - Impl::Instance().AddBackend(std::move(backend)); -} - -void RemoveBackend(std::string_view backend_name) { - Impl::Instance().RemoveBackend(backend_name); -} - -Backend* GetBackend(std::string_view backend_name) { - return Impl::Instance().GetBackend(backend_name); +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) { - auto& instance = Impl::Instance(); - const auto& filter = instance.GetGlobalFilter(); - if (!filter.CheckMessage(log_class, log_level)) - return; - - instance.PushEntry(log_class, log_level, filename, line_num, function, - fmt::vformat(format, args)); + if (!initialization_in_progress_suppress_logging) { + 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 index 4d9117951..cb7839ee9 100644 --- a/src/common/logging/backend.h +++ b/src/common/logging/backend.h @@ -4,136 +4,22 @@ #pragma once -#include -#include -#include +#include #include "common/logging/filter.h" -#include "common/logging/log.h" - -namespace FileUtil { -class IOFile; -} namespace Common::Log { -/** - * Interface for logging backends. As loggers can be created and removed at runtime, this can be - * used by a frontend for adding a custom logging backend as needed - */ -class Backend { -public: - virtual ~Backend() = default; +class Filter; - virtual void SetFilter(const Filter& new_filter) { - filter = new_filter; - } - virtual const char* GetName() const = 0; - virtual void Write(const Entry& entry) = 0; +/// Initializes the logging system. This should be the first thing called in main. +void Initialize(); -private: - Filter filter; -}; +void DisableLoggingInTests(); /** - * Backend that writes to stderr without any color commands - */ -class ConsoleBackend : public Backend { -public: - ~ConsoleBackend() override; - - static const char* Name() { - return "console"; - } - const char* GetName() const override { - return Name(); - } - void Write(const Entry& entry) override; -}; - -/** - * Backend that writes to stderr and with color - */ -class ColorConsoleBackend : public Backend { -public: - ~ColorConsoleBackend() override; - - static const char* Name() { - return "color_console"; - } - - const char* GetName() const override { - return Name(); - } - void Write(const Entry& entry) override; -}; - -/** - * Backend that writes to the Android logcat - */ -class LogcatBackend : public Backend { -public: - ~LogcatBackend() override; - - static const char* Name() { - return "logcat"; - } - - const char* GetName() const override { - return Name(); - } - void Write(const Entry& entry) override; -}; - -/** - * Backend that writes to a file passed into the constructor - */ -class FileBackend : public Backend { -public: - ~FileBackend() override; - - explicit FileBackend(const std::string& filename); - - static const char* Name() { - return "file"; - } - - const char* GetName() const override { - return Name(); - } - - void Write(const Entry& entry) override; - -private: - std::unique_ptr file; - std::size_t bytes_written = 0; -}; - -/** - * Backend that writes to Visual Studio's output window - */ -class DebuggerBackend : public Backend { -public: - ~DebuggerBackend() override; - - static const char* Name() { - return "debugger"; - } - const char* GetName() const override { - return Name(); - } - void Write(const Entry& entry) override; -}; - -void AddBackend(std::unique_ptr backend); - -void RemoveBackend(std::string_view backend_name); - -Backend* GetBackend(std::string_view backend_name); - -/** - * The global filter will prevent any messages from even being processed if they are filtered. Each - * backend can have a filter, but if the level is lower than the global filter, the backend will - * never get the message + * 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/filter.cpp b/src/common/logging/filter.cpp index 0d33872c8..6213e3f49 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -204,4 +204,11 @@ 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 index 26d5f7392..681e5922b 100644 --- a/src/common/logging/filter.h +++ b/src/common/logging/filter.h @@ -56,6 +56,9 @@ public: /// 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; }; diff --git a/src/core/core.cpp b/src/core/core.cpp index a4ad8a817..e9aff1a12 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -51,8 +51,6 @@ namespace Core { -/*static*/ System System::s_instance; - template <> Core::System& Global() { return System::GetInstance(); @@ -70,6 +68,13 @@ Core::Timing& Global() { System::~System() = default; +void System::InitializeGlobalInstance() { + if (s_instance) { + std::abort(); + } + s_instance = std::unique_ptr(new System); +} + System::ResultStatus System::RunLoop(bool tight_loop) { status = ResultStatus::Success; if (!IsPoweredOn()) { diff --git a/src/core/core.h b/src/core/core.h index b29fc20e2..b85febf26 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -74,9 +74,14 @@ public: * @returns Reference to the instance of the System singleton class. */ [[nodiscard]] static System& GetInstance() { - return s_instance; + if (!s_instance) { + std::abort(); + } + return *s_instance; } + static void InitializeGlobalInstance(); + /// Enumeration representing the return values of the System Initialize and Load process. enum class ResultStatus : u32 { Success, ///< Succeeded @@ -392,7 +397,7 @@ private: std::unique_ptr exclusive_monitor; private: - static System s_instance; + inline static std::unique_ptr s_instance; std::atomic_bool is_powered_on{}; diff --git a/src/dedicated_room/citra-room.cpp b/src/dedicated_room/citra-room.cpp index 4e68b3496..78fef0e90 100644 --- a/src/dedicated_room/citra-room.cpp +++ b/src/dedicated_room/citra-room.cpp @@ -150,17 +150,8 @@ static void SaveBanList(const Network::Room::BanList& ban_list, const std::strin } static void InitializeLogging(const std::string& log_file) { - using namespace Common; - - Log::AddBackend(std::make_unique()); - - const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); - FileUtil::CreateFullPath(log_dir); - Log::AddBackend(std::make_unique(log_dir + log_file)); - -#ifdef _WIN32 - Log::AddBackend(std::make_unique()); -#endif + Common::Log::Initialize(); + Common::Log::SetColorConsoleBackendEnabled(true); } /// Application entry point diff --git a/src/tests/common/param_package.cpp b/src/tests/common/param_package.cpp index 75442cea4..b2a349d50 100644 --- a/src/tests/common/param_package.cpp +++ b/src/tests/common/param_package.cpp @@ -4,11 +4,13 @@ #include #include +#include "common/logging/backend.h" #include "common/param_package.h" namespace Common { TEST_CASE("ParamPackage", "[common]") { + Common::Log::DisableLoggingInTests(); ParamPackage original{ {"abc", "xyz"}, {"def", "42"},