logging: Add support for per logger colors (fixes: #808)

This commit is contained in:
Thomas Adamcik 2015-02-25 22:16:30 +01:00
parent b11d89d72f
commit 5c833e106b
5 changed files with 55 additions and 27 deletions

View File

@ -53,6 +53,8 @@ v0.20.0 (UNRELEASED)
- Add custom log level ``TRACE`` (numerical level 5), which can be used by - Add custom log level ``TRACE`` (numerical level 5), which can be used by
Mopidy and extensions to log at an even more detailed level than ``DEBUG``. Mopidy and extensions to log at an even more detailed level than ``DEBUG``.
- Add support for per logger color overrides. (Fixes: :issue:`808`)
**Local backend** **Local backend**
- Add cover URL to all scanned files with MusicBrainz album IDs. (Fixes: - Add cover URL to all scanned files with MusicBrainz album IDs. (Fixes:

View File

@ -128,6 +128,14 @@ Logging configuration
The ``loglevels`` config section can be used to change the log level for The ``loglevels`` config section can be used to change the log level for
specific parts of Mopidy during development or debugging. Each key in the specific parts of Mopidy during development or debugging. Each key in the
config section should match the name of a logger. The value is the log config section should match the name of a logger. The value is the log
level to use for that logger, one of ``black``, ``red``, ``green``,
``yellow``, ``blue``, ``magenta``, ``cyan`` or ``white``.
.. confval:: logcolors/*
The ``logcolors`` config section can be used to change the log color for
specific parts of Mopidy during development or debugging. Each key in the
config section should match the name of a logger. The value is the log
level to use for that logger, one of ``debug``, ``info``, ``warning``, level to use for that logger, one of ``debug``, ``info``, ``warning``,
``error``, or ``critical``. ``error``, or ``critical``.

View File

@ -23,6 +23,7 @@ _logging_schema['debug_file'] = Path()
_logging_schema['config_file'] = Path(optional=True) _logging_schema['config_file'] = Path(optional=True)
_loglevels_schema = MapConfigSchema('loglevels', LogLevel()) _loglevels_schema = MapConfigSchema('loglevels', LogLevel())
_logcolors_schema = MapConfigSchema('logcolors', LogColor())
_audio_schema = ConfigSchema('audio') _audio_schema = ConfigSchema('audio')
_audio_schema['mixer'] = String() _audio_schema['mixer'] = String()
@ -42,7 +43,8 @@ _proxy_schema['password'] = Secret(optional=True)
# NOTE: if multiple outputs ever comes something like LogLevelConfigSchema # NOTE: if multiple outputs ever comes something like LogLevelConfigSchema
# _outputs_schema = config.AudioOutputConfigSchema() # _outputs_schema = config.AudioOutputConfigSchema()
_schemas = [_logging_schema, _loglevels_schema, _audio_schema, _proxy_schema] _schemas = [_logging_schema, _loglevels_schema, _logcolors_schema,
_audio_schema, _proxy_schema]
_INITIAL_HELP = """ _INITIAL_HELP = """
# For further information about options in this file see: # For further information about options in this file see:

View File

@ -6,7 +6,7 @@ import socket
from mopidy import compat from mopidy import compat
from mopidy.config import validators from mopidy.config import validators
from mopidy.utils import path from mopidy.utils import log, path
def decode(value): def decode(value):
@ -197,6 +197,17 @@ class List(ConfigValue):
return b'\n ' + b'\n '.join(encode(v) for v in value if v) return b'\n ' + b'\n '.join(encode(v) for v in value if v)
class LogColor(ConfigValue):
def deserialize(self, value):
validators.validate_choice(value.lower(), log.COLORS)
return value.lower()
def serialize(self, value, display=False):
if value.lower() in log.COLORS:
return value.lower()
return b''
class LogLevel(ConfigValue): class LogLevel(ConfigValue):
"""Log level value. """Log level value.

View File

@ -82,7 +82,7 @@ def setup_console_logging(config, verbosity_level):
formatter = logging.Formatter(log_format) formatter = logging.Formatter(log_format)
if config['logging']['color']: if config['logging']['color']:
handler = ColorizingStreamHandler() handler = ColorizingStreamHandler(config.get('logcolors', {}))
else: else:
handler = logging.StreamHandler() handler = logging.StreamHandler()
handler.addFilter(verbosity_filter) handler.addFilter(verbosity_filter)
@ -117,6 +117,11 @@ class VerbosityFilter(logging.Filter):
return record.levelno >= required_log_level return record.levelno >= required_log_level
#: Available log colors.
COLORS = [b'black', b'red', b'green', b'yellow', b'blue', b'magenta', b'cyan',
b'white']
class ColorizingStreamHandler(logging.StreamHandler): class ColorizingStreamHandler(logging.StreamHandler):
""" """
Stream handler which colorizes the log using ANSI escape sequences. Stream handler which colorizes the log using ANSI escape sequences.
@ -130,17 +135,6 @@ class ColorizingStreamHandler(logging.StreamHandler):
Licensed under the new BSD license. Licensed under the new BSD license.
""" """
color_map = {
'black': 0,
'red': 1,
'green': 2,
'yellow': 3,
'blue': 4,
'magenta': 5,
'cyan': 6,
'white': 7,
}
# Map logging levels to (background, foreground, bold/intense) # Map logging levels to (background, foreground, bold/intense)
level_map = { level_map = {
TRACE_LOG_LEVEL: (None, 'blue', False), TRACE_LOG_LEVEL: (None, 'blue', False),
@ -150,11 +144,18 @@ class ColorizingStreamHandler(logging.StreamHandler):
logging.ERROR: (None, 'red', False), logging.ERROR: (None, 'red', False),
logging.CRITICAL: ('red', 'white', True), logging.CRITICAL: ('red', 'white', True),
} }
# Map logger name to foreground colors
logger_map = {}
csi = '\x1b[' csi = '\x1b['
reset = '\x1b[0m' reset = '\x1b[0m'
is_windows = platform.system() == 'Windows' is_windows = platform.system() == 'Windows'
def __init__(self, logger_colors):
super(ColorizingStreamHandler, self).__init__()
self.logger_map = logger_colors
@property @property
def is_tty(self): def is_tty(self):
isatty = getattr(self.stream, 'isatty', None) isatty = getattr(self.stream, 'isatty', None)
@ -173,19 +174,23 @@ class ColorizingStreamHandler(logging.StreamHandler):
message = logging.StreamHandler.format(self, record) message = logging.StreamHandler.format(self, record)
if not self.is_tty or self.is_windows: if not self.is_tty or self.is_windows:
return message return message
return self.colorize(message, record) for name, color in self.logger_map.iteritems():
if record.name.startswith(name):
def colorize(self, message, record): return self.colorize(message, fg=color)
if record.levelno in self.level_map: if record.levelno in self.level_map:
bg, fg, bold = self.level_map[record.levelno] bg, fg, bold = self.level_map[record.levelno]
params = [] return self.colorize(message, bg=bg, fg=fg, bold=bold)
if bg in self.color_map: return message
params.append(str(self.color_map[bg] + 40))
if fg in self.color_map: def colorize(self, message, bg=None, fg=None, bold=False):
params.append(str(self.color_map[fg] + 30)) params = []
if bold: if bg in COLORS:
params.append('1') params.append(str(COLORS.index(bg) + 40))
if params: if fg in COLORS:
message = ''.join(( params.append(str(COLORS.index(fg) + 30))
self.csi, ';'.join(params), 'm', message, self.reset)) if bold:
params.append('1')
if params:
message = ''.join((
self.csi, ';'.join(params), 'm', message, self.reset))
return message return message