diff options
Diffstat (limited to 'src/common/logging')
| -rw-r--r-- | src/common/logging/backend.cpp | 157 | ||||
| -rw-r--r-- | src/common/logging/backend.h | 87 | ||||
| -rw-r--r-- | src/common/logging/filter.cpp | 8 | ||||
| -rw-r--r-- | src/common/logging/log.h | 14 | 
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__) | 
