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..5da16a845 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,36 @@ 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() + 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): - """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) + 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) return html.encode('UTF-8', errors='xmlcharrefreplace') 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..5ffa1274c 100644 --- a/tests/integration/features/misc.feature +++ b/tests/integration/features/misc.feature @@ -379,3 +379,42 @@ 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