// Copyright 2014 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include // For _SH_DENYWR #else #define _SH_DENYWR 0 #endif #include "common/assert.h" #include "common/logging/backend.h" #include "common/logging/log.h" #include "common/logging/text_formatter.h" #include "common/string_util.h" #include "common/threadsafe_queue.h" namespace Log { /** * Static state as a singleton. */ class Impl { public: static Impl& Instance() { static Impl backend; return backend; } Impl(Impl const&) = delete; const Impl& operator=(Impl const&) = delete; void PushEntry(Entry e) { std::lock_guard lock(message_mutex); message_queue.Push(std::move(e)); message_cv.notify_one(); } 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); const auto it = std::remove_if(backends.begin(), backends.end(), [&backend_name](const auto& i) { return backend_name == i->GetName(); }); backends.erase(it, backends.end()); } 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(); } 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) { { std::unique_lock lock(message_mutex); message_cv.wait(lock, [&] { return !running || message_queue.Pop(entry); }); } if (!running) { 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. const int MAX_LOGS_TO_WRITE = filter.IsDebug() ? INT_MAX : 100; int logs_written = 0; while (logs_written++ < MAX_LOGS_TO_WRITE && message_queue.Pop(entry)) { write_logs(entry); } }); } ~Impl() { running = false; message_cv.notify_one(); backend_thread.join(); } std::atomic_bool running{true}; std::mutex message_mutex, writing_mutex; std::condition_variable message_cv; std::thread backend_thread; std::vector> backends; Common::MPSCQueue message_queue; Filter filter; }; void ConsoleBackend::Write(const Entry& entry) { PrintMessage(entry); } void ColorConsoleBackend::Write(const Entry& entry) { PrintColoredMessage(entry); } // _SH_DENYWR allows read only access to the file for other programs. // It is #defined to 0 on other platforms FileBackend::FileBackend(const std::string& filename) : file(filename, "w", _SH_DENYWR), bytes_written(0) {} void FileBackend::Write(const Entry& entry) { // prevent logs from going over the maximum size (in case its spamming and the user doesn't // know) constexpr size_t MAX_BYTES_WRITTEN = 50 * 1024L * 1024L; if (!file.IsOpen() || bytes_written > MAX_BYTES_WRITTEN) { return; } bytes_written += file.WriteString(FormatLogMessage(entry) + '\n'); if (entry.log_level >= Level::Error) { file.Flush(); } } /// 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, ARM) \ SUB(Core, Timing) \ CLS(Config) \ CLS(Debug) \ SUB(Debug, Emulated) \ SUB(Debug, GPU) \ SUB(Debug, Breakpoint) \ SUB(Debug, GDBStub) \ CLS(Kernel) \ SUB(Kernel, SVC) \ CLS(Service) \ SUB(Service, ACC) \ SUB(Service, Audio) \ SUB(Service, AM) \ SUB(Service, AOC) \ SUB(Service, APM) \ SUB(Service, BCAT) \ SUB(Service, Fatal) \ SUB(Service, Friend) \ SUB(Service, FS) \ SUB(Service, HID) \ SUB(Service, LBL) \ SUB(Service, LDN) \ SUB(Service, LM) \ SUB(Service, MM) \ SUB(Service, NCM) \ SUB(Service, NFC) \ SUB(Service, NFP) \ SUB(Service, NIFM) \ SUB(Service, NS) \ SUB(Service, NVDRV) \ SUB(Service, PCTL) \ SUB(Service, PREPO) \ SUB(Service, SET) \ SUB(Service, SM) \ SUB(Service, SPL) \ SUB(Service, SSL) \ SUB(Service, Time) \ SUB(Service, VI) \ CLS(HW) \ SUB(HW, Memory) \ SUB(HW, LCD) \ SUB(HW, GPU) \ SUB(HW, AES) \ CLS(IPC) \ CLS(Frontend) \ CLS(Render) \ SUB(Render, Software) \ SUB(Render, OpenGL) \ CLS(Audio) \ SUB(Audio, DSP) \ SUB(Audio, Sink) \ CLS(Input) \ CLS(Network) \ CLS(Loader) \ CLS(WebService) // 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: 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: UNREACHABLE(); } #undef LVL } Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr, const char* function, std::string message) { using std::chrono::duration_cast; using std::chrono::steady_clock; static steady_clock::time_point time_origin = steady_clock::now(); Entry entry; entry.timestamp = duration_cast(steady_clock::now() - time_origin); entry.log_class = log_class; entry.log_level = log_level; entry.filename = Common::TrimSourcePath(filename); entry.line_num = line_nr; entry.function = function; entry.message = std::move(message); return entry; } 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 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 filter = Impl::Instance().GetGlobalFilter(); if (!filter.CheckMessage(log_class, log_level)) return; Entry entry = CreateEntry(log_class, log_level, filename, line_num, function, fmt::vformat(format, args)); Impl::Instance().PushEntry(std::move(entry)); } } // namespace Log