68 lines
2.4 KiB
Python
68 lines
2.4 KiB
Python
|
from __future__ import annotations
|
||
|
|
||
|
import logging
|
||
|
import sys
|
||
|
from pathlib import Path
|
||
|
from typing import Any, IO, NoReturn, Optional, Union
|
||
|
|
||
|
import coloredlogs
|
||
|
|
||
|
LOG_FORMAT = "{asctime} [{levelname[0]}] {name} : {message}"
|
||
|
LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
|
||
|
LOG_STYLE = "{"
|
||
|
LOG_FORMATTER = logging.Formatter(LOG_FORMAT, LOG_DATE_FORMAT, LOG_STYLE)
|
||
|
|
||
|
|
||
|
class Logger(logging.Logger):
|
||
|
def __init__(self, name: str = "root", level: int = logging.NOTSET, color: bool = True):
|
||
|
"""Initialize the logger with a name and an optional level."""
|
||
|
super().__init__(name, level)
|
||
|
if self.name == "root":
|
||
|
self.add_stream_handler()
|
||
|
if color:
|
||
|
self.install_color()
|
||
|
|
||
|
def exit(self, msg: str, *args: Any, code: int = 1, **kwargs: Any) -> NoReturn:
|
||
|
"""
|
||
|
Log 'msg % args' with severity 'CRITICAL' and terminate the program
|
||
|
with a default exit code of 1.
|
||
|
|
||
|
To pass exception information, use the keyword argument exc_info with
|
||
|
a true value, e.g.
|
||
|
|
||
|
logger.exit("Houston, we have a %s", "major disaster", exc_info=1)
|
||
|
"""
|
||
|
self.critical(msg, *args, **kwargs)
|
||
|
sys.exit(code)
|
||
|
|
||
|
def add_stream_handler(self, stream: Optional[IO[str]] = None) -> None:
|
||
|
"""Add a stream handler to log. Stream defaults to stdout."""
|
||
|
sh = logging.StreamHandler(stream)
|
||
|
sh.setFormatter(LOG_FORMATTER)
|
||
|
self.addHandler(sh)
|
||
|
|
||
|
def add_file_handler(self, fp: Union[IO, Path, str]) -> None:
|
||
|
"""Convenience alias func for add_stream_handler, deals with type of fp object input."""
|
||
|
if not isinstance(fp, IO):
|
||
|
fp = Path(fp)
|
||
|
fp = fp.open("w", encoding="utf8")
|
||
|
self.add_stream_handler(fp)
|
||
|
|
||
|
def install_color(self) -> None:
|
||
|
"""Use coloredlogs to set up colors on the log output."""
|
||
|
if self.level == logging.DEBUG:
|
||
|
coloredlogs.install(level=self.level, fmt=LOG_FORMAT, datefmt=LOG_DATE_FORMAT, style=LOG_STYLE)
|
||
|
coloredlogs.install(level=self.level, logger=self, fmt=LOG_FORMAT, datefmt=LOG_DATE_FORMAT, style=LOG_STYLE)
|
||
|
|
||
|
|
||
|
# Cache already used loggers to make sure their level is preserved
|
||
|
_loggers: dict[str, Logger] = {}
|
||
|
|
||
|
|
||
|
# noinspection PyPep8Naming
|
||
|
def getLogger(name: Optional[str] = None, level: int = logging.NOTSET) -> Logger:
|
||
|
name = name or "root"
|
||
|
_log = _loggers.get(name, Logger(name))
|
||
|
_log.setLevel(level)
|
||
|
return _log
|