Add basic HTML log

This commit is contained in:
Florian Bruhin 2014-06-25 10:00:27 +02:00
parent 411d57e539
commit c8cc92e8c2
4 changed files with 114 additions and 22 deletions

View File

@ -1,6 +1,8 @@
Bugs Bugs
==== ====
- download-page on qute:htmllog is broken
- Downloads: "connection closed" if user takes too long - Downloads: "connection closed" if user takes too long
- Download focus rectangle on windows - Download focus rectangle on windows

View File

@ -208,6 +208,7 @@ class DownloadItem(QObject):
filename: The full filename to save the download to. filename: The full filename to save the download to.
None: special value to stop the download. None: special value to stop the download.
""" """
logger.debug("Setting filename to {}".format(filename))
if self.filename is not None: if self.filename is not None:
raise ValueError("Filename was already set! filename: {}, " raise ValueError("Filename was already set! filename: {}, "
"existing: {}".format(filename, self.filename)) "existing: {}".format(filename, self.filename))

View File

@ -43,6 +43,7 @@ _HTML_TEMPLATE = """
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>{title}</title> <title>{title}</title>
{head}
</head> </head>
<body> <body>
{body} {body}
@ -54,18 +55,22 @@ _HTML_TEMPLATE = """
pyeval_output = ":pyeval was never called" 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. """Add HTML boilerplate to a html snippet.
Args: Args:
title: The title the page should have. title: The title the page should have.
snippet: The html snippet. snippet: The html snippet.
head: Additional stuff to put in <head>
Return: Return:
HTML content as bytes. 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') 'UTF-8', errors='xmlcharrefreplace')
return html
class QuteSchemeHandler(SchemeHandler): class QuteSchemeHandler(SchemeHandler):
@ -122,7 +127,7 @@ class QuteHandlers:
return _get_html('Version', html) return _get_html('Version', html)
@classmethod @classmethod
def log(cls): def plainlog(cls):
"""Handler for qute:log. Return HTML content as bytes.""" """Handler for qute:log. Return HTML content as bytes."""
if logutils.ram_handler is None: if logutils.ram_handler is None:
text = "Log output was disabled." text = "Log output was disabled."
@ -130,6 +135,34 @@ class QuteHandlers:
text = cgi.escape(logutils.ram_handler.dump_log()) text = cgi.escape(logutils.ram_handler.dump_log())
return _get_html('log', '<pre>{}</pre>'.format(text)) return _get_html('log', '<pre>{}</pre>'.format(text))
@classmethod
def log(cls):
"""Handler for qute:log. Return HTML content as bytes."""
style = """
<style type="text/css">
body {
background-color: black;
color: white;
}
table {
border: 1px solid grey;
border-collapse: collapse;
}
th, td {
border: 1px solid grey;
padding-left: 10px;
padding-right: 10px;
}
</style>
"""
if logutils.ram_handler is None:
html = "<p>Log output was disabled.</p>"
else:
html = logutils.ram_handler.dump_log(html=True)
return _get_html('log', html, head=style)
@classmethod @classmethod
def gpl(cls): def gpl(cls):
"""Handler for qute:gpl. Return HTML content as bytes.""" """Handler for qute:gpl. Return HTML content as bytes."""

View File

@ -22,6 +22,7 @@
import re import re
import os import os
import sys import sys
import cgi
import logging import logging
from logging import getLogger from logging import getLogger
from collections import deque from collections import deque
@ -45,11 +46,23 @@ SIMPLE_FMT = '{levelname}: {message}'
EXTENDED_FMT = ('{asctime:8} {levelname:8} {name:10} {module}:{funcName}:' EXTENDED_FMT = ('{asctime:8} {levelname:8} {name:10} {module}:{funcName}:'
'{lineno} {message}') '{lineno} {message}')
SIMPLE_FMT_COLORED = '%(log_color)s%(levelname)s%(reset)s: %(message)s' SIMPLE_FMT_COLORED = '%(log_color)s%(levelname)s%(reset)s: %(message)s'
EXTENDED_FMT_COLORED = ('%(green)s%(asctime)-8s%(reset)s %(log_color)' EXTENDED_FMT_COLORED = ('%(green)s%(asctime)-8s%(reset)s %(log_color)s'
's%(levelname)-8s%(reset)s %(yellow)s%(name)-10s ' '%(levelname)-8s%(reset)s %(yellow)s%(name)-10s '
'%(module)s:%(funcName)s:%(lineno)s%(reset)s ' '%(module)s:%(funcName)s:%(lineno)s%(reset)s '
'%(message)s') '%(message)s')
EXTENDED_FMT_HTML = ('<tr><td><pre>%(green)s%(asctime)-8s%(reset)s</pre></td>'
'<td><pre>%(log_color)s%(levelname)-8s%(reset)s</pre>'
'</td><td></pre>%(yellow)s%(name)-10s</pre></td>'
'<td><pre>%(module)s:%(funcName)s:%(lineno)s%(reset)s'
'</pre></td><td><pre>%(message)s</pre></td></tr>')
DATEFMT = '%H:%M:%S' DATEFMT = '%H:%M:%S'
LOG_COLORS = {
'DEBUG': 'cyan',
'INFO': 'green',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'red',
}
# The different loggers used. # The different loggers used.
@ -126,7 +139,7 @@ def _init_handlers(level, color, ram_capacity):
color: Whether to use color if available. color: Whether to use color if available.
""" """
global ram_handler global ram_handler
console_formatter, ram_formatter, use_colorama = _init_formatters( console_fmt, ram_fmt, html_fmt, use_colorama = _init_formatters(
level, color) level, color)
if sys.stderr is None: if sys.stderr is None:
@ -138,14 +151,15 @@ def _init_handlers(level, color, ram_capacity):
stream = sys.stderr stream = sys.stderr
console_handler = logging.StreamHandler(stream) console_handler = logging.StreamHandler(stream)
console_handler.setLevel(level) console_handler.setLevel(level)
console_handler.setFormatter(console_formatter) console_handler.setFormatter(console_fmt)
if ram_capacity == 0: if ram_capacity == 0:
ram_handler = None ram_handler = None
else: else:
ram_handler = RAMHandler(capacity=ram_capacity) ram_handler = RAMHandler(capacity=ram_capacity)
ram_handler.setLevel(logging.NOTSET) 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 return console_handler, ram_handler
@ -169,26 +183,21 @@ def _init_formatters(level, color):
console_fmt = SIMPLE_FMT console_fmt = SIMPLE_FMT
console_fmt_colored = SIMPLE_FMT_COLORED console_fmt_colored = SIMPLE_FMT_COLORED
ram_formatter = logging.Formatter(EXTENDED_FMT, DATEFMT, '{') ram_formatter = logging.Formatter(EXTENDED_FMT, DATEFMT, '{')
html_formatter = HTMLFormatter(EXTENDED_FMT_HTML, DATEFMT,
log_colors=LOG_COLORS)
if sys.stderr is None: if sys.stderr is None:
return None, ram_formatter, False return None, ram_formatter, html_formatter, False
use_colorama = False use_colorama = False
if (ColoredFormatter is not None and (os.name == 'posix' or colorama) and if (ColoredFormatter is not None and (os.name == 'posix' or colorama) and
sys.stderr.isatty() and color): sys.stderr.isatty() and color):
console_formatter = ColoredFormatter( console_formatter = ColoredFormatter(console_fmt_colored, DATEFMT,
console_fmt_colored, DATEFMT, log_colors={ log_colors=LOG_COLORS)
'DEBUG': 'cyan',
'INFO': 'green',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'red',
}
)
if colorama: if colorama:
colorama.init() colorama.init()
use_colorama = True use_colorama = True
else: else:
console_formatter = logging.Formatter(console_fmt, DATEFMT, '{') 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): def qt_message_handler(msg_type, context, msg):
@ -304,6 +313,7 @@ class RAMHandler(logging.Handler):
def __init__(self, capacity): def __init__(self, capacity):
super().__init__() super().__init__()
self.html_formatter = None
if capacity != -1: if capacity != -1:
self.data = deque(maxlen=capacity) self.data = deque(maxlen=capacity)
else: else:
@ -312,9 +322,55 @@ class RAMHandler(logging.Handler):
def emit(self, record): def emit(self, record):
self.data.append(record) self.data.append(record)
def dump_log(self): def dump_log(self, html=False):
"""Dump the complete formatted log data as as string.""" """Dump the complete formatted log data as as string."""
lines = [] if html:
fmt = self.html_formatter.format
lines = ['<table>']
else:
fmt = self.format
lines = []
for record in self.data: for record in self.data:
lines.append(self.format(record)) lines.append(fmt(record))
if html:
lines.append('</table>')
return '\n'.join(lines) 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] = '<font color="{}">'.format(color)
self._colordict['reset'] = '</font>'
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