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

58 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2024-07-29 07:42 +0000

1"""Somesy log configuration.""" 

2 

3import logging 

4from enum import Enum, auto 

5from typing import Optional 

6 

7from rich.logging import RichHandler 

8 

9logger = logging.getLogger("somesy") 

10 

11VERBOSE: int = 15 

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

13 

14 

15class SomesyLogLevel(Enum): 

16 """Somesy-specific log levels.""" 

17 

18 SILENT = auto() 

19 INFO = auto() 

20 VERBOSE = auto() 

21 DEBUG = auto() 

22 

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 

38 

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 

50 

51 

52_log_level: Optional[SomesyLogLevel] = None 

53 

54 

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

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

57 return _log_level 

58 

59 

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

69 

70 

71def init_log(): 

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

73 _add_verbose_level() 

74 _init_rich_handler(get_log_level()) 

75 

76 

77# ---- 

78 

79 

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 

84 

85 # add the new level, if not defined yet 

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

87 logger.propagate = False 

88 

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) 

93 

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

95 logging.basicConfig( 

96 format="%(message)s", 

97 datefmt="", 

98 ) 

99 

100 

101_rich_handler = None 

102 

103 

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 

108 

109 if _rich_handler is not None: # remove old handler 

110 logger.removeHandler(_rich_handler) 

111 

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)