diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index a11969268..d16e7570a 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -39,6 +39,8 @@ Added to focus the previous/next category in the completion (bound to `` and `` by default). - New `:click-element` command to fake a click on a element. +- New `:debug-log-filter` command to change console log filtering on-the-fly. +- New `:debug-log-level` command to change the console loglevel on-the-fly. Changed ~~~~~~~ diff --git a/README.asciidoc b/README.asciidoc index ebcc58316..6af28d775 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -199,6 +199,7 @@ Contributors, sorted by the number of commits in descending order: * Brian Jackson * sbinix * neeasade +* knaggita * jnphilipp * Tobias Patzl * Stefan Tatschner @@ -224,7 +225,6 @@ Contributors, sorted by the number of commits in descending order: * zwarag * xd1le * oniondreams -* knaggita * issue * haxwithaxe * evan diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index e73d6cf80..eb76fa84f 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -1449,6 +1449,8 @@ These commands are mainly intended for debugging. They are hidden if qutebrowser |<>|Crash for debugging purposes. |<>|Dump the current page's content to a file. |<>|Change the number of log lines to be stored in RAM. +|<>|Change the log filter for console logging. +|<>|Change the log level for console logging. |<>|Evaluate a python string and display the results as a web page. |<>|Put data into the fake clipboard and enable logging, used for tests. |<>|Trace executed code via hunter. @@ -1500,6 +1502,24 @@ Change the number of log lines to be stored in RAM. ==== positional arguments * +'capacity'+: Number of lines for the log. +[[debug-log-filter]] +=== debug-log-filter +Syntax: +:debug-log-filter 'filters'+ + +Change the log filter for console logging. + +==== positional arguments +* +'filters'+: A comma separated list of logger names. + +[[debug-log-level]] +=== debug-log-level +Syntax: +:debug-log-level 'level'+ + +Change the log level for console logging. + +==== positional arguments +* +'level'+: The log level to set. + [[debug-pyeval]] === debug-pyeval Syntax: +:debug-pyeval [*--quiet*] 's'+ diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index e99ad45fb..4d9ebb4a3 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -22,6 +22,7 @@ import functools import types import traceback +import logging try: import hunter @@ -249,6 +250,34 @@ def log_capacity(capacity: int): log.ram_handler.change_log_capacity(capacity) +@cmdutils.register(debug=True) +@cmdutils.argument('level', choices=sorted( + (level.lower() for level in log.LOG_LEVELS), + key=lambda e: log.LOG_LEVELS[e.upper()])) +def debug_log_level(level: str): + """Change the log level for console logging. + + Args: + level: The log level to set. + """ + log.console_handler.setLevel(log.LOG_LEVELS[level.upper()]) + + +@cmdutils.register(debug=True) +def debug_log_filter(filters: str): + """Change the log filter for console logging. + + Args: + filters: A comma separated list of logger names. + """ + if set(filters.split(',')).issubset(log.LOGGER_NAMES): + log.console_filter.names = filters.split(',') + else: + raise cmdexc.CommandError("filters: Invalid value {} - expected one " + "of: {}".format(filters, + ', '.join(log.LOGGER_NAMES))) + + @cmdutils.register() @cmdutils.argument('current_win_id', win_id=True) def window_only(current_win_id): diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 177c263f8..1ee91bad5 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -87,6 +87,15 @@ LOG_LEVELS = { 'CRITICAL': logging.CRITICAL, } +LOGGER_NAMES = [ + 'statusbar', 'completion', 'init', 'url', + 'destroy', 'modes', 'webview', 'misc', + 'mouse', 'procs', 'hints', 'keyboard', + 'commands', 'signals', 'downloads', + 'js', 'qt', 'rfc6266', 'ipc', 'shlexer', + 'save', 'message', 'config', 'sessions' +] + def vdebug(self, msg, *args, **kwargs): """Log with a VDEBUG level. @@ -131,6 +140,8 @@ sessions = logging.getLogger('sessions') ram_handler = None +console_handler = None +console_filter = None def stub(suffix=''): @@ -149,6 +160,7 @@ class CriticalQtWarning(Exception): def init_log(args): """Init loggers based on the argparse namespace passed.""" + global console level = args.loglevel.upper() try: numeric_level = getattr(logging, level) @@ -161,9 +173,11 @@ def init_log(args): console, ram = _init_handlers(numeric_level, args.color, args.force_color, args.json_logging, args.loglines) root = logging.getLogger() + global console_filter if console is not None: if args.logfilter is not None: - console.addFilter(LogFilter(args.logfilter.split(','))) + console_filter = LogFilter(args.logfilter.split(',')) + console.addFilter(console_filter) root.addHandler(console) if ram is not None: root.addHandler(ram) @@ -175,6 +189,10 @@ def init_log(args): _log_inited = True +def change(filters): + console.addFilter(LogFilter(filters.split(','))) + + def _init_py_warnings(): """Initialize Python warning handling.""" warnings.simplefilter('default') @@ -210,6 +228,7 @@ def _init_handlers(level, color, force_color, json_logging, ram_capacity): json_logging: Output log lines in JSON (this disables all colors). """ global ram_handler + global console_handler console_fmt, ram_fmt, html_fmt, use_colorama = _init_formatters( level, color, force_color, json_logging) @@ -448,16 +467,16 @@ class LogFilter(logging.Filter): def __init__(self, names): super().__init__() - self._names = names + self.names = names def filter(self, record): """Determine if the specified record is to be logged.""" - if self._names is None: + if self.names is None: return True if record.levelno > logging.DEBUG: # More important than DEBUG, so we won't filter at all return True - for name in self._names: + for name in self.names: if record.name == name: return True elif not record.name.startswith(name): diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index f8414999e..39f44e31e 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -471,10 +471,26 @@ Feature: Various utility commands. Then the page should contain the plaintext "newstuff" And the page should not contain the plaintext "oldstuff" + Scenario: Using :debug-log-capacity with negative capacity + When I run :debug-log-capacity -1 + Then the error "Can't set a negative log capacity!" should be shown + + # :debug-log-level / :debug-log-filter + # Other :debug-log-{level,filter} features are tested in + # unit/utils/test_log.py as using them would break end2end tests. + + Scenario: Using debug-log-level with invalid level + When I run :debug-log-level hello + Then the error "level: Invalid value hello - expected one of: vdebug, debug, info, warning, error, critical" should be shown + + Scenario: Using debug-log-filter with invalid filter + When I run :debug-log-filter blah + Then the error "filters: Invalid value blah - expected one of: statusbar, *" should be shown + ## https://github.com/The-Compiler/qutebrowser/issues/1523 Scenario: Completing a single option argument - When I run :set-cmd-text -s :-- + When I run :set-cmd-text -s :-- Then no crash should happen ## https://github.com/The-Compiler/qutebrowser/issues/1386 diff --git a/tests/unit/utils/test_log.py b/tests/unit/utils/test_log.py index 4bf12c526..c386b9694 100644 --- a/tests/unit/utils/test_log.py +++ b/tests/unit/utils/test_log.py @@ -29,6 +29,7 @@ import pytest import pytest_catchlog from qutebrowser.utils import log +from qutebrowser.misc import utilcmds @pytest.yield_fixture(autouse=True) @@ -167,6 +168,19 @@ class TestLogFilter: record = self._make_record(logger, "bacon", level=logging.INFO) assert logfilter.filter(record) + @pytest.mark.parametrize('category, logged_before, logged_after', [ + ('init', True, False), ('url', False, True), ('js', False, True)]) + def test_debug_log_filter_cmd(self, monkeypatch, logger, category, + logged_before, logged_after): + logfilter = log.LogFilter(["init"]) + monkeypatch.setattr(log, 'console_filter', logfilter) + + record = self._make_record(logger, category) + + assert logfilter.filter(record) == logged_before + utilcmds.debug_log_filter('url,js') + assert logfilter.filter(record) == logged_after + class TestRAMHandler: