# HG changeset patch # User Goffi # Date 1399309114 -7200 # Node ID 325fd230c15d31f5d63b92c7ecea4f487a1d4ec1 # Parent b4af31a8a4f2022aaaca1ddee8d3a44791c3db20 core (log): added advanced feature to basic backend (colors/formatting/level and logger filtering) diff -r b4af31a8a4f2 -r 325fd230c15d src/core/log.py --- a/src/core/log.py Mon May 05 18:58:34 2014 +0200 +++ b/src/core/log.py Mon May 05 18:58:34 2014 +0200 @@ -27,30 +27,74 @@ _loggers = {} _handlers = {} +class Filtered(Exception): + pass class Logger(object): - """ High level logging class """ + """High level logging class""" + fmt = None + filter_name = None + post_treat = None def __init__(self, name): self._name = name + def log(self, level, message): + """Print message + + @param level: one of C.LOG_LEVELS + @param message: message to format and print + """ + try: + formatted = self.format(level, message) + if self.post_treat is None: + print formatted + else: + print self.post_treat(level, formatted) + except Filtered: + pass + + def format(self, level, message): + """Format message according to Logger.fmt + + @param level: one of C.LOG_LEVELS + @param message: message to format + @return: formatted message + + @raise: Filtered the message must not be logged + """ + if self.fmt is None and self.filter_name is None: + return message + record = {'name': self._name, + 'message': message, + 'levelname': level, + } + try: + if not self.filter_name.dictFilter(record): + raise Filtered + except AttributeError: + if self.filter_name is not None: + raise ValueError("Bad filter: filters must have a .filter method") + return self.fmt % record + def debug(self, msg): - print msg + self.log(C.LOG_LVL_DEBUG, msg) def info(self, msg): - print msg + self.log(C.LOG_LVL_INFO, msg) def warning(self, msg): - print msg + self.log(C.LOG_LVL_WARNING, msg) def error(self, msg): - print msg + self.log(C.LOG_LVL_ERROR, msg) def critical(self, msg): - print msg + self.log(C.LOG_LVL_CRITICAL, msg) class FilterName(object): + """Filter on logger name according to a regex""" def __init__(self, name_re): """Initialise name filter @@ -66,8 +110,50 @@ return 1 return 0 + def dictFilter(self, dict_record): + """Filter using a dictionary record + + @param dict_record: dictionary with at list a key "name" with logger name + @return: True if message should be logged + """ + class LogRecord(object): + pass + log_record = LogRecord() + log_record.name = dict_record['name'] + return self.filter(log_record) == 1 + + +def _ansiColors(level, message): + """Colorise message depending on level for terminals + + @param level: one of C.LOG_LEVELS + @param message: formatted message to log + @return: message with ANSI escape codes for coloration + """ + if level == C.LOG_LVL_DEBUG: + out = (C.ANSI_FG_CYAN, message, C.ANSI_RESET) + elif level == C.LOG_LVL_WARNING: + out = (C.ANSI_FG_YELLOW, message, C.ANSI_RESET) + elif level == C.LOG_LVL_ERROR: + out = (C.ANSI_FG_RED, + C.ANSI_BLINK, + r'/!\ ', + C.ANSI_BLINK_OFF, + message, + C.ANSI_RESET) + elif level == C.LOG_LVL_CRITICAL: + out = (C.ANSI_BOLD, + C.ANSI_FG_RED, + 'Guru Meditation ', + C.ANSI_NORMAL_WEIGHT, + message, + C.ANSI_RESET) + else: + out = message + return ''.join(out) + def _manageOutputs(outputs_raw): - """ Parse output option in a backend agnostic way, and fill _backend consequently + """ Parse output option in a backend agnostic way, and fill _handlers consequently @param outputs_raw: output option as enterred in environment variable or in configuration """ @@ -143,28 +229,7 @@ def format(self, record): s = super(SatFormatter, self).format(record) if with_color: - if record.levelno == logging.DEBUG: - fmt = (C.ANSI_FG_CYAN, s, C.ANSI_RESET) - elif record.levelno == logging.WARNING: - fmt = (C.ANSI_FG_YELLOW, s, C.ANSI_RESET) - elif record.levelno == logging.ERROR: - fmt = (C.ANSI_FG_RED, - C.ANSI_BLINK, - r'/!\ ', - C.ANSI_BLINK_OFF, - s, - C.ANSI_RESET) - elif record.levelno == logging.CRITICAL: - fmt = (C.ANSI_BOLD, - C.ANSI_FG_RED, - 'Guru Meditation ', - C.ANSI_NORMAL_WEIGHT, - s, - C.ANSI_RESET) - else: - fmt = s - s = ''.join(fmt) - + s = _ansiColors(record.levelname, s) return s root_logger = logging.getLogger() @@ -178,6 +243,7 @@ elif handler == C.LOG_OPT_OUTPUT_MEMORY: import logging.handlers hdlr = logging.handlers.BufferingHandler(options) + _handlers[handler] = hdlr # we keep a reference to the handler to read the buffer later elif handler == C.LOG_OPT_OUTPUT_FILE: import os.path hdlr = logging.FileHandler(os.path.expanduser(options)) @@ -191,8 +257,43 @@ else: root_logger.warning(u"Handlers already set on root logger") +def _configureBasic(level=None, fmt=None, output=None, logger=None, colors=False, force_colors=False): + """Configure basic backend + @param level: same as _configureStdLogging.level + @param fmt: same as _configureStdLogging.fmt + @param output: not implemented yet TODO + @param logger: same as _configureStdLogging.logger + @param colors: same as _configureStdLogging.colors + @param force_colors: same as _configureStdLogging.force_colors + """ + if level is not None: + # we deactivate methods below level + level_idx = C.LOG_LEVELS.index(level) + def dev_null(self, msg): + pass + for _level in C.LOG_LEVELS[:level_idx]: + setattr(Logger, _level.lower(), dev_null) + if fmt is not None: + if fmt != '%(message)s': # %(message)s is the same as None + Logger.fmt = fmt + if output is not None: + if output != C.LOG_OPT_OUTPUT_SEP + C.LOG_OPT_OUTPUT_DEFAULT: + # TODO: manage other outputs + raise NotImplementedError("Basic backend only manage default output yet") + + if logger: + Logger.filter_name = FilterName(logger) + + if colors: + import sys + if force_colors or sys.stdout.isatty(): + # we need colors + Logger.post_treat = lambda self, level, message: _ansiColors(level, message) + elif force_colors: + raise ValueError("force_colors can't be used if colors is False") + def configure(backend=C.LOG_BACKEND_STANDARD, **options): - """Configure logging bejaviour + """Configure logging behaviour @param backend: can be: C.LOG_BACKEND_STANDARD: use standard logging module C.LOG_BACKEND_TWISTED: use twisted logging module (with standard logging observer) @@ -224,7 +325,7 @@ critical = logging.critical elif backend == C.LOG_BACKEND_BASIC: - pass + _configureBasic(**options) else: raise ValueError("unknown backend")