Source code for ms3.logger
import logging, sys, os
from functools import wraps
LEVELS = {
'DEBUG': logging.DEBUG,
'INFO': logging.INFO,
'WARNING': logging.WARNING,
'ERROR': logging.ERROR,
'CRITICAL': logging.CRITICAL,
'D': logging.DEBUG,
'I': logging.INFO,
'W': logging.WARNING,
'E': logging.ERROR,
'C': logging.CRITICAL,
}
CURRENT_LEVEL = logging.WARNING
[docs]class ContextAdapter(logging.LoggerAdapter):
""" This LoggerAdapter is designed to include the module and function that called the logger."""
[docs] def process(self, msg, overwrite={}, stack_info=False, **kwargs):
# my_context = kwargs.pop('my_context', self.extra['my_context'])
fn, l, f, s = self.logger.findCaller(stack_info=stack_info)
fname = os.path.basename(overwrite.pop('fname', fn))
line = overwrite.pop('line', l)
func = overwrite.pop('func', f)
stack = overwrite.pop('stack', s)
msg = msg.replace('\n', '\n\t')
message = f"{fname} (line {line}) {func}():\n\t{msg}" if stack is None else f"{fname} line {line}, {func}():\n\t{msg}\n{stack}"
return message, kwargs
[docs]def get_logger(name=None, level=None, adapter=ContextAdapter):
"""The function gets or creates the logger `name` and returns it, by default through the givven LoggerAdapter class."""
global CURRENT_LEVEL
if name not in logging.root.manager.loggerDict:
config_logger(name)
logger = logging.getLogger(name)
if level is not None:
CURRENT_LEVEL = LEVELS[level.upper()] if level.__class__ == str else level
logger.setLevel(CURRENT_LEVEL)
if adapter is not None:
return adapter(logger, {})
return logger
[docs]def config_logger(name, level=None, logfile=None):
"""Configs the logger with name `name`. Overwrites existing config."""
logger = logging.getLogger(name)
format = '%(levelname)-7s %(name)s -- %(message)s'
formatter = logging.Formatter(format)
if level is not None:
if level.__class__ == str:
level = LEVELS[level]
logger.setLevel(level)
existing_handlers = [h for h in logger.handlers]
if not any(True for h in existing_handlers if h.__class__ == logging.StreamHandler):
streamHandler = logging.StreamHandler(sys.stdout)
streamHandler.setFormatter(formatter)
logger.addHandler(streamHandler)
if logfile is not None:
if not any(True for h in existing_handlers if h.__class__ == logging.FileHandler and h.baseFilename == logfile):
fileHandler = logging.FileHandler(logfile, mode='w')
fileHandler.setFormatter(formatter)
logger.addHandler(fileHandler)
[docs]def function_logger(f):
"""This decorator ensures that the decorated function can use the variable `logger` for logging and
makes it possible to pass the function the keyword argument `logger` with either a Logger object or
the name of one. If the keyword argument is not passed, the root logger is used.
Example
-------
This is how the decorator can be used::
from ms3.logger import function_logger
@function_logger
def log_this(msg):
logger.warning(msg)
if __name__ == '__main__':
log_this('First test', logger='my_logger')
log_this('Second Test')
> WARNING my_logger -- function_logger.py (line 5) log_this():
> First test
> WARNING root -- function_logger.py (line 5) log_this():
> Second Test
"""
@wraps(f)
def logger(*args, **kwargs):
l = kwargs.pop('logger', None)
if l is None or l.__class__ == str:
logg = get_logger(l)
else:
logg = l
func_globals = f.__globals__
saved_values = func_globals.copy()
func_globals.update({'logger': logg})
try:
result = f(*args, **kwargs)
finally:
func_globals = saved_values # Undo changes.
return result
return logger