diff --git a/doc/contributing.asciidoc b/doc/contributing.asciidoc index 3845dfdf7..07c2dfe25 100644 --- a/doc/contributing.asciidoc +++ b/doc/contributing.asciidoc @@ -198,8 +198,8 @@ There are some useful functions for debugging in the `qutebrowser.utils.debug` module. When starting qutebrowser with the `--debug` flag, you also get useful debug -logs. You can add +--logfilter _category[,category,...]_+ to restrict logging -to the given categories. +logs. You can add +--logfilter _[!]category[,category,...]_+ to restrict +logging to the given categories. With `--debug` there are also some additional +debug-_*_+ commands available, for example `:debug-all-objects` and `:debug-all-widgets` which print a list of diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index 7a1bef736..6e2feea78 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -148,7 +148,7 @@ def logfilter_error(logfilter): Args: logfilter: A comma separated list of logger names. """ - if set(logfilter.split(',')).issubset(log.LOGGER_NAMES): + if set(logfilter.lstrip('!').split(',')).issubset(log.LOGGER_NAMES): return logfilter else: raise argparse.ArgumentTypeError( diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index d6cae1312..6f56f1876 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -182,9 +182,16 @@ def init_log(args): root = logging.getLogger() global console_filter if console is not None: - console_filter = LogFilter(None) - if args.logfilter is not None: - console_filter.names = args.logfilter.split(',') + if not args.logfilter: + negate = False + names = None + elif args.logfilter.startswith('!'): + negate = True + names = args.logfilter[1:].split(',') + else: + negate = False + names = args.logfilter.split(',') + console_filter = LogFilter(names, negate) console.addFilter(console_filter) root.addHandler(console) if ram is not None: @@ -498,12 +505,14 @@ class LogFilter(logging.Filter): comma-separated list instead. Attributes: - _names: A list of names that should be logged. + names: A list of record names to filter. + negated: Wether names is a list of records to log or to suppress. """ - def __init__(self, names): + def __init__(self, names, negate=False): super().__init__() self.names = names + self.negated = negate def filter(self, record): """Determine if the specified record is to be logged.""" @@ -514,12 +523,12 @@ class LogFilter(logging.Filter): return True for name in self.names: if record.name == name: - return True + return not self.negated elif not record.name.startswith(name): continue elif record.name[len(name)] == '.': - return True - return False + return not self.negated + return self.negated class RAMHandler(logging.Handler): diff --git a/tests/unit/utils/test_log.py b/tests/unit/utils/test_log.py index f7b954052..414a989f6 100644 --- a/tests/unit/utils/test_log.py +++ b/tests/unit/utils/test_log.py @@ -116,24 +116,36 @@ class TestLogFilter: return logger.makeRecord(name, level=level, fn=None, lno=0, msg="", args=None, exc_info=None) - @pytest.mark.parametrize('filters, category, logged', [ + @pytest.mark.parametrize('filters, negated, category, logged', [ # Filter letting all messages through - (None, 'eggs.bacon.spam', True), - (None, 'eggs', True), + (None, False, 'eggs.bacon.spam', True), + (None, False, 'eggs', True), + (None, True, 'ham', True), # Matching records - (['eggs', 'bacon'], 'eggs', True), - (['eggs', 'bacon'], 'bacon', True), - (['eggs.bacon'], 'eggs.bacon', True), + (['eggs', 'bacon'], False, 'eggs', True), + (['eggs', 'bacon'], False, 'bacon', True), + (['eggs.bacon'], False, 'eggs.bacon', True), # Non-matching records - (['eggs', 'bacon'], 'spam', False), - (['eggs'], 'eggsauce', False), - (['eggs.bacon'], 'eggs.baconstrips', False), + (['eggs', 'bacon'], False, 'spam', False), + (['eggs'], False, 'eggsauce', False), + (['eggs.bacon'], False, 'eggs.baconstrips', False), # Child loggers - (['eggs.bacon', 'spam.ham'], 'eggs.bacon.spam', True), - (['eggs.bacon', 'spam.ham'], 'spam.ham.salami', True), + (['eggs.bacon', 'spam.ham'], False, 'eggs.bacon.spam', True), + (['eggs.bacon', 'spam.ham'], False, 'spam.ham.salami', True), + # Suppressed records + (['eggs', 'bacon'], True, 'eggs', False), + (['eggs', 'bacon'], True, 'bacon', False), + (['eggs.bacon'], True, 'eggs.bacon', False), + # Non-suppressed records + (['eggs', 'bacon'], True, 'spam', True), + (['eggs'], True, 'eggsauce', True), + (['eggs.bacon'], True, 'eggs.baconstrips', True), ]) - def test_logfilter(self, logger, filters, category, logged): - logfilter = log.LogFilter(filters) + def test_logfilter(self, logger, filters, negated, category, logged): + """ + Check the multi-record filtering filterer filters multiple records. + """ + logfilter = log.LogFilter(filters, negated) record = self._make_record(logger, category) assert logfilter.filter(record) == logged @@ -204,6 +216,23 @@ class TestInitLog: log.init_log(args) sys.stderr = old_stderr + @pytest.mark.parametrize('logfilter, negated', [ + ('!one,two', True), + ('one,two', False), + ('one,!two', False), + (None, False), + ]) + def test_negation_parser(self, args, mocker, logfilter, negated): + """Test parsing the --logfilter argument.""" + filter_mock = mocker.patch('qutebrowser.utils.log.LogFilter', + autospec=True) + args.logfilter = logfilter + log.init_log(args) + expected_names = (logfilter.lstrip('!').split(',') if logfilter else + None) + assert filter_mock.called + assert filter_mock.call_args[0] == (expected_names, negated) + class TestHideQtWarning: