core (log): added advanced feature to basic backend (colors/formatting/level and logger filtering)
This commit is contained in:
parent
64384dc8bf
commit
737e784c3d
1 changed files with 132 additions and 31 deletions
163
src/core/log.py
163
src/core/log.py
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Reference in a new issue