From 0e0a007a2503d468391004c8ea2faae305232345 Mon Sep 17 00:00:00 2001 From: Yuri Kunde Schlesner Date: Sat, 6 Dec 2014 20:00:08 -0200 Subject: Add configurable per-class log filtering --- src/common/logging/filter.cpp | 132 ++++++++++++++++++++++++++++++++++ src/common/logging/filter.h | 63 ++++++++++++++++ src/common/logging/text_formatter.cpp | 8 ++- src/common/logging/text_formatter.h | 3 +- 4 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 src/common/logging/filter.cpp create mode 100644 src/common/logging/filter.h (limited to 'src/common/logging') diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp new file mode 100644 index 00000000..0cf9b05e --- /dev/null +++ b/src/common/logging/filter.cpp @@ -0,0 +1,132 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include + +#include "common/logging/filter.h" +#include "common/logging/backend.h" +#include "common/string_util.h" + +namespace Log { + +Filter::Filter(Level default_level) { + ResetAll(default_level); +} + +void Filter::ResetAll(Level level) { + class_levels.fill(level); +} + +void Filter::SetClassLevel(Class log_class, Level level) { + class_levels[static_cast(log_class)] = level; +} + +void Filter::SetSubclassesLevel(const ClassInfo& log_class, Level level) { + const size_t log_class_i = static_cast(log_class.log_class); + + const size_t begin = log_class_i + 1; + const size_t end = begin + log_class.num_children; + for (size_t i = begin; begin < end; ++i) { + class_levels[i] = level; + } +} + +void Filter::ParseFilterString(const std::string& filter_str) { + auto clause_begin = filter_str.cbegin(); + while (clause_begin != filter_str.cend()) { + auto clause_end = std::find(clause_begin, filter_str.cend(), ' '); + + // If clause isn't empty + if (clause_end != clause_begin) { + ParseFilterRule(clause_begin, clause_end); + } + + if (clause_end != filter_str.cend()) { + // Skip over the whitespace + ++clause_end; + } + clause_begin = clause_end; + } +} + +template +static Level GetLevelByName(const It begin, const It end) { + for (u8 i = 0; i < static_cast(Level::Count); ++i) { + const char* level_name = Logger::GetLevelName(static_cast(i)); + if (Common::ComparePartialString(begin, end, level_name)) { + return static_cast(i); + } + } + return Level::Count; +} + +template +static Class GetClassByName(const It begin, const It end) { + for (ClassType i = 0; i < static_cast(Class::Count); ++i) { + const char* level_name = Logger::GetLogClassName(static_cast(i)); + if (Common::ComparePartialString(begin, end, level_name)) { + return static_cast(i); + } + } + return Class::Count; +} + +template +static InputIt find_last(InputIt begin, const InputIt end, const T& value) { + auto match = end; + while (begin != end) { + auto new_match = std::find(begin, end, value); + if (new_match != end) { + match = new_match; + ++new_match; + } + begin = new_match; + } + return match; +} + +bool Filter::ParseFilterRule(const std::string::const_iterator begin, + const std::string::const_iterator end) { + auto level_separator = std::find(begin, end, ':'); + if (level_separator == end) { + LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: %s", + std::string(begin, end).c_str()); + return false; + } + + const Level level = GetLevelByName(level_separator + 1, end); + if (level == Level::Count) { + LOG_ERROR(Log, "Unknown log level in filter: %s", std::string(begin, end).c_str()); + return false; + } + + if (Common::ComparePartialString(begin, level_separator, "*")) { + ResetAll(level); + return true; + } + + auto class_name_end = find_last(begin, level_separator, '.'); + if (class_name_end != level_separator && + !Common::ComparePartialString(class_name_end + 1, level_separator, "*")) { + class_name_end = level_separator; + } + + const Class log_class = GetClassByName(begin, class_name_end); + if (log_class == Class::Count) { + LOG_ERROR(Log, "Unknown log class in filter: %s", std::string(begin, end).c_str()); + return false; + } + + if (class_name_end == level_separator) { + SetClassLevel(log_class, level); + } + SetSubclassesLevel(log_class, level); + return true; +} + +bool Filter::CheckMessage(Class log_class, Level level) const { + return static_cast(level) >= static_cast(class_levels[static_cast(log_class)]); +} + +} diff --git a/src/common/logging/filter.h b/src/common/logging/filter.h new file mode 100644 index 00000000..32b14b15 --- /dev/null +++ b/src/common/logging/filter.h @@ -0,0 +1,63 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include +#include + +#include "common/logging/log.h" + +namespace Log { + +struct ClassInfo; + +/** + * Implements a log message filter which allows different log classes to have different minimum + * severity levels. The filter can be changed at runtime and can be parsed from a string to allow + * editing via the interface or loading from a configuration file. + */ +class Filter { +public: + /// Initializes the filter with all classes having `default_level` as the minimum level. + Filter(Level default_level); + + /// Resets the filter so that all classes have `level` as the minimum displayed level. + void ResetAll(Level level); + /// Sets the minimum level of `log_class` (and not of its subclasses) to `level`. + void SetClassLevel(Class log_class, Level level); + /** + * Sets the minimum level of all of `log_class` subclasses to `level`. The level of `log_class` + * itself is not changed. + */ + void SetSubclassesLevel(const ClassInfo& log_class, Level level); + + /** + * Parses a filter string and applies it to this filter. + * + * A filter string consists of a space-separated list of filter rules, each of the format + * `:`. `` is a log class name, with subclasses separated using periods. + * A rule for a given class also affects all of its subclasses. `*` wildcards are allowed and + * can be used to apply a rule to all classes or to all subclasses of a class without affecting + * the parent class. `` a severity level name which will be set as the minimum logging + * level of the matched classes. Rules are applied left to right, with each rule overriding + * previous ones in the sequence. + * + * A few examples of filter rules: + * - `*:Info` -- Resets the level of all classes to Info. + * - `Service:Info` -- Sets the level of Service and all subclasses (Service.FS, Service.APT, + * etc.) to Info. + * - `Service.*:Debug` -- Sets the level of all Service subclasses to Debug, while leaving the + * level of Service unchanged. + * - `Service.FS:Trace` -- Sets the level of the Service.FS class to Trace. + */ + void ParseFilterString(const std::string& filter_str); + bool ParseFilterRule(const std::string::const_iterator start, const std::string::const_iterator end); + + /// Matches class/level combination against the filter, returning true if it passed. + bool CheckMessage(Class log_class, Level level) const; + +private: + std::array class_levels; +}; + +} diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp index 88deb150..3fe43534 100644 --- a/src/common/logging/text_formatter.cpp +++ b/src/common/logging/text_formatter.cpp @@ -11,6 +11,7 @@ #endif #include "common/logging/backend.h" +#include "common/logging/filter.h" #include "common/logging/log.h" #include "common/logging/text_formatter.h" @@ -105,7 +106,7 @@ void PrintMessage(const Entry& entry) { fputc('\n', stderr); } -void TextLoggingLoop(std::shared_ptr logger) { +void TextLoggingLoop(std::shared_ptr logger, const Filter* filter) { std::array entry_buffer; while (true) { @@ -114,7 +115,10 @@ void TextLoggingLoop(std::shared_ptr logger) { break; } for (size_t i = 0; i < num_entries; ++i) { - PrintMessage(entry_buffer[i]); + const Entry& entry = entry_buffer[i]; + if (filter->CheckMessage(entry.log_class, entry.log_level)) { + PrintMessage(entry); + } } } } diff --git a/src/common/logging/text_formatter.h b/src/common/logging/text_formatter.h index 04164600..d7e298e2 100644 --- a/src/common/logging/text_formatter.h +++ b/src/common/logging/text_formatter.h @@ -11,6 +11,7 @@ namespace Log { class Logger; struct Entry; +class Filter; /** * 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); * Logging loop that repeatedly reads messages from the provided logger and prints them to the * console. It is the baseline barebones log outputter. */ -void TextLoggingLoop(std::shared_ptr logger); +void TextLoggingLoop(std::shared_ptr logger, const Filter* filter); } -- cgit v1.2.3