From 6bc2ceb02ffa8869906a50bfaf61b5600d6b5747 Mon Sep 17 00:00:00 2001 From: Roland Kaminski Date: Fri, 23 Feb 2024 18:08:06 +0100 Subject: [PATCH] refine logging (#38) * refine logging --- src/fillname/__main__.py | 7 +++- src/fillname/utils/logger.py | 61 --------------------------- src/fillname/utils/logging.py | 79 +++++++++++++++++++++++++++++++++++ src/fillname/utils/parser.py | 4 +- tests/test_main.py | 10 ++--- 5 files changed, 90 insertions(+), 71 deletions(-) delete mode 100644 src/fillname/utils/logger.py create mode 100644 src/fillname/utils/logging.py diff --git a/src/fillname/__main__.py b/src/fillname/__main__.py index 5f48055..10f5353 100644 --- a/src/fillname/__main__.py +++ b/src/fillname/__main__.py @@ -2,7 +2,9 @@ The main entry point for the application. """ -from .utils.logger import setup_logger +import sys + +from .utils.logging import configure_logging, get_logger from .utils.parser import get_parser @@ -12,8 +14,9 @@ def main() -> None: """ parser = get_parser() args = parser.parse_args() - log = setup_logger("main", args.log) + configure_logging(sys.stderr, args.log, sys.stderr.isatty()) + log = get_logger("main") log.info("info") log.warning("warning") log.debug("debug") diff --git a/src/fillname/utils/logger.py b/src/fillname/utils/logger.py deleted file mode 100644 index a729a72..0000000 --- a/src/fillname/utils/logger.py +++ /dev/null @@ -1,61 +0,0 @@ -""" -Setup project wide loggers. -""" - -import logging -import sys - -COLORS = { - "GREY": "\033[90m", - "BLUE": "\033[94m", - "GREEN": "\033[92m", - "YELLOW": "\033[93m", - "RED": "\033[91m", - "NORMAL": "\033[0m", -} - - -class SingleLevelFilter(logging.Filter): - """ - Filter levels. - """ - - passlevel: int - reject: bool - - def __init__(self, passlevel: int, reject: bool): - # pylint: disable=super-init-not-called - self.passlevel = passlevel - self.reject = reject - - def filter(self, record: logging.LogRecord) -> bool: - if self.reject: - return record.levelno != self.passlevel # nocoverage - - return record.levelno == self.passlevel - - -def setup_logger(name: str, level: int) -> logging.Logger: - """ - Setup logger. - """ - - logger = logging.getLogger(name) - logger.propagate = False - logger.setLevel(level) - log_message_str = "{}%(levelname)s:{} - %(message)s{}" - - def set_handler(level: int, color: str) -> None: - handler = logging.StreamHandler(sys.stderr) - handler.addFilter(SingleLevelFilter(level, False)) - handler.setLevel(level) - formatter = logging.Formatter(log_message_str.format(COLORS[color], COLORS["GREY"], COLORS["NORMAL"])) - handler.setFormatter(formatter) - logger.addHandler(handler) - - set_handler(logging.INFO, "GREEN") - set_handler(logging.WARNING, "YELLOW") - set_handler(logging.DEBUG, "BLUE") - set_handler(logging.ERROR, "RED") - - return logger diff --git a/src/fillname/utils/logging.py b/src/fillname/utils/logging.py new file mode 100644 index 0000000..2ca276e --- /dev/null +++ b/src/fillname/utils/logging.py @@ -0,0 +1,79 @@ +""" +Setup project wide loggers. + +This is a thin wrapper around Python's logging module. It supports colored +logging. +""" + +import logging +from typing import TextIO + +NOTSET = logging.NOTSET +DEBUG = logging.DEBUG +INFO = logging.INFO +WARNING = logging.WARNING +ERROR = logging.ERROR +CRITICAL = logging.CRITICAL + +COLORS = { + "GREY": "\033[90m", + "BLUE": "\033[94m", + "GREEN": "\033[92m", + "YELLOW": "\033[93m", + "RED": "\033[91m", + "NORMAL": "\033[0m", +} + + +class SingleLevelFilter(logging.Filter): + """ + Filter levels. + """ + + passlevel: int + reject: bool + + def __init__(self, passlevel: int, reject: bool): + # pylint: disable=super-init-not-called + self.passlevel = passlevel + self.reject = reject + + def filter(self, record: logging.LogRecord) -> bool: + if self.reject: + return record.levelno != self.passlevel # nocoverage + + return record.levelno == self.passlevel + + +def configure_logging(stream: TextIO, level: int, use_color: bool) -> None: + """ + Configure application logging. + """ + + def format_str(color: str) -> str: + if use_color: + return f"{COLORS[color]}%(levelname)s:{COLORS['GREY']} - %(message)s{COLORS['NORMAL']}" + return "%(levelname)s: - %(message)s" # nocoverage + + def make_handler(level: int, color: str) -> "logging.StreamHandler[TextIO]": + handler = logging.StreamHandler(stream) + handler.addFilter(SingleLevelFilter(level, False)) + handler.setLevel(level) + formatter = logging.Formatter(format_str(color)) + handler.setFormatter(formatter) + return handler + + handlers = [ + make_handler(logging.INFO, "GREEN"), + make_handler(logging.WARNING, "YELLOW"), + make_handler(logging.DEBUG, "BLUE"), + make_handler(logging.ERROR, "RED"), + ] + logging.basicConfig(handlers=handlers, level=level) + + +def get_logger(name: str) -> logging.Logger: + """ + Get a logger with the given name. + """ + return logging.getLogger(name) diff --git a/src/fillname/utils/parser.py b/src/fillname/utils/parser.py index ad8e215..1be5ce8 100644 --- a/src/fillname/utils/parser.py +++ b/src/fillname/utils/parser.py @@ -2,12 +2,13 @@ The command line parser for the project. """ -import logging import sys from argparse import ArgumentParser from textwrap import dedent from typing import Any, Optional, cast +from . import logging + __all__ = ["get_parser"] if sys.version_info[1] < 8: @@ -31,7 +32,6 @@ def get_parser() -> ArgumentParser: """ ), ) - levels = [ ("error", logging.ERROR), ("warning", logging.WARNING), diff --git a/tests/test_main.py b/tests/test_main.py index c9dca4d..26f1aac 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -2,11 +2,11 @@ Test cases for main application functionality. """ -import logging from io import StringIO from unittest import TestCase -from fillname.utils.logger import setup_logger +from fillname.utils import logging +from fillname.utils.logging import configure_logging, get_logger from fillname.utils.parser import get_parser @@ -19,11 +19,9 @@ def test_logger(self) -> None: """ Test the logger. """ - log = setup_logger("global", logging.INFO) sio = StringIO() - for handler in log.handlers: - assert isinstance(handler, logging.StreamHandler) - handler.setStream(sio) + configure_logging(sio, logging.INFO, True) + log = get_logger("main") log.info("test123") self.assertRegex(sio.getvalue(), "test123")