Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/structlog/dev.py: 69%
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# SPDX-License-Identifier: MIT OR Apache-2.0
2# This file is dual licensed under the terms of the Apache License, Version
3# 2.0, and the MIT License. See the LICENSE file in the root of this
4# repository for complete details.
6"""
7Helpers that make development with *structlog* more pleasant.
9See also the narrative documentation in `console-output`.
10"""
12from __future__ import annotations
14import sys
15import warnings
17from collections.abc import Callable, Sequence
18from dataclasses import dataclass
19from io import StringIO
20from types import ModuleType
21from typing import (
22 Any,
23 Literal,
24 Protocol,
25 TextIO,
26 cast,
27)
29from ._frames import _format_exception
30from .exceptions import (
31 MultipleConsoleRenderersConfiguredError,
32 NoConsoleRendererConfiguredError,
33)
34from .processors import _figure_out_exc_info
35from .typing import EventDict, ExceptionRenderer, ExcInfo, WrappedLogger
38try:
39 import colorama
40except ImportError:
41 colorama = None
43try:
44 import better_exceptions
45except ImportError:
46 better_exceptions = None
48try:
49 import rich
51 from rich.console import Console
52 from rich.traceback import Traceback
53except ImportError:
54 rich = None # type: ignore[assignment]
56__all__ = [
57 "ConsoleRenderer",
58 "RichTracebackFormatter",
59 "better_traceback",
60 "plain_traceback",
61 "rich_traceback",
62]
64_IS_WINDOWS = sys.platform == "win32"
66_MISSING = "{who} requires the {package} package installed. "
67_EVENT_WIDTH = 30 # pad the event name to so many characters
70if _IS_WINDOWS: # pragma: no cover
72 def _init_terminal(who: str, force_colors: bool) -> None:
73 """
74 Initialize colorama on Windows systems for colorful console output.
76 Args:
77 who: The name of the caller for error messages.
79 force_colors:
80 Force colorful output even in non-interactive environments.
82 Raises:
83 SystemError:
84 When colorama is not installed.
85 """
86 # On Windows, we can't do colorful output without colorama.
87 if colorama is None:
88 raise SystemError(
89 _MISSING.format(
90 who=who + " with `colors=True` on Windows",
91 package="colorama",
92 )
93 )
94 # Colorama must be init'd on Windows, but must NOT be
95 # init'd on other OSes, because it can break colors.
96 if force_colors:
97 colorama.deinit()
98 colorama.init(strip=False)
99 else:
100 colorama.init()
101else:
103 def _init_terminal(who: str, force_colors: bool) -> None:
104 """
105 Currently, nothing to be done on non-Windows systems.
106 """
109def _pad(s: str, length: int) -> str:
110 """
111 Pads *s* to length *length*.
112 """
113 missing = length - len(s)
115 return s + " " * (max(0, missing))
118if colorama is not None:
119 RESET_ALL = colorama.Style.RESET_ALL
120 BRIGHT = colorama.Style.BRIGHT
121 DIM = colorama.Style.DIM
122 RED = colorama.Fore.RED
123 BLUE = colorama.Fore.BLUE
124 CYAN = colorama.Fore.CYAN
125 MAGENTA = colorama.Fore.MAGENTA
126 YELLOW = colorama.Fore.YELLOW
127 GREEN = colorama.Fore.GREEN
128 RED_BACK = colorama.Back.RED
129else:
130 # These are the same values as the Colorama color codes. Redefining them
131 # here allows users to specify that they want color without having to
132 # install Colorama, which is only supposed to be necessary in Windows.
133 RESET_ALL = "\033[0m"
134 BRIGHT = "\033[1m"
135 DIM = "\033[2m"
136 RED = "\033[31m"
137 BLUE = "\033[34m"
138 CYAN = "\033[36m"
139 MAGENTA = "\033[35m"
140 YELLOW = "\033[33m"
141 GREEN = "\033[32m"
142 RED_BACK = "\033[41m"
144# On Windows, colors are only available if Colorama is installed.
145_has_colors = not _IS_WINDOWS or colorama is not None
147# Prevent breakage of packages that used the old name of the variable.
148_use_colors = _has_colors
151@dataclass(frozen=True)
152class ColumnStyles:
153 """
154 Column styles settings for console rendering.
156 These are console ANSI codes that are printed before the respective fields.
157 This allows for a certain amount of customization if you don't want to
158 configure your columns.
160 .. versionadded:: 25.5.0
161 It was handled by private structures before.
162 """
164 reset: str
165 bright: str
167 level_critical: str
168 level_exception: str
169 level_error: str
170 level_warn: str
171 level_info: str
172 level_debug: str
173 level_notset: str
175 timestamp: str
176 logger_name: str
177 kv_key: str
178 kv_value: str
181_colorful_styles = ColumnStyles(
182 reset=RESET_ALL,
183 bright=BRIGHT,
184 level_critical=RED,
185 level_exception=RED,
186 level_error=RED,
187 level_warn=YELLOW,
188 level_info=GREEN,
189 level_debug=GREEN,
190 level_notset=RED_BACK,
191 timestamp=DIM,
192 logger_name=BLUE,
193 kv_key=CYAN,
194 kv_value=MAGENTA,
195)
197_plain_styles = ColumnStyles(
198 reset="",
199 bright="",
200 level_critical="",
201 level_exception="",
202 level_error="",
203 level_warn="",
204 level_info="",
205 level_debug="",
206 level_notset="",
207 timestamp="",
208 logger_name="",
209 kv_key="",
210 kv_value="",
211)
213# Backward compatibility aliases
214_ColorfulStyles = _colorful_styles
215_PlainStyles = _plain_styles
218class ColumnFormatter(Protocol):
219 """
220 :class:`~typing.Protocol` for column formatters.
222 See `KeyValueColumnFormatter` and `LogLevelColumnFormatter` for examples.
224 .. versionadded:: 23.3.0
225 """
227 def __call__(self, key: str, value: object) -> str:
228 """
229 Format *value* for *key*.
231 This method is responsible for formatting, *key*, the ``=``, and the
232 *value*. That means that it can use any string instead of the ``=`` and
233 it can leave out both the *key* or the *value*.
235 If it returns an empty string, the column is omitted completely.
236 """
239@dataclass
240class Column:
241 """
242 A column defines the way a key-value pair is formatted, and, by it's
243 position to the *columns* argument of `ConsoleRenderer`, the order in which
244 it is rendered.
246 Args:
247 key:
248 The key for which this column is responsible. Leave empty to define
249 it as the default formatter.
251 formatter: The formatter for columns with *key*.
253 .. versionadded:: 23.3.0
254 """
256 key: str
257 formatter: ColumnFormatter
260@dataclass
261class KeyValueColumnFormatter:
262 """
263 Format a key-value pair.
265 Args:
266 key_style: The style to apply to the key. If None, the key is omitted.
268 value_style: The style to apply to the value.
270 reset_style: The style to apply whenever a style is no longer needed.
272 value_repr:
273 A callable that returns the string representation of the value.
275 width: The width to pad the value to. If 0, no padding is done.
277 prefix:
278 A string to prepend to the formatted key-value pair. May contain
279 styles.
281 postfix:
282 A string to append to the formatted key-value pair. May contain
283 styles.
285 .. versionadded:: 23.3.0
286 """
288 key_style: str | None
289 value_style: str
290 reset_style: str
291 value_repr: Callable[[object], str]
292 width: int = 0
293 prefix: str = ""
294 postfix: str = ""
296 def __call__(self, key: str, value: object) -> str:
297 sio = StringIO()
299 if self.prefix:
300 sio.write(self.prefix)
301 sio.write(self.reset_style)
303 if self.key_style is not None:
304 sio.write(self.key_style)
305 sio.write(key)
306 sio.write(self.reset_style)
307 sio.write("=")
309 sio.write(self.value_style)
310 sio.write(_pad(self.value_repr(value), self.width))
311 sio.write(self.reset_style)
313 if self.postfix:
314 sio.write(self.postfix)
315 sio.write(self.reset_style)
317 return sio.getvalue()
320class LogLevelColumnFormatter:
321 """
322 Format a log level according to *level_styles*.
324 The width is padded to the longest level name (if *level_styles* is passed
325 -- otherwise there's no way to know the lengths of all levels).
327 Args:
328 level_styles:
329 A dictionary of level names to styles that are applied to it. If
330 None, the level is formatted as a plain ``[level]``.
332 reset_style:
333 What to use to reset the style after the level name. Ignored if
334 if *level_styles* is None.
336 width:
337 The width to pad the level to. If 0, no padding is done.
339 .. versionadded:: 23.3.0
340 .. versionadded:: 24.2.0 *width*
341 """
343 level_styles: dict[str, str] | None
344 reset_style: str
345 width: int
347 def __init__(
348 self,
349 level_styles: dict[str, str],
350 reset_style: str,
351 width: int | None = None,
352 ) -> None:
353 self.level_styles = level_styles
354 if level_styles:
355 self.width = (
356 0
357 if width == 0
358 else len(max(self.level_styles.keys(), key=len))
359 )
360 self.reset_style = reset_style
361 else:
362 self.width = 0
363 self.reset_style = ""
365 def __call__(self, key: str, value: object) -> str:
366 level = cast(str, value)
367 style = (
368 ""
369 if self.level_styles is None
370 else self.level_styles.get(level, "")
371 )
373 return f"[{style}{_pad(level, self.width)}{self.reset_style}]"
376_NOTHING = object()
379def plain_traceback(sio: TextIO, exc_info: ExcInfo) -> None:
380 """
381 "Pretty"-print *exc_info* to *sio* using our own plain formatter.
383 To be passed into `ConsoleRenderer`'s ``exception_formatter`` argument.
385 Used by default if neither Rich nor *better-exceptions* are present.
387 .. versionadded:: 21.2.0
388 """
389 sio.write("\n" + _format_exception(exc_info))
392@dataclass
393class RichTracebackFormatter:
394 """
395 A Rich traceback renderer with the given options.
397 Pass an instance as `ConsoleRenderer`'s ``exception_formatter`` argument.
399 See :class:`rich.traceback.Traceback` for details on the arguments.
401 If *width* is `None`, the terminal width is used. If the width can't be
402 determined, fall back to 80.
404 .. versionadded:: 23.2.0
406 .. versionchanged:: 25.4.0
407 Default *width* is ``None`` to have full width and reflow support.
408 Passing ``-1`` as width is deprecated, use ``None`` instead.
409 *word_wrap* is now True by default.
411 .. versionadded:: 25.4.0 *code_width*
413 .. versionchanged:: 26.1.0
414 ``None`` is now valid for *color_system* and disables color output.
415 """
417 color_system: (
418 Literal["auto", "standard", "256", "truecolor", "windows"] | None
419 ) = "truecolor"
420 show_locals: bool = True
421 max_frames: int = 100
422 theme: str | None = None
423 word_wrap: bool = True
424 extra_lines: int = 3
425 width: int | None = None
426 code_width: int | None = 88
427 indent_guides: bool = True
428 locals_max_length: int = 10
429 locals_max_string: int = 80
430 locals_hide_dunder: bool = True
431 locals_hide_sunder: bool = False
432 suppress: Sequence[str | ModuleType] = ()
434 def __call__(self, sio: TextIO, exc_info: ExcInfo) -> None:
435 if self.width == -1:
436 warnings.warn(
437 "Use None to use the terminal width instead of -1.",
438 DeprecationWarning,
439 stacklevel=2,
440 )
441 self.width = None
443 sio.write("\n")
445 console = Console(
446 file=sio, color_system=self.color_system, width=self.width
447 )
448 tb = Traceback.from_exception(
449 *exc_info,
450 show_locals=self.show_locals,
451 max_frames=self.max_frames,
452 theme=self.theme,
453 word_wrap=self.word_wrap,
454 extra_lines=self.extra_lines,
455 width=self.width,
456 indent_guides=self.indent_guides,
457 locals_max_length=self.locals_max_length,
458 locals_max_string=self.locals_max_string,
459 locals_hide_dunder=self.locals_hide_dunder,
460 locals_hide_sunder=self.locals_hide_sunder,
461 suppress=self.suppress,
462 )
463 if hasattr(tb, "code_width"):
464 # `code_width` requires `rich>=13.8.0`
465 tb.code_width = self.code_width
466 console.print(tb)
469if rich is None:
471 def rich_traceback(*args, **kw):
472 raise ModuleNotFoundError(
473 "RichTracebackFormatter requires Rich to be installed.",
474 name="rich",
475 )
477 rich_monochrome_traceback = rich_traceback
479else:
480 rich_traceback = RichTracebackFormatter()
481 """
482 Pretty-print *exc_info* to *sio* using the Rich package.
484 To be passed into `ConsoleRenderer`'s ``exception_formatter`` argument.
486 This is a `RichTracebackFormatter` with default arguments and used by default
487 if Rich is installed.
489 .. versionadded:: 21.2.0
490 """
492 rich_monochrome_traceback = RichTracebackFormatter(color_system=None)
493 """
494 Pretty-print *exc_info* to *sio* using the Rich package w/o colors.
496 To be passed into `ConsoleRenderer`'s ``exception_formatter`` argument.
498 This is a `RichTracebackFormatter` with default arguments except for
499 ``color_system=None``, and used by default if Rich is installed and colors
500 are disabled.
502 .. versionadded:: 26.1.0
503 """
506def better_traceback(sio: TextIO, exc_info: ExcInfo) -> None:
507 """
508 Pretty-print *exc_info* to *sio* using the *better-exceptions* package.
510 To be passed into `ConsoleRenderer`'s ``exception_formatter`` argument.
512 Used by default if *better-exceptions* is installed and Rich is absent.
514 .. versionadded:: 21.2.0
515 .. deprecated:: 26.1.0
516 *better-exceptions* support is deprecated and will be removed in a
517 future release. Use Rich instead.
518 """
519 warnings.warn(
520 "better-exceptions support is deprecated and will be removed "
521 "in a future release. Use Rich instead.",
522 DeprecationWarning,
523 stacklevel=2,
524 )
525 sio.write("\n" + "".join(better_exceptions.format_exception(*exc_info)))
528if rich is not None:
529 default_exception_formatter = rich_traceback
530 default_monochrome_exception_formatter = rich_monochrome_traceback
531elif better_exceptions is not None:
532 default_exception_formatter = default_monochrome_exception_formatter = (
533 better_traceback
534 )
535 warnings.warn(
536 "better-exceptions support is deprecated and will be removed "
537 "in a future release. Use Rich instead.",
538 DeprecationWarning,
539 stacklevel=2,
540 )
541else:
542 default_exception_formatter = default_monochrome_exception_formatter = (
543 plain_traceback
544 )
547class ConsoleRenderer:
548 r"""
549 Render ``event_dict`` nicely aligned, possibly in colors, and ordered.
551 If ``event_dict`` contains a true-ish ``exc_info`` key, it will be rendered
552 *after* the log line. If Rich_ is present, in colors and with extra
553 context.
555 Tip:
556 Since `ConsoleRenderer` is mainly a development helper, it is less
557 strict about immutability than the rest of *structlog* for better
558 ergonomics. Notably, the currently active instance can be obtained by
559 calling `ConsoleRenderer.get_active()` and it offers properties to
560 configure its behavior after instantiation.
562 Args:
563 columns:
564 A list of `Column` objects defining both the order and format of
565 the key-value pairs in the output. If passed, most other arguments
566 become meaningless.
568 **Must** contain a column with ``key=''`` that defines the default
569 formatter.
571 .. seealso:: `columns-config`
573 pad_event_to:
574 Pad the event to this many characters. Ignored if *columns* are
575 passed.
577 colors:
578 Use colors for a nicer output. `True` by default. On Windows only
579 if Colorama_ is installed. Ignored if *columns* are passed.
581 force_colors:
582 Force colors even for non-tty destinations. Use this option if your
583 logs are stored in a file that is meant to be streamed to the
584 console. Only meaningful on Windows. Ignored if *columns* are
585 passed.
587 repr_native_str:
588 When `True`, `repr` is also applied to ``str``\ s. The ``event``
589 key is *never* `repr` -ed. Ignored if *columns* are passed.
591 level_styles:
592 When present, use these styles for colors. This must be a dict from
593 level names (strings) to terminal sequences (for example, Colorama)
594 styles. The default can be obtained by calling
595 `ConsoleRenderer.get_default_level_styles`. Ignored when *columns*
596 are passed.
598 exception_formatter:
599 A callable to render ``exc_infos``. If Rich_ is installed, it is
600 used for pretty-printing by default. You can also manually set it
601 to `plain_traceback`, an instance of `RichTracebackFormatter` like
602 `rich_traceback`, or implement your own.
604 sort_keys:
605 Whether to sort keys when formatting. `True` by default. Ignored if
606 *columns* are passed.
608 event_key:
609 The key to look for the main log message. Needed when you rename it
610 e.g. using `structlog.processors.EventRenamer`. Ignored if
611 *columns* are passed.
613 timestamp_key:
614 The key to look for timestamp of the log message. Needed when you
615 rename it e.g. using `structlog.processors.EventRenamer`. Ignored
616 if *columns* are passed.
618 pad_level:
619 Whether to pad log level with blanks to the longest amongst all
620 level label.
622 Requires the Colorama_ package if *colors* is `True` **on Windows**.
624 Raises:
625 ValueError: If there's not exactly one default column formatter.
627 .. _Colorama: https://pypi.org/project/colorama/
628 .. _better-exceptions: https://pypi.org/project/better-exceptions/
629 .. _Rich: https://pypi.org/project/rich/
631 .. versionadded:: 16.0.0
632 .. versionadded:: 16.1.0 *colors*
633 .. versionadded:: 17.1.0 *repr_native_str*
634 .. versionadded:: 18.1.0 *force_colors*
635 .. versionadded:: 18.1.0 *level_styles*
636 .. versionchanged:: 19.2.0
637 Colorama now initializes lazily to avoid unwanted initializations as
638 ``ConsoleRenderer`` is used by default.
639 .. versionchanged:: 19.2.0 Can be pickled now.
640 .. versionchanged:: 20.1.0
641 Colorama does not initialize lazily on Windows anymore because it breaks
642 rendering.
643 .. versionchanged:: 21.1.0
644 It is additionally possible to set the logger name using the
645 ``logger_name`` key in the ``event_dict``.
646 .. versionadded:: 21.2.0 *exception_formatter*
647 .. versionchanged:: 21.2.0
648 `ConsoleRenderer` now handles the ``exc_info`` event dict key itself. Do
649 **not** use the `structlog.processors.format_exc_info` processor
650 together with `ConsoleRenderer` anymore! It will keep working, but you
651 can't have customize exception formatting and a warning will be raised
652 if you ask for it.
653 .. versionchanged:: 21.2.0
654 The colors keyword now defaults to True on non-Windows systems, and
655 either True or False in Windows depending on whether Colorama is
656 installed.
657 .. versionadded:: 21.3.0 *sort_keys*
658 .. versionadded:: 22.1.0 *event_key*
659 .. versionadded:: 23.2.0 *timestamp_key*
660 .. versionadded:: 23.3.0 *columns*
661 .. versionadded:: 24.2.0 *pad_level*
662 .. versionchanged:: 26.1.0
663 The default exception formatter is now monochrome if colors are disabled.
664 """
666 _default_column_formatter: ColumnFormatter
668 def __init__(
669 self,
670 pad_event_to: int = _EVENT_WIDTH,
671 colors: bool = _has_colors,
672 force_colors: bool = False,
673 repr_native_str: bool = False,
674 level_styles: dict[str, str] | None = None,
675 exception_formatter: ExceptionRenderer = default_exception_formatter,
676 sort_keys: bool = True,
677 event_key: str = "event",
678 timestamp_key: str = "timestamp",
679 columns: list[Column] | None = None,
680 pad_level: bool = True,
681 pad_event: int | None = None,
682 ):
683 if pad_event is not None:
684 if pad_event_to != _EVENT_WIDTH:
685 raise ValueError(
686 "Cannot set both `pad_event` and `pad_event_to`."
687 )
688 warnings.warn(
689 "The `pad_event` argument is deprecated. Use `pad_event_to` instead.",
690 DeprecationWarning,
691 stacklevel=2,
692 )
693 pad_event_to = pad_event
695 # Store all settings in case the user later switches from columns to
696 # defaults.
697 self.exception_formatter = exception_formatter
698 self._sort_keys = sort_keys
699 self._repr_native_str = repr_native_str
700 self._styles = self.get_default_column_styles(colors, force_colors)
701 self._colors = colors
702 self._force_colors = force_colors
703 self._level_styles = (
704 self.get_default_level_styles(colors)
705 if level_styles is None
706 else level_styles
707 )
708 self._pad_event_to = pad_event_to
709 self._timestamp_key = timestamp_key
710 self._event_key = event_key
711 self._pad_level = pad_level
713 if exception_formatter is default_exception_formatter and not colors:
714 self.exception_formatter = default_monochrome_exception_formatter
716 if columns is None:
717 self._configure_columns()
718 return
720 self.columns = columns
722 to_warn = []
724 def add_meaningless_arg(arg: str) -> None:
725 to_warn.append(
726 f"The `{arg}` argument is ignored when passing `columns`.",
727 )
729 if pad_event_to != _EVENT_WIDTH:
730 add_meaningless_arg("pad_event_to")
732 if colors != _has_colors:
733 add_meaningless_arg("colors")
735 if force_colors is not False:
736 add_meaningless_arg("force_colors")
738 if repr_native_str is not False:
739 add_meaningless_arg("repr_native_str")
741 if level_styles is not None:
742 add_meaningless_arg("level_styles")
744 if event_key != "event":
745 add_meaningless_arg("event_key")
747 if timestamp_key != "timestamp":
748 add_meaningless_arg("timestamp_key")
750 for w in to_warn:
751 warnings.warn(w, stacklevel=2)
753 @classmethod
754 def get_active(cls) -> ConsoleRenderer:
755 """
756 If *structlog* is configured to use `ConsoleRenderer`, it's returned.
758 It does not have to be the last processor.
760 Raises:
761 NoConsoleRendererConfiguredError:
762 If no ConsoleRenderer is found in the current configuration.
764 MultipleConsoleRenderersConfiguredError:
765 If more than one is found in the current configuration. This is
766 almost certainly a bug.
768 .. versionadded:: 25.5.0
769 """
770 from ._config import get_config
772 cr = None
773 for p in get_config()["processors"]:
774 if isinstance(p, ConsoleRenderer):
775 if cr is not None:
776 raise MultipleConsoleRenderersConfiguredError
778 cr = p
780 if cr is None:
781 raise NoConsoleRendererConfiguredError
783 return cr
785 @classmethod
786 def get_default_column_styles(
787 cls, colors: bool, force_colors: bool = False
788 ) -> ColumnStyles:
789 """
790 Configure and return the appropriate styles class for console output.
792 This method handles the setup of colorful or plain styles, including
793 proper colorama initialization on Windows systems when colors are
794 enabled.
796 Args:
797 colors: Whether to use colorful output styles.
799 force_colors:
800 Force colorful output even in non-interactive environments.
801 Only relevant on Windows with colorama.
803 Returns:
804 The configured styles.
806 Raises:
807 SystemError:
808 On Windows when colors=True but colorama is not installed.
810 .. versionadded:: 25.5.0
811 """
812 if not colors:
813 return _plain_styles
815 _init_terminal(cls.__name__, force_colors)
817 return _colorful_styles
819 @staticmethod
820 def get_default_level_styles(colors: bool = True) -> dict[str, str]:
821 """
822 Get the default styles for log levels
824 This is intended to be used with `ConsoleRenderer`'s ``level_styles``
825 parameter. For example, if you are adding custom levels in your
826 home-grown :func:`~structlog.stdlib.add_log_level` you could do::
828 my_styles = ConsoleRenderer.get_default_level_styles()
829 my_styles["EVERYTHING_IS_ON_FIRE"] = my_styles["critical"]
830 renderer = ConsoleRenderer(level_styles=my_styles)
832 Args:
833 colors:
834 Whether to use colorful styles. This must match the *colors*
835 parameter to `ConsoleRenderer`. Default: `True`.
836 """
837 styles: ColumnStyles
838 styles = _colorful_styles if colors else _plain_styles
839 return {
840 "critical": styles.level_critical,
841 "exception": styles.level_exception,
842 "error": styles.level_error,
843 "warn": styles.level_warn,
844 "warning": styles.level_warn,
845 "info": styles.level_info,
846 "debug": styles.level_debug,
847 "notset": styles.level_notset,
848 }
850 def _configure_columns(self) -> None:
851 """
852 Re-configure self._columns and self._default_column_formatter
853 according to our current settings.
855 Overwrite existing columns settings, regardless of whether they were
856 explicitly passed by the user or derived by us.
857 """
858 level_to_color = self._level_styles.copy()
860 for key in level_to_color:
861 level_to_color[key] += self._styles.bright
862 self._longest_level = len(max(level_to_color.keys(), key=len))
864 self._default_column_formatter = KeyValueColumnFormatter(
865 self._styles.kv_key,
866 self._styles.kv_value,
867 self._styles.reset,
868 value_repr=self._repr,
869 width=0,
870 )
872 logger_name_formatter = KeyValueColumnFormatter(
873 key_style=None,
874 value_style=self._styles.bright + self._styles.logger_name,
875 reset_style=self._styles.reset,
876 value_repr=str,
877 prefix="[",
878 postfix="]",
879 )
881 level_width = 0 if not self._pad_level else None
883 self._columns = [
884 Column(
885 self._timestamp_key,
886 KeyValueColumnFormatter(
887 key_style=None,
888 value_style=self._styles.timestamp,
889 reset_style=self._styles.reset,
890 value_repr=str,
891 ),
892 ),
893 Column(
894 "level",
895 LogLevelColumnFormatter(
896 level_to_color,
897 reset_style=self._styles.reset,
898 width=level_width,
899 ),
900 ),
901 Column(
902 self._event_key,
903 KeyValueColumnFormatter(
904 key_style=None,
905 value_style=self._styles.bright,
906 reset_style=self._styles.reset,
907 value_repr=str,
908 width=self._pad_event_to,
909 ),
910 ),
911 Column("logger", logger_name_formatter),
912 Column("logger_name", logger_name_formatter),
913 ]
915 def _repr(self, val: Any) -> str:
916 """
917 Determine representation of *val* depending on its type &
918 self._repr_native_str.
919 """
920 if self._repr_native_str is True:
921 return repr(val)
923 if isinstance(val, str):
924 if set(val) & {" ", "\t", "=", "\r", "\n", '"', "'"}:
925 return repr(val)
926 return val
928 return repr(val)
930 def __call__(
931 self, logger: WrappedLogger, name: str, event_dict: EventDict
932 ) -> str:
933 stack = event_dict.pop("stack", None)
934 exc = event_dict.pop("exception", None)
935 exc_info = event_dict.pop("exc_info", None)
937 kvs = [
938 col.formatter(col.key, val)
939 for col in self.columns
940 if (val := event_dict.pop(col.key, _NOTHING)) is not _NOTHING
941 ] + [
942 self._default_column_formatter(key, event_dict[key])
943 for key in (sorted(event_dict) if self._sort_keys else event_dict)
944 ]
946 sio = StringIO()
947 sio.write((" ".join(kv for kv in kvs if kv)).rstrip(" "))
949 if stack is not None:
950 sio.write("\n" + stack)
951 if exc_info or exc is not None:
952 sio.write("\n\n" + "=" * 79 + "\n")
954 exc_info = _figure_out_exc_info(exc_info)
955 if exc_info:
956 self._exception_formatter(sio, exc_info)
957 elif exc is not None:
958 sio.write("\n" + exc)
960 return sio.getvalue()
962 @property
963 def exception_formatter(self) -> ExceptionRenderer:
964 """
965 The exception formatter used by this console renderer.
967 .. versionadded:: 25.5.0
968 """
969 return self._exception_formatter
971 @exception_formatter.setter
972 def exception_formatter(self, value: ExceptionRenderer) -> None:
973 """
974 .. versionadded:: 25.5.0
975 """
976 self._exception_formatter = value
978 @property
979 def sort_keys(self) -> bool:
980 """
981 Whether to sort keys when formatting.
983 .. versionadded:: 25.5.0
984 """
985 return self._sort_keys
987 @sort_keys.setter
988 def sort_keys(self, value: bool) -> None:
989 """
990 .. versionadded:: 25.5.0
991 """
992 # _sort_keys is a format-time setting, so we can just set it directly.
993 self._sort_keys = value
995 @property
996 def columns(self) -> list[Column]:
997 """
998 The columns configuration for this console renderer.
1000 Warning:
1001 Just like with passing *columns* argument, many of the other
1002 arguments you may have passed are ignored.
1004 Args:
1005 value:
1006 A list of `Column` objects defining both the order and format
1007 of the key-value pairs in the output.
1009 **Must** contain a column with ``key=''`` that defines the
1010 default formatter.
1012 Raises:
1013 ValueError: If there's not exactly one default column formatter.
1015 .. versionadded:: 25.5.0
1016 """
1017 return [Column("", self._default_column_formatter), *self._columns]
1019 @columns.setter
1020 def columns(self, value: list[Column]) -> None:
1021 """
1022 .. versionadded:: 25.5.0
1023 """
1024 defaults = [col for col in value if col.key == ""]
1025 if not defaults:
1026 raise ValueError(
1027 "Must pass a default column formatter (a column with `key=''`)."
1028 )
1029 if len(defaults) > 1:
1030 raise ValueError("Only one default column formatter allowed.")
1032 self._default_column_formatter = defaults[0].formatter
1033 self._columns = [col for col in value if col.key]
1035 @property
1036 def colors(self) -> bool:
1037 """
1038 Whether to use colorful output styles.
1040 Setting this will update the renderer's styles immediately and reset
1041 level styles to the defaults according to the new color setting -- even
1042 if the color value is the same.
1044 .. versionadded:: 25.5.0
1045 """
1046 return self._colors
1048 @colors.setter
1049 def colors(self, value: bool) -> None:
1050 """
1051 .. versionadded:: 25.5.0
1053 .. versionchanged:: 26.1.0
1054 Also switches to monochrome exception formatting if colors are disabled.
1055 """
1056 self._colors = value
1057 self._styles = self.get_default_column_styles(
1058 value, self._force_colors
1059 )
1060 self._level_styles = self.get_default_level_styles(value)
1062 # Flip default exception formatter if configured to use one of the
1063 # default ones.
1064 if self.exception_formatter is (
1065 default_exception_formatter
1066 ) or self.exception_formatter is (
1067 default_monochrome_exception_formatter
1068 ):
1069 self.exception_formatter = (
1070 default_exception_formatter
1071 if value
1072 else default_monochrome_exception_formatter
1073 )
1075 self._configure_columns()
1077 @property
1078 def force_colors(self) -> bool:
1079 """
1080 Force colorful output even in non-interactive environments.
1082 Setting this will update the renderer's styles immediately and reset
1083 level styles to the defaults according to the current color setting --
1084 even if the value is the same.
1086 .. versionadded:: 25.5.0
1087 """
1088 return self._force_colors
1090 @force_colors.setter
1091 def force_colors(self, value: bool) -> None:
1092 """
1093 .. versionadded:: 25.5.0
1094 """
1095 self._force_colors = value
1096 self._styles = self.get_default_column_styles(self._colors, value)
1097 self._level_styles = self.get_default_level_styles(self._colors)
1099 self._configure_columns()
1101 @property
1102 def level_styles(self) -> dict[str, str]:
1103 """
1104 The level styles mapping for this console renderer.
1106 Setting this property will reset to defaults if set to None, otherwise
1107 it applies the provided mapping. In all cases, columns are rebuilt to
1108 reflect the change.
1110 .. versionadded:: 25.5.0
1111 """
1112 return self._level_styles
1114 @level_styles.setter
1115 def level_styles(self, value: dict[str, str] | None) -> None:
1116 """
1117 .. versionadded:: 25.5.0
1118 """
1119 self._level_styles = (
1120 self.get_default_level_styles(self._colors)
1121 if value is None
1122 else value
1123 )
1124 self._configure_columns()
1126 @property
1127 def pad_level(self) -> bool:
1128 """
1129 Whether to pad log level with blanks to the longest amongst all
1130 level labels.
1132 Setting this will rebuild columns to reflect the change.
1134 .. versionadded:: 25.5.0
1135 """
1136 return self._pad_level
1138 @pad_level.setter
1139 def pad_level(self, value: bool) -> None:
1140 """
1141 .. versionadded:: 25.5.0
1142 """
1143 self._pad_level = value
1144 self._configure_columns()
1146 @property
1147 def pad_event_to(self) -> int:
1148 """
1149 The number of characters to pad the event to.
1151 Setting this will rebuild columns to reflect the change.
1153 .. versionadded:: 25.5.0
1154 """
1155 return self._pad_event_to
1157 @pad_event_to.setter
1158 def pad_event_to(self, value: int) -> None:
1159 """
1160 .. versionadded:: 25.5.0
1161 """
1162 self._pad_event_to = value
1163 self._configure_columns()
1165 @property
1166 def event_key(self) -> str:
1167 """
1168 The key to look for the main log message.
1170 Setting this will rebuild columns to reflect the change.
1172 .. versionadded:: 25.5.0
1173 """
1174 return self._event_key
1176 @event_key.setter
1177 def event_key(self, value: str) -> None:
1178 """
1179 .. versionadded:: 25.5.0
1180 """
1181 self._event_key = value
1182 self._configure_columns()
1184 @property
1185 def timestamp_key(self) -> str:
1186 """
1187 The key to look for the timestamp of the log message.
1189 Setting this will rebuild columns to reflect the change.
1191 .. versionadded:: 25.5.0
1192 """
1193 return self._timestamp_key
1195 @timestamp_key.setter
1196 def timestamp_key(self, value: str) -> None:
1197 """
1198 .. versionadded:: 25.5.0
1199 """
1200 self._timestamp_key = value
1201 self._configure_columns()
1203 @property
1204 def repr_native_str(self) -> bool:
1205 """
1206 Whether native strings are passed through repr() in non-event values.
1208 .. versionadded:: 25.5.0
1209 """
1210 return self._repr_native_str
1212 @repr_native_str.setter
1213 def repr_native_str(self, value: bool) -> None:
1214 """
1215 .. versionadded:: 25.5.0
1216 """
1217 self._repr_native_str = value
1220_SENTINEL = object()
1223def set_exc_info(
1224 logger: WrappedLogger, method_name: str, event_dict: EventDict
1225) -> EventDict:
1226 """
1227 Set ``event_dict["exc_info"] = True`` if *method_name* is ``"exception"``.
1229 Do nothing if the name is different or ``exc_info`` is already set.
1231 .. versionadded:: 19.2.0
1232 """
1233 if (
1234 method_name != "exception"
1235 or event_dict.get("exc_info", _SENTINEL) is not _SENTINEL
1236 ):
1237 return event_dict
1239 event_dict["exc_info"] = True
1241 return event_dict