diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index ba3cfd92c..48eca3e7a 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -46,6 +46,8 @@ Changed not be. - Show favicons as window icon with `tabs-are-windows` set. - `:bind ` without a command now shows the existing binding +- The optional `colorlog` dependency got removed, as qutebrowser now displays + colored logs without it. Fixed ----- diff --git a/CONTRIBUTING.asciidoc b/CONTRIBUTING.asciidoc index ef1c3e85f..edd1a5f74 100644 --- a/CONTRIBUTING.asciidoc +++ b/CONTRIBUTING.asciidoc @@ -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: diff --git a/README.asciidoc b/README.asciidoc index 839b80c1f..eb9f74213 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -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. diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 3ef1b11b2..577383e1b 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -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 = ( '' '
%(green)s%(asctime)-8s%(reset)s
' @@ -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] = ''.format(color) self._colordict['reset'] = '' diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index e2ca84fe2..0e67d19c3 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -129,7 +129,6 @@ def _module_versions(): lines = [] modules = collections.OrderedDict([ ('sip', ['SIP_VERSION_STR']), - ('colorlog', []), ('colorama', ['VERSION', '__version__']), ('pypeg2', ['__version__']), ('jinja2', ['__version__']), diff --git a/requirements.txt b/requirements.txt index 7d481cda7..196950578 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py index 6c62fc0f9..68ab81a48 100644 --- a/tests/unit/utils/test_version.py +++ b/tests/unit/utils/test_version.py @@ -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.