From 800c1c3cf8a002f5b28dc4259f6c7939646df4ca Mon Sep 17 00:00:00 2001 From: Ryan Roden-Corrent Date: Wed, 11 May 2016 21:23:38 -0400 Subject: [PATCH 1/7] Add :messages command to show past messages. This adds a 'level' query parameter to qute://log and qute://plainlog. For example, qute://log?level=warning will show an html page containing log entries with severity warning or greater. If the query is omitted, the original behavior of qute://log is preserved. :messages [level] is a command that opens qute://log?level=. By default, level defaults to 'error' as an easy way to see missed error messages. --- qutebrowser/browser/commands.py | 16 +++++++++++ qutebrowser/browser/network/qutescheme.py | 25 +++++++++++++---- qutebrowser/utils/log.py | 7 +++-- tests/integration/features/misc.feature | 34 ++++++++++++++++++++++- 4 files changed, 74 insertions(+), 8 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 865990693..c6c6a85ef 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1339,6 +1339,22 @@ 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). + """ + 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..f3fe0b799 100644 --- a/qutebrowser/browser/network/qutescheme.py +++ b/qutebrowser/browser/network/qutescheme.py @@ -27,7 +27,7 @@ import functools import configparser import mimetypes -from PyQt5.QtCore import pyqtSlot, QObject +from PyQt5.QtCore import pyqtSlot, QObject, QUrlQuery from PyQt5.QtNetwork import QNetworkReply import qutebrowser @@ -159,22 +159,37 @@ def qute_version(_win_id, _request): @add_handler('plainlog') def qute_plainlog(_win_id, _request): - """Handler for qute:plainlog. Return HTML content as bytes.""" + """Handler for qute:plainlog. Return HTML content as bytes. + + An optional query parameter specifies the min 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() + query = QUrlQuery(_request.url().query()) + level = query.queryItemValue('level') + 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.""" + """Handler for qute:log. Return HTML content as bytes. + + An optional query parameter specifies the min 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) + query = QUrlQuery(_request.url().query()) + level = query.queryItemValue('level') + 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/utils/log.py b/qutebrowser/utils/log.py index 577383e1b..0bce09030 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -429,13 +429,15 @@ 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 """ + # pylint: disable=protected-access + minlevel = logging._nameToLevel.get(level.upper(), VDEBUG_LEVEL) lines = [] fmt = self.html_formatter.format if html else self.format self.acquire() @@ -444,7 +446,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/misc.feature b/tests/integration/features/misc.feature index 13a3c33c6..7714385b5 100644 --- a/tests/integration/features/misc.feature +++ b/tests/integration/features/misc.feature @@ -342,7 +342,7 @@ Feature: Various utility commands. Scenario: Running :pyeval with --quiet When I run :debug-pyeval --quiet 1+1 Then "pyeval output: 2" should be logged - + ## https://github.com/The-Compiler/qutebrowser/issues/504 Scenario: Focusing download widget via Tab @@ -367,3 +367,35 @@ 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" + + 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" + + 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" From c36f760114163c3f70520fd8ff28de8466e09e4d Mon Sep 17 00:00:00 2001 From: Ryan Roden-Corrent Date: Thu, 12 May 2016 07:45:48 -0400 Subject: [PATCH 2/7] Add bdd test for page not containing plaintext. You can now use 'the page should not contain the plaintext ...' in a feature test. --- tests/integration/features/conftest.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/integration/features/conftest.py b/tests/integration/features/conftest.py index 060bb00dd..12a3c906b 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_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.""" From fcd233a6755d0b578262bac7e35b0bae2e482757 Mon Sep 17 00:00:00 2001 From: Ryan Roden-Corrent Date: Thu, 12 May 2016 07:47:20 -0400 Subject: [PATCH 3/7] Clean up :messages implementation. - Add log.LOG_LEVELS to map names to levels (instead of using logging._levelToName) - Test that log pages do not contain messages below the requested level - Use pythons urllib.parse.parse_qs instead of Qt's UrlQuery - Document tab, bg, window args for :messages - Clean up style --- qutebrowser/browser/commands.py | 5 +++++ qutebrowser/browser/network/qutescheme.py | 17 ++++++++--------- qutebrowser/utils/log.py | 13 ++++++++++--- tests/integration/features/conftest.py | 2 +- tests/integration/features/misc.feature | 7 +++++++ 5 files changed, 31 insertions(+), 13 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index c6c6a85ef..98c3efcb7 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1348,7 +1348,12 @@ class CommandDispatcher: 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: diff --git a/qutebrowser/browser/network/qutescheme.py b/qutebrowser/browser/network/qutescheme.py index f3fe0b799..5da16a845 100644 --- a/qutebrowser/browser/network/qutescheme.py +++ b/qutebrowser/browser/network/qutescheme.py @@ -26,8 +26,9 @@ Module attributes: import functools import configparser import mimetypes +import urllib.parse -from PyQt5.QtCore import pyqtSlot, QObject, QUrlQuery +from PyQt5.QtCore import pyqtSlot, QObject from PyQt5.QtNetwork import QNetworkReply import qutebrowser @@ -158,36 +159,34 @@ def qute_version(_win_id, _request): @add_handler('plainlog') -def qute_plainlog(_win_id, _request): +def qute_plainlog(_win_id, request): """Handler for qute:plainlog. Return HTML content as bytes. - An optional query parameter specifies the min log level to print. + 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: - query = QUrlQuery(_request.url().query()) - level = query.queryItemValue('level') + level = urllib.parse.parse_qs(request.url().query()).get('level')[0] 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): +def qute_log(_win_id, request): """Handler for qute:log. Return HTML content as bytes. - An optional query parameter specifies the min log level to print. + 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: - query = QUrlQuery(_request.url().query()) - level = query.queryItemValue('level') + level = urllib.parse.parse_qs(request.url().query()).get('level')[0] html_log = log.ram_handler.dump_log(html=True, level=level) html = jinja.render('log.html', title='log', content=html_log) diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 0bce09030..6286a441c 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. @@ -436,8 +444,7 @@ class RAMHandler(logging.Handler): (probably obsolete when moving to a widget for logging, https://github.com/The-Compiler/qutebrowser/issues/34 """ - # pylint: disable=protected-access - minlevel = logging._nameToLevel.get(level.upper(), VDEBUG_LEVEL) + minlevel = LOG_LEVELS.get(level.upper(), VDEBUG_LEVEL) lines = [] fmt = self.html_formatter.format if html else self.format self.acquire() diff --git a/tests/integration/features/conftest.py b/tests/integration/features/conftest.py index 12a3c906b..29f719080 100644 --- a/tests/integration/features/conftest.py +++ b/tests/integration/features/conftest.py @@ -368,7 +368,7 @@ def check_contents_plain(quteproc, text): @bdd.then(bdd.parsers.parse('the page should not contain the plaintext ' '"{text}"')) -def check_contents_plain(quteproc, 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 diff --git a/tests/integration/features/misc.feature b/tests/integration/features/misc.feature index 7714385b5..1d225bb6b 100644 --- a/tests/integration/features/misc.feature +++ b/tests/integration/features/misc.feature @@ -378,6 +378,8 @@ Feature: Various utility commands. 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 @@ -388,6 +390,7 @@ Feature: Various utility commands. 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 @@ -399,3 +402,7 @@ Feature: Various utility commands. 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 From bb31787931d342230426144d9eea6d5b99785dc3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 15 May 2016 11:21:02 +0200 Subject: [PATCH 4/7] Regenerate docs --- README.asciidoc | 2 +- doc/help/commands.asciidoc | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) 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'+ From b704c911ae2a90ce47f039a8bafaaac4854df192 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 15 May 2016 11:33:30 +0200 Subject: [PATCH 5/7] Fix running qute:log and qute:plainlog This failed because dict.get('level') returned None with no level parameter, and the subsequent [0] raised: Traceback (most recent call last): File ".../qutebrowser/utils/utils.py", line 624, in wrapper return func(*args, **kwargs) File ".../qutebrowser/browser/network/networkmanager.py", line 445, in createRequest op, req, outgoing_data) File ".../qutebrowser/browser/network/qutescheme.py", line 107, in createRequest data = handler(self._win_id, request) File ".../qutebrowser/browser/network/qutescheme.py", line 189, in qute_log level = urllib.parse.parse_qs(request.url().query()).get('level')[0] TypeError: 'NoneType' object is not subscriptable --- qutebrowser/browser/network/qutescheme.py | 10 ++++++++-- tests/integration/features/misc.feature | 8 ++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/network/qutescheme.py b/qutebrowser/browser/network/qutescheme.py index 5da16a845..bf653ec6e 100644 --- a/qutebrowser/browser/network/qutescheme.py +++ b/qutebrowser/browser/network/qutescheme.py @@ -169,7 +169,10 @@ def qute_plainlog(_win_id, request): if log.ram_handler is None: text = "Log output was disabled." else: - level = urllib.parse.parse_qs(request.url().query()).get('level')[0] + 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') @@ -186,7 +189,10 @@ def qute_log(_win_id, request): if log.ram_handler is None: html_log = None else: - level = urllib.parse.parse_qs(request.url().query()).get('level')[0] + 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) diff --git a/tests/integration/features/misc.feature b/tests/integration/features/misc.feature index 5ffa1274c..d9376fd96 100644 --- a/tests/integration/features/misc.feature +++ b/tests/integration/features/misc.feature @@ -418,3 +418,11 @@ Feature: Various utility commands. 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 From 9b28d90543bd64a3d370efaf140454ae25b70f2d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 15 May 2016 11:50:29 +0200 Subject: [PATCH 6/7] Improve output if :messages is used without errors --- qutebrowser/html/log.html | 2 ++ tests/integration/features/misc.feature | 5 +++++ tests/integration/test_invocations.py | 9 +++++++++ 3 files changed, 16 insertions(+) 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/tests/integration/features/misc.feature b/tests/integration/features/misc.feature index d9376fd96..372bcf48a 100644 --- a/tests/integration/features/misc.feature +++ b/tests/integration/features/misc.feature @@ -426,3 +426,8 @@ Feature: Various utility commands. 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.' From 76a755634bc48b5f12c8837a402a43037fa41fe6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 15 May 2016 11:51:09 +0200 Subject: [PATCH 7/7] Update changelog --- CHANGELOG.asciidoc | 1 + 1 file changed, 1 insertion(+) 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 ~~~~~~~