I am working on a project, neurodecode, which uses a logger based on the logging library. As I am not very familiar with logging, I don't know how to further improve this aspect of the project.
The project is constructed as:
neurodecode
|_ __init__.py
|_ logger.py
|_ folder1
|_ script1.py
|_ script2.py
...
The logger.py contains: (I removed the docstring to compact the code).
import sys
import logging
LOG_LEVELS = {
'DEBUG': logging.DEBUG,
'INFO': logging.INFO,
'WARNING': logging.WARNING,
'ERROR': logging.ERROR
}
def init_logger(logger, verbosity='INFO'):
if not logger.hasHandlers():
logger.setLevel(verbosity)
add_logger_handler(logger, sys.stdout, verbosity=verbosity)
def add_logger_handler(logger, stream, verbosity='INFO'):
c_handler = logging.StreamHandler(stream)
c_handler.setFormatter(neurodecodeFormatter())
logger.addHandler(c_handler)
set_log_level(logger, verbosity, -1)
return logger
def set_log_level(logger, verbosity, handler_id=0):
logger.handlers[handler_id].level = LOG_LEVELS[verbosity]
class neurodecodeFormatter(logging.Formatter):
# Format string syntax for the different Log levels
fmt_debug = "[%(module)s:%(funcName)s:%(lineno)d] DEBUG: %(message)s (%(asctime)s)"
fmt_info = "[%(module)s.%(funcName)s] %(message)s"
fmt_warning = "[%(module)s.%(funcName)s] WARNING: %(message)s"
fmt_error = "[%(module)s:%(funcName)s:%(lineno)d] ERROR: %(message)s"
def __init__(self, fmt='%(levelno)s: %(message)s'):
super().__init__(fmt)
def format(self, record):
if record.levelno == LOG_LEVELS['DEBUG']:
self._fmt = self.fmt_debug
elif record.levelno == LOG_LEVELS['INFO']:
self._fmt = self.fmt_info
elif record.levelno == LOG_LEVELS['WARNING']:
self._fmt = self.fmt_warning
elif record.levelno >= LOG_LEVELS['ERROR']:
self._fmt = self.fmt_error
self._style = logging.PercentStyle(self._fmt)
return logging.Formatter.format(self, record)
And the __init__.py contains:
import logging
from .logger import init_logger
# set loggers
logging.getLogger('matplotlib').setLevel(logging.ERROR)
logger = logging.getLogger('neurodecode')
logger.propagate = False
init_logger(logger, verbosity='INFO')
First, do you see any improvements which could be made to those 2 parts? Maybe I could add from .logger import set_log_level to the __init__.py to be able to set the log level as neurodecode.set_log_level().
Second, in the different subfolders and scripts, I am using the logger as:
from pathlib import Path
from .. import logger # relative import vary depending on the script
class Foo:
def __init__(self, param1, param2):
self.param1 = str(param1)
logger.info(f'Param1 set as {self.param1}')
param2 = Foo._check_param2(param2)
def method_to_do_stuff(self, n):
try:
# do stuff
x = 2/n
except Exception as error:
logger.error('Something didn't work..')
raise error
@staticmethod
def _check_param2(param2):
# let's imagine it's a path -> pathlib
param2 = Path(param2)
if not param2.exists():
logger.error('The path to param2 was not found')
raise IOError
In this dummy example above, which sums up well most of my use case of the logger, I feel like I am not using it correctly. Shouldn't the logger catch the exception and the message attached directly, e.g. with raise IOError('The path to param2 was not found')?