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 @@ | |||
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <utility> | 5 | #include <algorithm> |
| 6 | #include <array> | ||
| 7 | #include <chrono> | ||
| 8 | #include <condition_variable> | ||
| 9 | #include <memory> | ||
| 10 | #include <thread> | ||
| 11 | #ifdef _WIN32 | ||
| 12 | #include <share.h> // For _SH_DENYWR | ||
| 13 | #else | ||
| 14 | #define _SH_DENYWR 0 | ||
| 15 | #endif | ||
| 6 | #include "common/assert.h" | 16 | #include "common/assert.h" |
| 17 | #include "common/common_funcs.h" // snprintf compatibility define | ||
| 7 | #include "common/logging/backend.h" | 18 | #include "common/logging/backend.h" |
| 8 | #include "common/logging/filter.h" | ||
| 9 | #include "common/logging/log.h" | 19 | #include "common/logging/log.h" |
| 10 | #include "common/logging/text_formatter.h" | 20 | #include "common/logging/text_formatter.h" |
| 11 | #include "common/string_util.h" | 21 | #include "common/string_util.h" |
| 22 | #include "common/threadsafe_queue.h" | ||
| 12 | 23 | ||
| 13 | namespace Log { | 24 | namespace Log { |
| 14 | 25 | ||
| 26 | /** | ||
| 27 | * Static state as a singleton. | ||
| 28 | */ | ||
| 29 | class Impl { | ||
| 30 | public: | ||
| 31 | static Impl& Instance() { | ||
| 32 | static Impl backend; | ||
| 33 | return backend; | ||
| 34 | } | ||
| 35 | |||
| 36 | Impl(Impl const&) = delete; | ||
| 37 | const Impl& operator=(Impl const&) = delete; | ||
| 38 | |||
| 39 | void PushEntry(Entry e) { | ||
| 40 | std::lock_guard<std::mutex> lock(message_mutex); | ||
| 41 | message_queue.Push(std::move(e)); | ||
| 42 | message_cv.notify_one(); | ||
| 43 | } | ||
| 44 | |||
| 45 | void AddBackend(std::unique_ptr<Backend> backend) { | ||
| 46 | std::lock_guard<std::mutex> lock(writing_mutex); | ||
| 47 | backends.push_back(std::move(backend)); | ||
| 48 | } | ||
| 49 | |||
| 50 | void RemoveBackend(const std::string& backend_name) { | ||
| 51 | std::lock_guard<std::mutex> lock(writing_mutex); | ||
| 52 | auto it = std::remove_if(backends.begin(), backends.end(), [&backend_name](const auto& i) { | ||
| 53 | return !strcmp(i->GetName(), backend_name.c_str()); | ||
| 54 | }); | ||
| 55 | backends.erase(it, backends.end()); | ||
| 56 | } | ||
| 57 | |||
| 58 | const Filter& GetGlobalFilter() const { | ||
| 59 | return filter; | ||
| 60 | } | ||
| 61 | |||
| 62 | void SetGlobalFilter(const Filter& f) { | ||
| 63 | filter = f; | ||
| 64 | } | ||
| 65 | |||
| 66 | Backend* GetBackend(const std::string& backend_name) { | ||
| 67 | auto it = std::find_if(backends.begin(), backends.end(), [&backend_name](const auto& i) { | ||
| 68 | return !strcmp(i->GetName(), backend_name.c_str()); | ||
| 69 | }); | ||
| 70 | if (it == backends.end()) | ||
| 71 | return nullptr; | ||
| 72 | return it->get(); | ||
| 73 | } | ||
| 74 | |||
| 75 | private: | ||
| 76 | Impl() { | ||
| 77 | backend_thread = std::thread([&] { | ||
| 78 | Entry entry; | ||
| 79 | auto write_logs = [&](Entry& e) { | ||
| 80 | std::lock_guard<std::mutex> lock(writing_mutex); | ||
| 81 | for (const auto& backend : backends) { | ||
| 82 | backend->Write(e); | ||
| 83 | } | ||
| 84 | }; | ||
| 85 | while (true) { | ||
| 86 | std::unique_lock<std::mutex> lock(message_mutex); | ||
| 87 | message_cv.wait(lock, [&] { return !running || message_queue.Pop(entry); }); | ||
| 88 | if (!running) { | ||
| 89 | break; | ||
| 90 | } | ||
| 91 | write_logs(entry); | ||
| 92 | } | ||
| 93 | // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a case | ||
| 94 | // where a system is repeatedly spamming logs even on close. | ||
| 95 | constexpr int MAX_LOGS_TO_WRITE = 100; | ||
| 96 | int logs_written = 0; | ||
| 97 | while (logs_written++ < MAX_LOGS_TO_WRITE && message_queue.Pop(entry)) { | ||
| 98 | write_logs(entry); | ||
| 99 | } | ||
| 100 | }); | ||
| 101 | } | ||
| 102 | |||
| 103 | ~Impl() { | ||
| 104 | running = false; | ||
| 105 | message_cv.notify_one(); | ||
| 106 | backend_thread.join(); | ||
| 107 | } | ||
| 108 | |||
| 109 | std::atomic_bool running{true}; | ||
| 110 | std::mutex message_mutex, writing_mutex; | ||
| 111 | std::condition_variable message_cv; | ||
| 112 | std::thread backend_thread; | ||
| 113 | std::vector<std::unique_ptr<Backend>> backends; | ||
| 114 | Common::MPSCQueue<Log::Entry> message_queue; | ||
| 115 | Filter filter; | ||
| 116 | }; | ||
| 117 | |||
| 118 | void ConsoleBackend::Write(const Entry& entry) { | ||
| 119 | PrintMessage(entry); | ||
| 120 | } | ||
| 121 | |||
| 122 | void ColorConsoleBackend::Write(const Entry& entry) { | ||
| 123 | PrintColoredMessage(entry); | ||
| 124 | } | ||
| 125 | |||
| 126 | // _SH_DENYWR allows read only access to the file for other programs. | ||
| 127 | // It is #defined to 0 on other platforms | ||
| 128 | FileBackend::FileBackend(const std::string& filename) | ||
| 129 | : file(filename, "w", _SH_DENYWR), bytes_written(0) {} | ||
| 130 | |||
| 131 | void FileBackend::Write(const Entry& entry) { | ||
| 132 | // prevent logs from going over the maximum size (in case its spamming and the user doesn't | ||
| 133 | // know) | ||
| 134 | constexpr size_t MAX_BYTES_WRITTEN = 50 * 1024L * 1024L; | ||
| 135 | if (!file.IsOpen() || bytes_written > MAX_BYTES_WRITTEN) { | ||
| 136 | return; | ||
| 137 | } | ||
| 138 | bytes_written += file.WriteString(FormatLogMessage(entry) + '\n'); | ||
| 139 | if (entry.log_level >= Level::Error) { | ||
| 140 | file.Flush(); | ||
| 141 | } | ||
| 142 | } | ||
| 143 | |||
| 15 | /// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this. | 144 | /// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this. |
| 16 | #define ALL_LOG_CLASSES() \ | 145 | #define ALL_LOG_CLASSES() \ |
| 17 | CLS(Log) \ | 146 | CLS(Log) \ |
| @@ -125,20 +254,32 @@ Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsign | |||
| 125 | return entry; | 254 | return entry; |
| 126 | } | 255 | } |
| 127 | 256 | ||
| 128 | static Filter* filter = nullptr; | 257 | void SetGlobalFilter(const Filter& filter) { |
| 258 | Impl::Instance().SetGlobalFilter(filter); | ||
| 259 | } | ||
| 260 | |||
| 261 | void AddBackend(std::unique_ptr<Backend> backend) { | ||
| 262 | Impl::Instance().AddBackend(std::move(backend)); | ||
| 263 | } | ||
| 129 | 264 | ||
| 130 | void SetFilter(Filter* new_filter) { | 265 | void RemoveBackend(const std::string& backend_name) { |
| 131 | filter = new_filter; | 266 | Impl::Instance().RemoveBackend(backend_name); |
| 267 | } | ||
| 268 | |||
| 269 | Backend* GetBackend(const std::string& backend_name) { | ||
| 270 | return Impl::Instance().GetBackend(backend_name); | ||
| 132 | } | 271 | } |
| 133 | 272 | ||
| 134 | void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, | 273 | void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, |
| 135 | unsigned int line_num, const char* function, const char* format, | 274 | unsigned int line_num, const char* function, const char* format, |
| 136 | const fmt::format_args& args) { | 275 | const fmt::format_args& args) { |
| 137 | if (filter && !filter->CheckMessage(log_class, log_level)) | 276 | auto filter = Impl::Instance().GetGlobalFilter(); |
| 277 | if (!filter.CheckMessage(log_class, log_level)) | ||
| 138 | return; | 278 | return; |
| 279 | |||
| 139 | Entry entry = | 280 | Entry entry = |
| 140 | CreateEntry(log_class, log_level, filename, line_num, function, fmt::vformat(format, args)); | 281 | CreateEntry(log_class, log_level, filename, line_num, function, fmt::vformat(format, args)); |
| 141 | 282 | ||
| 142 | PrintColoredMessage(entry); | 283 | Impl::Instance().PushEntry(std::move(entry)); |
| 143 | } | 284 | } |
| 144 | } // namespace Log | 285 | } // namespace Log \ No newline at end of file |