core (log): added advanced feature to basic backend (colors/formatting/level and logger filtering)

This commit is contained in:
Goffi 2014-05-05 18:58:34 +02:00
commit 737e784c3d

View file

@ -27,30 +27,74 @@ _backend = None
_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 @@ class FilterName(object):
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 _configureStdLogging(logging, level=None, fmt=C.LOG_OPT_FORMAT[1], output=C.
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 @@ def _configureStdLogging(logging, level=None, fmt=C.LOG_OPT_FORMAT[1], output=C.
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 @@ def _configureStdLogging(logging, level=None, fmt=C.LOG_OPT_FORMAT[1], output=C.
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 @@ def configure(backend=C.LOG_BACKEND_STANDARD, **options):
critical = logging.critical
elif backend == C.LOG_BACKEND_BASIC:
pass
_configureBasic(**options)
else:
raise ValueError("unknown backend")