| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326 |
- // Copyright 2014 Citra Emulator Project
- // Licensed under GPLv2 or any later version
- // Refer to the license.txt file included.
- #include <algorithm>
- #include <atomic>
- #include <chrono>
- #include <climits>
- #include <condition_variable>
- #include <memory>
- #include <mutex>
- #include <thread>
- #include <vector>
- #ifdef _WIN32
- #include <share.h> // For _SH_DENYWR
- #include <windows.h> // For OutputDebugStringA
- #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<std::mutex> lock(message_mutex);
- message_queue.Push(std::move(e));
- message_cv.notify_one();
- }
- void AddBackend(std::unique_ptr<Backend> backend) {
- std::lock_guard<std::mutex> lock(writing_mutex);
- backends.push_back(std::move(backend));
- }
- void RemoveBackend(std::string_view backend_name) {
- std::lock_guard<std::mutex> 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<std::mutex> lock(writing_mutex);
- for (const auto& backend : backends) {
- backend->Write(e);
- }
- };
- while (true) {
- {
- std::unique_lock<std::mutex> 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<std::unique_ptr<Backend>> backends;
- Common::MPSCQueue<Log::Entry> 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 std::size_t MAX_BYTES_WRITTEN = 50 * 1024L * 1024L;
- if (!file.IsOpen() || bytes_written > MAX_BYTES_WRITTEN) {
- return;
- }
- bytes_written += file.WriteString(FormatLogMessage(entry).append(1, '\n'));
- if (entry.log_level >= Level::Error) {
- file.Flush();
- }
- }
- void DebuggerBackend::Write(const Entry& entry) {
- #ifdef _WIN32
- ::OutputDebugStringA(FormatLogMessage(entry).append(1, '\n').c_str());
- #endif
- }
- /// 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, ARP) \
- SUB(Service, BCAT) \
- SUB(Service, BPC) \
- SUB(Service, BTDRV) \
- SUB(Service, BTM) \
- SUB(Service, Capture) \
- SUB(Service, ERPT) \
- SUB(Service, ETicket) \
- SUB(Service, EUPLD) \
- SUB(Service, Fatal) \
- SUB(Service, FGM) \
- SUB(Service, Friend) \
- SUB(Service, FS) \
- SUB(Service, GRC) \
- SUB(Service, HID) \
- SUB(Service, IRS) \
- SUB(Service, LBL) \
- SUB(Service, LDN) \
- SUB(Service, LDR) \
- SUB(Service, LM) \
- SUB(Service, Migration) \
- SUB(Service, Mii) \
- SUB(Service, MM) \
- SUB(Service, NCM) \
- SUB(Service, NFC) \
- SUB(Service, NFP) \
- SUB(Service, NIFM) \
- SUB(Service, NIM) \
- SUB(Service, NPNS) \
- SUB(Service, NS) \
- SUB(Service, NVDRV) \
- SUB(Service, PCIE) \
- SUB(Service, PCTL) \
- SUB(Service, PCV) \
- SUB(Service, PM) \
- SUB(Service, PREPO) \
- SUB(Service, PSC) \
- SUB(Service, PSM) \
- SUB(Service, SET) \
- SUB(Service, SM) \
- SUB(Service, SPL) \
- SUB(Service, SSL) \
- SUB(Service, TCAP) \
- SUB(Service, Time) \
- SUB(Service, USB) \
- SUB(Service, VI) \
- SUB(Service, WLAN) \
- 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(Crypto) \
- 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<std::chrono::microseconds>(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> 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& instance = Impl::Instance();
- const auto& filter = 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));
- instance.PushEntry(std::move(entry));
- }
- } // namespace Log
|