diff options
Diffstat (limited to '')
| -rw-r--r-- | src/common/logging/backend.cpp | 151 | ||||
| -rw-r--r-- | src/common/logging/backend.h | 134 | ||||
| -rw-r--r-- | src/common/logging/log.h | 115 | ||||
| -rw-r--r-- | src/common/logging/text_formatter.cpp | 47 | ||||
| -rw-r--r-- | src/common/logging/text_formatter.h | 26 |
5 files changed, 473 insertions, 0 deletions
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp new file mode 100644 index 000000000..e79b84604 --- /dev/null +++ b/src/common/logging/backend.cpp | |||
| @@ -0,0 +1,151 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2+ | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <algorithm> | ||
| 6 | |||
| 7 | #include "common/log.h" // For _dbg_assert_ | ||
| 8 | |||
| 9 | #include "common/logging/backend.h" | ||
| 10 | #include "common/logging/log.h" | ||
| 11 | #include "common/logging/text_formatter.h" | ||
| 12 | |||
| 13 | namespace Log { | ||
| 14 | |||
| 15 | static std::shared_ptr<Logger> global_logger; | ||
| 16 | |||
| 17 | /// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this. | ||
| 18 | #define ALL_LOG_CLASSES() \ | ||
| 19 | CLS(Log) \ | ||
| 20 | CLS(Common) \ | ||
| 21 | SUB(Common, Filesystem) \ | ||
| 22 | SUB(Common, Memory) \ | ||
| 23 | CLS(Core) \ | ||
| 24 | SUB(Core, ARM11) \ | ||
| 25 | CLS(Config) \ | ||
| 26 | CLS(Debug) \ | ||
| 27 | SUB(Debug, Emulated) \ | ||
| 28 | SUB(Debug, GPU) \ | ||
| 29 | SUB(Debug, Breakpoint) \ | ||
| 30 | CLS(Kernel) \ | ||
| 31 | SUB(Kernel, SVC) \ | ||
| 32 | CLS(Service) \ | ||
| 33 | SUB(Service, SRV) \ | ||
| 34 | SUB(Service, FS) \ | ||
| 35 | SUB(Service, APT) \ | ||
| 36 | SUB(Service, GSP) \ | ||
| 37 | SUB(Service, AC) \ | ||
| 38 | SUB(Service, PTM) \ | ||
| 39 | SUB(Service, CFG) \ | ||
| 40 | SUB(Service, DSP) \ | ||
| 41 | SUB(Service, HID) \ | ||
| 42 | CLS(HW) \ | ||
| 43 | SUB(HW, Memory) \ | ||
| 44 | SUB(HW, GPU) \ | ||
| 45 | CLS(Frontend) \ | ||
| 46 | CLS(Render) \ | ||
| 47 | SUB(Render, Software) \ | ||
| 48 | SUB(Render, OpenGL) \ | ||
| 49 | CLS(Loader) | ||
| 50 | |||
| 51 | Logger::Logger() { | ||
| 52 | // Register logging classes so that they can be queried at runtime | ||
| 53 | size_t parent_class; | ||
| 54 | all_classes.reserve((size_t)Class::Count); | ||
| 55 | |||
| 56 | #define CLS(x) \ | ||
| 57 | all_classes.push_back(Class::x); \ | ||
| 58 | parent_class = all_classes.size() - 1; | ||
| 59 | #define SUB(x, y) \ | ||
| 60 | all_classes.push_back(Class::x##_##y); \ | ||
| 61 | all_classes[parent_class].num_children += 1; | ||
| 62 | |||
| 63 | ALL_LOG_CLASSES() | ||
| 64 | #undef CLS | ||
| 65 | #undef SUB | ||
| 66 | |||
| 67 | // Ensures that ALL_LOG_CLASSES isn't missing any entries. | ||
| 68 | _dbg_assert_(Log, all_classes.size() == (size_t)Class::Count); | ||
| 69 | } | ||
| 70 | |||
| 71 | // GetClassName is a macro defined by Windows.h, grrr... | ||
| 72 | const char* Logger::GetLogClassName(Class log_class) { | ||
| 73 | switch (log_class) { | ||
| 74 | #define CLS(x) case Class::x: return #x; | ||
| 75 | #define SUB(x, y) case Class::x##_##y: return #x "." #y; | ||
| 76 | ALL_LOG_CLASSES() | ||
| 77 | #undef CLS | ||
| 78 | #undef SUB | ||
| 79 | } | ||
| 80 | return "Unknown"; | ||
| 81 | } | ||
| 82 | |||
| 83 | const char* Logger::GetLevelName(Level log_level) { | ||
| 84 | #define LVL(x) case Level::x: return #x | ||
| 85 | switch (log_level) { | ||
| 86 | LVL(Trace); | ||
| 87 | LVL(Debug); | ||
| 88 | LVL(Info); | ||
| 89 | LVL(Warning); | ||
| 90 | LVL(Error); | ||
| 91 | LVL(Critical); | ||
| 92 | } | ||
| 93 | return "Unknown"; | ||
| 94 | #undef LVL | ||
| 95 | } | ||
| 96 | |||
| 97 | void Logger::LogMessage(Entry entry) { | ||
| 98 | ring_buffer.Push(std::move(entry)); | ||
| 99 | } | ||
| 100 | |||
| 101 | size_t Logger::GetEntries(Entry* out_buffer, size_t buffer_len) { | ||
| 102 | return ring_buffer.BlockingPop(out_buffer, buffer_len); | ||
| 103 | } | ||
| 104 | |||
| 105 | std::shared_ptr<Logger> InitGlobalLogger() { | ||
| 106 | global_logger = std::make_shared<Logger>(); | ||
| 107 | return global_logger; | ||
| 108 | } | ||
| 109 | |||
| 110 | Entry CreateEntry(Class log_class, Level log_level, | ||
| 111 | const char* filename, unsigned int line_nr, const char* function, | ||
| 112 | const char* format, va_list args) { | ||
| 113 | using std::chrono::steady_clock; | ||
| 114 | using std::chrono::duration_cast; | ||
| 115 | |||
| 116 | static steady_clock::time_point time_origin = steady_clock::now(); | ||
| 117 | |||
| 118 | std::array<char, 4 * 1024> formatting_buffer; | ||
| 119 | |||
| 120 | Entry entry; | ||
| 121 | entry.timestamp = duration_cast<std::chrono::microseconds>(steady_clock::now() - time_origin); | ||
| 122 | entry.log_class = log_class; | ||
| 123 | entry.log_level = log_level; | ||
| 124 | |||
| 125 | snprintf(formatting_buffer.data(), formatting_buffer.size(), "%s:%s:%u", filename, function, line_nr); | ||
| 126 | entry.location = std::string(formatting_buffer.data()); | ||
| 127 | |||
| 128 | vsnprintf(formatting_buffer.data(), formatting_buffer.size(), format, args); | ||
| 129 | entry.message = std::string(formatting_buffer.data()); | ||
| 130 | |||
| 131 | return std::move(entry); | ||
| 132 | } | ||
| 133 | |||
| 134 | void LogMessage(Class log_class, Level log_level, | ||
| 135 | const char* filename, unsigned int line_nr, const char* function, | ||
| 136 | const char* format, ...) { | ||
| 137 | va_list args; | ||
| 138 | va_start(args, format); | ||
| 139 | Entry entry = CreateEntry(log_class, log_level, | ||
| 140 | filename, line_nr, function, format, args); | ||
| 141 | va_end(args); | ||
| 142 | |||
| 143 | if (global_logger != nullptr && !global_logger->IsClosed()) { | ||
| 144 | global_logger->LogMessage(std::move(entry)); | ||
| 145 | } else { | ||
| 146 | // Fall back to directly printing to stderr | ||
| 147 | PrintMessage(entry); | ||
| 148 | } | ||
| 149 | } | ||
| 150 | |||
| 151 | } | ||
diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h new file mode 100644 index 000000000..ae270efe8 --- /dev/null +++ b/src/common/logging/backend.h | |||
| @@ -0,0 +1,134 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2+ | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <cstdarg> | ||
| 8 | #include <memory> | ||
| 9 | #include <vector> | ||
| 10 | |||
| 11 | #include "common/concurrent_ring_buffer.h" | ||
| 12 | |||
| 13 | #include "common/logging/log.h" | ||
| 14 | |||
| 15 | namespace Log { | ||
| 16 | |||
| 17 | /** | ||
| 18 | * A log entry. Log entries are store in a structured format to permit more varied output | ||
| 19 | * formatting on different frontends, as well as facilitating filtering and aggregation. | ||
| 20 | */ | ||
| 21 | struct Entry { | ||
| 22 | std::chrono::microseconds timestamp; | ||
| 23 | Class log_class; | ||
| 24 | Level log_level; | ||
| 25 | std::string location; | ||
| 26 | std::string message; | ||
| 27 | |||
| 28 | Entry() = default; | ||
| 29 | |||
| 30 | // TODO(yuriks) Use defaulted move constructors once MSVC supports them | ||
| 31 | #define MOVE(member) member(std::move(o.member)) | ||
| 32 | Entry(Entry&& o) | ||
| 33 | : MOVE(timestamp), MOVE(log_class), MOVE(log_level), | ||
| 34 | MOVE(location), MOVE(message) | ||
| 35 | {} | ||
| 36 | #undef MOVE | ||
| 37 | |||
| 38 | Entry& operator=(const Entry&& o) { | ||
| 39 | #define MOVE(member) member = std::move(o.member) | ||
| 40 | MOVE(timestamp); | ||
| 41 | MOVE(log_class); | ||
| 42 | MOVE(log_level); | ||
| 43 | MOVE(location); | ||
| 44 | MOVE(message); | ||
| 45 | #undef MOVE | ||
| 46 | return *this; | ||
| 47 | } | ||
| 48 | }; | ||
| 49 | |||
| 50 | struct ClassInfo { | ||
| 51 | Class log_class; | ||
| 52 | |||
| 53 | /** | ||
| 54 | * Total number of (direct or indirect) sub classes this class has. If any, they follow in | ||
| 55 | * sequence after this class in the class list. | ||
| 56 | */ | ||
| 57 | unsigned int num_children = 0; | ||
| 58 | |||
| 59 | ClassInfo(Class log_class) : log_class(log_class) {} | ||
| 60 | }; | ||
| 61 | |||
| 62 | /** | ||
| 63 | * Logging management class. This class has the dual purpose of acting as an exchange point between | ||
| 64 | * the logging clients and the log outputter, as well as containing reflection info about available | ||
| 65 | * log classes. | ||
| 66 | */ | ||
| 67 | class Logger { | ||
| 68 | private: | ||
| 69 | using Buffer = Common::ConcurrentRingBuffer<Entry, 16 * 1024 / sizeof(Entry)>; | ||
| 70 | |||
| 71 | public: | ||
| 72 | static const size_t QUEUE_CLOSED = Buffer::QUEUE_CLOSED; | ||
| 73 | |||
| 74 | Logger(); | ||
| 75 | |||
| 76 | /** | ||
| 77 | * Returns a list of all vector classes and subclasses. The sequence returned is a pre-order of | ||
| 78 | * classes and subclasses, which together with the `num_children` field in ClassInfo, allows | ||
| 79 | * you to recover the hierarchy. | ||
| 80 | */ | ||
| 81 | const std::vector<ClassInfo>& GetClasses() const { return all_classes; } | ||
| 82 | |||
| 83 | /** | ||
| 84 | * Returns the name of the passed log class as a C-string. Subclasses are separated by periods | ||
| 85 | * instead of underscores as in the enumeration. | ||
| 86 | */ | ||
| 87 | static const char* GetLogClassName(Class log_class); | ||
| 88 | |||
| 89 | /** | ||
| 90 | * Returns the name of the passed log level as a C-string. | ||
| 91 | */ | ||
| 92 | static const char* GetLevelName(Level log_level); | ||
| 93 | |||
| 94 | /** | ||
| 95 | * Appends a messages to the log buffer. | ||
| 96 | * @note This function is thread safe. | ||
| 97 | */ | ||
| 98 | void LogMessage(Entry entry); | ||
| 99 | |||
| 100 | /** | ||
| 101 | * Retrieves a batch of messages from the log buffer, blocking until they are available. | ||
| 102 | * @note This function is thread safe. | ||
| 103 | * | ||
| 104 | * @param out_buffer Destination buffer that will receive the log entries. | ||
| 105 | * @param buffer_len The maximum size of `out_buffer`. | ||
| 106 | * @return The number of entries stored. In case the logger is shutting down, `QUEUE_CLOSED` is | ||
| 107 | * returned, no entries are stored and the logger should shutdown. | ||
| 108 | */ | ||
| 109 | size_t GetEntries(Entry* out_buffer, size_t buffer_len); | ||
| 110 | |||
| 111 | /** | ||
| 112 | * Initiates a shutdown of the logger. This will indicate to log output clients that they | ||
| 113 | * should shutdown. | ||
| 114 | */ | ||
| 115 | void Close() { ring_buffer.Close(); } | ||
| 116 | |||
| 117 | /** | ||
| 118 | * Returns true if Close() has already been called on the Logger. | ||
| 119 | */ | ||
| 120 | bool IsClosed() const { return ring_buffer.IsClosed(); } | ||
| 121 | |||
| 122 | private: | ||
| 123 | Buffer ring_buffer; | ||
| 124 | std::vector<ClassInfo> all_classes; | ||
| 125 | }; | ||
| 126 | |||
| 127 | /// Creates a log entry by formatting the given source location, and message. | ||
| 128 | Entry CreateEntry(Class log_class, Level log_level, | ||
| 129 | const char* filename, unsigned int line_nr, const char* function, | ||
| 130 | const char* format, va_list args); | ||
| 131 | /// Initializes the default Logger. | ||
| 132 | std::shared_ptr<Logger> InitGlobalLogger(); | ||
| 133 | |||
| 134 | } | ||
diff --git a/src/common/logging/log.h b/src/common/logging/log.h new file mode 100644 index 000000000..1eec34fbb --- /dev/null +++ b/src/common/logging/log.h | |||
| @@ -0,0 +1,115 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2+ | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <cassert> | ||
| 8 | #include <chrono> | ||
| 9 | #include <string> | ||
| 10 | |||
| 11 | #include "common/common_types.h" | ||
| 12 | |||
| 13 | namespace Log { | ||
| 14 | |||
| 15 | /// Specifies the severity or level of detail of the log message. | ||
| 16 | enum class Level : u8 { | ||
| 17 | Trace, ///< Extremely detailed and repetitive debugging information that is likely to | ||
| 18 | /// pollute logs. | ||
| 19 | Debug, ///< Less detailed debugging information. | ||
| 20 | Info, ///< Status information from important points during execution. | ||
| 21 | Warning, ///< Minor or potential problems found during execution of a task. | ||
| 22 | Error, ///< Major problems found during execution of a task that prevent it from being | ||
| 23 | /// completed. | ||
| 24 | Critical, ///< Major problems during execution that threathen the stability of the entire | ||
| 25 | /// application. | ||
| 26 | |||
| 27 | Count ///< Total number of logging levels | ||
| 28 | }; | ||
| 29 | |||
| 30 | typedef u8 ClassType; | ||
| 31 | |||
| 32 | /** | ||
| 33 | * Specifies the sub-system that generated the log message. | ||
| 34 | * | ||
| 35 | * @note If you add a new entry here, also add a corresponding one to `ALL_LOG_CLASSES` in log.cpp. | ||
| 36 | */ | ||
| 37 | enum class Class : ClassType { | ||
| 38 | Log, ///< Messages about the log system itself | ||
| 39 | Common, ///< Library routines | ||
| 40 | Common_Filesystem, ///< Filesystem interface library | ||
| 41 | Common_Memory, ///< Memory mapping and management functions | ||
| 42 | Core, ///< LLE emulation core | ||
| 43 | Core_ARM11, ///< ARM11 CPU core | ||
| 44 | Config, ///< Emulator configuration (including commandline) | ||
| 45 | Debug, ///< Debugging tools | ||
| 46 | Debug_Emulated, ///< Debug messages from the emulated programs | ||
| 47 | Debug_GPU, ///< GPU debugging tools | ||
| 48 | Debug_Breakpoint, ///< Logging breakpoints and watchpoints | ||
| 49 | Kernel, ///< The HLE implementation of the CTR kernel | ||
| 50 | Kernel_SVC, ///< Kernel system calls | ||
| 51 | Service, ///< HLE implementation of system services. Each major service | ||
| 52 | /// should have its own subclass. | ||
| 53 | Service_SRV, ///< The SRV (Service Directory) implementation | ||
| 54 | Service_FS, ///< The FS (Filesystem) service implementation | ||
| 55 | Service_APT, ///< The APT (Applets) service | ||
| 56 | Service_GSP, ///< The GSP (GPU control) service | ||
| 57 | Service_AC, ///< The AC (WiFi status) service | ||
| 58 | Service_PTM, ///< The PTM (Power status & misc.) service | ||
| 59 | Service_CFG, ///< The CFG (Configuration) service | ||
| 60 | Service_DSP, ///< The DSP (DSP control) service | ||
| 61 | Service_HID, ///< The HID (User input) service | ||
| 62 | HW, ///< Low-level hardware emulation | ||
| 63 | HW_Memory, ///< Memory-map and address translation | ||
| 64 | HW_GPU, ///< GPU control emulation | ||
| 65 | Frontend, ///< Emulator UI | ||
| 66 | Render, ///< Emulator video output and hardware acceleration | ||
| 67 | Render_Software, ///< Software renderer backend | ||
| 68 | Render_OpenGL, ///< OpenGL backend | ||
| 69 | Loader, ///< ROM loader | ||
| 70 | |||
| 71 | Count ///< Total number of logging classes | ||
| 72 | }; | ||
| 73 | |||
| 74 | /** | ||
| 75 | * Level below which messages are simply discarded without buffering regardless of the display | ||
| 76 | * settings. | ||
| 77 | */ | ||
| 78 | const Level MINIMUM_LEVEL = | ||
| 79 | #ifdef _DEBUG | ||
| 80 | Level::Trace; | ||
| 81 | #else | ||
| 82 | Level::Debug; | ||
| 83 | #endif | ||
| 84 | |||
| 85 | /** | ||
| 86 | * Logs a message to the global logger. This proxy exists to avoid exposing the details of the | ||
| 87 | * Logger class, including the ConcurrentRingBuffer template, to all files that desire to log | ||
| 88 | * messages, reducing unecessary recompilations. | ||
| 89 | */ | ||
| 90 | void LogMessage(Class log_class, Level log_level, | ||
| 91 | const char* filename, unsigned int line_nr, const char* function, | ||
| 92 | #ifdef _MSC_VER | ||
| 93 | _Printf_format_string_ | ||
| 94 | #endif | ||
| 95 | const char* format, ...) | ||
| 96 | #ifdef __GNUC__ | ||
| 97 | __attribute__((format(printf, 6, 7))) | ||
| 98 | #endif | ||
| 99 | ; | ||
| 100 | |||
| 101 | } // namespace Log | ||
| 102 | |||
| 103 | #define LOG_GENERIC(log_class, log_level, ...) \ | ||
| 104 | do { \ | ||
| 105 | if (::Log::Level::log_level >= ::Log::MINIMUM_LEVEL) \ | ||
| 106 | ::Log::LogMessage(::Log::Class::log_class, ::Log::Level::log_level, \ | ||
| 107 | __FILE__, __LINE__, __func__, __VA_ARGS__); \ | ||
| 108 | } while (0) | ||
| 109 | |||
| 110 | #define LOG_TRACE( log_class, ...) LOG_GENERIC(log_class, Trace, __VA_ARGS__) | ||
| 111 | #define LOG_DEBUG( log_class, ...) LOG_GENERIC(log_class, Debug, __VA_ARGS__) | ||
| 112 | #define LOG_INFO( log_class, ...) LOG_GENERIC(log_class, Info, __VA_ARGS__) | ||
| 113 | #define LOG_WARNING( log_class, ...) LOG_GENERIC(log_class, Warning, __VA_ARGS__) | ||
| 114 | #define LOG_ERROR( log_class, ...) LOG_GENERIC(log_class, Error, __VA_ARGS__) | ||
| 115 | #define LOG_CRITICAL(log_class, ...) LOG_GENERIC(log_class, Critical, __VA_ARGS__) | ||
diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp new file mode 100644 index 000000000..01c355bb6 --- /dev/null +++ b/src/common/logging/text_formatter.cpp | |||
| @@ -0,0 +1,47 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2+ | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <array> | ||
| 6 | #include <cstdio> | ||
| 7 | |||
| 8 | #include "common/logging/backend.h" | ||
| 9 | #include "common/logging/log.h" | ||
| 10 | #include "common/logging/text_formatter.h" | ||
| 11 | |||
| 12 | namespace Log { | ||
| 13 | |||
| 14 | void FormatLogMessage(const Entry& entry, char* out_text, size_t text_len) { | ||
| 15 | unsigned int time_seconds = static_cast<unsigned int>(entry.timestamp.count() / 1000000); | ||
| 16 | unsigned int time_fractional = static_cast<unsigned int>(entry.timestamp.count() % 1000000); | ||
| 17 | |||
| 18 | const char* class_name = Logger::GetLogClassName(entry.log_class); | ||
| 19 | const char* level_name = Logger::GetLevelName(entry.log_level); | ||
| 20 | |||
| 21 | snprintf(out_text, text_len, "[%4u.%06u] %s <%s> %s: %s", | ||
| 22 | time_seconds, time_fractional, class_name, level_name, | ||
| 23 | entry.location.c_str(), entry.message.c_str()); | ||
| 24 | } | ||
| 25 | |||
| 26 | void PrintMessage(const Entry& entry) { | ||
| 27 | std::array<char, 4 * 1024> format_buffer; | ||
| 28 | FormatLogMessage(entry, format_buffer.data(), format_buffer.size()); | ||
| 29 | fputs(format_buffer.data(), stderr); | ||
| 30 | fputc('\n', stderr); | ||
| 31 | } | ||
| 32 | |||
| 33 | void TextLoggingLoop(std::shared_ptr<Logger> logger) { | ||
| 34 | std::array<Entry, 256> entry_buffer; | ||
| 35 | |||
| 36 | while (true) { | ||
| 37 | size_t num_entries = logger->GetEntries(entry_buffer.data(), entry_buffer.size()); | ||
| 38 | if (num_entries == Logger::QUEUE_CLOSED) { | ||
| 39 | break; | ||
| 40 | } | ||
| 41 | for (size_t i = 0; i < num_entries; ++i) { | ||
| 42 | PrintMessage(entry_buffer[i]); | ||
| 43 | } | ||
| 44 | } | ||
| 45 | } | ||
| 46 | |||
| 47 | } | ||
diff --git a/src/common/logging/text_formatter.h b/src/common/logging/text_formatter.h new file mode 100644 index 000000000..6c2a6f1ea --- /dev/null +++ b/src/common/logging/text_formatter.h | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2+ | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <cstddef> | ||
| 8 | #include <memory> | ||
| 9 | |||
| 10 | namespace Log { | ||
| 11 | |||
| 12 | class Logger; | ||
| 13 | struct Entry; | ||
| 14 | |||
| 15 | /// Formats a log entry into the provided text buffer. | ||
| 16 | void FormatLogMessage(const Entry& entry, char* out_text, size_t text_len); | ||
| 17 | /// Formats and prints a log entry to stderr. | ||
| 18 | void PrintMessage(const Entry& entry); | ||
| 19 | |||
| 20 | /** | ||
| 21 | * Logging loop that repeatedly reads messages from the provided logger and prints them to the | ||
| 22 | * console. It is the baseline barebones log outputter. | ||
| 23 | */ | ||
| 24 | void TextLoggingLoop(std::shared_ptr<Logger> logger); | ||
| 25 | |||
| 26 | } | ||