From 6269a01b4e9963ffdaf98ddf5d5f2a90d49e58ff Mon Sep 17 00:00:00 2001
From: James Rowe <jroweboy@gmail.com>
Date: Mon, 2 Jul 2018 11:10:41 -0600
Subject: Add configurable logging backends

---
 src/common/logging/backend.cpp | 157 ++++++++++++++++++++++++++++++++++++++---
 1 file changed, 149 insertions(+), 8 deletions(-)

(limited to 'src/common/logging/backend.cpp')

diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index c26b200620..242914c6ae 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
-- 
cgit v1.2.3-70-g09d2