Merge branch 'rcorre-messages'

This commit is contained in:
Florian Bruhin 2016-05-15 11:51:20 +02:00
commit 6fc2217b07
10 changed files with 150 additions and 10 deletions

View File

@ -29,6 +29,7 @@ Added
- The `'` mark gets set when moving away (hinting link with anchor, searching, etc.) so you can move back with `''` - 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 - New `--force-color` argument to force colored logging even if stdout is not a
terminal terminal
- New `:messages` command to show error messages
Changed Changed
~~~~~~~ ~~~~~~~

View File

@ -145,8 +145,8 @@ Contributors, sorted by the number of commits in descending order:
* Alexander Cogneau * Alexander Cogneau
* Felix Van der Jeugt * Felix Van der Jeugt
* Martin Tournoij * Martin Tournoij
* Raphael Pierzina
* Ryan Roden-Corrent * Ryan Roden-Corrent
* Raphael Pierzina
* Joel Torstensson * Joel Torstensson
* Patric Schmitz * Patric Schmitz
* Claude * Claude

View File

@ -31,6 +31,7 @@
|<<jseval,jseval>>|Evaluate a JavaScript string. |<<jseval,jseval>>|Evaluate a JavaScript string.
|<<jump-mark,jump-mark>>|Jump to the mark named by `key`. |<<jump-mark,jump-mark>>|Jump to the mark named by `key`.
|<<later,later>>|Execute a command after some time. |<<later,later>>|Execute a command after some time.
|<<messages,messages>>|Show a log of past messages.
|<<navigate,navigate>>|Open typical prev/next links or navigate using the URL path. |<<navigate,navigate>>|Open typical prev/next links or navigate using the URL path.
|<<open,open>>|Open a URL in the current/[count]th tab. |<<open,open>>|Open a URL in the current/[count]th tab.
|<<paste,paste>>|Open a page from the clipboard. |<<paste,paste>>|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. * 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. * 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]]
=== navigate === navigate
Syntax: +:navigate [*--tab*] [*--bg*] [*--window*] 'where'+ Syntax: +:navigate [*--tab*] [*--bg*] [*--window*] 'where'+

View File

@ -1339,6 +1339,27 @@ class CommandDispatcher:
url = QUrl('qute://help/{}'.format(path)) url = QUrl('qute://help/{}'.format(path))
self._open(url, tab, bg, window) 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', @cmdutils.register(instance='command-dispatcher',
modes=[KeyMode.insert], hide=True, scope='window') modes=[KeyMode.insert], hide=True, scope='window')
def open_editor(self): def open_editor(self):

View File

@ -26,6 +26,7 @@ Module attributes:
import functools import functools
import configparser import configparser
import mimetypes import mimetypes
import urllib.parse
from PyQt5.QtCore import pyqtSlot, QObject from PyQt5.QtCore import pyqtSlot, QObject
from PyQt5.QtNetwork import QNetworkReply from PyQt5.QtNetwork import QNetworkReply
@ -158,23 +159,42 @@ def qute_version(_win_id, _request):
@add_handler('plainlog') @add_handler('plainlog')
def qute_plainlog(_win_id, _request): 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 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: if log.ram_handler is None:
text = "Log output was disabled." text = "Log output was disabled."
else: 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) html = jinja.render('pre.html', title='log', content=text)
return html.encode('UTF-8', errors='xmlcharrefreplace') return html.encode('UTF-8', errors='xmlcharrefreplace')
@add_handler('log') @add_handler('log')
def qute_log(_win_id, _request): 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 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: if log.ram_handler is None:
html_log = None html_log = None
else: 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) html = jinja.render('log.html', title='log', content=html_log)
return html.encode('UTF-8', errors='xmlcharrefreplace') return html.encode('UTF-8', errors='xmlcharrefreplace')

View File

@ -37,6 +37,8 @@ th, td {
<table> <table>
{{ content | safe() }} {{ content | safe() }}
</table> </table>
{% elif content is not none %}
<p>No messages to show.</p>
{% else %} {% else %}
<p>Log output was disabled.</p> <p>Log output was disabled.</p>
{% endif %} {% endif %}

View File

@ -64,13 +64,21 @@ LOG_COLORS = {
'CRITICAL': 'red', 'CRITICAL': 'red',
} }
# We first monkey-patch logging to support our VDEBUG level before getting the # We first monkey-patch logging to support our VDEBUG level before getting the
# loggers. Based on http://stackoverflow.com/a/13638084 # loggers. Based on http://stackoverflow.com/a/13638084
VDEBUG_LEVEL = 9 VDEBUG_LEVEL = 9
logging.addLevelName(VDEBUG_LEVEL, 'VDEBUG') logging.addLevelName(VDEBUG_LEVEL, 'VDEBUG')
logging.VDEBUG = VDEBUG_LEVEL 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): def vdebug(self, msg, *args, **kwargs):
"""Log with a VDEBUG level. """Log with a VDEBUG level.
@ -430,13 +438,14 @@ class RAMHandler(logging.Handler):
# We don't log VDEBUG to RAM. # We don't log VDEBUG to RAM.
self._data.append(record) 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. """Dump the complete formatted log data as as string.
FIXME: We should do all the HTML formatter via jinja2. FIXME: We should do all the HTML formatter via jinja2.
(probably obsolete when moving to a widget for logging, (probably obsolete when moving to a widget for logging,
https://github.com/The-Compiler/qutebrowser/issues/34 https://github.com/The-Compiler/qutebrowser/issues/34
""" """
minlevel = LOG_LEVELS.get(level.upper(), VDEBUG_LEVEL)
lines = [] lines = []
fmt = self.html_formatter.format if html else self.format fmt = self.html_formatter.format if html else self.format
self.acquire() self.acquire()
@ -445,6 +454,7 @@ class RAMHandler(logging.Handler):
finally: finally:
self.release() self.release()
for record in records: for record in records:
if record.levelno >= minlevel:
lines.append(fmt(record)) lines.append(fmt(record))
return '\n'.join(lines) return '\n'.join(lines)

View File

@ -366,6 +366,14 @@ def check_contents_plain(quteproc, text):
assert text in content 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}')) @bdd.then(bdd.parsers.parse('the json on the page should be:\n{text}'))
def check_contents_json(quteproc, text): def check_contents_json(quteproc, text):
"""Check the current page's content as json.""" """Check the current page's content as json."""

View File

@ -379,3 +379,55 @@ Feature: Various utility commands.
When I set network -> custom-headers to {"X-Qute-Test": "testvalue"} When I set network -> custom-headers to {"X-Qute-Test": "testvalue"}
And I open headers And I open headers
Then the header X-Qute-Test should be set to testvalue 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."

View File

@ -85,3 +85,12 @@ def test_ascii_locale(httpbin, tmpdir, quteproc_new):
assert len(tmpdir.listdir()) == 1 assert len(tmpdir.listdir()) == 1
assert (tmpdir / '?-issue908.bin').exists() 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.'