mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 22:00:05 +00:00 
			
		
		
		
	Logging: Add customizable logging backends and fmtlib based macros
* Change the logging backend to support multiple sinks through the Backend Interface * Add a new set of logging macros to use fmtlib instead. * Qt: Compile as GUI application on windows to make the console hidden by default. Add filter configuration and a button to open log location. * SDL: Migrate to the new logging macros
This commit is contained in:
		
							parent
							
								
									7d8b7d93fc
								
							
						
					
					
						commit
						51398e0301
					
				
					 17 changed files with 405 additions and 24 deletions
				
			
		|  | @ -4,17 +4,144 @@ | |||
| 
 | ||||
| #include <algorithm> | ||||
| #include <array> | ||||
| #include <chrono> | ||||
| #include <condition_variable> | ||||
| #include <cstdio> | ||||
| #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)                                                                                       \ | ||||
|  | @ -132,15 +259,26 @@ 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 SetFilter(Filter* new_filter) { | ||||
|     filter = new_filter; | ||||
| void AddBackend(std::unique_ptr<Backend> backend) { | ||||
|     Impl::Instance().AddBackend(std::move(backend)); | ||||
| } | ||||
| 
 | ||||
| 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 LogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num, | ||||
|                 const char* function, const char* format, ...) { | ||||
|     if (filter && !filter->CheckMessage(log_class, log_level)) | ||||
|     auto filter = Impl::Instance().GetGlobalFilter(); | ||||
|     if (!filter.CheckMessage(log_class, log_level)) | ||||
|         return; | ||||
|     std::array<char, 4 * 1024> formatting_buffer; | ||||
|     va_list args; | ||||
|  | @ -150,17 +288,19 @@ void LogMessage(Class log_class, Level log_level, const char* filename, unsigned | |||
|     Entry entry = CreateEntry(log_class, log_level, filename, line_num, function, | ||||
|                               std::string(formatting_buffer.data())); | ||||
| 
 | ||||
|     PrintColoredMessage(entry); | ||||
|     Impl::Instance().PushEntry(std::move(entry)); | ||||
| } | ||||
| 
 | ||||
| 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
 | ||||
|  |  | |||
|  | @ -6,8 +6,11 @@ | |||
| 
 | ||||
| #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 { | ||||
|  | @ -34,6 +37,80 @@ struct Entry { | |||
|     Entry& operator=(const Entry& o) = default; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * 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 +126,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); | ||||
| /**
 | ||||
|  * 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
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue