diff options
Diffstat (limited to 'src/common/logging')
| -rw-r--r-- | src/common/logging/backend.cpp | 151 | ||||
| -rw-r--r-- | src/common/logging/backend.h | 134 | ||||
| -rw-r--r-- | src/common/logging/filter.cpp | 132 | ||||
| -rw-r--r-- | src/common/logging/filter.h | 63 | ||||
| -rw-r--r-- | src/common/logging/log.h | 115 | ||||
| -rw-r--r-- | src/common/logging/text_formatter.cpp | 126 | ||||
| -rw-r--r-- | src/common/logging/text_formatter.h | 39 |
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 | |||
| 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/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 | |||
| 11 | namespace Log { | ||
| 12 | |||
| 13 | Filter::Filter(Level default_level) { | ||
| 14 | ResetAll(default_level); | ||
| 15 | } | ||
| 16 | |||
| 17 | void Filter::ResetAll(Level level) { | ||
| 18 | class_levels.fill(level); | ||
| 19 | } | ||
| 20 | |||
| 21 | void Filter::SetClassLevel(Class log_class, Level level) { | ||
| 22 | class_levels[static_cast<size_t>(log_class)] = level; | ||
| 23 | } | ||
| 24 | |||
| 25 | void 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 | |||
| 35 | void 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 | |||
| 53 | template <typename It> | ||
| 54 | static 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 | |||
| 64 | template <typename It> | ||
| 65 | static 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 | |||
| 75 | template <typename InputIt, typename T> | ||
| 76 | static 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 | |||
| 89 | bool 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 | |||
| 128 | bool 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 | |||
| 10 | namespace Log { | ||
| 11 | |||
| 12 | struct 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 | */ | ||
| 19 | class Filter { | ||
| 20 | public: | ||
| 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 | |||
| 59 | private: | ||
| 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 | |||
| 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..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 | |||
| 20 | namespace Log { | ||
| 21 | |||
| 22 | // TODO(bunnei): This should be moved to a generic path manipulation library | ||
| 23 | const 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 | |||
| 45 | void 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 | |||
| 57 | static 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 | |||
| 101 | void 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 | |||
| 109 | void 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 | |||
| 10 | namespace Log { | ||
| 11 | |||
| 12 | class Logger; | ||
| 13 | struct Entry; | ||
| 14 | class 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 | */ | ||
| 26 | const char* TrimSourcePath(const char* path, const char* root = "src"); | ||
| 27 | |||
| 28 | /// Formats a log entry into the provided text buffer. | ||
| 29 | void FormatLogMessage(const Entry& entry, char* out_text, size_t text_len); | ||
| 30 | /// Formats and prints a log entry to stderr. | ||
| 31 | void 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 | */ | ||
| 37 | void TextLoggingLoop(std::shared_ptr<Logger> logger, const Filter* filter); | ||
| 38 | |||
| 39 | } | ||