| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 |
- // SPDX-FileCopyrightText: 2014 Citra Emulator Project
- // SPDX-License-Identifier: GPL-2.0-or-later
- #include <atomic>
- #include <chrono>
- #include <climits>
- #include <thread>
- #include <fmt/format.h>
- #ifdef _WIN32
- #include <windows.h> // For OutputDebugStringW
- #endif
- #include "common/fs/file.h"
- #include "common/fs/fs.h"
- #include "common/fs/fs_paths.h"
- #include "common/fs/path_util.h"
- #include "common/literals.h"
- #include "common/polyfill_thread.h"
- #include "common/thread.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/settings.h"
- #ifdef _WIN32
- #include "common/string_util.h"
- #endif
- #include "common/bounded_threadsafe_queue.h"
- 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::filesystem::path& 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<void>(FS::RemoveFile(old_filename));
- static_cast<void>(FS::RenameFile(filename, old_filename));
- file = std::make_unique<FS::IOFile>(filename, FS::FileAccessMode::Write,
- FS::FileType::TextFile);
- }
- ~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 = Settings::values.extended_logging ? 1_GiB : 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<FS::IOFile> 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 {}
- };
- 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() {
- if (instance) {
- LOG_WARNING(Log, "Reinitializing logging backend");
- return;
- }
- using namespace Common::FS;
- const auto& log_dir = GetYuzuPath(YuzuPath::LogDir);
- void(CreateDir(log_dir));
- Filter filter;
- filter.ParseFilterString(Settings::values.log_filter.GetValue());
- instance = std::unique_ptr<Impl, decltype(&Deleter)>(new Impl(log_dir / LOG_FILE, filter),
- Deleter);
- initialization_in_progress_suppress_logging = false;
- }
- static void Start() {
- instance->StartBackendThread();
- }
- 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)) {
- return;
- }
- message_queue.EmplaceWait(
- CreateEntry(log_class, log_level, filename, line_num, function, std::move(message)));
- }
- 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) {
- Common::SetCurrentThreadName("Logger");
- Entry entry;
- const auto write_logs = [this, &entry]() {
- ForEachBackend([&entry](Backend& 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() ? INT_MAX : 100;
- while (max_logs_to_write-- && message_queue.TryPop(entry)) {
- write_logs();
- }
- });
- }
- Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
- const char* function, std::string&& message) const {
- using std::chrono::duration_cast;
- using std::chrono::microseconds;
- using std::chrono::steady_clock;
- return {
- .timestamp = duration_cast<microseconds>(steady_clock::now() - time_origin),
- .log_class = log_class,
- .log_level = log_level,
- .filename = filename,
- .line_num = line_nr,
- .function = function,
- .message = std::move(message),
- };
- }
- void ForEachBackend(auto lambda) {
- lambda(static_cast<Backend&>(debugger_backend));
- lambda(static_cast<Backend&>(color_console_backend));
- lambda(static_cast<Backend&>(file_backend));
- }
- static void Deleter(Impl* ptr) {
- delete ptr;
- }
- static inline std::unique_ptr<Impl, decltype(&Deleter)> instance{nullptr, Deleter};
- Filter filter;
- DebuggerBackend debugger_backend{};
- ColorConsoleBackend color_console_backend{};
- FileBackend file_backend;
- MPSCQueue<Entry> message_queue{};
- std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
- std::jthread backend_thread;
- };
- } // namespace
- void Initialize() {
- Impl::Initialize();
- }
- void Start() {
- Impl::Start();
- }
- void DisableLoggingInTests() {
- initialization_in_progress_suppress_logging = true;
- }
- 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) {
- Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function,
- fmt::vformat(format, args));
- }
- }
- } // namespace Common::Log
|