2014-05-23 16:11:55 +02:00
|
|
|
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
|
|
|
#
|
|
|
|
# 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-06-02 22:43:32 +02:00
|
|
|
import re
|
2014-05-25 20:08:07 +02:00
|
|
|
import os
|
|
|
|
import sys
|
2014-05-23 16:57:08 +02:00
|
|
|
import logging
|
2014-05-23 16:11:55 +02:00
|
|
|
from logging import getLogger
|
2014-05-25 18:46:15 +02:00
|
|
|
from collections import deque
|
2014-05-23 16:11:55 +02:00
|
|
|
|
2014-06-02 22:43:32 +02:00
|
|
|
from PyQt5.QtCore import (QtDebugMsg, QtWarningMsg, QtCriticalMsg, QtFatalMsg,
|
|
|
|
qInstallMessageHandler)
|
2014-05-25 20:00:48 +02:00
|
|
|
try:
|
|
|
|
# pylint: disable=import-error
|
|
|
|
from colorlog import ColoredFormatter
|
|
|
|
except ImportError:
|
|
|
|
ColoredFormatter = None
|
|
|
|
|
2014-05-23 16:11:55 +02:00
|
|
|
# The different loggers used.
|
|
|
|
|
|
|
|
statusbar = getLogger('statusbar')
|
|
|
|
completion = getLogger('completion')
|
|
|
|
destroy = getLogger('destroy')
|
|
|
|
modes = getLogger('modes')
|
|
|
|
webview = getLogger('webview')
|
|
|
|
mouse = getLogger('mouse')
|
|
|
|
misc = getLogger('misc')
|
|
|
|
url = getLogger('url')
|
|
|
|
procs = getLogger('procs')
|
|
|
|
commands = getLogger('commands')
|
|
|
|
init = getLogger('init')
|
|
|
|
signals = getLogger('signals')
|
|
|
|
hints = getLogger('hints')
|
|
|
|
keyboard = getLogger('keyboard')
|
2014-05-26 20:04:44 +02:00
|
|
|
js = getLogger('js')
|
2014-06-02 22:43:32 +02:00
|
|
|
qt = getLogger('qt')
|
2014-05-23 16:57:08 +02:00
|
|
|
|
|
|
|
|
2014-05-25 18:46:15 +02:00
|
|
|
ram_handler = None
|
|
|
|
|
|
|
|
|
2014-05-23 16:57:08 +02:00
|
|
|
def init_log(args):
|
|
|
|
"""Init loggers based on the argparse namespace passed."""
|
2014-05-25 18:46:15 +02:00
|
|
|
global ram_handler
|
2014-05-23 16:57:08 +02:00
|
|
|
logfilter = LogFilter(None if args.logfilter is None
|
|
|
|
else args.logfilter.split(','))
|
2014-05-23 18:22:22 +02:00
|
|
|
level = 'DEBUG' if args.debug else args.loglevel.upper()
|
|
|
|
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-05-25 20:00:48 +02:00
|
|
|
simple_fmt = '{levelname}: {message}'
|
2014-05-25 20:13:58 +02:00
|
|
|
extended_fmt = ('{asctime:8} {levelname:8} {name:10} {module}:{funcName}:'
|
|
|
|
'{lineno} {message}')
|
2014-05-25 20:00:48 +02:00
|
|
|
simple_fmt_colored = '%(log_color)s%(levelname)s%(reset)s: %(message)s'
|
2014-05-25 20:13:58 +02:00
|
|
|
extended_fmt_colored = ('%(green)s%(asctime)-8s%(reset)s '
|
|
|
|
'%(log_color)s%(levelname)-8s%(reset)s '
|
|
|
|
'%(yellow)s%(name)-10s %(module)s:%(funcName)s:'
|
2014-05-25 20:00:48 +02:00
|
|
|
'%(lineno)s%(reset)s %(message)s')
|
|
|
|
datefmt = '%H:%M:%S'
|
2014-05-25 18:46:15 +02:00
|
|
|
|
2014-05-23 18:22:22 +02:00
|
|
|
if numeric_level <= logging.DEBUG:
|
2014-05-25 20:08:07 +02:00
|
|
|
console_fmt = extended_fmt
|
|
|
|
console_fmt_colored = extended_fmt_colored
|
2014-05-25 20:00:48 +02:00
|
|
|
else:
|
2014-05-25 20:08:07 +02:00
|
|
|
console_fmt = simple_fmt
|
|
|
|
console_fmt_colored = simple_fmt_colored
|
|
|
|
if (ColoredFormatter is not None and os.name == 'posix' and
|
|
|
|
sys.stderr.isatty() and args.color):
|
|
|
|
console_formatter = ColoredFormatter(
|
|
|
|
console_fmt_colored, datefmt, log_colors={
|
2014-05-25 20:08:37 +02:00
|
|
|
'DEBUG': 'cyan',
|
|
|
|
'INFO': 'green',
|
|
|
|
'WARNING': 'yellow',
|
|
|
|
'ERROR': 'red',
|
2014-05-25 20:00:48 +02:00
|
|
|
'CRITICAL': 'red',
|
|
|
|
}
|
|
|
|
)
|
2014-05-25 20:08:07 +02:00
|
|
|
else:
|
|
|
|
console_formatter = logging.Formatter(console_fmt, datefmt, '{')
|
2014-05-23 16:57:08 +02:00
|
|
|
console_handler = logging.StreamHandler()
|
|
|
|
console_handler.addFilter(logfilter)
|
2014-05-23 18:22:22 +02:00
|
|
|
console_handler.setLevel(level)
|
2014-05-25 18:46:15 +02:00
|
|
|
console_handler.setFormatter(console_formatter)
|
|
|
|
|
2014-05-25 20:00:48 +02:00
|
|
|
ram_formatter = logging.Formatter(extended_fmt, datefmt, '{')
|
2014-05-25 20:28:24 +02:00
|
|
|
ram_handler = RAMHandler(capacity=500)
|
2014-05-25 18:46:15 +02:00
|
|
|
ram_handler.setLevel(logging.NOTSET)
|
|
|
|
ram_handler.setFormatter(ram_formatter)
|
|
|
|
|
2014-05-23 18:22:22 +02:00
|
|
|
root = getLogger()
|
|
|
|
root.addHandler(console_handler)
|
2014-05-25 18:46:15 +02:00
|
|
|
root.addHandler(ram_handler)
|
2014-05-23 18:22:22 +02:00
|
|
|
root.setLevel(logging.NOTSET)
|
2014-05-23 16:57:08 +02:00
|
|
|
|
2014-06-02 22:19:35 +02:00
|
|
|
logging.captureWarnings(True)
|
2014-06-02 22:43:32 +02:00
|
|
|
qInstallMessageHandler(qt_message_handler)
|
|
|
|
|
|
|
|
|
|
|
|
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 = {
|
2014-06-02 22:43:32 +02:00
|
|
|
QtDebugMsg: logging.DEBUG,
|
|
|
|
QtWarningMsg: logging.WARNING,
|
|
|
|
QtCriticalMsg: logging.ERROR,
|
|
|
|
QtFatalMsg: logging.CRITICAL,
|
|
|
|
}
|
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.
|
2014-06-03 17:04:22 +02:00
|
|
|
suppressed_msgs = ["libpng warning: iCCP: Not recognizing known sRGB "
|
|
|
|
"profile that has been edited",
|
|
|
|
"OpenType support missing for script 19"]
|
|
|
|
if msg.strip() in suppressed_msgs:
|
2014-06-03 17:18:20 +02:00
|
|
|
level = logging.DEBUG
|
|
|
|
else:
|
|
|
|
level = qt_to_logging[msg_type]
|
2014-06-02 22:43:32 +02:00
|
|
|
# We get something like "void qt_png_warning(png_structp, png_const_charp)"
|
|
|
|
# from Qt, but only want "qt_png_warning".
|
2014-06-03 17:01:42 +02:00
|
|
|
match = re.match(r'.* (\w*)\(.*\)', context.function)
|
2014-06-03 06:54:55 +02:00
|
|
|
if match is not None:
|
|
|
|
func = match.group(1)
|
|
|
|
else:
|
|
|
|
func = context.function
|
2014-06-02 23:16:09 +02:00
|
|
|
name = 'qt' if context.category == 'default' else 'qt-' + context.category
|
2014-06-03 17:18:20 +02:00
|
|
|
record = qt.makeRecord(name, level, context.file, context.line, msg, None,
|
|
|
|
None, func)
|
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
|
|
|
|
|
|
|
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:
|
|
|
|
names: A list of names that should be logged.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, names):
|
|
|
|
super().__init__()
|
|
|
|
self.names = names
|
|
|
|
|
|
|
|
def filter(self, record):
|
|
|
|
"""Determine if the specified record is to be logged."""
|
|
|
|
if self.names is None:
|
|
|
|
return True
|
|
|
|
for name in self.names:
|
|
|
|
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.
|
|
|
|
|
|
|
|
Loosly based on logging.BufferingHandler which is unsuitable because it
|
|
|
|
uses a simple list rather than a deque.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
data: A deque containing the logging records.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, capacity):
|
|
|
|
super().__init__()
|
|
|
|
self.data = deque(maxlen=capacity)
|
|
|
|
|
|
|
|
def emit(self, record):
|
|
|
|
self.data.append(record)
|
|
|
|
|
|
|
|
def dump_log(self):
|
|
|
|
"""Dump the complete formatted log data as as string."""
|
|
|
|
lines = []
|
|
|
|
for record in self.data:
|
|
|
|
lines.append(self.format(record))
|
|
|
|
return '\n'.join(lines)
|