summaryrefslogtreecommitdiffstats
path: root/src/common/logging
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/logging')
-rw-r--r--src/common/logging/backend.cpp157
-rw-r--r--src/common/logging/backend.h87
-rw-r--r--src/common/logging/filter.cpp8
-rw-r--r--src/common/logging/log.h14
4 files changed, 244 insertions, 22 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
diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h
index 7e81efb23..57cdf6b2d 100644
--- a/src/common/logging/backend.h
+++ b/src/common/logging/backend.h
@@ -1,13 +1,15 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-
#pragma once
#include <chrono>
#include <cstdarg>
+#include <memory>
#include <string>
#include <utility>
+#include "common/file_util.h"
+#include "common/logging/filter.h"
#include "common/logging/log.h"
namespace Log {
@@ -35,6 +37,80 @@ struct Entry {
};
/**
+ * Interface for logging backends. As loggers can be created and removed at runtime, this can be
+ * used by a frontend for adding a custom logging backend as needed
+ */
+class Backend {
+public:
+ virtual ~Backend() = default;
+ virtual void SetFilter(const Filter& new_filter) {
+ filter = new_filter;
+ }
+ virtual const char* GetName() const = 0;
+ virtual void Write(const Entry& entry) = 0;
+
+private:
+ Filter filter;
+};
+
+/**
+ * Backend that writes to stderr without any color commands
+ */
+class ConsoleBackend : public Backend {
+public:
+ static const char* Name() {
+ return "console";
+ }
+ const char* GetName() const override {
+ return Name();
+ }
+ void Write(const Entry& entry) override;
+};
+
+/**
+ * Backend that writes to stderr and with color
+ */
+class ColorConsoleBackend : public Backend {
+public:
+ static const char* Name() {
+ return "color_console";
+ }
+
+ const char* GetName() const override {
+ return Name();
+ }
+ void Write(const Entry& entry) override;
+};
+
+/**
+ * Backend that writes to a file passed into the constructor
+ */
+class FileBackend : public Backend {
+public:
+ explicit FileBackend(const std::string& filename);
+
+ static const char* Name() {
+ return "file";
+ }
+
+ const char* GetName() const override {
+ return Name();
+ }
+
+ void Write(const Entry& entry) override;
+
+private:
+ FileUtil::IOFile file;
+ size_t bytes_written;
+};
+
+void AddBackend(std::unique_ptr<Backend> backend);
+
+void RemoveBackend(const std::string& backend_name);
+
+Backend* GetBackend(const std::string& backend_name);
+
+/**
* Returns the name of the passed log class as a C-string. Subclasses are separated by periods
* instead of underscores as in the enumeration.
*/
@@ -49,5 +125,10 @@ const char* GetLevelName(Level log_level);
Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
const char* function, std::string message);
-void SetFilter(Filter* filter);
-} // namespace Log
+/**
+ * The global filter will prevent any messages from even being processed if they are filtered. Each
+ * backend can have a filter, but if the level is lower than the global filter, the backend will
+ * never get the message
+ */
+void SetGlobalFilter(const Filter& filter);
+} // namespace Log \ No newline at end of file
diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp
index 428723dce..4e783a577 100644
--- a/src/common/logging/filter.cpp
+++ b/src/common/logging/filter.cpp
@@ -65,14 +65,14 @@ bool Filter::ParseFilterRule(const std::string::const_iterator begin,
const std::string::const_iterator end) {
auto level_separator = std::find(begin, end, ':');
if (level_separator == end) {
- NGLOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: %s",
- std::string(begin, end).c_str());
+ LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: {}",
+ std::string(begin, end));
return false;
}
const Level level = GetLevelByName(level_separator + 1, end);
if (level == Level::Count) {
- NGLOG_ERROR(Log, "Unknown log level in filter: %s", std::string(begin, end).c_str());
+ LOG_ERROR(Log, "Unknown log level in filter: {}", std::string(begin, end));
return false;
}
@@ -83,7 +83,7 @@ bool Filter::ParseFilterRule(const std::string::const_iterator begin,
const Class log_class = GetClassByName(begin, level_separator);
if (log_class == Class::Count) {
- NGLOG_ERROR(Log, "Unknown log class in filter: %s", std::string(begin, end).c_str());
+ LOG_ERROR(Log, "Unknown log class in filter: {}", std::string(begin, end));
return false;
}
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index c5015531c..e96c90e16 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -109,25 +109,25 @@ void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsig
} // namespace Log
#ifdef _DEBUG
-#define NGLOG_TRACE(log_class, ...) \
+#define LOG_TRACE(log_class, ...) \
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Trace, __FILE__, __LINE__, \
__func__, __VA_ARGS__)
#else
-#define NGLOG_TRACE(log_class, fmt, ...) (void(0))
+#define LOG_TRACE(log_class, fmt, ...) (void(0))
#endif
-#define NGLOG_DEBUG(log_class, ...) \
+#define LOG_DEBUG(log_class, ...) \
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Debug, __FILE__, __LINE__, \
__func__, __VA_ARGS__)
-#define NGLOG_INFO(log_class, ...) \
+#define LOG_INFO(log_class, ...) \
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Info, __FILE__, __LINE__, \
__func__, __VA_ARGS__)
-#define NGLOG_WARNING(log_class, ...) \
+#define LOG_WARNING(log_class, ...) \
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Warning, __FILE__, __LINE__, \
__func__, __VA_ARGS__)
-#define NGLOG_ERROR(log_class, ...) \
+#define LOG_ERROR(log_class, ...) \
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Error, __FILE__, __LINE__, \
__func__, __VA_ARGS__)
-#define NGLOG_CRITICAL(log_class, ...) \
+#define LOG_CRITICAL(log_class, ...) \
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Critical, __FILE__, __LINE__, \
__func__, __VA_ARGS__)