Coverage for src/cosmic_toolbox/logger.py: 87%

52 statements  

« prev     ^ index     » next       coverage.py v7.15.0, created at 2026-07-10 10:31 +0000

1# Copyright (C) 2017 ETH Zurich 

2# Cosmology Research Group 

3# Author: Joerg Herbel, Silvan Fischbacher 

4 

5""" 

6Logging utilities with colored output and progress bars. 

7 

8Provides a customized logger with: 

9- Color-coded log levels (debug=violet, warning=orange, error=red) 

10- Progress bar integration via tqdm 

11- Environment variable control (PYTHON_LOGGER_LEVEL) 

12 

13""" 

14 

15import logging 

16import os 

17import sys 

18 

19logging_levels = { 

20 "critical": logging.CRITICAL, 

21 "error": logging.ERROR, 

22 "warning": logging.WARNING, 

23 "info": logging.INFO, 

24 "debug": logging.DEBUG, 

25} 

26 

27 

28def get_logger(filepath, logging_level=None): 

29 """ 

30 Get a configured logger with colored output and progress bar support. 

31 

32 If logging_level is unspecified, uses the PYTHON_LOGGER_LEVEL 

33 environment variable, defaulting to 'info'. 

34 

35 :param filepath: name of the file calling the logger (used for logger name) 

36 :param logging_level: logging level 

37 ('debug', 'info', 'warning', 'error', 'critical') 

38 :return: configured logger object with progressbar attribute 

39 """ 

40 

41 if logging_level is None: 

42 logging_level = os.environ.get("PYTHON_LOGGER_LEVEL", "info") 

43 

44 logger = logging.getLogger(os.path.basename(filepath)[:10]) 

45 

46 if len(logger.handlers) == 0: 

47 stream_handler = logging.StreamHandler(sys.stdout) 

48 stream_handler.setFormatter(ColorFormatter()) 

49 logger.addHandler(stream_handler) 

50 logger.propagate = False 

51 set_logger_level(logger, logging_level) 

52 

53 logger.progressbar = Progressbar(logger) 

54 

55 return logger 

56 

57 

58class ColorFormatter(logging.Formatter): 

59 """ 

60 Custom log formatter with color-coded output based on log level. 

61 

62 Colors: 

63 - DEBUG: violet 

64 - INFO: default 

65 - WARNING: bold orange 

66 - ERROR/CRITICAL: bold red 

67 """ 

68 

69 RED = "\033[91m" 

70 VIOLET = "\033[95m" 

71 YELLOW = "\033[93m" 

72 ORANGE = "\033[33m" 

73 BOLD = "\033[1m" 

74 UNDERLINE = "\033[4m" 

75 reset = "\033[0m" 

76 format = "%(asctime)s %(name)10s %(levelname).3s %(message)s " 

77 

78 FORMATS = { 

79 logging.DEBUG: VIOLET + format + reset, 

80 logging.INFO: format, 

81 logging.WARNING: BOLD + ORANGE + format + reset, 

82 logging.ERROR: BOLD + RED + format + reset, 

83 logging.CRITICAL: BOLD + RED + format + reset, 

84 } 

85 

86 def format(self, record): 

87 """ 

88 Format the log record with appropriate colors. 

89 

90 :param record: log record to format 

91 :return: formatted string with ANSI color codes 

92 """ 

93 log_fmt = self.FORMATS.get(record.levelno) 

94 formatter = logging.Formatter(log_fmt, datefmt="%y-%m-%d %H:%M:%S") 

95 return formatter.format(record) 

96 

97 

98class Progressbar: 

99 """ 

100 Progress bar wrapper for use with logger. 

101 

102 Wraps tqdm with sensible defaults and respects the logger's level. 

103 """ 

104 

105 def __init__(self, logger=None): 

106 """ 

107 Initialize the progress bar. 

108 

109 :param logger: logger instance to check level against 

110 """ 

111 self.logger = logger 

112 

113 def __call__(self, collection, at_level="info", **kw): 

114 """ 

115 Create a progress bar for iterating over a collection. 

116 

117 :param collection: iterable to wrap with progress bar 

118 :param at_level: only show progress bar if logger is at this level 

119 :param kw: additional arguments passed to tqdm 

120 :return: tqdm progress bar iterator 

121 """ 

122 kw.setdefault("bar_format", "{percentage:3.0f}%|{bar:28}| {r_bar:<40} {desc}") 

123 kw.setdefault("disable", self.logger.level != logging_levels[at_level]) 

124 kw.setdefault("colour", "blue") 

125 kw.setdefault("mininterval", 1) 

126 kw.setdefault("file", sys.stdout) 

127 from tqdm import tqdm 

128 

129 return tqdm(collection, **kw) 

130 

131 

132def set_logger_level(logger, level): 

133 """ 

134 Set the logging level for a logger. 

135 

136 :param logger: logger instance 

137 :param level: level string ('debug', 'info', 'warning', 'error', 'critical') 

138 """ 

139 logger.setLevel(logging_levels[level]) 

140 

141 

142def set_all_loggers_level(level): 

143 """ 

144 Set the logging level for all loggers and future loggers. 

145 

146 Sets the PYTHON_LOGGER_LEVEL environment variable and updates 

147 all existing loggers. 

148 

149 :param level: level string ('debug', 'info', 'warning', 'error', 'critical') 

150 """ 

151 os.environ["PYTHON_LOGGER_LEVEL"] = level 

152 

153 loggerDict = logging.root.manager.loggerDict 

154 for key in loggerDict: 

155 try: 

156 set_logger_level(logger=loggerDict[key], level=level) 

157 except Exception as err: 

158 LOGGER.debug(err) 

159 

160 

161LOGGER = get_logger(__file__)