diff options
| author | 2014-12-06 20:00:08 -0200 | |
|---|---|---|
| committer | 2014-12-13 02:08:06 -0200 | |
| commit | 0e0a007a2503d468391004c8ea2faae305232345 (patch) | |
| tree | 75feea527bd46aa4c534b77b560c89d538757f7f /src/common/logging | |
| parent | Convert old logging calls to new logging macros (diff) | |
| download | yuzu-0e0a007a2503d468391004c8ea2faae305232345.tar.gz yuzu-0e0a007a2503d468391004c8ea2faae305232345.tar.xz yuzu-0e0a007a2503d468391004c8ea2faae305232345.zip | |
Add configurable per-class log filtering
Diffstat (limited to 'src/common/logging')
| -rw-r--r-- | src/common/logging/filter.cpp | 132 | ||||
| -rw-r--r-- | src/common/logging/filter.h | 63 | ||||
| -rw-r--r-- | src/common/logging/text_formatter.cpp | 8 | ||||
| -rw-r--r-- | src/common/logging/text_formatter.h | 3 |
4 files changed, 203 insertions, 3 deletions
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/text_formatter.cpp b/src/common/logging/text_formatter.cpp index 88deb150e..3fe435346 100644 --- a/src/common/logging/text_formatter.cpp +++ b/src/common/logging/text_formatter.cpp | |||
| @@ -11,6 +11,7 @@ | |||
| 11 | #endif | 11 | #endif |
| 12 | 12 | ||
| 13 | #include "common/logging/backend.h" | 13 | #include "common/logging/backend.h" |
| 14 | #include "common/logging/filter.h" | ||
| 14 | #include "common/logging/log.h" | 15 | #include "common/logging/log.h" |
| 15 | #include "common/logging/text_formatter.h" | 16 | #include "common/logging/text_formatter.h" |
| 16 | 17 | ||
| @@ -105,7 +106,7 @@ void PrintMessage(const Entry& entry) { | |||
| 105 | fputc('\n', stderr); | 106 | fputc('\n', stderr); |
| 106 | } | 107 | } |
| 107 | 108 | ||
| 108 | void TextLoggingLoop(std::shared_ptr<Logger> logger) { | 109 | void TextLoggingLoop(std::shared_ptr<Logger> logger, const Filter* filter) { |
| 109 | std::array<Entry, 256> entry_buffer; | 110 | std::array<Entry, 256> entry_buffer; |
| 110 | 111 | ||
| 111 | while (true) { | 112 | while (true) { |
| @@ -114,7 +115,10 @@ void TextLoggingLoop(std::shared_ptr<Logger> logger) { | |||
| 114 | break; | 115 | break; |
| 115 | } | 116 | } |
| 116 | for (size_t i = 0; i < num_entries; ++i) { | 117 | for (size_t i = 0; i < num_entries; ++i) { |
| 117 | PrintMessage(entry_buffer[i]); | 118 | const Entry& entry = entry_buffer[i]; |
| 119 | if (filter->CheckMessage(entry.log_class, entry.log_level)) { | ||
| 120 | PrintMessage(entry); | ||
| 121 | } | ||
| 118 | } | 122 | } |
| 119 | } | 123 | } |
| 120 | } | 124 | } |
diff --git a/src/common/logging/text_formatter.h b/src/common/logging/text_formatter.h index 04164600f..d7e298e28 100644 --- a/src/common/logging/text_formatter.h +++ b/src/common/logging/text_formatter.h | |||
| @@ -11,6 +11,7 @@ namespace Log { | |||
| 11 | 11 | ||
| 12 | class Logger; | 12 | class Logger; |
| 13 | struct Entry; | 13 | struct Entry; |
| 14 | class Filter; | ||
| 14 | 15 | ||
| 15 | /** | 16 | /** |
| 16 | * Attempts to trim an arbitrary prefix from `path`, leaving only the part starting at `root`. It's | 17 | * Attempts to trim an arbitrary prefix from `path`, leaving only the part starting at `root`. It's |
| @@ -33,6 +34,6 @@ void PrintMessage(const Entry& entry); | |||
| 33 | * Logging loop that repeatedly reads messages from the provided logger and prints them to the | 34 | * Logging loop that repeatedly reads messages from the provided logger and prints them to the |
| 34 | * console. It is the baseline barebones log outputter. | 35 | * console. It is the baseline barebones log outputter. |
| 35 | */ | 36 | */ |
| 36 | void TextLoggingLoop(std::shared_ptr<Logger> logger); | 37 | void TextLoggingLoop(std::shared_ptr<Logger> logger, const Filter* filter); |
| 37 | 38 | ||
| 38 | } | 39 | } |