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 = {}
|
_loggers = {}
|
||||||
_handlers = {}
|
_handlers = {}
|
||||||
|
|
||||||
|
class Filtered(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
class Logger(object):
|
class Logger(object):
|
||||||
""" High level logging class """
|
"""High level logging class"""
|
||||||
|
fmt = None
|
||||||
|
filter_name = None
|
||||||
|
post_treat = None
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self._name = 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):
|
def debug(self, msg):
|
||||||
print msg
|
self.log(C.LOG_LVL_DEBUG, msg)
|
||||||
|
|
||||||
def info(self, msg):
|
def info(self, msg):
|
||||||
print msg
|
self.log(C.LOG_LVL_INFO, msg)
|
||||||
|
|
||||||
def warning(self, msg):
|
def warning(self, msg):
|
||||||
print msg
|
self.log(C.LOG_LVL_WARNING, msg)
|
||||||
|
|
||||||
def error(self, msg):
|
def error(self, msg):
|
||||||
print msg
|
self.log(C.LOG_LVL_ERROR, msg)
|
||||||
|
|
||||||
def critical(self, msg):
|
def critical(self, msg):
|
||||||
print msg
|
self.log(C.LOG_LVL_CRITICAL, msg)
|
||||||
|
|
||||||
|
|
||||||
class FilterName(object):
|
class FilterName(object):
|
||||||
|
"""Filter on logger name according to a regex"""
|
||||||
|
|
||||||
def __init__(self, name_re):
|
def __init__(self, name_re):
|
||||||
"""Initialise name filter
|
"""Initialise name filter
|
||||||
|
|
@ -66,8 +110,50 @@ class FilterName(object):
|
||||||
return 1
|
return 1
|
||||||
return 0
|
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):
|
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
|
@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):
|
def format(self, record):
|
||||||
s = super(SatFormatter, self).format(record)
|
s = super(SatFormatter, self).format(record)
|
||||||
if with_color:
|
if with_color:
|
||||||
if record.levelno == logging.DEBUG:
|
s = _ansiColors(record.levelname, s)
|
||||||
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)
|
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
|
||||||
root_logger = logging.getLogger()
|
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:
|
elif handler == C.LOG_OPT_OUTPUT_MEMORY:
|
||||||
import logging.handlers
|
import logging.handlers
|
||||||
hdlr = logging.handlers.BufferingHandler(options)
|
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:
|
elif handler == C.LOG_OPT_OUTPUT_FILE:
|
||||||
import os.path
|
import os.path
|
||||||
hdlr = logging.FileHandler(os.path.expanduser(options))
|
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:
|
else:
|
||||||
root_logger.warning(u"Handlers already set on root logger")
|
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):
|
def configure(backend=C.LOG_BACKEND_STANDARD, **options):
|
||||||
"""Configure logging bejaviour
|
"""Configure logging behaviour
|
||||||
@param backend: can be:
|
@param backend: can be:
|
||||||
C.LOG_BACKEND_STANDARD: use standard logging module
|
C.LOG_BACKEND_STANDARD: use standard logging module
|
||||||
C.LOG_BACKEND_TWISTED: use twisted logging module (with standard logging observer)
|
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
|
critical = logging.critical
|
||||||
|
|
||||||
elif backend == C.LOG_BACKEND_BASIC:
|
elif backend == C.LOG_BACKEND_BASIC:
|
||||||
pass
|
_configureBasic(**options)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise ValueError("unknown backend")
|
raise ValueError("unknown backend")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue