Coverage for src/somesy/core/log.py: 88%
58 statements
« prev ^ index » next coverage.py v7.6.0, created at 2024-07-29 07:50 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2024-07-29 07:50 +0000
1"""Somesy log configuration."""
3import logging
4from enum import Enum, auto
5from typing import Optional
7from rich.logging import RichHandler
9logger = logging.getLogger("somesy")
11VERBOSE: int = 15
12"""Custom logging level between INFO and DEBUG."""
15class SomesyLogLevel(Enum):
16 """Somesy-specific log levels."""
18 SILENT = auto()
19 INFO = auto()
20 VERBOSE = auto()
21 DEBUG = auto()
23 @staticmethod
24 def from_flags(
25 *,
26 info: Optional[bool] = None,
27 verbose: Optional[bool] = None,
28 debug: Optional[bool] = None,
29 ):
30 """Convert CLI/config flags into a log level."""
31 if debug:
32 return SomesyLogLevel.DEBUG
33 elif verbose:
34 return SomesyLogLevel.VERBOSE
35 elif info:
36 return SomesyLogLevel.INFO
37 return SomesyLogLevel.SILENT
39 @staticmethod
40 def to_logging(lv):
41 """Convert a somesy log level into a logging log level."""
42 if lv == SomesyLogLevel.SILENT:
43 return logging.WARNING
44 if lv == SomesyLogLevel.INFO:
45 return logging.INFO
46 if lv == SomesyLogLevel.VERBOSE:
47 return VERBOSE
48 if lv == SomesyLogLevel.DEBUG:
49 return logging.DEBUG
52_log_level: Optional[SomesyLogLevel] = None
55def get_log_level() -> Optional[SomesyLogLevel]:
56 """Return current user-defined log level."""
57 return _log_level
60def set_log_level(log_level: SomesyLogLevel) -> None:
61 """Set the current log level."""
62 global _log_level
63 # update current somesy log level
64 _log_level = log_level
65 # (re-)init logging (rich formatter config depends on passed log level)
66 init_log()
67 # set the current logging log level
68 logger.setLevel(SomesyLogLevel.to_logging(log_level))
71def init_log():
72 """Initialize logging (add VERBOSE log level and Rich formatter)."""
73 _add_verbose_level()
74 _init_rich_handler(get_log_level())
77# ----
80def _add_verbose_level():
81 """Add a VERBOSE level to logging, if not already existing."""
82 if isinstance(logging.getLevelName("VERBOSE"), int):
83 return # nothing to do
85 # add the new level, if not defined yet
86 logging.addLevelName(level=VERBOSE, levelName="VERBOSE")
87 logger.propagate = False
89 def verbose_print(self, message, *args, **kwargs):
90 """Verbose logging level print function."""
91 if self.isEnabledFor(VERBOSE):
92 self._log(VERBOSE, message.format(args), (), **kwargs)
94 setattr(logging.Logger, "verbose", verbose_print) # noqa: B010
95 logging.basicConfig(
96 format="%(message)s",
97 datefmt="",
98 )
101_rich_handler = None
104def _init_rich_handler(log_level):
105 """(Re-)initialize rich logging handler, based on current log level."""
106 global _rich_handler
107 debug = log_level == SomesyLogLevel.DEBUG
109 if _rich_handler is not None: # remove old handler
110 logger.removeHandler(_rich_handler)
112 # create and add new handler (based on log level)
113 _rich_handler = RichHandler(
114 show_time=False,
115 rich_tracebacks=True,
116 show_level=debug,
117 show_path=debug,
118 tracebacks_show_locals=debug,
119 markup=True,
120 )
121 logger.addHandler(_rich_handler)