Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/colorlog/formatter.py: 38%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

82 statements  

1"""The ColoredFormatter class.""" 

2 

3import logging 

4import os 

5import sys 

6import typing 

7import traceback 

8import io 

9 

10import colorlog.escape_codes 

11 

12__all__ = ( 

13 "default_log_colors", 

14 "ColoredFormatter", 

15 "LevelFormatter", 

16 "TTYColoredFormatter", 

17) 

18 

19# Type aliases used in function signatures. 

20EscapeCodes = typing.Mapping[str, str] 

21LogColors = typing.Mapping[str, str] 

22SecondaryLogColors = typing.Mapping[str, LogColors] 

23if sys.version_info >= (3, 8): 

24 _FormatStyle = typing.Literal["%", "{", "$"] 

25else: 

26 _FormatStyle = str 

27 

28# The default colors to use for the debug levels 

29default_log_colors = { 

30 "DEBUG": "white", 

31 "INFO": "green", 

32 "WARNING": "yellow", 

33 "ERROR": "red", 

34 "CRITICAL": "bold_red", 

35} 

36 

37# The default format to use for each style 

38default_formats = { 

39 "%": "%(log_color)s%(levelname)s:%(name)s:%(message)s", 

40 "{": "{log_color}{levelname}:{name}:{message}", 

41 "$": "${log_color}${levelname}:${name}:${message}", 

42} 

43 

44 

45class ColoredRecord: 

46 """ 

47 Wraps a LogRecord, adding escape codes to the internal dict. 

48 

49 The internal dict is used when formatting the message (by the PercentStyle, 

50 StrFormatStyle, and StringTemplateStyle classes). 

51 """ 

52 

53 def __init__(self, record: logging.LogRecord, escapes: EscapeCodes) -> None: 

54 self.__dict__.update(record.__dict__) 

55 self.__dict__.update(escapes) 

56 

57 

58class ColoredFormatter(logging.Formatter): 

59 """ 

60 A formatter that allows colors to be placed in the format string. 

61 

62 Intended to help in creating more readable logging output. 

63 """ 

64 

65 def __init__( 

66 self, 

67 fmt: typing.Optional[str] = None, 

68 datefmt: typing.Optional[str] = None, 

69 style: _FormatStyle = "%", 

70 log_colors: typing.Optional[LogColors] = None, 

71 reset: bool = True, 

72 secondary_log_colors: typing.Optional[SecondaryLogColors] = None, 

73 validate: bool = True, 

74 stream: typing.Optional[typing.IO] = None, 

75 no_color: bool = False, 

76 force_color: bool = False, 

77 defaults: typing.Optional[typing.Mapping[str, typing.Any]] = None, 

78 ) -> None: 

79 """ 

80 Set the format and colors the ColoredFormatter will use. 

81 

82 The ``fmt``, ``datefmt``, ``style``, and ``default`` args are passed on to the 

83 ``logging.Formatter`` constructor. 

84 

85 The ``secondary_log_colors`` argument can be used to create additional 

86 ``log_color`` attributes. Each key in the dictionary will set 

87 ``{key}_log_color``, using the value to select from a different 

88 ``log_colors`` set. 

89 

90 :Parameters: 

91 - fmt (str): The format string to use. 

92 - datefmt (str): A format string for the date. 

93 - log_colors (dict): 

94 A mapping of log level names to color names. 

95 - reset (bool): 

96 Implicitly append a color reset to all records unless False. 

97 - style ('%' or '{' or '$'): 

98 The format style to use. 

99 - secondary_log_colors (dict): 

100 Map secondary ``log_color`` attributes. (*New in version 2.6.*) 

101 - validate (bool) 

102 Validate the format string. 

103 - stream (typing.IO) 

104 The stream formatted messages will be printed to. Used to toggle colour 

105 on non-TTY outputs. Optional. 

106 - no_color (bool): 

107 Disable color output. 

108 - force_color (bool): 

109 Enable color output. Takes precedence over `no_color`. 

110 """ 

111 

112 # Select a default format if `fmt` is not provided. 

113 fmt = default_formats[style] if fmt is None else fmt 

114 

115 if sys.version_info >= (3, 10): 

116 super().__init__(fmt, datefmt, style, validate, defaults=defaults) 

117 elif sys.version_info >= (3, 8): 

118 super().__init__(fmt, datefmt, style, validate) 

119 else: 

120 super().__init__(fmt, datefmt, style) 

121 

122 self.log_colors = log_colors if log_colors is not None else default_log_colors 

123 self.secondary_log_colors = ( 

124 secondary_log_colors if secondary_log_colors is not None else {} 

125 ) 

126 self.reset = reset 

127 self.stream = stream 

128 self.no_color = no_color 

129 self.force_color = force_color 

130 

131 def formatMessage(self, record: logging.LogRecord) -> str: 

132 """Format a message from a record object.""" 

133 escapes = self._escape_code_map(record.levelname) 

134 wrapper = ColoredRecord(record, escapes) 

135 message = super().formatMessage(wrapper) # type: ignore 

136 message = self._append_reset(message, escapes) 

137 return message 

138 

139 def _escape_code_map(self, item: str) -> EscapeCodes: 

140 """ 

141 Build a map of keys to escape codes for use in message formatting. 

142 

143 If _color() returns False, all values will be an empty string. 

144 """ 

145 codes = {**colorlog.escape_codes.escape_codes} 

146 codes.setdefault("log_color", self._get_escape_code(self.log_colors, item)) 

147 for name, colors in self.secondary_log_colors.items(): 

148 codes.setdefault("%s_log_color" % name, self._get_escape_code(colors, item)) 

149 if not self._colorize(): 

150 codes = {key: "" for key in codes.keys()} 

151 return codes 

152 

153 def _colorize(self): 

154 """Return False if we should be prevented from printing escape codes.""" 

155 if self.force_color or "FORCE_COLOR" in os.environ: 

156 return True 

157 

158 if self.no_color or "NO_COLOR" in os.environ: 

159 return False 

160 

161 if self.stream is not None and not self.stream.isatty(): 

162 return False 

163 

164 return True 

165 

166 @staticmethod 

167 def _get_escape_code(log_colors: LogColors, item: str) -> str: 

168 """Extract a color sequence from a mapping, and return escape codes.""" 

169 return colorlog.escape_codes.parse_colors(log_colors.get(item, "")) 

170 

171 def _append_reset(self, message: str, escapes: EscapeCodes) -> str: 

172 """Add a reset code to the end of the message, if it's not already there.""" 

173 reset_escape_code = escapes["reset"] 

174 

175 if self.reset and not message.endswith(reset_escape_code): 

176 message += reset_escape_code 

177 

178 return message 

179 

180 if sys.version_info >= (3, 13): 

181 

182 def formatException(self, ei) -> str: 

183 """ 

184 Format and return the specified exception information as a string. 

185 

186 This is a copy of logging.Formatter.formatException that passes in 

187 an appropriate value for colorize to print_exception. 

188 """ 

189 kwargs = dict(colorize=self._colorize()) 

190 

191 sio = io.StringIO() 

192 tb = ei[2] 

193 traceback.print_exception(ei[0], ei[1], tb, limit=None, file=sio, **kwargs) 

194 s = sio.getvalue() 

195 sio.close() 

196 if s[-1:] == "\n": 

197 s = s[:-1] 

198 return s 

199 

200 

201class LevelFormatter: 

202 """An extension of ColoredFormatter that uses per-level format strings.""" 

203 

204 def __init__(self, fmt: typing.Mapping[str, str], **kwargs: typing.Any) -> None: 

205 """ 

206 Configure a ColoredFormatter with its own format string for each log level. 

207 

208 Supports fmt as a dict. All other args are passed on to the 

209 ``colorlog.ColoredFormatter`` constructor. 

210 

211 :Parameters: 

212 - fmt (dict): 

213 A mapping of log levels (represented as strings, e.g. 'WARNING') to 

214 format strings. (*New in version 2.7.0) 

215 (All other parameters are the same as in colorlog.ColoredFormatter) 

216 

217 Example: 

218 

219 formatter = colorlog.LevelFormatter( 

220 fmt={ 

221 "DEBUG": "%(log_color)s%(message)s (%(module)s:%(lineno)d)", 

222 "INFO": "%(log_color)s%(message)s", 

223 "WARNING": "%(log_color)sWRN: %(message)s (%(module)s:%(lineno)d)", 

224 "ERROR": "%(log_color)sERR: %(message)s (%(module)s:%(lineno)d)", 

225 "CRITICAL": "%(log_color)sCRT: %(message)s (%(module)s:%(lineno)d)", 

226 } 

227 ) 

228 """ 

229 self.formatters = { 

230 level: ColoredFormatter(fmt=f, **kwargs) for level, f in fmt.items() 

231 } 

232 

233 def format(self, record: logging.LogRecord) -> str: 

234 return self.formatters[record.levelname].format(record) 

235 

236 

237# Provided for backwards compatibility. The features provided by this subclass are now 

238# included directly in the `ColoredFormatter` class. 

239TTYColoredFormatter = ColoredFormatter