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
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
1"""The ColoredFormatter class."""
3import logging
4import os
5import sys
6import typing
7import traceback
8import io
10import colorlog.escape_codes
12__all__ = (
13 "default_log_colors",
14 "ColoredFormatter",
15 "LevelFormatter",
16 "TTYColoredFormatter",
17)
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
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}
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}
45class ColoredRecord:
46 """
47 Wraps a LogRecord, adding escape codes to the internal dict.
49 The internal dict is used when formatting the message (by the PercentStyle,
50 StrFormatStyle, and StringTemplateStyle classes).
51 """
53 def __init__(self, record: logging.LogRecord, escapes: EscapeCodes) -> None:
54 self.__dict__.update(record.__dict__)
55 self.__dict__.update(escapes)
58class ColoredFormatter(logging.Formatter):
59 """
60 A formatter that allows colors to be placed in the format string.
62 Intended to help in creating more readable logging output.
63 """
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.
82 The ``fmt``, ``datefmt``, ``style``, and ``default`` args are passed on to the
83 ``logging.Formatter`` constructor.
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.
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 """
112 # Select a default format if `fmt` is not provided.
113 fmt = default_formats[style] if fmt is None else fmt
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)
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
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
139 def _escape_code_map(self, item: str) -> EscapeCodes:
140 """
141 Build a map of keys to escape codes for use in message formatting.
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
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
158 if self.no_color or "NO_COLOR" in os.environ:
159 return False
161 if self.stream is not None and not self.stream.isatty():
162 return False
164 return True
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, ""))
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"]
175 if self.reset and not message.endswith(reset_escape_code):
176 message += reset_escape_code
178 return message
180 if sys.version_info >= (3, 13):
182 def formatException(self, ei) -> str:
183 """
184 Format and return the specified exception information as a string.
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())
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
201class LevelFormatter:
202 """An extension of ColoredFormatter that uses per-level format strings."""
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.
208 Supports fmt as a dict. All other args are passed on to the
209 ``colorlog.ColoredFormatter`` constructor.
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)
217 Example:
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 }
233 def format(self, record: logging.LogRecord) -> str:
234 return self.formatters[record.levelname].format(record)
237# Provided for backwards compatibility. The features provided by this subclass are now
238# included directly in the `ColoredFormatter` class.
239TTYColoredFormatter = ColoredFormatter