# -*- coding: utf-8 -*-
"""
Module et_micc.logger
=====================
Helper functions for logging.
"""
import sys
from contextlib import contextmanager
import logging
from datetime import datetime
[docs]def verbosity_to_loglevel(verbosity):
"""Tranlate :py:obj:`verbosity` into a loglevel.
:param int verbosity:
"""
if verbosity==0:
return logging.CRITICAL
elif verbosity==1:
return logging.INFO
else:
return logging.DEBUG
# # Use static vare to implement a singleton (the micc_logger)
# @static_vars(the_logger=None)
# def get_micc_logger(global_options=None):
# """Set up and store a et_micc logger that writes to the console (taking verbosity
# into account) and to a log file ``et_micc.log``.
#
# :param types.SimpleNamespace global_options: namespace object with options
# accepted by (almost) all et_micc commands. If None, the static :py:obj:`the_logger`
# is returned.
# :returns: a Logger object.
# """
#
# # if global_options is None:
# # if get_micc_logger.the_logger is None:
# # raise RuntimeError("Micc logger not created yet - this is a bug.")
# # return get_micc_logger.the_logger
#
# if not get_micc_logger.the_logger is None:
# # verify that the current logger is for the current project directory
# # (When running pytest, et_micc commands are run in several different
# # project directories created on the fly. the micc_logger must adjust
# # to this situation and log to a et_micc.log file in the project directory.
# current_logfile = get_micc_logger.the_logger.logfile
# current_project_path = global_options.project_path
# if not current_logfile.parent == current_project_path:
# get_micc_logger.the_logger = None
#
# if get_micc_logger.the_logger is None:
#
# if getattr(global_options,'clear_log',False):
# logfile = global_options.project_path / 'et_micc.log'
# if logfile.exists():
# logfile.unlink()
# else:
# global_options.clear_log = False
#
# # create a new logger object that will write to et_micc.log
# # in the current project directory
# p = global_options.project_path / 'et_micc.log'
# get_micc_logger.the_logger = create_logger(p)
# get_micc_logger.the_logger.logfile = p
# if global_options.verbosity>2:
# print(f"micc_logger.logfile = {get_micc_logger.the_logger.logfile}")
#
#
# # set the log level from the verbosity
# get_micc_logger.the_logger.console_handler.setLevel(verbosity_to_loglevel(global_options.verbosity))
#
# if getattr(global_options,'clear_log',False):
# global_options.clear_log = False
# get_micc_logger.the_logger.debug("The log file was cleared.")
#
# return get_micc_logger.the_logger
#
[docs]class IndentingLogger(logging.Logger):
"""Cuastom Logger class for creating indented logs.
This is the class for the et_micc logger.
"""
def __init__(self, name, level=logging.NOTSET):
super().__init__(name, level)
self._indent = ''
self._stack = []
def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False):
"""Overloaded functon from logging.Logger"""
msg = msg.strip()
# ensure that the indentation is independent of the lenght of the levelname.
w = (10-len(logging.getLevelName(level))) * ' '
if self._indent:
msg = w + self._indent + msg.replace('\n','\n' + 10*' ' + w + self._indent)
super()._log(level, msg, args, exc_info, extra)
[docs] def indent(self,n=4):
"""Increase the indentation level.
Future log messages will shift to the right by n spaces.
"""
self._indent += n*' '
self._stack.append(n)
[docs] def dedent(self):
"""Increase the indentation level.
Future log messages will shift to the left. The width of the shift
is determined by the last call to :py:meth:`~et_micc.logger.IndentingLogger.indent`
"""
if self._stack:
n = self._stack.pop()
length = len(self._indent) - n
self._indent = self._indent[0:length]
[docs]def create_logger(path_to_log_file,filemode='a'):
"""Create a logger object for et_micc.
It will log to:
* the console
* file *path_to_log_file*. By default log message will be appended to the
"""
# create formatters and add it to the handlers
format_string = f"[%(levelname)s] %(message)s"
console_formatter = logging.Formatter(format_string)
logfile_formatter = logging.Formatter(format_string)
# create and add a console handler
console_handler = logging.StreamHandler(sys.stderr)
console_handler.setFormatter(console_formatter)
console_handler.setLevel(1)
# create and add a logfile handler
logfile_handler = logging.FileHandler(path_to_log_file,mode=filemode)
logfile_handler.setFormatter(logfile_formatter)
logfile_handler.setLevel(logging.DEBUG)
# set custom logger class
# logging.setLoggerClass(IndentingLogger)
# create logger
# logger = logging.getLogger(path_to_log_file.name)
logger = IndentingLogger(name="ok")
logger.setLevel(logging.DEBUG)
logger.addHandler(logfile_handler)
logger.addHandler(console_handler)
# add the handlers as a member, so we can still modify them on the fly.
logger.logfile_handler = logfile_handler
logger.console_handler = console_handler
logger.log_file = path_to_log_file
return logger
[docs]@contextmanager
def log(logfun=None, before='doing', after='done.',bracket=True):
"""Print a message before and after executing the body of the contextmanager.
:param callable logfun: a function that can print a log message, e.g. :py:meth:`print`, :py:meth:`~et_micc.logger.get_micc_logger.the_logger.info`.
:param str before: print this before body is executed
:param str after: print this after body is executed
:param bool bracket: append ' [' to before and prepend '] ' to after.
This works best with the :py:class:`~et_micc.logger.IndentingLogger`.
"""
if logfun:
if bracket:
msg = '[ ' + before
logfun(msg)
try:
logfun.__self__.indent()
is_logger = True
except AttributeError:
is_logger = False
yield
if logfun:
if is_logger:
logfun.__self__.dedent()
if bracket:
msg = '] '+ after
logfun(msg)
[docs]@contextmanager
def logtime(project=None):
"""Log start time, end time and duration of the task in the body of the context manager
to the et_micc logger.
This logs on debug level. To see in in the console output you must pass ``-vv`` to et_micc.
:param SimpleNameSpace global_options: pass verbosity to the et_micc logger.
"""
if project is None:
logfun = print
else:
logfun = project.logger.debug
start = datetime.now()
logfun(f"start = {start}")
logfun.__self__.indent()
yield
logfun.__self__.dedent()
stop = datetime.now()
logfun(f"stop = {stop}")
spent = stop - start
logfun(f"spent = {spent}")
#eof