Add basic HTML log
This commit is contained in:
parent
411d57e539
commit
c8cc92e8c2
2
doc/BUGS
2
doc/BUGS
@ -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
|
||||||
|
@ -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))
|
||||||
|
@ -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."""
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user