diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 48eca3e7a..7cb58afbd 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -29,6 +29,7 @@ Added - The `'` mark gets set when moving away (hinting link with anchor, searching, etc.) so you can move back with `''` - New `--force-color` argument to force colored logging even if stdout is not a terminal +- New `:messages` command to show error messages Changed ~~~~~~~ diff --git a/README.asciidoc b/README.asciidoc index 2b36cacb8..c23b6c86b 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -145,8 +145,8 @@ Contributors, sorted by the number of commits in descending order: * Alexander Cogneau * Felix Van der Jeugt * Martin Tournoij -* Raphael Pierzina * Ryan Roden-Corrent +* Raphael Pierzina * Joel Torstensson * Patric Schmitz * Claude diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index e9cb4571c..da912ed74 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -31,6 +31,7 @@ |<>|Evaluate a JavaScript string. |<>|Jump to the mark named by `key`. |<>|Execute a command after some time. +|<>|Show a log of past messages. |<>|Open typical prev/next links or navigate using the URL path. |<>|Open a URL in the current/[count]th tab. |<>|Open a page from the clipboard. @@ -403,6 +404,22 @@ Execute a command after some time. * This command does not split arguments after the last argument and handles quotes literally. * With this command, +;;+ is interpreted literally instead of splitting off a second command. +[[messages]] +=== messages +Syntax: +:messages [*--plain*] [*--tab*] [*--bg*] [*--window*] ['level']+ + +Show a log of past messages. + +==== positional arguments +* +'level'+: Include messages with `level` or higher severity. Valid values: vdebug, debug, info, warning, error, critical. + + +==== optional arguments +* +*-p*+, +*--plain*+: Whether to show plaintext (as opposed to html). +* +*-t*+, +*--tab*+: Open in a new tab. +* +*-b*+, +*--bg*+: Open in a background tab. +* +*-w*+, +*--window*+: Open in a new window. + [[navigate]] === navigate Syntax: +:navigate [*--tab*] [*--bg*] [*--window*] 'where'+ diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 865990693..98c3efcb7 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1339,6 +1339,27 @@ class CommandDispatcher: url = QUrl('qute://help/{}'.format(path)) self._open(url, tab, bg, window) + @cmdutils.register(instance='command-dispatcher', scope='window') + def messages(self, level='error', plain=False, tab=False, bg=False, + window=False): + """Show a log of past messages. + + Args: + level: Include messages with `level` or higher severity. + Valid values: vdebug, debug, info, warning, error, critical. + plain: Whether to show plaintext (as opposed to html). + tab: Open in a new tab. + bg: Open in a background tab. + window: Open in a new window. + """ + if level.upper() not in log.LOG_LEVELS: + raise cmdexc.CommandError("Invalid log level {}!".format(level)) + if plain: + url = QUrl('qute://plainlog?level={}'.format(level)) + else: + url = QUrl('qute://log?level={}'.format(level)) + self._open(url, tab, bg, window) + @cmdutils.register(instance='command-dispatcher', modes=[KeyMode.insert], hide=True, scope='window') def open_editor(self): diff --git a/qutebrowser/browser/network/qutescheme.py b/qutebrowser/browser/network/qutescheme.py index a608dd304..bf653ec6e 100644 --- a/qutebrowser/browser/network/qutescheme.py +++ b/qutebrowser/browser/network/qutescheme.py @@ -26,6 +26,7 @@ Module attributes: import functools import configparser import mimetypes +import urllib.parse from PyQt5.QtCore import pyqtSlot, QObject from PyQt5.QtNetwork import QNetworkReply @@ -158,23 +159,42 @@ def qute_version(_win_id, _request): @add_handler('plainlog') -def qute_plainlog(_win_id, _request): - """Handler for qute:plainlog. Return HTML content as bytes.""" +def qute_plainlog(_win_id, request): + """Handler for qute:plainlog. Return HTML content as bytes. + + An optional query parameter specifies the minimum log level to print. + For example, qute://log?level=warning prints warnings and errors. + Level can be one of: vdebug, debug, info, warning, error, critical. + """ if log.ram_handler is None: text = "Log output was disabled." else: - text = log.ram_handler.dump_log() + try: + level = urllib.parse.parse_qs(request.url().query())['level'][0] + except KeyError: + level = 'vdebug' + text = log.ram_handler.dump_log(html=False, level=level) html = jinja.render('pre.html', title='log', content=text) return html.encode('UTF-8', errors='xmlcharrefreplace') @add_handler('log') -def qute_log(_win_id, _request): - """Handler for qute:log. Return HTML content as bytes.""" +def qute_log(_win_id, request): + """Handler for qute:log. Return HTML content as bytes. + + An optional query parameter specifies the minimum log level to print. + For example, qute://log?level=warning prints warnings and errors. + Level can be one of: vdebug, debug, info, warning, error, critical. + """ if log.ram_handler is None: html_log = None else: - html_log = log.ram_handler.dump_log(html=True) + try: + level = urllib.parse.parse_qs(request.url().query())['level'][0] + except KeyError: + level = 'vdebug' + html_log = log.ram_handler.dump_log(html=True, level=level) + html = jinja.render('log.html', title='log', content=html_log) return html.encode('UTF-8', errors='xmlcharrefreplace') diff --git a/qutebrowser/html/log.html b/qutebrowser/html/log.html index bf6663ea0..4e5b28069 100644 --- a/qutebrowser/html/log.html +++ b/qutebrowser/html/log.html @@ -37,6 +37,8 @@ th, td { {{ content | safe() }}
+{% elif content is not none %} +

No messages to show.

{% else %}

Log output was disabled.

{% endif %} diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 16b197873..072b63c05 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -64,13 +64,21 @@ LOG_COLORS = { 'CRITICAL': 'red', } - # We first monkey-patch logging to support our VDEBUG level before getting the # loggers. Based on http://stackoverflow.com/a/13638084 VDEBUG_LEVEL = 9 logging.addLevelName(VDEBUG_LEVEL, 'VDEBUG') logging.VDEBUG = VDEBUG_LEVEL +LOG_LEVELS = { + 'VDEBUG': logging.VDEBUG, + 'DEBUG': logging.DEBUG, + 'INFO': logging.INFO, + 'WARNING': logging.WARNING, + 'ERROR': logging.ERROR, + 'CRITICAL': logging.CRITICAL, +} + def vdebug(self, msg, *args, **kwargs): """Log with a VDEBUG level. @@ -430,13 +438,14 @@ class RAMHandler(logging.Handler): # We don't log VDEBUG to RAM. self._data.append(record) - def dump_log(self, html=False): + def dump_log(self, html=False, level='vdebug'): """Dump the complete formatted log data as as string. FIXME: We should do all the HTML formatter via jinja2. (probably obsolete when moving to a widget for logging, https://github.com/The-Compiler/qutebrowser/issues/34 """ + minlevel = LOG_LEVELS.get(level.upper(), VDEBUG_LEVEL) lines = [] fmt = self.html_formatter.format if html else self.format self.acquire() @@ -445,7 +454,8 @@ class RAMHandler(logging.Handler): finally: self.release() for record in records: - lines.append(fmt(record)) + if record.levelno >= minlevel: + lines.append(fmt(record)) return '\n'.join(lines) diff --git a/tests/integration/features/conftest.py b/tests/integration/features/conftest.py index 060bb00dd..29f719080 100644 --- a/tests/integration/features/conftest.py +++ b/tests/integration/features/conftest.py @@ -366,6 +366,14 @@ def check_contents_plain(quteproc, text): assert text in content +@bdd.then(bdd.parsers.parse('the page should not contain the plaintext ' + '"{text}"')) +def check_not_contents_plain(quteproc, text): + """Check the current page's content based on a substring.""" + content = quteproc.get_content().strip() + assert text not in content + + @bdd.then(bdd.parsers.parse('the json on the page should be:\n{text}')) def check_contents_json(quteproc, text): """Check the current page's content as json.""" diff --git a/tests/integration/features/misc.feature b/tests/integration/features/misc.feature index 1356a3cf3..372bcf48a 100644 --- a/tests/integration/features/misc.feature +++ b/tests/integration/features/misc.feature @@ -379,3 +379,55 @@ Feature: Various utility commands. When I set network -> custom-headers to {"X-Qute-Test": "testvalue"} And I open headers Then the header X-Qute-Test should be set to testvalue + + ## :messages + + Scenario: Showing error messages + When I run :message-error the-error-message + And I run :message-warning the-warning-message + And I run :message-info the-info-message + And I run :messages + Then the error "the-error-message" should be shown + And the warning "the-warning-message" should be shown + And the page should contain the plaintext "the-error-message" + And the page should not contain the plaintext "the-warning-message" + And the page should not contain the plaintext "the-info-message" + + Scenario: Showing messages of type 'warning' or greater + When I run :message-error the-error-message + And I run :message-warning the-warning-message + And I run :message-info the-info-message + And I run :messages warning + Then the error "the-error-message" should be shown + And the warning "the-warning-message" should be shown + And the page should contain the plaintext "the-error-message" + And the page should contain the plaintext "the-warning-message" + And the page should not contain the plaintext "the-info-message" + + Scenario: Showing messages of type 'info' or greater + When I run :message-error the-error-message + And I run :message-warning the-warning-message + And I run :message-info the-info-message + And I run :messages info + Then the error "the-error-message" should be shown + And the warning "the-warning-message" should be shown + And the page should contain the plaintext "the-error-message" + And the page should contain the plaintext "the-warning-message" + And the page should contain the plaintext "the-info-message" + + Scenario: Showing messages of an invalid level + When I run :messages cataclysmic + Then the error "Invalid log level cataclysmic!" should be shown + + Scenario: Using qute:log directly + When I open qute:log + Then no crash should happen + + Scenario: Using qute:plainlog directly + When I open qute:plainlog + Then no crash should happen + + Scenario: Using :messages without messages + Given I have a fresh instance + When I run :messages + Then the page should contain the plaintext "No messages to show." diff --git a/tests/integration/test_invocations.py b/tests/integration/test_invocations.py index b987d9416..a77857405 100644 --- a/tests/integration/test_invocations.py +++ b/tests/integration/test_invocations.py @@ -85,3 +85,12 @@ def test_ascii_locale(httpbin, tmpdir, quteproc_new): assert len(tmpdir.listdir()) == 1 assert (tmpdir / '?-issue908.bin').exists() + + +def test_no_loglines(quteproc_new): + """Test qute:log with --loglines=0.""" + quteproc_new.start(args=['--debug', '--no-err-windows', '--temp-basedir', + '--loglines=0', 'about:blank']) + quteproc_new.open_path('qute:log') + quteproc_new.wait_for_load_finished('qute:log') + assert quteproc_new.get_content() == 'Log output was disabled.'