2014-06-19 09:04:37 +02:00
|
|
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
|
|
|
|
2016-01-04 07:12:39 +01:00
|
|
|
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
2014-05-23 16:11:55 +02:00
|
|
|
#
|
|
|
|
# This file is part of qutebrowser.
|
|
|
|
#
|
|
|
|
# qutebrowser is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# qutebrowser is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
"""Loggers and utilities related to logging."""
|
|
|
|
|
2014-05-25 20:08:07 +02:00
|
|
|
import os
|
|
|
|
import sys
|
2014-08-05 23:23:15 +02:00
|
|
|
import html as pyhtml
|
2014-05-23 16:57:08 +02:00
|
|
|
import logging
|
2014-08-26 19:10:14 +02:00
|
|
|
import contextlib
|
|
|
|
import collections
|
2014-11-09 22:59:14 +01:00
|
|
|
import faulthandler
|
2015-02-24 07:12:25 +01:00
|
|
|
import traceback
|
2015-03-23 07:40:22 +01:00
|
|
|
import warnings
|
2016-05-25 00:12:51 +02:00
|
|
|
import json
|
2016-07-08 12:53:48 +02:00
|
|
|
import inspect
|
2014-05-23 16:11:55 +02:00
|
|
|
|
2015-05-15 20:15:09 +02:00
|
|
|
from PyQt5 import QtCore
|
2014-06-06 12:13:32 +02:00
|
|
|
# Optional imports
|
2014-05-25 20:00:48 +02:00
|
|
|
try:
|
2014-08-25 22:26:52 +02:00
|
|
|
import colorama
|
2014-05-25 20:00:48 +02:00
|
|
|
except ImportError:
|
2014-08-25 22:26:52 +02:00
|
|
|
colorama = None
|
2016-05-13 21:08:50 +02:00
|
|
|
|
2016-07-25 13:03:58 +02:00
|
|
|
_log_inited = False
|
|
|
|
|
2016-05-13 21:08:50 +02:00
|
|
|
COLORS = ['black', 'red', 'green', 'yellow', 'blue', 'purple', 'cyan', 'white']
|
2016-06-06 23:09:19 +02:00
|
|
|
COLOR_ESCAPES = {color: '\033[{}m'.format(i)
|
|
|
|
for i, color in enumerate(COLORS, start=30)}
|
|
|
|
RESET_ESCAPE = '\033[0m'
|
|
|
|
|
2014-05-25 20:00:48 +02:00
|
|
|
|
2014-06-06 12:29:33 +02:00
|
|
|
# Log formats to use.
|
2016-05-13 21:08:50 +02:00
|
|
|
SIMPLE_FMT = ('{green}{asctime:8}{reset} {log_color}{levelname}{reset}: '
|
|
|
|
'{message}')
|
|
|
|
EXTENDED_FMT = ('{green}{asctime:8}{reset} '
|
|
|
|
'{log_color}{levelname:8}{reset} '
|
|
|
|
'{cyan}{name:10} {module}:{funcName}:{lineno}{reset} '
|
|
|
|
'{log_color}{message}{reset}')
|
2014-08-05 23:35:20 +02:00
|
|
|
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>%(cyan)s%(name)-10s</pre></td>'
|
|
|
|
'<td><pre>%(cyan)s%(module)s:%(funcName)s:%(lineno)s%(reset)s</pre></td>'
|
|
|
|
'<td><pre>%(log_color)s%(message)s%(reset)s</pre></td>'
|
|
|
|
'</tr>'
|
|
|
|
)
|
2014-06-06 12:29:33 +02:00
|
|
|
DATEFMT = '%H:%M:%S'
|
2014-06-25 10:00:27 +02:00
|
|
|
LOG_COLORS = {
|
2014-08-29 07:33:37 +02:00
|
|
|
'VDEBUG': 'white',
|
2014-08-05 23:07:53 +02:00
|
|
|
'DEBUG': 'white',
|
2014-06-25 10:00:27 +02:00
|
|
|
'INFO': 'green',
|
|
|
|
'WARNING': 'yellow',
|
|
|
|
'ERROR': 'red',
|
|
|
|
'CRITICAL': 'red',
|
|
|
|
}
|
2014-06-06 12:29:33 +02:00
|
|
|
|
2014-08-29 07:33:37 +02:00
|
|
|
# We first monkey-patch logging to support our VDEBUG level before getting the
|
|
|
|
# loggers. Based on http://stackoverflow.com/a/13638084
|
|
|
|
VDEBUG_LEVEL = 9
|
|
|
|
logging.addLevelName(VDEBUG_LEVEL, 'VDEBUG')
|
|
|
|
logging.VDEBUG = VDEBUG_LEVEL
|
|
|
|
|
2016-05-12 13:47:20 +02:00
|
|
|
LOG_LEVELS = {
|
|
|
|
'VDEBUG': logging.VDEBUG,
|
|
|
|
'DEBUG': logging.DEBUG,
|
|
|
|
'INFO': logging.INFO,
|
|
|
|
'WARNING': logging.WARNING,
|
|
|
|
'ERROR': logging.ERROR,
|
|
|
|
'CRITICAL': logging.CRITICAL,
|
|
|
|
}
|
|
|
|
|
2016-08-09 13:56:26 +02:00
|
|
|
LOGGER_NAMES = [
|
|
|
|
'statusbar', 'completion', 'init', 'url',
|
|
|
|
'destroy', 'modes', 'webview', 'misc',
|
|
|
|
'mouse', 'procs', 'hints', 'keyboard',
|
|
|
|
'commands', 'signals', 'downloads',
|
|
|
|
'js', 'qt', 'rfc6266', 'ipc', 'shlexer',
|
2016-09-07 11:51:23 +02:00
|
|
|
'save', 'message', 'config', 'sessions',
|
|
|
|
'webelem'
|
2016-08-09 13:56:26 +02:00
|
|
|
]
|
|
|
|
|
2014-08-29 07:33:37 +02:00
|
|
|
|
2015-04-13 08:52:58 +02:00
|
|
|
def vdebug(self, msg, *args, **kwargs):
|
2014-08-29 07:33:37 +02:00
|
|
|
"""Log with a VDEBUG level.
|
|
|
|
|
|
|
|
VDEBUG is used when a debug message is rather verbose, and probably of
|
|
|
|
little use to the end user or for post-mortem debugging, i.e. the content
|
|
|
|
probably won't change unless the code changes.
|
|
|
|
"""
|
|
|
|
if self.isEnabledFor(VDEBUG_LEVEL):
|
|
|
|
# pylint: disable=protected-access
|
2015-04-13 08:52:58 +02:00
|
|
|
self._log(VDEBUG_LEVEL, msg, args, **kwargs)
|
2014-05-23 16:11:55 +02:00
|
|
|
|
2014-08-29 07:33:37 +02:00
|
|
|
|
|
|
|
logging.Logger.vdebug = vdebug
|
|
|
|
|
|
|
|
|
|
|
|
# The different loggers used.
|
2014-08-26 19:10:14 +02:00
|
|
|
statusbar = logging.getLogger('statusbar')
|
|
|
|
completion = logging.getLogger('completion')
|
|
|
|
destroy = logging.getLogger('destroy')
|
|
|
|
modes = logging.getLogger('modes')
|
|
|
|
webview = logging.getLogger('webview')
|
|
|
|
mouse = logging.getLogger('mouse')
|
|
|
|
misc = logging.getLogger('misc')
|
|
|
|
url = logging.getLogger('url')
|
|
|
|
procs = logging.getLogger('procs')
|
|
|
|
commands = logging.getLogger('commands')
|
|
|
|
init = logging.getLogger('init')
|
|
|
|
signals = logging.getLogger('signals')
|
|
|
|
hints = logging.getLogger('hints')
|
|
|
|
keyboard = logging.getLogger('keyboard')
|
|
|
|
downloads = logging.getLogger('downloads')
|
|
|
|
js = logging.getLogger('js') # Javascript console messages
|
|
|
|
qt = logging.getLogger('qt') # Warnings produced by Qt
|
|
|
|
rfc6266 = logging.getLogger('rfc6266')
|
2014-10-13 21:38:28 +02:00
|
|
|
ipc = logging.getLogger('ipc')
|
2014-11-04 20:06:58 +01:00
|
|
|
shlexer = logging.getLogger('shlexer')
|
2015-01-31 22:56:23 +01:00
|
|
|
save = logging.getLogger('save')
|
2015-04-13 08:49:04 +02:00
|
|
|
message = logging.getLogger('message')
|
|
|
|
config = logging.getLogger('config')
|
2015-04-13 08:52:58 +02:00
|
|
|
sessions = logging.getLogger('sessions')
|
2016-09-07 11:51:23 +02:00
|
|
|
webelem = logging.getLogger('webelem')
|
2014-05-23 16:57:08 +02:00
|
|
|
|
|
|
|
|
2014-05-25 18:46:15 +02:00
|
|
|
ram_handler = None
|
2016-08-09 13:56:26 +02:00
|
|
|
console_handler = None
|
|
|
|
console_filter = None
|
2014-05-25 18:46:15 +02:00
|
|
|
|
|
|
|
|
2016-07-08 12:53:48 +02:00
|
|
|
def stub(suffix=''):
|
|
|
|
"""Show a STUB: message for the calling function."""
|
|
|
|
function = inspect.stack()[1][3]
|
|
|
|
text = "STUB: {}".format(function)
|
|
|
|
if suffix:
|
|
|
|
text = '{} ({})'.format(text, suffix)
|
|
|
|
misc.warning(text)
|
|
|
|
|
|
|
|
|
2015-10-16 18:26:34 +02:00
|
|
|
class CriticalQtWarning(Exception):
|
|
|
|
|
|
|
|
"""Exception raised when there's a critical Qt warning."""
|
|
|
|
|
|
|
|
|
2014-05-23 16:57:08 +02:00
|
|
|
def init_log(args):
|
|
|
|
"""Init loggers based on the argparse namespace passed."""
|
2015-11-23 21:40:11 +01:00
|
|
|
level = args.loglevel.upper()
|
2014-05-23 18:22:22 +02:00
|
|
|
try:
|
2014-05-26 09:00:45 +02:00
|
|
|
numeric_level = getattr(logging, level)
|
|
|
|
except AttributeError:
|
2014-05-23 18:22:22 +02:00
|
|
|
raise ValueError("Invalid log level: {}".format(args.loglevel))
|
2014-06-06 12:29:33 +02:00
|
|
|
|
2015-11-23 21:40:11 +01:00
|
|
|
if numeric_level > logging.DEBUG and args.debug:
|
|
|
|
numeric_level = logging.DEBUG
|
|
|
|
|
2016-04-30 17:01:45 +02:00
|
|
|
console, ram = _init_handlers(numeric_level, args.color, args.force_color,
|
2016-05-25 00:12:51 +02:00
|
|
|
args.json_logging, args.loglines)
|
2014-08-26 19:10:14 +02:00
|
|
|
root = logging.getLogger()
|
2016-08-09 13:56:26 +02:00
|
|
|
global console_filter
|
2014-06-19 10:19:00 +02:00
|
|
|
if console is not None:
|
2014-08-25 06:58:41 +02:00
|
|
|
if args.logfilter is not None:
|
2016-08-09 13:56:26 +02:00
|
|
|
console_filter = LogFilter(args.logfilter.split(','))
|
|
|
|
console.addFilter(console_filter)
|
2014-06-19 10:19:00 +02:00
|
|
|
root.addHandler(console)
|
2014-06-19 12:06:36 +02:00
|
|
|
if ram is not None:
|
|
|
|
root.addHandler(ram)
|
2014-06-06 12:29:33 +02:00
|
|
|
root.setLevel(logging.NOTSET)
|
|
|
|
logging.captureWarnings(True)
|
2016-07-23 13:04:45 +02:00
|
|
|
_init_py_warnings()
|
|
|
|
QtCore.qInstallMessageHandler(qt_message_handler)
|
2016-07-25 13:03:58 +02:00
|
|
|
global _log_inited
|
|
|
|
_log_inited = True
|
2016-07-23 13:04:45 +02:00
|
|
|
|
|
|
|
|
|
|
|
def _init_py_warnings():
|
|
|
|
"""Initialize Python warning handling."""
|
2015-03-23 07:40:22 +01:00
|
|
|
warnings.simplefilter('default')
|
2016-06-06 22:37:29 +02:00
|
|
|
warnings.filterwarnings('ignore', module='pdb', category=ResourceWarning)
|
2014-06-06 12:29:33 +02:00
|
|
|
|
|
|
|
|
2014-08-26 19:10:14 +02:00
|
|
|
@contextlib.contextmanager
|
2014-06-25 20:18:00 +02:00
|
|
|
def disable_qt_msghandler():
|
2014-09-22 20:44:07 +02:00
|
|
|
"""Contextmanager which temporarily disables the Qt message handler."""
|
2015-05-15 20:15:09 +02:00
|
|
|
old_handler = QtCore.qInstallMessageHandler(None)
|
2015-06-15 06:18:16 +02:00
|
|
|
try:
|
|
|
|
yield
|
|
|
|
finally:
|
|
|
|
QtCore.qInstallMessageHandler(old_handler)
|
2014-06-25 20:18:00 +02:00
|
|
|
|
|
|
|
|
2016-07-23 13:04:45 +02:00
|
|
|
@contextlib.contextmanager
|
|
|
|
def ignore_py_warnings(**kwargs):
|
2016-07-25 13:03:58 +02:00
|
|
|
"""Contextmanager to temporarily disable certain Python warnings."""
|
2016-07-23 13:04:45 +02:00
|
|
|
warnings.filterwarnings('ignore', **kwargs)
|
|
|
|
yield
|
2016-07-25 13:03:58 +02:00
|
|
|
if _log_inited:
|
|
|
|
_init_py_warnings()
|
2016-07-23 13:04:45 +02:00
|
|
|
|
|
|
|
|
2016-05-25 00:12:51 +02:00
|
|
|
def _init_handlers(level, color, force_color, json_logging, ram_capacity):
|
2014-06-06 12:29:33 +02:00
|
|
|
"""Init log handlers.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
level: The numeric logging level.
|
|
|
|
color: Whether to use color if available.
|
2016-04-30 17:01:45 +02:00
|
|
|
force_color: Force colored output.
|
2016-05-25 00:12:51 +02:00
|
|
|
json_logging: Output log lines in JSON (this disables all colors).
|
2014-06-06 12:29:33 +02:00
|
|
|
"""
|
|
|
|
global ram_handler
|
2016-08-09 13:56:26 +02:00
|
|
|
global console_handler
|
2014-06-25 10:00:27 +02:00
|
|
|
console_fmt, ram_fmt, html_fmt, use_colorama = _init_formatters(
|
2016-05-25 00:12:51 +02:00
|
|
|
level, color, force_color, json_logging)
|
2014-06-06 12:29:33 +02:00
|
|
|
|
2014-06-19 10:19:00 +02:00
|
|
|
if sys.stderr is None:
|
|
|
|
console_handler = None
|
2014-06-06 12:29:33 +02:00
|
|
|
else:
|
2016-04-30 17:01:45 +02:00
|
|
|
strip = False if force_color else None
|
2014-06-19 10:19:00 +02:00
|
|
|
if use_colorama:
|
2016-04-30 17:01:45 +02:00
|
|
|
stream = colorama.AnsiToWin32(sys.stderr, strip=strip)
|
2014-06-19 10:19:00 +02:00
|
|
|
else:
|
|
|
|
stream = sys.stderr
|
|
|
|
console_handler = logging.StreamHandler(stream)
|
|
|
|
console_handler.setLevel(level)
|
2014-06-25 10:00:27 +02:00
|
|
|
console_handler.setFormatter(console_fmt)
|
2014-06-06 12:29:33 +02:00
|
|
|
|
2014-06-19 12:06:36 +02:00
|
|
|
if ram_capacity == 0:
|
|
|
|
ram_handler = None
|
|
|
|
else:
|
|
|
|
ram_handler = RAMHandler(capacity=ram_capacity)
|
|
|
|
ram_handler.setLevel(logging.NOTSET)
|
2014-06-25 10:00:27 +02:00
|
|
|
ram_handler.setFormatter(ram_fmt)
|
|
|
|
ram_handler.html_formatter = html_fmt
|
2014-06-06 12:29:33 +02:00
|
|
|
|
|
|
|
return console_handler, ram_handler
|
|
|
|
|
|
|
|
|
2016-08-22 06:59:14 +02:00
|
|
|
def get_console_format(level):
|
2016-08-25 23:17:10 +02:00
|
|
|
"""Get the log format the console logger should use.
|
2016-08-22 06:59:14 +02:00
|
|
|
|
|
|
|
Args:
|
|
|
|
level: The numeric logging level.
|
|
|
|
|
|
|
|
Return:
|
|
|
|
Format of the requested level.
|
|
|
|
"""
|
|
|
|
return EXTENDED_FMT if level <= logging.DEBUG else SIMPLE_FMT
|
|
|
|
|
|
|
|
|
2016-05-25 00:12:51 +02:00
|
|
|
def _init_formatters(level, color, force_color, json_logging):
|
2014-06-06 12:29:33 +02:00
|
|
|
"""Init log formatters.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
level: The numeric logging level.
|
|
|
|
color: Whether to use color if available.
|
2016-04-30 17:01:45 +02:00
|
|
|
force_color: Force colored output.
|
2016-05-25 00:12:51 +02:00
|
|
|
json_logging: Format lines as JSON (disables all color).
|
2014-06-06 12:29:33 +02:00
|
|
|
|
|
|
|
Return:
|
|
|
|
A (console_formatter, ram_formatter, use_colorama) tuple.
|
|
|
|
console_formatter/ram_formatter: logging.Formatter instances.
|
|
|
|
use_colorama: Whether to use colorama.
|
|
|
|
"""
|
2016-08-22 06:59:14 +02:00
|
|
|
console_fmt = get_console_format(level)
|
2016-05-15 10:46:46 +02:00
|
|
|
ram_formatter = ColoredFormatter(EXTENDED_FMT, DATEFMT, '{',
|
|
|
|
use_colors=False)
|
2014-06-25 10:00:27 +02:00
|
|
|
html_formatter = HTMLFormatter(EXTENDED_FMT_HTML, DATEFMT,
|
|
|
|
log_colors=LOG_COLORS)
|
2014-06-19 10:19:00 +02:00
|
|
|
if sys.stderr is None:
|
2014-06-25 10:00:27 +02:00
|
|
|
return None, ram_formatter, html_formatter, False
|
2016-04-30 18:38:20 +02:00
|
|
|
|
2016-05-25 00:12:51 +02:00
|
|
|
if json_logging:
|
|
|
|
console_formatter = JSONFormatter()
|
|
|
|
return console_formatter, ram_formatter, html_formatter, False
|
|
|
|
|
2014-06-06 12:29:33 +02:00
|
|
|
use_colorama = False
|
2016-05-13 21:08:50 +02:00
|
|
|
color_supported = os.name == 'posix' or colorama
|
|
|
|
|
2016-04-30 18:38:20 +02:00
|
|
|
if color_supported and (sys.stderr.isatty() or force_color) and color:
|
2016-05-13 21:08:50 +02:00
|
|
|
use_colors = True
|
2016-04-30 16:59:59 +02:00
|
|
|
if colorama and os.name != 'posix':
|
2014-06-06 12:29:33 +02:00
|
|
|
use_colorama = True
|
2014-05-25 20:08:07 +02:00
|
|
|
else:
|
2016-05-13 21:08:50 +02:00
|
|
|
use_colors = False
|
|
|
|
|
|
|
|
console_formatter = ColoredFormatter(console_fmt, DATEFMT, '{',
|
|
|
|
use_colors=use_colors)
|
2014-06-25 10:00:27 +02:00
|
|
|
return console_formatter, ram_formatter, html_formatter, use_colorama
|
2014-06-02 22:43:32 +02:00
|
|
|
|
|
|
|
|
2016-08-22 06:59:14 +02:00
|
|
|
def change_console_formatter(level):
|
|
|
|
"""Change console formatter based on level.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
level: The numeric logging level
|
|
|
|
"""
|
|
|
|
if not isinstance(console_handler.formatter, ColoredFormatter):
|
|
|
|
# JSON Formatter being used for end2end tests
|
|
|
|
pass
|
|
|
|
|
|
|
|
use_colors = console_handler.formatter.use_colors
|
|
|
|
console_fmt = get_console_format(level)
|
|
|
|
console_formatter = ColoredFormatter(console_fmt, DATEFMT, '{',
|
|
|
|
use_colors=use_colors)
|
|
|
|
console_handler.setFormatter(console_formatter)
|
|
|
|
|
|
|
|
|
2014-06-02 22:43:32 +02:00
|
|
|
def qt_message_handler(msg_type, context, msg):
|
|
|
|
"""Qt message handler to redirect qWarning etc. to the logging system.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
QtMsgType msg_type: The level of the message.
|
|
|
|
QMessageLogContext context: The source code location of the message.
|
|
|
|
msg: The message text.
|
|
|
|
"""
|
|
|
|
# Mapping from Qt logging levels to the matching logging module levels.
|
|
|
|
# Note we map critical to ERROR as it's actually "just" an error, and fatal
|
|
|
|
# to critical.
|
2014-06-03 14:34:38 +02:00
|
|
|
qt_to_logging = {
|
2015-05-15 20:15:09 +02:00
|
|
|
QtCore.QtDebugMsg: logging.DEBUG,
|
|
|
|
QtCore.QtWarningMsg: logging.WARNING,
|
|
|
|
QtCore.QtCriticalMsg: logging.ERROR,
|
|
|
|
QtCore.QtFatalMsg: logging.CRITICAL,
|
2014-06-02 22:43:32 +02:00
|
|
|
}
|
2015-05-15 20:15:09 +02:00
|
|
|
try:
|
2015-12-01 23:01:09 +01:00
|
|
|
# pylint: disable=no-member,useless-suppression
|
2015-05-15 20:15:09 +02:00
|
|
|
qt_to_logging[QtCore.QtInfoMsg] = logging.INFO
|
|
|
|
except AttributeError:
|
|
|
|
# Qt < 5.5
|
|
|
|
pass
|
2015-09-09 22:53:37 +02:00
|
|
|
|
2014-06-03 17:18:20 +02:00
|
|
|
# Change levels of some well-known messages to debug so they don't get
|
|
|
|
# shown to the user.
|
2015-09-09 22:53:37 +02:00
|
|
|
#
|
|
|
|
# If a message starts with any text in suppressed_msgs, it's not logged as
|
|
|
|
# error.
|
2015-09-09 22:50:43 +02:00
|
|
|
suppressed_msgs = [
|
2014-08-12 06:58:37 +02:00
|
|
|
# PNGs in Qt with broken color profile
|
2015-06-12 16:59:33 +02:00
|
|
|
# https://bugreports.qt.io/browse/QTBUG-39788
|
2015-09-09 22:53:37 +02:00
|
|
|
'libpng warning: iCCP: Not recognizing known sRGB profile that has '
|
2016-01-21 22:53:53 +01:00
|
|
|
'been edited', # flake8: disable=E131
|
2015-11-24 16:59:35 +01:00
|
|
|
'libpng warning: iCCP: known incorrect sRGB profile',
|
2014-08-12 06:58:37 +02:00
|
|
|
# Hopefully harmless warning
|
2015-09-09 22:53:37 +02:00
|
|
|
'OpenType support missing for script ',
|
2014-08-12 06:58:37 +02:00
|
|
|
# Error if a QNetworkReply gets two different errors set. Harmless Qt
|
|
|
|
# bug on some pages.
|
2015-06-12 16:59:33 +02:00
|
|
|
# https://bugreports.qt.io/browse/QTBUG-30298
|
2015-09-09 22:53:37 +02:00
|
|
|
'QNetworkReplyImplPrivate::error: Internal problem, this method must '
|
|
|
|
'only be called once.',
|
2014-11-16 16:25:37 +01:00
|
|
|
# Sometimes indicates missing text, but most of the time harmless
|
2015-09-09 22:53:37 +02:00
|
|
|
'load glyph failed ',
|
2015-06-12 16:59:33 +02:00
|
|
|
# Harmless, see https://bugreports.qt.io/browse/QTBUG-42479
|
2015-09-09 22:53:37 +02:00
|
|
|
'content-type missing in HTTP POST, defaulting to '
|
|
|
|
'application/x-www-form-urlencoded. '
|
|
|
|
'Use QNetworkRequest::setHeader() to fix this problem.',
|
2015-06-12 16:59:33 +02:00
|
|
|
# https://bugreports.qt.io/browse/QTBUG-43118
|
2015-09-09 22:53:37 +02:00
|
|
|
'Using blocking call!',
|
2015-01-07 06:59:48 +01:00
|
|
|
# Hopefully harmless
|
|
|
|
'"Method "GetAll" with signature "s" on interface '
|
2015-09-09 22:53:37 +02:00
|
|
|
'"org.freedesktop.DBus.Properties" doesn\'t exist',
|
2016-01-24 19:12:43 +01:00
|
|
|
'"Method \\"GetAll\\" with signature \\"s\\" on interface '
|
|
|
|
'\\"org.freedesktop.DBus.Properties\\" doesn\'t exist\\n"',
|
2015-05-31 00:11:33 +02:00
|
|
|
'WOFF support requires QtWebKit to be built with zlib support.',
|
|
|
|
# Weird Enlightment/GTK X extensions
|
|
|
|
'QXcbWindow: Unhandled client message: "_E_',
|
|
|
|
'QXcbWindow: Unhandled client message: "_ECORE_',
|
|
|
|
'QXcbWindow: Unhandled client message: "_GTK_',
|
2015-06-10 18:30:42 +02:00
|
|
|
# Happens on AppVeyor CI
|
|
|
|
'SetProcessDpiAwareness failed:',
|
2015-11-20 21:56:01 +01:00
|
|
|
# https://bugreports.qt.io/browse/QTBUG-49174
|
|
|
|
'QObject::connect: Cannot connect (null)::stateChanged('
|
|
|
|
'QNetworkSession::State) to '
|
|
|
|
'QNetworkReplyHttpImpl::_q_networkSessionStateChanged('
|
|
|
|
'QNetworkSession::State)',
|
2016-06-28 20:46:06 +02:00
|
|
|
# https://bugreports.qt.io/browse/QTBUG-53989
|
|
|
|
"Image of format '' blocked because it is not considered safe. If you "
|
|
|
|
"are sure it is safe to do so, you can white-list the format by "
|
|
|
|
"setting the environment variable QTWEBKIT_IMAGEFORMAT_WHITELIST=",
|
2016-07-23 12:01:56 +02:00
|
|
|
# Installing Qt from the installer may cause it looking for SSL3 which
|
|
|
|
# may not be available on the system
|
|
|
|
"QSslSocket: cannot resolve SSLv3_client_method",
|
|
|
|
"QSslSocket: cannot resolve SSLv3_server_method",
|
2016-08-04 12:09:16 +02:00
|
|
|
# When enabling debugging with QtWebEngine
|
|
|
|
"Remote debugging server started successfully. Try pointing a "
|
|
|
|
"Chromium-based browser to ",
|
2016-08-05 08:31:44 +02:00
|
|
|
# https://github.com/The-Compiler/qutebrowser/issues/1287
|
|
|
|
"QXcbClipboard: SelectionRequest too old",
|
2015-09-09 22:50:43 +02:00
|
|
|
]
|
|
|
|
if sys.platform == 'darwin':
|
|
|
|
suppressed_msgs += [
|
2015-09-09 22:53:37 +02:00
|
|
|
'libpng warning: iCCP: known incorrect sRGB profile',
|
|
|
|
# https://bugreports.qt.io/browse/QTBUG-47154
|
|
|
|
'virtual void QSslSocketBackendPrivate::transmit() SSLRead failed '
|
2016-01-21 22:53:53 +01:00
|
|
|
'with: -9805', # flake8: disable=E131
|
2015-09-09 22:50:43 +02:00
|
|
|
]
|
|
|
|
|
2015-10-16 18:26:34 +02:00
|
|
|
# Messages which will trigger an exception immediately
|
|
|
|
critical_msgs = [
|
|
|
|
'Could not parse stylesheet of object',
|
|
|
|
]
|
|
|
|
|
|
|
|
if any(msg.strip().startswith(pattern) for pattern in critical_msgs):
|
|
|
|
# For some reason, the stack gets lost when raising here...
|
|
|
|
logger = logging.getLogger('misc')
|
|
|
|
logger.error("Got critical Qt warning!", stack_info=True)
|
|
|
|
raise CriticalQtWarning(msg)
|
|
|
|
elif any(msg.strip().startswith(pattern) for pattern in suppressed_msgs):
|
2014-06-03 17:18:20 +02:00
|
|
|
level = logging.DEBUG
|
|
|
|
else:
|
|
|
|
level = qt_to_logging[msg_type]
|
2015-11-09 19:55:05 +01:00
|
|
|
|
2014-11-09 22:57:39 +01:00
|
|
|
if context.function is None:
|
|
|
|
func = 'none'
|
2015-11-09 19:55:05 +01:00
|
|
|
elif ':' in context.function:
|
|
|
|
func = '"{}"'.format(context.function)
|
2014-06-03 06:54:55 +02:00
|
|
|
else:
|
2014-11-19 22:43:49 +01:00
|
|
|
func = context.function
|
2015-11-09 19:55:05 +01:00
|
|
|
|
2014-12-22 17:33:35 +01:00
|
|
|
if context.category is None or context.category == 'default':
|
|
|
|
name = 'qt'
|
|
|
|
else:
|
|
|
|
name = 'qt-' + context.category
|
2014-06-10 23:11:17 +02:00
|
|
|
if msg.splitlines()[0] == ('This application failed to start because it '
|
|
|
|
'could not find or load the Qt platform plugin '
|
|
|
|
'"xcb".'):
|
|
|
|
# Handle this message specially.
|
|
|
|
msg += ("\n\nOn Archlinux, this should fix the problem:\n"
|
|
|
|
" pacman -S libxkbcommon-x11")
|
2014-11-09 22:59:14 +01:00
|
|
|
faulthandler.disable()
|
2015-03-06 16:33:22 +01:00
|
|
|
stack = ''.join(traceback.format_stack())
|
2014-06-03 17:18:20 +02:00
|
|
|
record = qt.makeRecord(name, level, context.file, context.line, msg, None,
|
2015-02-24 07:12:25 +01:00
|
|
|
None, func, sinfo=stack)
|
2014-06-02 22:43:32 +02:00
|
|
|
qt.handle(record)
|
2014-06-02 22:19:35 +02:00
|
|
|
|
2014-05-23 16:57:08 +02:00
|
|
|
|
2015-03-06 16:32:26 +01:00
|
|
|
@contextlib.contextmanager
|
|
|
|
def hide_qt_warning(pattern, logger='qt'):
|
|
|
|
"""Hide Qt warnings matching the given regex."""
|
|
|
|
log_filter = QtWarningFilter(pattern)
|
|
|
|
logger_obj = logging.getLogger(logger)
|
|
|
|
logger_obj.addFilter(log_filter)
|
2015-06-15 06:18:16 +02:00
|
|
|
try:
|
|
|
|
yield
|
|
|
|
finally:
|
|
|
|
logger_obj.removeFilter(log_filter)
|
2015-03-06 16:32:26 +01:00
|
|
|
|
|
|
|
|
|
|
|
class QtWarningFilter(logging.Filter):
|
|
|
|
|
|
|
|
"""Filter to filter Qt warnings.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
_pattern: The start of the message.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, pattern):
|
|
|
|
super().__init__()
|
|
|
|
self._pattern = pattern
|
|
|
|
|
|
|
|
def filter(self, record):
|
|
|
|
"""Determine if the specified record is to be logged."""
|
2015-11-13 22:27:41 +01:00
|
|
|
do_log = not record.msg.strip().startswith(self._pattern)
|
|
|
|
return do_log
|
2015-03-06 16:32:26 +01:00
|
|
|
|
|
|
|
|
2014-05-23 16:57:08 +02:00
|
|
|
class LogFilter(logging.Filter):
|
|
|
|
|
|
|
|
"""Filter to filter log records based on the commandline argument.
|
|
|
|
|
|
|
|
The default Filter only supports one name to show - we support a
|
|
|
|
comma-separated list instead.
|
|
|
|
|
|
|
|
Attributes:
|
2014-09-24 22:17:53 +02:00
|
|
|
_names: A list of names that should be logged.
|
2014-05-23 16:57:08 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, names):
|
|
|
|
super().__init__()
|
2016-08-19 15:08:15 +02:00
|
|
|
self.names = names
|
2014-05-23 16:57:08 +02:00
|
|
|
|
|
|
|
def filter(self, record):
|
|
|
|
"""Determine if the specified record is to be logged."""
|
2016-08-19 15:08:15 +02:00
|
|
|
if self.names is None:
|
2014-05-23 16:57:08 +02:00
|
|
|
return True
|
2014-06-06 16:51:24 +02:00
|
|
|
if record.levelno > logging.DEBUG:
|
|
|
|
# More important than DEBUG, so we won't filter at all
|
2014-06-06 15:48:42 +02:00
|
|
|
return True
|
2016-08-19 15:08:15 +02:00
|
|
|
for name in self.names:
|
2014-05-23 16:57:08 +02:00
|
|
|
if record.name == name:
|
|
|
|
return True
|
|
|
|
elif not record.name.startswith(name):
|
|
|
|
continue
|
|
|
|
elif record.name[len(name)] == '.':
|
|
|
|
return True
|
|
|
|
return False
|
2014-05-25 18:46:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
class RAMHandler(logging.Handler):
|
|
|
|
|
|
|
|
"""Logging handler which keeps the messages in a deque in RAM.
|
|
|
|
|
2015-06-07 01:11:16 +02:00
|
|
|
Loosely based on logging.BufferingHandler which is unsuitable because it
|
2014-05-25 18:46:15 +02:00
|
|
|
uses a simple list rather than a deque.
|
|
|
|
|
|
|
|
Attributes:
|
2014-09-24 22:17:53 +02:00
|
|
|
_data: A deque containing the logging records.
|
2014-05-25 18:46:15 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, capacity):
|
|
|
|
super().__init__()
|
2014-06-25 10:00:27 +02:00
|
|
|
self.html_formatter = None
|
2014-06-19 12:06:36 +02:00
|
|
|
if capacity != -1:
|
2014-09-24 22:17:53 +02:00
|
|
|
self._data = collections.deque(maxlen=capacity)
|
2014-06-19 12:06:36 +02:00
|
|
|
else:
|
2014-09-24 22:17:53 +02:00
|
|
|
self._data = collections.deque()
|
2014-05-25 18:46:15 +02:00
|
|
|
|
|
|
|
def emit(self, record):
|
2014-08-29 07:33:37 +02:00
|
|
|
if record.levelno >= logging.DEBUG:
|
|
|
|
# We don't log VDEBUG to RAM.
|
2014-09-24 22:17:53 +02:00
|
|
|
self._data.append(record)
|
2014-05-25 18:46:15 +02:00
|
|
|
|
2016-05-12 03:23:38 +02:00
|
|
|
def dump_log(self, html=False, level='vdebug'):
|
2016-05-27 12:07:00 +02:00
|
|
|
"""Dump the complete formatted log data as string.
|
2014-08-29 06:58:13 +02:00
|
|
|
|
|
|
|
FIXME: We should do all the HTML formatter via jinja2.
|
2014-10-01 22:23:27 +02:00
|
|
|
(probably obsolete when moving to a widget for logging,
|
|
|
|
https://github.com/The-Compiler/qutebrowser/issues/34
|
2014-08-29 06:58:13 +02:00
|
|
|
"""
|
2016-05-12 13:47:20 +02:00
|
|
|
minlevel = LOG_LEVELS.get(level.upper(), VDEBUG_LEVEL)
|
2014-08-29 06:58:13 +02:00
|
|
|
lines = []
|
|
|
|
fmt = self.html_formatter.format if html else self.format
|
2014-08-05 23:07:25 +02:00
|
|
|
self.acquire()
|
|
|
|
try:
|
2014-09-24 22:17:53 +02:00
|
|
|
records = list(self._data)
|
2014-08-05 23:07:25 +02:00
|
|
|
finally:
|
|
|
|
self.release()
|
|
|
|
for record in records:
|
2016-05-12 03:23:38 +02:00
|
|
|
if record.levelno >= minlevel:
|
|
|
|
lines.append(fmt(record))
|
2014-05-25 18:46:15 +02:00
|
|
|
return '\n'.join(lines)
|
2014-06-25 10:00:27 +02:00
|
|
|
|
2016-08-03 19:18:51 +02:00
|
|
|
def change_log_capacity(self, capacity):
|
|
|
|
self._data = collections.deque(self._data, maxlen=capacity)
|
|
|
|
|
2014-06-25 10:00:27 +02:00
|
|
|
|
2016-05-13 21:08:50 +02:00
|
|
|
class ColoredFormatter(logging.Formatter):
|
|
|
|
|
|
|
|
"""Logging formatter to output colored logs.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
use_colors: Whether to do colored logging or not.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, fmt, datefmt, style, *, use_colors):
|
|
|
|
super().__init__(fmt, datefmt, style)
|
2016-08-22 06:59:14 +02:00
|
|
|
self.use_colors = use_colors
|
2016-05-13 21:08:50 +02:00
|
|
|
|
|
|
|
def format(self, record):
|
2016-08-22 06:59:14 +02:00
|
|
|
if self.use_colors:
|
2016-06-06 23:09:19 +02:00
|
|
|
color_dict = dict(COLOR_ESCAPES)
|
|
|
|
color_dict['reset'] = RESET_ESCAPE
|
2016-05-13 21:08:50 +02:00
|
|
|
log_color = LOG_COLORS[record.levelname]
|
2016-06-06 23:09:19 +02:00
|
|
|
color_dict['log_color'] = COLOR_ESCAPES[log_color]
|
2016-05-13 21:08:50 +02:00
|
|
|
else:
|
2016-06-06 23:09:19 +02:00
|
|
|
color_dict = {color: '' for color in COLOR_ESCAPES}
|
2016-05-13 21:08:50 +02:00
|
|
|
color_dict['reset'] = ''
|
|
|
|
color_dict['log_color'] = ''
|
|
|
|
record.__dict__.update(color_dict)
|
|
|
|
return super().format(record)
|
|
|
|
|
|
|
|
|
2014-06-25 10:00:27 +02:00
|
|
|
class HTMLFormatter(logging.Formatter):
|
|
|
|
|
2016-05-13 21:08:50 +02:00
|
|
|
"""Formatter for HTML-colored log messages.
|
2014-09-24 22:22:02 +02:00
|
|
|
|
|
|
|
Attributes:
|
|
|
|
_log_colors: The colors to use for logging levels.
|
|
|
|
_colordict: The colordict passed to the logger.
|
|
|
|
"""
|
2014-06-25 10:00:27 +02:00
|
|
|
|
|
|
|
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.
|
2016-05-13 21:08:50 +02:00
|
|
|
for color in COLORS:
|
2014-06-25 10:00:27 +02:00
|
|
|
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 = ''
|
2014-06-25 21:11:03 +02:00
|
|
|
for field in ['msg', 'filename', 'funcName', 'levelname', 'module',
|
|
|
|
'name', 'pathname', 'processName', 'threadName']:
|
|
|
|
data = str(getattr(record, field))
|
2014-08-05 23:23:15 +02:00
|
|
|
setattr(record, field, pyhtml.escape(data))
|
2015-04-13 08:52:58 +02:00
|
|
|
msg = super().format(record)
|
|
|
|
if not msg.endswith(self._colordict['reset']):
|
|
|
|
msg += self._colordict['reset']
|
|
|
|
return msg
|
2014-06-25 21:11:03 +02:00
|
|
|
|
|
|
|
def formatTime(self, record, datefmt=None):
|
|
|
|
out = super().formatTime(record, datefmt)
|
2014-08-05 23:23:15 +02:00
|
|
|
return pyhtml.escape(out)
|
2016-05-25 00:12:51 +02:00
|
|
|
|
|
|
|
|
|
|
|
class JSONFormatter(logging.Formatter):
|
|
|
|
|
|
|
|
"""Formatter for JSON-encoded log messages."""
|
|
|
|
|
|
|
|
def format(self, record):
|
|
|
|
obj = {}
|
2016-09-06 16:22:31 +02:00
|
|
|
for field in ['created', 'msecs', 'levelname', 'name', 'module',
|
|
|
|
'funcName', 'lineno', 'levelno']:
|
2016-05-25 00:12:51 +02:00
|
|
|
obj[field] = getattr(record, field)
|
|
|
|
obj['message'] = record.getMessage()
|
2016-05-25 21:57:55 +02:00
|
|
|
if record.exc_info is not None:
|
|
|
|
obj['traceback'] = super().formatException(record.exc_info)
|
2016-05-25 00:12:51 +02:00
|
|
|
return json.dumps(obj)
|