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
====
- download-page on qute:htmllog is broken
- Downloads: "connection closed" if user takes too long
- Download focus rectangle on windows

View File

@ -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))

View File

@ -43,6 +43,7 @@ _HTML_TEMPLATE = """
<head>
<meta charset="utf-8">
<title>{title}</title>
{head}
</head>
<body>
{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 <head>
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', '<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
def gpl(cls):
"""Handler for qute:gpl. Return HTML content as bytes."""

View File

@ -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 = ('<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'
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 = ['<table>']
else:
fmt = self.format
lines = []
for record in self.data:
lines.append(self.format(record))
lines.append(fmt(record))
if html:
lines.append('</table>')
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