diff --git a/doc/BUGS b/doc/BUGS index 8de1c58d3..5725f5e0b 100644 --- a/doc/BUGS +++ b/doc/BUGS @@ -1,6 +1,8 @@ Bugs ==== +- download-page on qute:htmllog is broken + - Downloads: "connection closed" if user takes too long - Download focus rectangle on windows diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index 8a4547b9b..e8b669452 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -208,6 +208,7 @@ class DownloadItem(QObject): filename: The full filename to save the download to. None: special value to stop the download. """ + logger.debug("Setting filename to {}".format(filename)) if self.filename is not None: raise ValueError("Filename was already set! filename: {}, " "existing: {}".format(filename, self.filename)) diff --git a/qutebrowser/network/qutescheme.py b/qutebrowser/network/qutescheme.py index 7e6cd86a6..a3bc688f9 100644 --- a/qutebrowser/network/qutescheme.py +++ b/qutebrowser/network/qutescheme.py @@ -43,6 +43,7 @@ _HTML_TEMPLATE = """ {title} + {head} {body} @@ -54,18 +55,22 @@ _HTML_TEMPLATE = """ pyeval_output = ":pyeval was never called" -def _get_html(title, snippet): +def _get_html(title, snippet, head=None): """Add HTML boilerplate to a html snippet. Args: title: The title the page should have. snippet: The html snippet. + head: Additional stuff to put in Return: HTML content as bytes. """ - return _HTML_TEMPLATE.format(title=title, body=snippet).encode( + if head is None: + head = "" + html = _HTML_TEMPLATE.format(title=title, body=snippet, head=head).encode( 'UTF-8', errors='xmlcharrefreplace') + return html class QuteSchemeHandler(SchemeHandler): @@ -122,7 +127,7 @@ class QuteHandlers: return _get_html('Version', html) @classmethod - def log(cls): + def plainlog(cls): """Handler for qute:log. Return HTML content as bytes.""" if logutils.ram_handler is None: text = "Log output was disabled." @@ -130,6 +135,34 @@ class QuteHandlers: text = cgi.escape(logutils.ram_handler.dump_log()) return _get_html('log', '
{}
'.format(text)) + @classmethod + def log(cls): + """Handler for qute:log. Return HTML content as bytes.""" + style = """ + + """ + if logutils.ram_handler is None: + html = "

Log output was disabled.

" + else: + html = logutils.ram_handler.dump_log(html=True) + return _get_html('log', html, head=style) + @classmethod def gpl(cls): """Handler for qute:gpl. Return HTML content as bytes.""" diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 6bbf943ba..41738397c 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -22,6 +22,7 @@ import re import os import sys +import cgi import logging from logging import getLogger from collections import deque @@ -45,11 +46,23 @@ SIMPLE_FMT = '{levelname}: {message}' EXTENDED_FMT = ('{asctime:8} {levelname:8} {name:10} {module}:{funcName}:' '{lineno} {message}') SIMPLE_FMT_COLORED = '%(log_color)s%(levelname)s%(reset)s: %(message)s' -EXTENDED_FMT_COLORED = ('%(green)s%(asctime)-8s%(reset)s %(log_color)' - 's%(levelname)-8s%(reset)s %(yellow)s%(name)-10s ' +EXTENDED_FMT_COLORED = ('%(green)s%(asctime)-8s%(reset)s %(log_color)s' + '%(levelname)-8s%(reset)s %(yellow)s%(name)-10s ' '%(module)s:%(funcName)s:%(lineno)s%(reset)s ' '%(message)s') +EXTENDED_FMT_HTML = ('
%(green)s%(asctime)-8s%(reset)s
' + '
%(log_color)s%(levelname)-8s%(reset)s
' + '%(yellow)s%(name)-10s' + '
%(module)s:%(funcName)s:%(lineno)s%(reset)s'
+                     '
%(message)s
') DATEFMT = '%H:%M:%S' +LOG_COLORS = { + 'DEBUG': 'cyan', + 'INFO': 'green', + 'WARNING': 'yellow', + 'ERROR': 'red', + 'CRITICAL': 'red', +} # The different loggers used. @@ -126,7 +139,7 @@ def _init_handlers(level, color, ram_capacity): color: Whether to use color if available. """ global ram_handler - console_formatter, ram_formatter, use_colorama = _init_formatters( + console_fmt, ram_fmt, html_fmt, use_colorama = _init_formatters( level, color) if sys.stderr is None: @@ -138,14 +151,15 @@ def _init_handlers(level, color, ram_capacity): stream = sys.stderr console_handler = logging.StreamHandler(stream) console_handler.setLevel(level) - console_handler.setFormatter(console_formatter) + console_handler.setFormatter(console_fmt) if ram_capacity == 0: ram_handler = None else: ram_handler = RAMHandler(capacity=ram_capacity) ram_handler.setLevel(logging.NOTSET) - ram_handler.setFormatter(ram_formatter) + ram_handler.setFormatter(ram_fmt) + ram_handler.html_formatter = html_fmt return console_handler, ram_handler @@ -169,26 +183,21 @@ def _init_formatters(level, color): console_fmt = SIMPLE_FMT console_fmt_colored = SIMPLE_FMT_COLORED ram_formatter = logging.Formatter(EXTENDED_FMT, DATEFMT, '{') + html_formatter = HTMLFormatter(EXTENDED_FMT_HTML, DATEFMT, + log_colors=LOG_COLORS) if sys.stderr is None: - return None, ram_formatter, False + return None, ram_formatter, html_formatter, False use_colorama = False if (ColoredFormatter is not None and (os.name == 'posix' or colorama) and sys.stderr.isatty() and color): - console_formatter = ColoredFormatter( - console_fmt_colored, DATEFMT, log_colors={ - 'DEBUG': 'cyan', - 'INFO': 'green', - 'WARNING': 'yellow', - 'ERROR': 'red', - 'CRITICAL': 'red', - } - ) + console_formatter = ColoredFormatter(console_fmt_colored, DATEFMT, + log_colors=LOG_COLORS) if colorama: colorama.init() use_colorama = True else: console_formatter = logging.Formatter(console_fmt, DATEFMT, '{') - return console_formatter, ram_formatter, use_colorama + return console_formatter, ram_formatter, html_formatter, use_colorama def qt_message_handler(msg_type, context, msg): @@ -304,6 +313,7 @@ class RAMHandler(logging.Handler): def __init__(self, capacity): super().__init__() + self.html_formatter = None if capacity != -1: self.data = deque(maxlen=capacity) else: @@ -312,9 +322,55 @@ class RAMHandler(logging.Handler): def emit(self, record): self.data.append(record) - def dump_log(self): + def dump_log(self, html=False): """Dump the complete formatted log data as as string.""" - lines = [] + if html: + fmt = self.html_formatter.format + lines = [''] + else: + fmt = self.format + lines = [] for record in self.data: - lines.append(self.format(record)) + lines.append(fmt(record)) + if html: + lines.append('
') return '\n'.join(lines) + + +class HTMLFormatter(logging.Formatter): + + """Formatter for HTML-colored log messages, similiar to colorlog.""" + + def __init__(self, fmt, datefmt, log_colors): + """Constructor. + + Args: + fmt: The format string to use. + datefmt: The date format to use. + log_colors: The colors to use for logging levels. + """ + super().__init__(fmt, datefmt) + self._log_colors = log_colors + self._colordict = {} + # We could solve this nicer by using CSS, but for this simple case this + # works. + for color in ['black', 'red', 'green', 'yellow', 'blue', 'purple', + 'cyan', 'white']: + self._colordict[color] = ''.format(color) + self._colordict['reset'] = '' + + def format(self, record): + record.__dict__.update(self._colordict) + if record.levelname in self._log_colors: + color = self._log_colors[record.levelname] + record.log_color = self._colordict[color] + else: + record.log_color = '' + for field in ['asctime', 'filename', 'funcName', 'levelname', + 'module', 'message', 'name', 'pathname', 'processName', + 'threadName']: + setattr(record, field, cgi.escape(getattr(record, field))) + message = super().format(record) + if not message.endswith(self._colordict['reset']): + message += self._colordict['reset'] + return message