Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/structlog/dev.py: 70%
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 dataclasses import dataclass
18from io import StringIO
19from types import ModuleType
20from typing import (
21 Any,
22 Callable,
23 Literal,
24 Protocol,
25 Sequence,
26 TextIO,
27 cast,
28)
30from ._frames import _format_exception
31from .exceptions import (
32 MultipleConsoleRenderersConfiguredError,
33 NoConsoleRendererConfiguredError,
34)
35from .processors import _figure_out_exc_info
36from .typing import EventDict, ExceptionRenderer, ExcInfo, WrappedLogger
39try:
40 import colorama
41except ImportError:
42 colorama = None
44try:
45 import better_exceptions
46except ImportError:
47 better_exceptions = None
49try:
50 import rich
52 from rich.console import Console
53 from rich.traceback import Traceback
54except ImportError:
55 rich = None # type: ignore[assignment]
57__all__ = [
58 "ConsoleRenderer",
59 "RichTracebackFormatter",
60 "better_traceback",
61 "plain_traceback",
62 "rich_traceback",
63]
65_IS_WINDOWS = sys.platform == "win32"
67_MISSING = "{who} requires the {package} package installed. "
68_EVENT_WIDTH = 30 # pad the event name to so many characters
71if _IS_WINDOWS: # pragma: no cover
73 def _init_terminal(who: str, force_colors: bool) -> None:
74 """
75 Initialize colorama on Windows systems for colorful console output.
77 Args:
78 who: The name of the caller for error messages.
80 force_colors:
81 Force colorful output even in non-interactive environments.
83 Raises:
84 SystemError:
85 When colorama is not installed.
86 """
87 # On Windows, we can't do colorful output without colorama.
88 if colorama is None:
89 raise SystemError(
90 _MISSING.format(
91 who=who + " with `colors=True` on Windows",
92 package="colorama",
93 )
94 )
95 # Colorama must be init'd on Windows, but must NOT be
96 # init'd on other OSes, because it can break colors.
97 if force_colors:
98 colorama.deinit()
99 colorama.init(strip=False)
100 else:
101 colorama.init()
102else:
104 def _init_terminal(who: str, force_colors: bool) -> None:
105 """
106 Currently, nothing to be done on non-Windows systems.
107 """
110def _pad(s: str, length: int) -> str:
111 """
112 Pads *s* to length *length*.
113 """
114 missing = length - len(s)
116 return s + " " * (max(0, missing))
119if colorama is not None:
120 RESET_ALL = colorama.Style.RESET_ALL
121 BRIGHT = colorama.Style.BRIGHT
122 DIM = colorama.Style.DIM
123 RED = colorama.Fore.RED
124 BLUE = colorama.Fore.BLUE
125 CYAN = colorama.Fore.CYAN
126 MAGENTA = colorama.Fore.MAGENTA
127 YELLOW = colorama.Fore.YELLOW
128 GREEN = colorama.Fore.GREEN
129 RED_BACK = colorama.Back.RED
130else:
131 # These are the same values as the Colorama color codes. Redefining them
132 # here allows users to specify that they want color without having to
133 # install Colorama, which is only supposed to be necessary in Windows.
134 RESET_ALL = "\033[0m"
135 BRIGHT = "\033[1m"
136 DIM = "\033[2m"
137 RED = "\033[31m"
138 BLUE = "\033[34m"
139 CYAN = "\033[36m"
140 MAGENTA = "\033[35m"
141 YELLOW = "\033[33m"
142 GREEN = "\033[32m"
143 RED_BACK = "\033[41m"
145# On Windows, colors are only available if Colorama is installed.
146_has_colors = not _IS_WINDOWS or colorama is not None
148# Prevent breakage of packages that used the old name of the variable.
149_use_colors = _has_colors
152@dataclass(frozen=True)
153class ColumnStyles:
154 """
155 Column styles settings for console rendering.
157 These are console ANSI codes that are printed before the respective fields.
158 This allows for a certain amount of customization if you don't want to
159 configure your columns.
161 .. versionadded:: 25.5.0
162 It was handled by private structures before.
163 """
165 reset: str
166 bright: str
168 level_critical: str
169 level_exception: str
170 level_error: str
171 level_warn: str
172 level_info: str
173 level_debug: str
174 level_notset: str
176 timestamp: str
177 logger_name: str
178 kv_key: str
179 kv_value: str
182_colorful_styles = ColumnStyles(
183 reset=RESET_ALL,
184 bright=BRIGHT,
185 level_critical=RED,
186 level_exception=RED,
187 level_error=RED,
188 level_warn=YELLOW,
189 level_info=GREEN,
190 level_debug=GREEN,
191 level_notset=RED_BACK,
192 timestamp=DIM,
193 logger_name=BLUE,
194 kv_key=CYAN,
195 kv_value=MAGENTA,
196)
198_plain_styles = ColumnStyles(
199 reset="",
200 bright="",
201 level_critical="",
202 level_exception="",
203 level_error="",
204 level_warn="",
205 level_info="",
206 level_debug="",
207 level_notset="",
208 timestamp="",
209 logger_name="",
210 kv_key="",
211 kv_value="",
212)
214# Backward compatibility aliases
215_ColorfulStyles = _colorful_styles
216_PlainStyles = _plain_styles
219class ColumnFormatter(Protocol):
220 """
221 :class:`~typing.Protocol` for column formatters.
223 See `KeyValueColumnFormatter` and `LogLevelColumnFormatter` for examples.
225 .. versionadded:: 23.3.0
226 """
228 def __call__(self, key: str, value: object) -> str:
229 """
230 Format *value* for *key*.
232 This method is responsible for formatting, *key*, the ``=``, and the
233 *value*. That means that it can use any string instead of the ``=`` and
234 it can leave out both the *key* or the *value*.
236 If it returns an empty string, the column is omitted completely.
237 """
240@dataclass
241class Column:
242 """
243 A column defines the way a key-value pair is formatted, and, by it's
244 position to the *columns* argument of `ConsoleRenderer`, the order in which
245 it is rendered.
247 Args:
248 key:
249 The key for which this column is responsible. Leave empty to define
250 it as the default formatter.
252 formatter: The formatter for columns with *key*.
254 .. versionadded:: 23.3.0
255 """
257 key: str
258 formatter: ColumnFormatter
261@dataclass
262class KeyValueColumnFormatter:
263 """
264 Format a key-value pair.
266 Args:
267 key_style: The style to apply to the key. If None, the key is omitted.
269 value_style: The style to apply to the value.
271 reset_style: The style to apply whenever a style is no longer needed.
273 value_repr:
274 A callable that returns the string representation of the value.
276 width: The width to pad the value to. If 0, no padding is done.
278 prefix:
279 A string to prepend to the formatted key-value pair. May contain
280 styles.
282 postfix:
283 A string to append to the formatted key-value pair. May contain
284 styles.
286 .. versionadded:: 23.3.0
287 """
289 key_style: str | None
290 value_style: str
291 reset_style: str
292 value_repr: Callable[[object], str]
293 width: int = 0
294 prefix: str = ""
295 postfix: str = ""
297 def __call__(self, key: str, value: object) -> str:
298 sio = StringIO()
300 if self.prefix:
301 sio.write(self.prefix)
302 sio.write(self.reset_style)
304 if self.key_style is not None:
305 sio.write(self.key_style)
306 sio.write(key)
307 sio.write(self.reset_style)
308 sio.write("=")
310 sio.write(self.value_style)
311 sio.write(_pad(self.value_repr(value), self.width))
312 sio.write(self.reset_style)
314 if self.postfix:
315 sio.write(self.postfix)
316 sio.write(self.reset_style)
318 return sio.getvalue()
321class LogLevelColumnFormatter:
322 """
323 Format a log level according to *level_styles*.
325 The width is padded to the longest level name (if *level_styles* is passed
326 -- otherwise there's no way to know the lengths of all levels).
328 Args:
329 level_styles:
330 A dictionary of level names to styles that are applied to it. If
331 None, the level is formatted as a plain ``[level]``.
333 reset_style:
334 What to use to reset the style after the level name. Ignored if
335 if *level_styles* is None.
337 width:
338 The width to pad the level to. If 0, no padding is done.
340 .. versionadded:: 23.3.0
341 .. versionadded:: 24.2.0 *width*
342 """
344 level_styles: dict[str, str] | None
345 reset_style: str
346 width: int
348 def __init__(
349 self,
350 level_styles: dict[str, str],
351 reset_style: str,
352 width: int | None = None,
353 ) -> None:
354 self.level_styles = level_styles
355 if level_styles:
356 self.width = (
357 0
358 if width == 0
359 else len(max(self.level_styles.keys(), key=lambda e: len(e)))
360 )
361 self.reset_style = reset_style
362 else:
363 self.width = 0
364 self.reset_style = ""
366 def __call__(self, key: str, value: object) -> str:
367 level = cast(str, value)
368 style = (
369 ""
370 if self.level_styles is None
371 else self.level_styles.get(level, "")
372 )
374 return f"[{style}{_pad(level, self.width)}{self.reset_style}]"
377_NOTHING = object()
380def plain_traceback(sio: TextIO, exc_info: ExcInfo) -> None:
381 """
382 "Pretty"-print *exc_info* to *sio* using our own plain formatter.
384 To be passed into `ConsoleRenderer`'s ``exception_formatter`` argument.
386 Used by default if neither Rich nor *better-exceptions* are present.
388 .. versionadded:: 21.2.0
389 """
390 sio.write("\n" + _format_exception(exc_info))
393@dataclass
394class RichTracebackFormatter:
395 """
396 A Rich traceback renderer with the given options.
398 Pass an instance as `ConsoleRenderer`'s ``exception_formatter`` argument.
400 See :class:`rich.traceback.Traceback` for details on the arguments.
402 If *width* is `None`, the terminal width is used. If the width can't be
403 determined, fall back to 80.
405 .. versionadded:: 23.2.0
407 .. versionchanged:: 25.4.0
408 Default *width* is ``None`` to have full width and reflow support.
409 Passing ``-1`` as width is deprecated, use ``None`` instead.
410 *word_wrap* is now True by default.
412 .. versionadded:: 25.4.0 *code_width*
413 """
415 color_system: Literal[
416 "auto", "standard", "256", "truecolor", "windows"
417 ] = "truecolor"
418 show_locals: bool = True
419 max_frames: int = 100
420 theme: str | None = None
421 word_wrap: bool = True
422 extra_lines: int = 3
423 width: int | None = None
424 code_width: int | None = 88
425 indent_guides: bool = True
426 locals_max_length: int = 10
427 locals_max_string: int = 80
428 locals_hide_dunder: bool = True
429 locals_hide_sunder: bool = False
430 suppress: Sequence[str | ModuleType] = ()
432 def __call__(self, sio: TextIO, exc_info: ExcInfo) -> None:
433 if self.width == -1:
434 warnings.warn(
435 "Use None to use the terminal width instead of -1.",
436 DeprecationWarning,
437 stacklevel=2,
438 )
439 self.width = None
441 sio.write("\n")
443 console = Console(
444 file=sio, color_system=self.color_system, width=self.width
445 )
446 tb = Traceback.from_exception(
447 *exc_info,
448 show_locals=self.show_locals,
449 max_frames=self.max_frames,
450 theme=self.theme,
451 word_wrap=self.word_wrap,
452 extra_lines=self.extra_lines,
453 width=self.width,
454 indent_guides=self.indent_guides,
455 locals_max_length=self.locals_max_length,
456 locals_max_string=self.locals_max_string,
457 locals_hide_dunder=self.locals_hide_dunder,
458 locals_hide_sunder=self.locals_hide_sunder,
459 suppress=self.suppress,
460 )
461 if hasattr(tb, "code_width"):
462 # `code_width` requires `rich>=13.8.0`
463 tb.code_width = self.code_width
464 console.print(tb)
467if rich is None:
469 def rich_traceback(*args, **kw):
470 raise ModuleNotFoundError(
471 "RichTracebackFormatter requires Rich to be installed.",
472 name="rich",
473 )
475else:
476 rich_traceback = RichTracebackFormatter()
477 """
478 Pretty-print *exc_info* to *sio* using the Rich package.
480 To be passed into `ConsoleRenderer`'s ``exception_formatter`` argument.
482 This is a `RichTracebackFormatter` with default arguments and used by default
483 if Rich is installed.
485 .. versionadded:: 21.2.0
486 """
489def better_traceback(sio: TextIO, exc_info: ExcInfo) -> None:
490 """
491 Pretty-print *exc_info* to *sio* using the *better-exceptions* package.
493 To be passed into `ConsoleRenderer`'s ``exception_formatter`` argument.
495 Used by default if *better-exceptions* is installed and Rich is absent.
497 .. versionadded:: 21.2.0
498 """
499 sio.write("\n" + "".join(better_exceptions.format_exception(*exc_info)))
502if rich is not None:
503 default_exception_formatter = rich_traceback
504elif better_exceptions is not None:
505 default_exception_formatter = better_traceback
506else:
507 default_exception_formatter = plain_traceback
510class ConsoleRenderer:
511 r"""
512 Render ``event_dict`` nicely aligned, possibly in colors, and ordered.
514 If ``event_dict`` contains a true-ish ``exc_info`` key, it will be rendered
515 *after* the log line. If Rich_ or better-exceptions_ are present, in colors
516 and with extra context.
518 Tip:
519 Since `ConsoleRenderer` is mainly a development helper, it is less
520 strict about immutability than the rest of *structlog* for better
521 ergonomics. Notably, the currently active instance can be obtained by
522 calling `ConsoleRenderer.get_active()` and it offers properties to
523 configure its behavior after instantiation.
525 Args:
526 columns:
527 A list of `Column` objects defining both the order and format of
528 the key-value pairs in the output. If passed, most other arguments
529 become meaningless.
531 **Must** contain a column with ``key=''`` that defines the default
532 formatter.
534 .. seealso:: `columns-config`
536 pad_event_to:
537 Pad the event to this many characters. Ignored if *columns* are
538 passed.
540 colors:
541 Use colors for a nicer output. `True` by default. On Windows only
542 if Colorama_ is installed. Ignored if *columns* are passed.
544 force_colors:
545 Force colors even for non-tty destinations. Use this option if your
546 logs are stored in a file that is meant to be streamed to the
547 console. Only meaningful on Windows. Ignored if *columns* are
548 passed.
550 repr_native_str:
551 When `True`, `repr` is also applied to ``str``\ s. The ``event``
552 key is *never* `repr` -ed. Ignored if *columns* are passed.
554 level_styles:
555 When present, use these styles for colors. This must be a dict from
556 level names (strings) to terminal sequences (for example, Colorama)
557 styles. The default can be obtained by calling
558 `ConsoleRenderer.get_default_level_styles`. Ignored when *columns*
559 are passed.
561 exception_formatter:
562 A callable to render ``exc_infos``. If Rich_ or better-exceptions_
563 are installed, they are used for pretty-printing by default (rich_
564 taking precedence). You can also manually set it to
565 `plain_traceback`, `better_traceback`, an instance of
566 `RichTracebackFormatter` like `rich_traceback`, or implement your
567 own.
569 sort_keys:
570 Whether to sort keys when formatting. `True` by default. Ignored if
571 *columns* are passed.
573 event_key:
574 The key to look for the main log message. Needed when you rename it
575 e.g. using `structlog.processors.EventRenamer`. Ignored if
576 *columns* are passed.
578 timestamp_key:
579 The key to look for timestamp of the log message. Needed when you
580 rename it e.g. using `structlog.processors.EventRenamer`. Ignored
581 if *columns* are passed.
583 pad_level:
584 Whether to pad log level with blanks to the longest amongst all
585 level label.
587 Requires the Colorama_ package if *colors* is `True` **on Windows**.
589 Raises:
590 ValueError: If there's not exactly one default column formatter.
592 .. _Colorama: https://pypi.org/project/colorama/
593 .. _better-exceptions: https://pypi.org/project/better-exceptions/
594 .. _Rich: https://pypi.org/project/rich/
596 .. versionadded:: 16.0.0
597 .. versionadded:: 16.1.0 *colors*
598 .. versionadded:: 17.1.0 *repr_native_str*
599 .. versionadded:: 18.1.0 *force_colors*
600 .. versionadded:: 18.1.0 *level_styles*
601 .. versionchanged:: 19.2.0
602 Colorama now initializes lazily to avoid unwanted initializations as
603 ``ConsoleRenderer`` is used by default.
604 .. versionchanged:: 19.2.0 Can be pickled now.
605 .. versionchanged:: 20.1.0
606 Colorama does not initialize lazily on Windows anymore because it breaks
607 rendering.
608 .. versionchanged:: 21.1.0
609 It is additionally possible to set the logger name using the
610 ``logger_name`` key in the ``event_dict``.
611 .. versionadded:: 21.2.0 *exception_formatter*
612 .. versionchanged:: 21.2.0
613 `ConsoleRenderer` now handles the ``exc_info`` event dict key itself. Do
614 **not** use the `structlog.processors.format_exc_info` processor
615 together with `ConsoleRenderer` anymore! It will keep working, but you
616 can't have customize exception formatting and a warning will be raised
617 if you ask for it.
618 .. versionchanged:: 21.2.0
619 The colors keyword now defaults to True on non-Windows systems, and
620 either True or False in Windows depending on whether Colorama is
621 installed.
622 .. versionadded:: 21.3.0 *sort_keys*
623 .. versionadded:: 22.1.0 *event_key*
624 .. versionadded:: 23.2.0 *timestamp_key*
625 .. versionadded:: 23.3.0 *columns*
626 .. versionadded:: 24.2.0 *pad_level*
627 """
629 _default_column_formatter: ColumnFormatter
631 def __init__(
632 self,
633 pad_event_to: int = _EVENT_WIDTH,
634 colors: bool = _has_colors,
635 force_colors: bool = False,
636 repr_native_str: bool = False,
637 level_styles: dict[str, str] | None = None,
638 exception_formatter: ExceptionRenderer = default_exception_formatter,
639 sort_keys: bool = True,
640 event_key: str = "event",
641 timestamp_key: str = "timestamp",
642 columns: list[Column] | None = None,
643 pad_level: bool = True,
644 pad_event: int | None = None,
645 ):
646 if pad_event is not None:
647 if pad_event_to != _EVENT_WIDTH:
648 raise ValueError(
649 "Cannot set both `pad_event` and `pad_event_to`."
650 )
651 warnings.warn(
652 "The `pad_event` argument is deprecated. Use `pad_event_to` instead.",
653 DeprecationWarning,
654 stacklevel=2,
655 )
656 pad_event_to = pad_event
658 # Store all settings in case the user later switches from columns to
659 # defaults.
660 self.exception_formatter = exception_formatter
661 self._sort_keys = sort_keys
662 self._repr_native_str = repr_native_str
663 self._styles = self.get_default_column_styles(colors, force_colors)
664 self._colors = colors
665 self._force_colors = force_colors
666 self._level_styles = (
667 self.get_default_level_styles(colors)
668 if level_styles is None
669 else level_styles
670 )
671 self._pad_event_to = pad_event_to
672 self._timestamp_key = timestamp_key
673 self._event_key = event_key
674 self._pad_level = pad_level
676 if columns is None:
677 self._configure_columns()
678 return
680 self.columns = columns
682 to_warn = []
684 def add_meaningless_arg(arg: str) -> None:
685 to_warn.append(
686 f"The `{arg}` argument is ignored when passing `columns`.",
687 )
689 if pad_event_to != _EVENT_WIDTH:
690 add_meaningless_arg("pad_event_to")
692 if colors != _has_colors:
693 add_meaningless_arg("colors")
695 if force_colors is not False:
696 add_meaningless_arg("force_colors")
698 if repr_native_str is not False:
699 add_meaningless_arg("repr_native_str")
701 if level_styles is not None:
702 add_meaningless_arg("level_styles")
704 if event_key != "event":
705 add_meaningless_arg("event_key")
707 if timestamp_key != "timestamp":
708 add_meaningless_arg("timestamp_key")
710 for w in to_warn:
711 warnings.warn(w, stacklevel=2)
713 @classmethod
714 def get_active(cls) -> ConsoleRenderer:
715 """
716 If *structlog* is configured to use `ConsoleRenderer`, it's returned.
718 It does not have to be the last processor.
720 Raises:
721 NoConsoleRendererConfiguredError:
722 If no ConsoleRenderer is found in the current configuration.
724 MultipleConsoleRenderersConfiguredError:
725 If more than one is found in the current configuration. This is
726 almost certainly a bug.
728 .. versionadded:: 25.5.0
729 """
730 from ._config import get_config
732 cr = None
733 for p in get_config()["processors"]:
734 if isinstance(p, ConsoleRenderer):
735 if cr is not None:
736 raise MultipleConsoleRenderersConfiguredError
738 cr = p
740 if cr is None:
741 raise NoConsoleRendererConfiguredError
743 return cr
745 @classmethod
746 def get_default_column_styles(
747 cls, colors: bool, force_colors: bool = False
748 ) -> ColumnStyles:
749 """
750 Configure and return the appropriate styles class for console output.
752 This method handles the setup of colorful or plain styles, including
753 proper colorama initialization on Windows systems when colors are
754 enabled.
756 Args:
757 colors: Whether to use colorful output styles.
759 force_colors:
760 Force colorful output even in non-interactive environments.
761 Only relevant on Windows with colorama.
763 Returns:
764 The configured styles.
766 Raises:
767 SystemError:
768 On Windows when colors=True but colorama is not installed.
770 .. versionadded:: 25.5.0
771 """
772 if not colors:
773 return _plain_styles
775 _init_terminal(cls.__name__, force_colors)
777 return _colorful_styles
779 @staticmethod
780 def get_default_level_styles(colors: bool = True) -> dict[str, str]:
781 """
782 Get the default styles for log levels
784 This is intended to be used with `ConsoleRenderer`'s ``level_styles``
785 parameter. For example, if you are adding custom levels in your
786 home-grown :func:`~structlog.stdlib.add_log_level` you could do::
788 my_styles = ConsoleRenderer.get_default_level_styles()
789 my_styles["EVERYTHING_IS_ON_FIRE"] = my_styles["critical"]
790 renderer = ConsoleRenderer(level_styles=my_styles)
792 Args:
793 colors:
794 Whether to use colorful styles. This must match the *colors*
795 parameter to `ConsoleRenderer`. Default: `True`.
796 """
797 styles: ColumnStyles
798 styles = _colorful_styles if colors else _plain_styles
799 return {
800 "critical": styles.level_critical,
801 "exception": styles.level_exception,
802 "error": styles.level_error,
803 "warn": styles.level_warn,
804 "warning": styles.level_warn,
805 "info": styles.level_info,
806 "debug": styles.level_debug,
807 "notset": styles.level_notset,
808 }
810 def _configure_columns(self) -> None:
811 """
812 Re-configure self._columns and self._default_column_formatter
813 according to our current settings.
815 Overwrite existing columns settings, regardless of whether they were
816 explicitly passed by the user or derived by us.
817 """
818 level_to_color = self._level_styles.copy()
820 for key in level_to_color:
821 level_to_color[key] += self._styles.bright
822 self._longest_level = len(
823 max(level_to_color.keys(), key=lambda e: len(e))
824 )
826 self._default_column_formatter = KeyValueColumnFormatter(
827 self._styles.kv_key,
828 self._styles.kv_value,
829 self._styles.reset,
830 value_repr=self._repr,
831 width=0,
832 )
834 logger_name_formatter = KeyValueColumnFormatter(
835 key_style=None,
836 value_style=self._styles.bright + self._styles.logger_name,
837 reset_style=self._styles.reset,
838 value_repr=str,
839 prefix="[",
840 postfix="]",
841 )
843 level_width = 0 if not self._pad_level else None
845 self._columns = [
846 Column(
847 self._timestamp_key,
848 KeyValueColumnFormatter(
849 key_style=None,
850 value_style=self._styles.timestamp,
851 reset_style=self._styles.reset,
852 value_repr=str,
853 ),
854 ),
855 Column(
856 "level",
857 LogLevelColumnFormatter(
858 level_to_color,
859 reset_style=self._styles.reset,
860 width=level_width,
861 ),
862 ),
863 Column(
864 self._event_key,
865 KeyValueColumnFormatter(
866 key_style=None,
867 value_style=self._styles.bright,
868 reset_style=self._styles.reset,
869 value_repr=str,
870 width=self._pad_event_to,
871 ),
872 ),
873 Column("logger", logger_name_formatter),
874 Column("logger_name", logger_name_formatter),
875 ]
877 def _repr(self, val: Any) -> str:
878 """
879 Determine representation of *val* depending on its type &
880 self._repr_native_str.
881 """
882 if self._repr_native_str is True:
883 return repr(val)
885 if isinstance(val, str):
886 if set(val) & {" ", "\t", "=", "\r", "\n", '"', "'"}:
887 return repr(val)
888 return val
890 return repr(val)
892 def __call__(
893 self, logger: WrappedLogger, name: str, event_dict: EventDict
894 ) -> str:
895 stack = event_dict.pop("stack", None)
896 exc = event_dict.pop("exception", None)
897 exc_info = event_dict.pop("exc_info", None)
899 kvs = [
900 col.formatter(col.key, val)
901 for col in self.columns
902 if (val := event_dict.pop(col.key, _NOTHING)) is not _NOTHING
903 ] + [
904 self._default_column_formatter(key, event_dict[key])
905 for key in (sorted(event_dict) if self._sort_keys else event_dict)
906 ]
908 sio = StringIO()
909 sio.write((" ".join(kv for kv in kvs if kv)).rstrip(" "))
911 if stack is not None:
912 sio.write("\n" + stack)
913 if exc_info or exc is not None:
914 sio.write("\n\n" + "=" * 79 + "\n")
916 exc_info = _figure_out_exc_info(exc_info)
917 if exc_info:
918 self._exception_formatter(sio, exc_info)
919 elif exc is not None:
920 if self._exception_formatter is not plain_traceback:
921 warnings.warn(
922 "Remove `format_exc_info` from your processor chain "
923 "if you want pretty exceptions.",
924 stacklevel=2,
925 )
927 sio.write("\n" + exc)
929 return sio.getvalue()
931 @property
932 def exception_formatter(self) -> ExceptionRenderer:
933 """
934 The exception formatter used by this console renderer.
936 .. versionadded:: 25.5.0
937 """
938 return self._exception_formatter
940 @exception_formatter.setter
941 def exception_formatter(self, value: ExceptionRenderer) -> None:
942 """
943 .. versionadded:: 25.5.0
944 """
945 self._exception_formatter = value
947 @property
948 def sort_keys(self) -> bool:
949 """
950 Whether to sort keys when formatting.
952 .. versionadded:: 25.5.0
953 """
954 return self._sort_keys
956 @sort_keys.setter
957 def sort_keys(self, value: bool) -> None:
958 """
959 .. versionadded:: 25.5.0
960 """
961 # _sort_keys is a format-time setting, so we can just set it directly.
962 self._sort_keys = value
964 @property
965 def columns(self) -> list[Column]:
966 """
967 The columns configuration for this console renderer.
969 Warning:
970 Just like with passing *columns* argument, many of the other
971 arguments you may have passed are ignored.
973 Args:
974 value:
975 A list of `Column` objects defining both the order and format
976 of the key-value pairs in the output.
978 **Must** contain a column with ``key=''`` that defines the
979 default formatter.
981 Raises:
982 ValueError: If there's not exactly one default column formatter.
984 .. versionadded:: 25.5.0
985 """
986 return [Column("", self._default_column_formatter), *self._columns]
988 @columns.setter
989 def columns(self, value: list[Column]) -> None:
990 """
991 .. versionadded:: 25.5.0
992 """
993 defaults = [col for col in value if col.key == ""]
994 if not defaults:
995 raise ValueError(
996 "Must pass a default column formatter (a column with `key=''`)."
997 )
998 if len(defaults) > 1:
999 raise ValueError("Only one default column formatter allowed.")
1001 self._default_column_formatter = defaults[0].formatter
1002 self._columns = [col for col in value if col.key]
1004 @property
1005 def colors(self) -> bool:
1006 """
1007 Whether to use colorful output styles.
1009 Setting this will update the renderer's styles immediately and reset
1010 level styles to the defaults according to the new color setting -- even
1011 if the color value is the same.
1013 .. versionadded:: 25.5.0
1014 """
1015 return self._colors
1017 @colors.setter
1018 def colors(self, value: bool) -> None:
1019 """
1020 .. versionadded:: 25.5.0
1021 """
1022 self._colors = value
1023 self._styles = self.get_default_column_styles(
1024 value, self._force_colors
1025 )
1026 self._level_styles = self.get_default_level_styles(value)
1028 self._configure_columns()
1030 @property
1031 def force_colors(self) -> bool:
1032 """
1033 Force colorful output even in non-interactive environments.
1035 Setting this will update the renderer's styles immediately and reset
1036 level styles to the defaults according to the current color setting --
1037 even if the value is the same.
1039 .. versionadded:: 25.5.0
1040 """
1041 return self._force_colors
1043 @force_colors.setter
1044 def force_colors(self, value: bool) -> None:
1045 """
1046 .. versionadded:: 25.5.0
1047 """
1048 self._force_colors = value
1049 self._styles = self.get_default_column_styles(self._colors, value)
1050 self._level_styles = self.get_default_level_styles(self._colors)
1052 self._configure_columns()
1054 @property
1055 def level_styles(self) -> dict[str, str]:
1056 """
1057 The level styles mapping for this console renderer.
1059 Setting this property will reset to defaults if set to None, otherwise
1060 it applies the provided mapping. In all cases, columns are rebuilt to
1061 reflect the change.
1063 .. versionadded:: 25.5.0
1064 """
1065 return self._level_styles
1067 @level_styles.setter
1068 def level_styles(self, value: dict[str, str] | None) -> None:
1069 """
1070 .. versionadded:: 25.5.0
1071 """
1072 self._level_styles = (
1073 self.get_default_level_styles(self._colors)
1074 if value is None
1075 else value
1076 )
1077 self._configure_columns()
1079 @property
1080 def pad_level(self) -> bool:
1081 """
1082 Whether to pad log level with blanks to the longest amongst all
1083 level labels.
1085 Setting this will rebuild columns to reflect the change.
1087 .. versionadded:: 25.5.0
1088 """
1089 return self._pad_level
1091 @pad_level.setter
1092 def pad_level(self, value: bool) -> None:
1093 """
1094 .. versionadded:: 25.5.0
1095 """
1096 self._pad_level = value
1097 self._configure_columns()
1099 @property
1100 def pad_event_to(self) -> int:
1101 """
1102 The number of characters to pad the event to.
1104 Setting this will rebuild columns to reflect the change.
1106 .. versionadded:: 25.5.0
1107 """
1108 return self._pad_event_to
1110 @pad_event_to.setter
1111 def pad_event_to(self, value: int) -> None:
1112 """
1113 .. versionadded:: 25.5.0
1114 """
1115 self._pad_event_to = value
1116 self._configure_columns()
1118 @property
1119 def event_key(self) -> str:
1120 """
1121 The key to look for the main log message.
1123 Setting this will rebuild columns to reflect the change.
1125 .. versionadded:: 25.5.0
1126 """
1127 return self._event_key
1129 @event_key.setter
1130 def event_key(self, value: str) -> None:
1131 """
1132 .. versionadded:: 25.5.0
1133 """
1134 self._event_key = value
1135 self._configure_columns()
1137 @property
1138 def timestamp_key(self) -> str:
1139 """
1140 The key to look for the timestamp of the log message.
1142 Setting this will rebuild columns to reflect the change.
1144 .. versionadded:: 25.5.0
1145 """
1146 return self._timestamp_key
1148 @timestamp_key.setter
1149 def timestamp_key(self, value: str) -> None:
1150 """
1151 .. versionadded:: 25.5.0
1152 """
1153 self._timestamp_key = value
1154 self._configure_columns()
1156 @property
1157 def repr_native_str(self) -> bool:
1158 """
1159 Whether native strings are passed through repr() in non-event values.
1161 .. versionadded:: 25.5.0
1162 """
1163 return self._repr_native_str
1165 @repr_native_str.setter
1166 def repr_native_str(self, value: bool) -> None:
1167 """
1168 .. versionadded:: 25.5.0
1169 """
1170 self._repr_native_str = value
1173_SENTINEL = object()
1176def set_exc_info(
1177 logger: WrappedLogger, method_name: str, event_dict: EventDict
1178) -> EventDict:
1179 """
1180 Set ``event_dict["exc_info"] = True`` if *method_name* is ``"exception"``.
1182 Do nothing if the name is different or ``exc_info`` is already set.
1184 .. versionadded:: 19.2.0
1185 """
1186 if (
1187 method_name != "exception"
1188 or event_dict.get("exc_info", _SENTINEL) is not _SENTINEL
1189 ):
1190 return event_dict
1192 event_dict["exc_info"] = True
1194 return event_dict