summaryrefslogtreecommitdiff
path: root/src/common/logging
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/logging')
-rw-r--r--src/common/logging/backend.cpp151
-rw-r--r--src/common/logging/backend.h134
-rw-r--r--src/common/logging/filter.cpp132
-rw-r--r--src/common/logging/filter.h63
-rw-r--r--src/common/logging/log.h115
-rw-r--r--src/common/logging/text_formatter.cpp126
-rw-r--r--src/common/logging/text_formatter.h39
7 files changed, 760 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
13namespace Log {
14
15static 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
51Logger::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...
72const 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
83const 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
97void Logger::LogMessage(Entry entry) {
98 ring_buffer.Push(std::move(entry));
99}
100
101size_t Logger::GetEntries(Entry* out_buffer, size_t buffer_len) {
102 return ring_buffer.BlockingPop(out_buffer, buffer_len);
103}
104
105std::shared_ptr<Logger> InitGlobalLogger() {
106 global_logger = std::make_shared<Logger>();
107 return global_logger;
108}
109
110Entry 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
134void 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
15namespace 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 */
21struct 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
50struct 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 */
67class Logger {
68private:
69 using Buffer = Common::ConcurrentRingBuffer<Entry, 16 * 1024 / sizeof(Entry)>;
70
71public:
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
122private:
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.
128Entry 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.
132std::shared_ptr<Logger> InitGlobalLogger();
133
134}
diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp
new file mode 100644
index 000000000..0cf9b05e7
--- /dev/null
+++ b/src/common/logging/filter.cpp
@@ -0,0 +1,132 @@
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/logging/filter.h"
8#include "common/logging/backend.h"
9#include "common/string_util.h"
10
11namespace Log {
12
13Filter::Filter(Level default_level) {
14 ResetAll(default_level);
15}
16
17void Filter::ResetAll(Level level) {
18 class_levels.fill(level);
19}
20
21void Filter::SetClassLevel(Class log_class, Level level) {
22 class_levels[static_cast<size_t>(log_class)] = level;
23}
24
25void Filter::SetSubclassesLevel(const ClassInfo& log_class, Level level) {
26 const size_t log_class_i = static_cast<size_t>(log_class.log_class);
27
28 const size_t begin = log_class_i + 1;
29 const size_t end = begin + log_class.num_children;
30 for (size_t i = begin; begin < end; ++i) {
31 class_levels[i] = level;
32 }
33}
34
35void Filter::ParseFilterString(const std::string& filter_str) {
36 auto clause_begin = filter_str.cbegin();
37 while (clause_begin != filter_str.cend()) {
38 auto clause_end = std::find(clause_begin, filter_str.cend(), ' ');
39
40 // If clause isn't empty
41 if (clause_end != clause_begin) {
42 ParseFilterRule(clause_begin, clause_end);
43 }
44
45 if (clause_end != filter_str.cend()) {
46 // Skip over the whitespace
47 ++clause_end;
48 }
49 clause_begin = clause_end;
50 }
51}
52
53template <typename It>
54static Level GetLevelByName(const It begin, const It end) {
55 for (u8 i = 0; i < static_cast<u8>(Level::Count); ++i) {
56 const char* level_name = Logger::GetLevelName(static_cast<Level>(i));
57 if (Common::ComparePartialString(begin, end, level_name)) {
58 return static_cast<Level>(i);
59 }
60 }
61 return Level::Count;
62}
63
64template <typename It>
65static Class GetClassByName(const It begin, const It end) {
66 for (ClassType i = 0; i < static_cast<ClassType>(Class::Count); ++i) {
67 const char* level_name = Logger::GetLogClassName(static_cast<Class>(i));
68 if (Common::ComparePartialString(begin, end, level_name)) {
69 return static_cast<Class>(i);
70 }
71 }
72 return Class::Count;
73}
74
75template <typename InputIt, typename T>
76static InputIt find_last(InputIt begin, const InputIt end, const T& value) {
77 auto match = end;
78 while (begin != end) {
79 auto new_match = std::find(begin, end, value);
80 if (new_match != end) {
81 match = new_match;
82 ++new_match;
83 }
84 begin = new_match;
85 }
86 return match;
87}
88
89bool Filter::ParseFilterRule(const std::string::const_iterator begin,
90 const std::string::const_iterator end) {
91 auto level_separator = std::find(begin, end, ':');
92 if (level_separator == end) {
93 LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: %s",
94 std::string(begin, end).c_str());
95 return false;
96 }
97
98 const Level level = GetLevelByName(level_separator + 1, end);
99 if (level == Level::Count) {
100 LOG_ERROR(Log, "Unknown log level in filter: %s", std::string(begin, end).c_str());
101 return false;
102 }
103
104 if (Common::ComparePartialString(begin, level_separator, "*")) {
105 ResetAll(level);
106 return true;
107 }
108
109 auto class_name_end = find_last(begin, level_separator, '.');
110 if (class_name_end != level_separator &&
111 !Common::ComparePartialString(class_name_end + 1, level_separator, "*")) {
112 class_name_end = level_separator;
113 }
114
115 const Class log_class = GetClassByName(begin, class_name_end);
116 if (log_class == Class::Count) {
117 LOG_ERROR(Log, "Unknown log class in filter: %s", std::string(begin, end).c_str());
118 return false;
119 }
120
121 if (class_name_end == level_separator) {
122 SetClassLevel(log_class, level);
123 }
124 SetSubclassesLevel(log_class, level);
125 return true;
126}
127
128bool Filter::CheckMessage(Class log_class, Level level) const {
129 return static_cast<u8>(level) >= static_cast<u8>(class_levels[static_cast<size_t>(log_class)]);
130}
131
132}
diff --git a/src/common/logging/filter.h b/src/common/logging/filter.h
new file mode 100644
index 000000000..32b14b159
--- /dev/null
+++ b/src/common/logging/filter.h
@@ -0,0 +1,63 @@
1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2+
3// Refer to the license.txt file included.
4
5#include <array>
6#include <string>
7
8#include "common/logging/log.h"
9
10namespace Log {
11
12struct ClassInfo;
13
14/**
15 * Implements a log message filter which allows different log classes to have different minimum
16 * severity levels. The filter can be changed at runtime and can be parsed from a string to allow
17 * editing via the interface or loading from a configuration file.
18 */
19class Filter {
20public:
21 /// Initializes the filter with all classes having `default_level` as the minimum level.
22 Filter(Level default_level);
23
24 /// Resets the filter so that all classes have `level` as the minimum displayed level.
25 void ResetAll(Level level);
26 /// Sets the minimum level of `log_class` (and not of its subclasses) to `level`.
27 void SetClassLevel(Class log_class, Level level);
28 /**
29 * Sets the minimum level of all of `log_class` subclasses to `level`. The level of `log_class`
30 * itself is not changed.
31 */
32 void SetSubclassesLevel(const ClassInfo& log_class, Level level);
33
34 /**
35 * Parses a filter string and applies it to this filter.
36 *
37 * A filter string consists of a space-separated list of filter rules, each of the format
38 * `<class>:<level>`. `<class>` is a log class name, with subclasses separated using periods.
39 * A rule for a given class also affects all of its subclasses. `*` wildcards are allowed and
40 * can be used to apply a rule to all classes or to all subclasses of a class without affecting
41 * the parent class. `<level>` a severity level name which will be set as the minimum logging
42 * level of the matched classes. Rules are applied left to right, with each rule overriding
43 * previous ones in the sequence.
44 *
45 * A few examples of filter rules:
46 * - `*:Info` -- Resets the level of all classes to Info.
47 * - `Service:Info` -- Sets the level of Service and all subclasses (Service.FS, Service.APT,
48 * etc.) to Info.
49 * - `Service.*:Debug` -- Sets the level of all Service subclasses to Debug, while leaving the
50 * level of Service unchanged.
51 * - `Service.FS:Trace` -- Sets the level of the Service.FS class to Trace.
52 */
53 void ParseFilterString(const std::string& filter_str);
54 bool ParseFilterRule(const std::string::const_iterator start, const std::string::const_iterator end);
55
56 /// Matches class/level combination against the filter, returning true if it passed.
57 bool CheckMessage(Class log_class, Level level) const;
58
59private:
60 std::array<Level, (size_t)Class::Count> class_levels;
61};
62
63}
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
13namespace Log {
14
15/// Specifies the severity or level of detail of the log message.
16enum 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
30typedef 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 */
37enum 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 */
78const 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 */
90void 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..3fe435346
--- /dev/null
+++ b/src/common/logging/text_formatter.cpp
@@ -0,0 +1,126 @@
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#ifdef _WIN32
9# define WIN32_LEAN_AND_MEAN
10# include <Windows.h>
11#endif
12
13#include "common/logging/backend.h"
14#include "common/logging/filter.h"
15#include "common/logging/log.h"
16#include "common/logging/text_formatter.h"
17
18#include "common/string_util.h"
19
20namespace Log {
21
22// TODO(bunnei): This should be moved to a generic path manipulation library
23const char* TrimSourcePath(const char* path, const char* root) {
24 const char* p = path;
25
26 while (*p != '\0') {
27 const char* next_slash = p;
28 while (*next_slash != '\0' && *next_slash != '/' && *next_slash != '\\') {
29 ++next_slash;
30 }
31
32 bool is_src = Common::ComparePartialString(p, next_slash, root);
33 p = next_slash;
34
35 if (*p != '\0') {
36 ++p;
37 }
38 if (is_src) {
39 path = p;
40 }
41 }
42 return path;
43}
44
45void FormatLogMessage(const Entry& entry, char* out_text, size_t text_len) {
46 unsigned int time_seconds = static_cast<unsigned int>(entry.timestamp.count() / 1000000);
47 unsigned int time_fractional = static_cast<unsigned int>(entry.timestamp.count() % 1000000);
48
49 const char* class_name = Logger::GetLogClassName(entry.log_class);
50 const char* level_name = Logger::GetLevelName(entry.log_level);
51
52 snprintf(out_text, text_len, "[%4u.%06u] %s <%s> %s: %s",
53 time_seconds, time_fractional, class_name, level_name,
54 TrimSourcePath(entry.location.c_str()), entry.message.c_str());
55}
56
57static void ChangeConsoleColor(Level level) {
58#ifdef _WIN32
59 static HANDLE console_handle = GetStdHandle(STD_ERROR_HANDLE);
60
61 WORD color = 0;
62 switch (level) {
63 case Level::Trace: // Grey
64 color = FOREGROUND_INTENSITY; break;
65 case Level::Debug: // Cyan
66 color = FOREGROUND_GREEN | FOREGROUND_BLUE; break;
67 case Level::Info: // Bright gray
68 color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; break;
69 case Level::Warning: // Bright yellow
70 color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; break;
71 case Level::Error: // Bright red
72 color = FOREGROUND_RED | FOREGROUND_INTENSITY; break;
73 case Level::Critical: // Bright magenta
74 color = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY; break;
75 }
76
77 SetConsoleTextAttribute(console_handle, color);
78#else
79#define ESC "\x1b"
80 const char* color = "";
81 switch (level) {
82 case Level::Trace: // Grey
83 color = ESC "[1;30m"; break;
84 case Level::Debug: // Cyan
85 color = ESC "[0;36m"; break;
86 case Level::Info: // Bright gray
87 color = ESC "[0;37m"; break;
88 case Level::Warning: // Bright yellow
89 color = ESC "[1;33m"; break;
90 case Level::Error: // Bright red
91 color = ESC "[1;31m"; break;
92 case Level::Critical: // Bright magenta
93 color = ESC "[1;35m"; break;
94 }
95#undef ESC
96
97 fputs(color, stderr);
98#endif
99}
100
101void PrintMessage(const Entry& entry) {
102 ChangeConsoleColor(entry.log_level);
103 std::array<char, 4 * 1024> format_buffer;
104 FormatLogMessage(entry, format_buffer.data(), format_buffer.size());
105 fputs(format_buffer.data(), stderr);
106 fputc('\n', stderr);
107}
108
109void TextLoggingLoop(std::shared_ptr<Logger> logger, const Filter* filter) {
110 std::array<Entry, 256> entry_buffer;
111
112 while (true) {
113 size_t num_entries = logger->GetEntries(entry_buffer.data(), entry_buffer.size());
114 if (num_entries == Logger::QUEUE_CLOSED) {
115 break;
116 }
117 for (size_t i = 0; i < num_entries; ++i) {
118 const Entry& entry = entry_buffer[i];
119 if (filter->CheckMessage(entry.log_class, entry.log_level)) {
120 PrintMessage(entry);
121 }
122 }
123 }
124}
125
126}
diff --git a/src/common/logging/text_formatter.h b/src/common/logging/text_formatter.h
new file mode 100644
index 000000000..d7e298e28
--- /dev/null
+++ b/src/common/logging/text_formatter.h
@@ -0,0 +1,39 @@
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
10namespace Log {
11
12class Logger;
13struct Entry;
14class Filter;
15
16/**
17 * Attempts to trim an arbitrary prefix from `path`, leaving only the part starting at `root`. It's
18 * intended to be used to strip a system-specific build directory from the `__FILE__` macro,
19 * leaving only the path relative to the sources root.
20 *
21 * @param path The input file path as a null-terminated string
22 * @param root The name of the root source directory as a null-terminated string. Path up to and
23 * including the last occurence of this name will be stripped
24 * @return A pointer to the same string passed as `path`, but starting at the trimmed portion
25 */
26const char* TrimSourcePath(const char* path, const char* root = "src");
27
28/// Formats a log entry into the provided text buffer.
29void FormatLogMessage(const Entry& entry, char* out_text, size_t text_len);
30/// Formats and prints a log entry to stderr.
31void PrintMessage(const Entry& entry);
32
33/**
34 * Logging loop that repeatedly reads messages from the provided logger and prints them to the
35 * console. It is the baseline barebones log outputter.
36 */
37void TextLoggingLoop(std::shared_ptr<Logger> logger, const Filter* filter);
38
39}