| |
| |
|
|
| #include <atomic> |
| #include <chrono> |
| #include <climits> |
| #include <thread> |
|
|
| #include <fmt/format.h> |
|
|
| #ifdef _WIN32 |
| #include <windows.h> |
| #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 { |
|
|
| |
| |
| |
| class Backend { |
| public: |
| virtual ~Backend() = default; |
|
|
| virtual void Write(const Entry& entry) = 0; |
|
|
| virtual void EnableForStacktrace() = 0; |
|
|
| virtual void Flush() = 0; |
| }; |
|
|
| |
| |
| |
| 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 { |
| |
| } |
|
|
| void EnableForStacktrace() override { |
| enabled = true; |
| } |
|
|
| void SetEnabled(bool enabled_) { |
| enabled = enabled_; |
| } |
|
|
| private: |
| std::atomic_bool enabled{false}; |
| }; |
|
|
| |
| |
| |
| class FileBackend final : public Backend { |
| public: |
| explicit FileBackend(const std::filesystem::path& filename) { |
| auto old_filename = filename; |
| old_filename += ".old.txt"; |
|
|
| |
| |
| 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; |
| |
| const auto write_limit = Settings::values.extended_logging.GetValue() ? 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) { |
| |
| |
| 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; |
| }; |
|
|
| |
| |
| |
| 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 |
| |
| |
| |
| 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 = true; |
|
|
| |
| |
| |
| 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(); |
| } |
|
|
| 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)) { |
| 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(); |
| } |
| } |
| |
| |
| int max_logs_to_write = filter.IsDebug() ? INT_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([](Backend& backend) { backend.Flush(); }); |
| } |
|
|
| 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)); |
| #ifdef ANDROID |
| lambda(static_cast<Backend&>(lc_backend)); |
| #endif |
| } |
|
|
| 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; |
| #ifdef ANDROID |
| LogcatBackend lc_backend{}; |
| #endif |
|
|
| MPSCQueue<Entry> message_queue{}; |
| std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()}; |
| std::jthread backend_thread; |
| }; |
| } |
|
|
| void Initialize() { |
| Impl::Initialize(); |
| } |
|
|
| void Start() { |
| Impl::Start(); |
| } |
|
|
| void Stop() { |
| Impl::Stop(); |
| } |
|
|
| 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)); |
| } |
| } |
| } |
|
|