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/3] 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/3] 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/3] 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