From 304b1eb34badc0e4c6d74816addede3b620b4f86 Mon Sep 17 00:00:00 2001 From: Goffi Date: Mon, 18 Apr 2016 18:33:59 +0200 Subject: [PATCH] core (logs): log color location can now be specified with %(color_start)s and %(color_end)s --- src/core/log.py | 47 ++++++++++++++++++++++++++---------------- src/core/log_config.py | 12 ++++++++++- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/src/core/log.py b/src/core/log.py index ac7d552d..2cd5a4e9 100644 --- a/src/core/log.py +++ b/src/core/log.py @@ -19,6 +19,7 @@ """High level logging functions""" # XXX: this module use standard logging module when possible, but as SàT can work in different cases where logging is not the best choice (twisted, pyjamas, etc), it is necessary to have a dedicated module. Additional feature like environment variables and colors are also managed. +# TODO: change formatting from "%s" style to "{}" when moved to Python 3 from sat.core.constants import Const as C from sat.core import exceptions @@ -26,6 +27,8 @@ from sat.core import exceptions backend = None _loggers = {} handlers = {} +COLOR_START = '%(color_start)s' +COLOR_END = '%(color_end)s' class Filtered(Exception): @@ -154,6 +157,7 @@ class FilterName(object): class ConfigureBase(object): LOGGER_CLASS = Logger + _color_location = False # True if color location is specified in fmt (with COLOR_START) def __init__(self, level=None, fmt=None, output=None, logger=None, colors=False, force_colors=False, backend_data=None): """Configure a backend @@ -201,6 +205,11 @@ class ConfigureBase(object): if fmt is not None: if fmt != '%(message)s': # %(message)s is the same as None Logger.fmt = fmt + if COLOR_START in fmt: + ConfigureBase._color_location = True + if fmt.find(COLOR_END,fmt.rfind(COLOR_START))<0: + # color_start not followed by an end, we add it + Logger.fmt += COLOR_END def configureOutput(self, output): if output is not None: @@ -249,7 +258,7 @@ class ConfigureBase(object): handlers[output] = None elif output == C.LOG_OPT_OUTPUT_FILE: if not options: - ValueError("%(handler)s output need a path as option" % {'handler': output}) + ValueError("{handler} output need a path as option" .format(handle=output)) handlers.setdefault(output, []).append(options) options = None # option are parsed, we can empty them elif output == C.LOG_OPT_OUTPUT_MEMORY: @@ -262,7 +271,7 @@ class ConfigureBase(object): handlers[output] = limit if options: # we should not have unparsed options - raise ValueError(u"options [%(options)s] are not supported for %(handler)s output" % {'options': options, 'handler': output}) + raise ValueError(u"options [{options}] are not supported for {handler} output".format(options=options, handler=output)) @staticmethod def memoryGet(size=None): @@ -272,8 +281,8 @@ class ConfigureBase(object): """ raise NotImplementedError - @staticmethod - def ansiColors(level, message): + @classmethod + def ansiColors(cls, level, message): """Colorise message depending on level for terminals @param level: one of C.LOG_LEVELS @@ -281,26 +290,28 @@ class ConfigureBase(object): @return: message with ANSI escape codes for coloration """ if level == C.LOG_LVL_DEBUG: - out = (C.ANSI_FG_CYAN, message, C.ANSI_RESET) + start = (C.ANSI_FG_CYAN,) + elif level == C.LOG_LVL_INFO: + start = () elif level == C.LOG_LVL_WARNING: - out = (C.ANSI_FG_YELLOW, message, C.ANSI_RESET) + start = (C.ANSI_FG_YELLOW,) elif level == C.LOG_LVL_ERROR: - out = (C.ANSI_FG_RED, + start = (C.ANSI_FG_RED, C.ANSI_BLINK, r'/!\ ', - C.ANSI_BLINK_OFF, - message, - C.ANSI_RESET) + C.ANSI_BLINK_OFF) elif level == C.LOG_LVL_CRITICAL: - out = (C.ANSI_BOLD, + start = (C.ANSI_BOLD, C.ANSI_FG_RED, 'Guru Meditation ', - C.ANSI_NORMAL_WEIGHT, - message, - C.ANSI_RESET) + C.ANSI_NORMAL_WEIGHT) else: - out = message - return ''.join(out) + start = () + if cls._color_location: + return message % {'color_start': ''.join(start), + 'color_end': C.ANSI_RESET} + else: + return '%s%s%s' % (''.join(start), message, C.ANSI_RESET) @staticmethod def getProfile(): @@ -334,7 +345,7 @@ def configure(backend_, **options): try: configure_class = configure_cls[backend] except KeyError: - raise ValueError("unknown backend [%s]" % backend) + raise ValueError("unknown backend [{}]".format(backend)) if backend == C.LOG_BACKEND_CUSTOM: logger_class = options.pop('logger_class') configure_class(logger_class, **options) @@ -350,7 +361,7 @@ def getLogger(name=C.LOG_BASE_LOGGER): try: logger_class = configure_cls[backend].LOGGER_CLASS except KeyError: - raise ValueError("This method should not be called with backend [%s]" % backend) + raise ValueError("This method should not be called with backend [{}]".format(backend)) return _loggers.setdefault(name, logger_class(name)) _root_logger = getLogger() diff --git a/src/core/log_config.py b/src/core/log_config.py index b9cb617d..87fd22c5 100644 --- a/src/core/log_config.py +++ b/src/core/log_config.py @@ -258,6 +258,7 @@ class ConfigureStandard(ConfigureBasic): self.level = level def configureFormat(self, fmt): + super(ConfigureStandard, self).configureFormat(fmt) import logging class SatFormatter(logging.Formatter): @@ -272,8 +273,17 @@ class ConfigureStandard(ConfigureBasic): def format(self, record): if self._with_profile: record.profile = ConfigureStandard.getProfile() + do_color = self.with_colors and (self.can_colors or self.force_colors) + if ConfigureStandard._color_location: + # we copy raw formatting strings for color_* + # as formatting is handled in ansiColors in this case + if do_color: + record.color_start = log.COLOR_START + record.color_end = log.COLOR_END + else: + record.color_start = record.color_end = '' s = super(SatFormatter, self).format(record) - if self.with_colors and (self.can_colors or self.force_colors): + if do_color: s = ConfigureStandard.ansiColors(record.levelname, s) return s