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