from __future__ import annotations
from pathlib import Path
import logging, sys
from .collections import merge_dicts
__all__ = ['setup_logging', 'ColorizeFilter']
[docs]def setup_logging(
name_or_log: str | logging.Logger = '',
reset: bool = False,
path: Path | str = None,
console: bool = False,
level: int | str = logging.DEBUG,
colorize: bool = False,
color_by_level: dict[int | str, str] = None,
fmt: str = '%(asctime)s %(levelname)-8s - %(message)s',
datefmt: str = '%d/%m/%Y %H:%M:%S'
) -> logging.Logger:
"""
Setup logging.
**Example usage**
Setup a console output for logger with name *test*:
>>> log = setup_logging('a', reset=True, console=True, fmt=None, datefmt=None)
>>> log.info('this is my info')
this is my info
>>> log.debug('this is my debug')
this is my debug
>>> log.setLevel(logging.INFO)
>>> log.debug('this is my hidden debug')
>>> log.handlers = [] # Remove handlers manually: pas de bras, pas de chocolat !
>>> log.info('no handlers, no messages ;-)')
Colorization is not guaranteed (your environment may disable it).
Use `pytoolbox.console.toggle_colors` appropriately to ensure it.
Colorize (test is disabled because pytest disable colored outputs):
>> log = setup_logging('foo', console=True, colorize=True, fmt='%(levelname)-8s - %(message)s')
>> log.warning('Attention please!')
WARNING - \x1b[33mAttention please!\x1b[0m
Show how to reset handlers of the logger to avoid duplicated messages (e.g. in doctest):
>>> log = setup_logging('test', console=True, fmt=None, datefmt=None)
>>> log = setup_logging('test', console=True, fmt=None, datefmt=None)
>>> log.info('double message, tu radote pépé')
double message, tu radote pépé
double message, tu radote pépé
Resetting works as expected:
>>> _ = setup_logging('test', reset=True, console=True, fmt=None, datefmt=None)
>>> log.info('single message')
single message
Logging to a file instead:
>>> import os, tempfile
>>>
>>> with tempfile.NamedTemporaryFile('r') as f:
... log = setup_logging('my-logger', path=f.name)
... log.info('Ceci va probablement jamais être lu!')
... lines = f.read().split(os.linesep)
...
>>> assert 'INFO - Ceci va probablement jamais être lu!' in lines[0]
"""
log = logging.getLogger(name_or_log) if isinstance(name_or_log, str) else name_or_log
if reset:
log.handlers = []
log.setLevel(level)
if colorize:
log.addFilter(ColorizeFilter(color_by_level=color_by_level))
if path:
handler = logging.FileHandler(path)
handler.setFormatter(logging.Formatter(fmt=fmt, datefmt=datefmt))
log.addHandler(handler)
if console:
handler = logging.StreamHandler(sys.stdout) # pylint:disable=redefined-variable-type
handler.setFormatter(logging.Formatter(fmt=fmt, datefmt=datefmt))
log.addHandler(handler)
return log
[docs]class ColorizeFilter(logging.Filter): # pylint:disable=too-few-public-methods
color_by_level = {
logging.DEBUG: 'cyan',
logging.ERROR: 'red',
logging.INFO: 'white',
logging.WARNING: 'yellow'
}
[docs] def __init__(self, *args, **kwargs):
self.color_by_level = merge_dicts(
self.color_by_level,
kwargs.pop('color_by_level', None) or {})
super().__init__(*args, **kwargs)
[docs] def filter(self, record):
record.raw_msg = record.msg
if color := self.color_by_level.get(record.levelno):
import termcolor
record.msg = termcolor.colored(record.msg, color)
return True