Coverage for src/somesy/core/log.py: 88%

58 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2024-04-30 09:42 +0000

1"""Somesy log configuration.""" 

2import logging 

3from enum import Enum, auto 

4from typing import Optional 

5 

6from rich.logging import RichHandler 

7 

8logger = logging.getLogger("somesy") 

9 

10VERBOSE: int = 15 

11"""Custom logging level between INFO and DEBUG.""" 

12 

13 

14class SomesyLogLevel(Enum): 

15 """Somesy-specific log levels.""" 

16 

17 SILENT = auto() 

18 INFO = auto() 

19 VERBOSE = auto() 

20 DEBUG = auto() 

21 

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 

37 

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 

49 

50 

51_log_level: Optional[SomesyLogLevel] = None 

52 

53 

54def get_log_level() -> Optional[SomesyLogLevel]: 

55 """Return current user-defined log level.""" 

56 return _log_level 

57 

58 

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)) 

68 

69 

70def init_log(): 

71 """Initialize logging (add VERBOSE log level and Rich formatter).""" 

72 _add_verbose_level() 

73 _init_rich_handler(get_log_level()) 

74 

75 

76# ---- 

77 

78 

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 

83 

84 # add the new level, if not defined yet 

85 logging.addLevelName(level=VERBOSE, levelName="VERBOSE") 

86 logger.propagate = False 

87 

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) 

92 

93 setattr(logging.Logger, "verbose", verbose_print) # noqa: B010 

94 logging.basicConfig( 

95 format="%(message)s", 

96 datefmt="", 

97 ) 

98 

99 

100_rich_handler = None 

101 

102 

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 

107 

108 if _rich_handler is not None: # remove old handler 

109 logger.removeHandler(_rich_handler) 

110 

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)