Get rid of the colorlog dependency

colorlog was problematic for various reasons:

- Not commonly packaged for Linux distributions
- Calling colorama.init() automatically on import
- Not supporting {foo} log formatting
- Not supporting an easy way to turn colors off

Instead we now do the log coloring by hand, which is simpler and means
everyone will have colored logs.
This commit is contained in:
Florian Bruhin 2016-05-13 21:08:50 +02:00
parent a9a853baf0
commit c64e5c9bd5
7 changed files with 70 additions and 64 deletions

View File

@ -46,6 +46,8 @@ Changed
not be.
- Show favicons as window icon with `tabs-are-windows` set.
- `:bind <key>` without a command now shows the existing binding
- The optional `colorlog` dependency got removed, as qutebrowser now displays
colored logs without it.
Fixed
-----

View File

@ -212,7 +212,6 @@ Documentation of used Python libraries:
* http://pythonhosted.org/setuptools/[setuptools]
* http://cx-freeze.readthedocs.org/en/latest/overview.html[cx_Freeze]
* https://pypi.python.org/pypi/colorama[colorama]
* https://pypi.python.org/pypi/colorlog[colorlog]
Related RFCs and standards:

View File

@ -109,11 +109,8 @@ The following libraries are optional and provide a better user experience:
To generate the documentation for the `:help` command, when using the git
repository (rather than a release), http://asciidoc.org/[asciidoc] is needed.
The following libraries are optional and provide colored logging in the
console:
* https://pypi.python.org/pypi/colorlog/[colorlog]
* On Windows: https://pypi.python.org/pypi/colorama/[colorama]
On Windows, https://pypi.python.org/pypi/colorama/[colorama] is needed to
display colored log output.
See link:INSTALL.asciidoc[INSTALL] for directions on how to install qutebrowser
and its dependencies.

View File

@ -35,33 +35,16 @@ try:
import colorama
except ImportError:
colorama = None
try:
import colorlog
except ImportError:
colorlog = None
else:
# WORKAROUND
# colorlog calls colorama.init() which we don't want, also it breaks our
# sys.stdout/sys.stderr if they are None. Bugreports:
# https://code.google.com/p/colorama/issues/detail?id=61
# https://github.com/borntyping/python-colorlog/issues/13
# This should be (partially) fixed in colorama 0.3.2.
# (stream only gets wrapped if it's not None)
if colorama is not None:
colorama.deinit()
COLORS = ['black', 'red', 'green', 'yellow', 'blue', 'purple', 'cyan', 'white']
# Log formats to use.
SIMPLE_FMT = '{asctime:8} {levelname}: {message}'
EXTENDED_FMT = ('{asctime:8} {levelname:8} {name:10} {module}:{funcName}:'
'{lineno} {message}')
SIMPLE_FMT_COLORED = ('%(green)s%(asctime)-8s%(reset)s '
'%(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 '
'%(cyan)s%(name)-10s %(module)s:%(funcName)s:%(lineno)s%(reset)s '
'%(log_color)s%(message)s%(reset)s'
)
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}')
EXTENDED_FMT_HTML = (
'<tr>'
'<td><pre>%(green)s%(asctime)-8s%(reset)s</pre></td>'
@ -223,12 +206,7 @@ def _init_formatters(level, color, force_color):
console_formatter/ram_formatter: logging.Formatter instances.
use_colorama: Whether to use colorama.
"""
if level <= logging.DEBUG:
console_fmt = EXTENDED_FMT
console_fmt_colored = EXTENDED_FMT_COLORED
else:
console_fmt = SIMPLE_FMT
console_fmt_colored = SIMPLE_FMT_COLORED
console_fmt = EXTENDED_FMT if level <= logging.DEBUG else SIMPLE_FMT
ram_formatter = logging.Formatter(EXTENDED_FMT, DATEFMT, '{')
html_formatter = HTMLFormatter(EXTENDED_FMT_HTML, DATEFMT,
log_colors=LOG_COLORS)
@ -236,14 +214,17 @@ def _init_formatters(level, color, force_color):
return None, ram_formatter, html_formatter, False
use_colorama = False
color_supported = colorlog is not None and (os.name == 'posix' or colorama)
color_supported = os.name == 'posix' or colorama
if color_supported and (sys.stderr.isatty() or force_color) and color:
console_formatter = colorlog.ColoredFormatter(
console_fmt_colored, DATEFMT, log_colors=LOG_COLORS)
use_colors = True
if colorama and os.name != 'posix':
use_colorama = True
else:
console_formatter = logging.Formatter(console_fmt, DATEFMT, '{')
use_colors = False
console_formatter = ColoredFormatter(console_fmt, DATEFMT, '{',
use_colors=use_colors)
return console_formatter, ram_formatter, html_formatter, use_colorama
@ -467,9 +448,43 @@ class RAMHandler(logging.Handler):
return '\n'.join(lines)
class ColoredFormatter(logging.Formatter):
"""Logging formatter to output colored logs.
Attributes:
use_colors: Whether to do colored logging or not.
Class attributes:
COLOR_ESCAPES: A dict mapping color names to escape sequences
RESET_ESCAPE: The escape sequence using for resetting colors
"""
COLOR_ESCAPES = {color: '\033[{}m'.format(i)
for i, color in enumerate(COLORS, start=30)}
RESET_ESCAPE = '\033[0m'
def __init__(self, fmt, datefmt, style, *, use_colors):
super().__init__(fmt, datefmt, style)
self._use_colors = use_colors
def format(self, record):
if self._use_colors:
color_dict = dict(self.COLOR_ESCAPES)
color_dict['reset'] = self.RESET_ESCAPE
log_color = LOG_COLORS[record.levelname]
color_dict['log_color'] = self.COLOR_ESCAPES[log_color]
else:
color_dict = {color: '' for color in self.COLOR_ESCAPES}
color_dict['reset'] = ''
color_dict['log_color'] = ''
record.__dict__.update(color_dict)
return super().format(record)
class HTMLFormatter(logging.Formatter):
"""Formatter for HTML-colored log messages, similar to colorlog.
"""Formatter for HTML-colored log messages.
Attributes:
_log_colors: The colors to use for logging levels.
@ -489,8 +504,7 @@ class HTMLFormatter(logging.Formatter):
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']:
for color in COLORS:
self._colordict[color] = '<font color="{}">'.format(color)
self._colordict['reset'] = '</font>'

View File

@ -129,7 +129,6 @@ def _module_versions():
lines = []
modules = collections.OrderedDict([
('sip', ['SIP_VERSION_STR']),
('colorlog', []),
('colorama', ['VERSION', '__version__']),
('pypeg2', ['__version__']),
('jinja2', ['__version__']),

View File

@ -4,5 +4,4 @@ Pygments==2.1.3
pyPEG2==2.15.2
PyYAML==3.11
colorama==0.3.7
colorlog==2.6.3
cssutils==1.0.1

View File

@ -319,7 +319,6 @@ class ImportFake:
def __init__(self):
self.exists = {
'sip': True,
'colorlog': True,
'colorama': True,
'pypeg2': True,
'jinja2': True,
@ -383,15 +382,14 @@ class TestModuleVersions:
@pytest.mark.usefixtures('import_fake')
def test_all_present(self):
"""Test with all modules present in version 1.2.3."""
expected = ['sip: yes', 'colorlog: yes', 'colorama: 1.2.3',
'pypeg2: 1.2.3', 'jinja2: 1.2.3', 'pygments: 1.2.3',
'yaml: 1.2.3', 'cssutils: 1.2.3']
expected = ['sip: yes', 'colorama: 1.2.3', 'pypeg2: 1.2.3',
'jinja2: 1.2.3', 'pygments: 1.2.3', 'yaml: 1.2.3',
'cssutils: 1.2.3']
assert version._module_versions() == expected
@pytest.mark.parametrize('module, idx, expected', [
('colorlog', 1, 'colorlog: no'),
('colorama', 2, 'colorama: no'),
('cssutils', 7, 'cssutils: no'),
('colorama', 1, 'colorama: no'),
('cssutils', 6, 'cssutils: no'),
])
def test_missing_module(self, module, idx, expected, import_fake):
"""Test with a module missing.
@ -405,15 +403,14 @@ class TestModuleVersions:
assert version._module_versions()[idx] == expected
@pytest.mark.parametrize('value, expected', [
('VERSION', ['sip: yes', 'colorlog: yes', 'colorama: 1.2.3',
'pypeg2: yes', 'jinja2: yes', 'pygments: yes',
'yaml: yes', 'cssutils: yes']),
('SIP_VERSION_STR', ['sip: 1.2.3', 'colorlog: yes', 'colorama: yes',
'pypeg2: yes', 'jinja2: yes', 'pygments: yes',
'yaml: yes', 'cssutils: yes']),
(None, ['sip: yes', 'colorlog: yes', 'colorama: yes', 'pypeg2: yes',
'jinja2: yes', 'pygments: yes', 'yaml: yes',
'cssutils: yes']),
('VERSION', ['sip: yes', 'colorama: 1.2.3', 'pypeg2: yes',
'jinja2: yes', 'pygments: yes', 'yaml: yes',
'cssutils: yes']),
('SIP_VERSION_STR', ['sip: 1.2.3', 'colorama: yes', 'pypeg2: yes',
'jinja2: yes', 'pygments: yes', 'yaml: yes',
'cssutils: yes']),
(None, ['sip: yes', 'colorama: yes', 'pypeg2: yes', 'jinja2: yes',
'pygments: yes', 'yaml: yes', 'cssutils: yes']),
])
def test_version_attribute(self, value, expected, import_fake):
"""Test with a different version attribute.
@ -429,7 +426,6 @@ class TestModuleVersions:
assert version._module_versions() == expected
@pytest.mark.parametrize('name, has_version', [
('colorlog', False),
('sip', False),
('colorama', True),
('pypeg2', True),
@ -442,7 +438,7 @@ class TestModuleVersions:
"""Check if all dependencies have an expected __version__ attribute.
The aim of this test is to fail if modules suddenly don't have a
__version__ attribute anymore in a newer version, or colorlog has one.
__version__ attribute anymore in a newer version.
Args:
name: The name of the module to check.