diff options
Diffstat (limited to 'src/common/logging/backend.cpp')
-rw-r--r-- | src/common/logging/backend.cpp | 157 |
1 files changed, 149 insertions, 8 deletions
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index c26b20062..242914c6a 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -2,16 +2,145 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <utility> +#include <algorithm> +#include <array> +#include <chrono> +#include <condition_variable> +#include <memory> +#include <thread> +#ifdef _WIN32 +#include <share.h> // For _SH_DENYWR +#else +#define _SH_DENYWR 0 +#endif #include "common/assert.h" +#include "common/common_funcs.h" // snprintf compatibility define #include "common/logging/backend.h" -#include "common/logging/filter.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(const std::string& backend_name) { + std::lock_guard<std::mutex> lock(writing_mutex); + auto it = std::remove_if(backends.begin(), backends.end(), [&backend_name](const auto& i) { + return !strcmp(i->GetName(), backend_name.c_str()); + }); + backends.erase(it, backends.end()); + } + + const Filter& GetGlobalFilter() const { + return filter; + } + + void SetGlobalFilter(const Filter& f) { + filter = f; + } + + Backend* GetBackend(const std::string& backend_name) { + auto it = std::find_if(backends.begin(), backends.end(), [&backend_name](const auto& i) { + return !strcmp(i->GetName(), backend_name.c_str()); + }); + 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. + 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() { + 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 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) \ @@ -125,20 +254,32 @@ Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsign return entry; } -static Filter* filter = nullptr; +void SetGlobalFilter(const Filter& filter) { + Impl::Instance().SetGlobalFilter(filter); +} + +void AddBackend(std::unique_ptr<Backend> backend) { + Impl::Instance().AddBackend(std::move(backend)); +} -void SetFilter(Filter* new_filter) { - filter = new_filter; +void RemoveBackend(const std::string& backend_name) { + Impl::Instance().RemoveBackend(backend_name); +} + +Backend* GetBackend(const std::string& 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) { - if (filter && !filter->CheckMessage(log_class, log_level)) + 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)); - PrintColoredMessage(entry); + Impl::Instance().PushEntry(std::move(entry)); } -} // namespace Log +} // namespace Log
\ No newline at end of file |