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

354 statements  

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. 

5 

6""" 

7Helpers that make development with *structlog* more pleasant. 

8 

9See also the narrative documentation in `console-output`. 

10""" 

11 

12from __future__ import annotations 

13 

14import sys 

15import warnings 

16 

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) 

29 

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 

37 

38 

39try: 

40 import colorama 

41except ImportError: 

42 colorama = None 

43 

44try: 

45 import better_exceptions 

46except ImportError: 

47 better_exceptions = None 

48 

49try: 

50 import rich 

51 

52 from rich.console import Console 

53 from rich.traceback import Traceback 

54except ImportError: 

55 rich = None # type: ignore[assignment] 

56 

57__all__ = [ 

58 "ConsoleRenderer", 

59 "RichTracebackFormatter", 

60 "better_traceback", 

61 "plain_traceback", 

62 "rich_traceback", 

63] 

64 

65_IS_WINDOWS = sys.platform == "win32" 

66 

67_MISSING = "{who} requires the {package} package installed. " 

68_EVENT_WIDTH = 30 # pad the event name to so many characters 

69 

70 

71if _IS_WINDOWS: # pragma: no cover 

72 

73 def _init_terminal(who: str, force_colors: bool) -> None: 

74 """ 

75 Initialize colorama on Windows systems for colorful console output. 

76 

77 Args: 

78 who: The name of the caller for error messages. 

79 

80 force_colors: 

81 Force colorful output even in non-interactive environments. 

82 

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: 

103 

104 def _init_terminal(who: str, force_colors: bool) -> None: 

105 """ 

106 Currently, nothing to be done on non-Windows systems. 

107 """ 

108 

109 

110def _pad(s: str, length: int) -> str: 

111 """ 

112 Pads *s* to length *length*. 

113 """ 

114 missing = length - len(s) 

115 

116 return s + " " * (max(0, missing)) 

117 

118 

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" 

144 

145# On Windows, colors are only available if Colorama is installed. 

146_has_colors = not _IS_WINDOWS or colorama is not None 

147 

148# Prevent breakage of packages that used the old name of the variable. 

149_use_colors = _has_colors 

150 

151 

152@dataclass(frozen=True) 

153class ColumnStyles: 

154 """ 

155 Column styles settings for console rendering. 

156 

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. 

160 

161 .. versionadded:: 25.5.0 

162 It was handled by private structures before. 

163 """ 

164 

165 reset: str 

166 bright: str 

167 

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 

175 

176 timestamp: str 

177 logger_name: str 

178 kv_key: str 

179 kv_value: str 

180 

181 

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) 

197 

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) 

213 

214# Backward compatibility aliases 

215_ColorfulStyles = _colorful_styles 

216_PlainStyles = _plain_styles 

217 

218 

219class ColumnFormatter(Protocol): 

220 """ 

221 :class:`~typing.Protocol` for column formatters. 

222 

223 See `KeyValueColumnFormatter` and `LogLevelColumnFormatter` for examples. 

224 

225 .. versionadded:: 23.3.0 

226 """ 

227 

228 def __call__(self, key: str, value: object) -> str: 

229 """ 

230 Format *value* for *key*. 

231 

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*. 

235 

236 If it returns an empty string, the column is omitted completely. 

237 """ 

238 

239 

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. 

246 

247 Args: 

248 key: 

249 The key for which this column is responsible. Leave empty to define 

250 it as the default formatter. 

251 

252 formatter: The formatter for columns with *key*. 

253 

254 .. versionadded:: 23.3.0 

255 """ 

256 

257 key: str 

258 formatter: ColumnFormatter 

259 

260 

261@dataclass 

262class KeyValueColumnFormatter: 

263 """ 

264 Format a key-value pair. 

265 

266 Args: 

267 key_style: The style to apply to the key. If None, the key is omitted. 

268 

269 value_style: The style to apply to the value. 

270 

271 reset_style: The style to apply whenever a style is no longer needed. 

272 

273 value_repr: 

274 A callable that returns the string representation of the value. 

275 

276 width: The width to pad the value to. If 0, no padding is done. 

277 

278 prefix: 

279 A string to prepend to the formatted key-value pair. May contain 

280 styles. 

281 

282 postfix: 

283 A string to append to the formatted key-value pair. May contain 

284 styles. 

285 

286 .. versionadded:: 23.3.0 

287 """ 

288 

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 = "" 

296 

297 def __call__(self, key: str, value: object) -> str: 

298 sio = StringIO() 

299 

300 if self.prefix: 

301 sio.write(self.prefix) 

302 sio.write(self.reset_style) 

303 

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("=") 

309 

310 sio.write(self.value_style) 

311 sio.write(_pad(self.value_repr(value), self.width)) 

312 sio.write(self.reset_style) 

313 

314 if self.postfix: 

315 sio.write(self.postfix) 

316 sio.write(self.reset_style) 

317 

318 return sio.getvalue() 

319 

320 

321class LogLevelColumnFormatter: 

322 """ 

323 Format a log level according to *level_styles*. 

324 

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). 

327 

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]``. 

332 

333 reset_style: 

334 What to use to reset the style after the level name. Ignored if 

335 if *level_styles* is None. 

336 

337 width: 

338 The width to pad the level to. If 0, no padding is done. 

339 

340 .. versionadded:: 23.3.0 

341 .. versionadded:: 24.2.0 *width* 

342 """ 

343 

344 level_styles: dict[str, str] | None 

345 reset_style: str 

346 width: int 

347 

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 = "" 

365 

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 ) 

373 

374 return f"[{style}{_pad(level, self.width)}{self.reset_style}]" 

375 

376 

377_NOTHING = object() 

378 

379 

380def plain_traceback(sio: TextIO, exc_info: ExcInfo) -> None: 

381 """ 

382 "Pretty"-print *exc_info* to *sio* using our own plain formatter. 

383 

384 To be passed into `ConsoleRenderer`'s ``exception_formatter`` argument. 

385 

386 Used by default if neither Rich nor *better-exceptions* are present. 

387 

388 .. versionadded:: 21.2.0 

389 """ 

390 sio.write("\n" + _format_exception(exc_info)) 

391 

392 

393@dataclass 

394class RichTracebackFormatter: 

395 """ 

396 A Rich traceback renderer with the given options. 

397 

398 Pass an instance as `ConsoleRenderer`'s ``exception_formatter`` argument. 

399 

400 See :class:`rich.traceback.Traceback` for details on the arguments. 

401 

402 If *width* is `None`, the terminal width is used. If the width can't be 

403 determined, fall back to 80. 

404 

405 .. versionadded:: 23.2.0 

406 

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. 

411 

412 .. versionadded:: 25.4.0 *code_width* 

413 """ 

414 

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] = () 

431 

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 

440 

441 sio.write("\n") 

442 

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) 

465 

466 

467if rich is None: 

468 

469 def rich_traceback(*args, **kw): 

470 raise ModuleNotFoundError( 

471 "RichTracebackFormatter requires Rich to be installed.", 

472 name="rich", 

473 ) 

474 

475else: 

476 rich_traceback = RichTracebackFormatter() 

477 """ 

478 Pretty-print *exc_info* to *sio* using the Rich package. 

479 

480 To be passed into `ConsoleRenderer`'s ``exception_formatter`` argument. 

481 

482 This is a `RichTracebackFormatter` with default arguments and used by default 

483 if Rich is installed. 

484 

485 .. versionadded:: 21.2.0 

486 """ 

487 

488 

489def better_traceback(sio: TextIO, exc_info: ExcInfo) -> None: 

490 """ 

491 Pretty-print *exc_info* to *sio* using the *better-exceptions* package. 

492 

493 To be passed into `ConsoleRenderer`'s ``exception_formatter`` argument. 

494 

495 Used by default if *better-exceptions* is installed and Rich is absent. 

496 

497 .. versionadded:: 21.2.0 

498 """ 

499 sio.write("\n" + "".join(better_exceptions.format_exception(*exc_info))) 

500 

501 

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 

508 

509 

510class ConsoleRenderer: 

511 r""" 

512 Render ``event_dict`` nicely aligned, possibly in colors, and ordered. 

513 

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. 

517 

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. 

524 

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. 

530 

531 **Must** contain a column with ``key=''`` that defines the default 

532 formatter. 

533 

534 .. seealso:: `columns-config` 

535 

536 pad_event_to: 

537 Pad the event to this many characters. Ignored if *columns* are 

538 passed. 

539 

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. 

543 

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. 

549 

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. 

553 

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. 

560 

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. 

568 

569 sort_keys: 

570 Whether to sort keys when formatting. `True` by default. Ignored if 

571 *columns* are passed. 

572 

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. 

577 

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. 

582 

583 pad_level: 

584 Whether to pad log level with blanks to the longest amongst all 

585 level label. 

586 

587 Requires the Colorama_ package if *colors* is `True` **on Windows**. 

588 

589 Raises: 

590 ValueError: If there's not exactly one default column formatter. 

591 

592 .. _Colorama: https://pypi.org/project/colorama/ 

593 .. _better-exceptions: https://pypi.org/project/better-exceptions/ 

594 .. _Rich: https://pypi.org/project/rich/ 

595 

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 """ 

628 

629 _default_column_formatter: ColumnFormatter 

630 

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 

657 

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 

675 

676 if columns is None: 

677 self._configure_columns() 

678 return 

679 

680 self.columns = columns 

681 

682 to_warn = [] 

683 

684 def add_meaningless_arg(arg: str) -> None: 

685 to_warn.append( 

686 f"The `{arg}` argument is ignored when passing `columns`.", 

687 ) 

688 

689 if pad_event_to != _EVENT_WIDTH: 

690 add_meaningless_arg("pad_event_to") 

691 

692 if colors != _has_colors: 

693 add_meaningless_arg("colors") 

694 

695 if force_colors is not False: 

696 add_meaningless_arg("force_colors") 

697 

698 if repr_native_str is not False: 

699 add_meaningless_arg("repr_native_str") 

700 

701 if level_styles is not None: 

702 add_meaningless_arg("level_styles") 

703 

704 if event_key != "event": 

705 add_meaningless_arg("event_key") 

706 

707 if timestamp_key != "timestamp": 

708 add_meaningless_arg("timestamp_key") 

709 

710 for w in to_warn: 

711 warnings.warn(w, stacklevel=2) 

712 

713 @classmethod 

714 def get_active(cls) -> ConsoleRenderer: 

715 """ 

716 If *structlog* is configured to use `ConsoleRenderer`, it's returned. 

717 

718 It does not have to be the last processor. 

719 

720 Raises: 

721 NoConsoleRendererConfiguredError: 

722 If no ConsoleRenderer is found in the current configuration. 

723 

724 MultipleConsoleRenderersConfiguredError: 

725 If more than one is found in the current configuration. This is 

726 almost certainly a bug. 

727 

728 .. versionadded:: 25.5.0 

729 """ 

730 from ._config import get_config 

731 

732 cr = None 

733 for p in get_config()["processors"]: 

734 if isinstance(p, ConsoleRenderer): 

735 if cr is not None: 

736 raise MultipleConsoleRenderersConfiguredError 

737 

738 cr = p 

739 

740 if cr is None: 

741 raise NoConsoleRendererConfiguredError 

742 

743 return cr 

744 

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. 

751 

752 This method handles the setup of colorful or plain styles, including 

753 proper colorama initialization on Windows systems when colors are 

754 enabled. 

755 

756 Args: 

757 colors: Whether to use colorful output styles. 

758 

759 force_colors: 

760 Force colorful output even in non-interactive environments. 

761 Only relevant on Windows with colorama. 

762 

763 Returns: 

764 The configured styles. 

765 

766 Raises: 

767 SystemError: 

768 On Windows when colors=True but colorama is not installed. 

769 

770 .. versionadded:: 25.5.0 

771 """ 

772 if not colors: 

773 return _plain_styles 

774 

775 _init_terminal(cls.__name__, force_colors) 

776 

777 return _colorful_styles 

778 

779 @staticmethod 

780 def get_default_level_styles(colors: bool = True) -> dict[str, str]: 

781 """ 

782 Get the default styles for log levels 

783 

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:: 

787 

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) 

791 

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 } 

809 

810 def _configure_columns(self) -> None: 

811 """ 

812 Re-configure self._columns and self._default_column_formatter 

813 according to our current settings. 

814 

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() 

819 

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 ) 

825 

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 ) 

833 

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 ) 

842 

843 level_width = 0 if not self._pad_level else None 

844 

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 ] 

876 

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) 

884 

885 if isinstance(val, str): 

886 if set(val) & {" ", "\t", "=", "\r", "\n", '"', "'"}: 

887 return repr(val) 

888 return val 

889 

890 return repr(val) 

891 

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) 

898 

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 ] 

907 

908 sio = StringIO() 

909 sio.write((" ".join(kv for kv in kvs if kv)).rstrip(" ")) 

910 

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") 

915 

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 ) 

926 

927 sio.write("\n" + exc) 

928 

929 return sio.getvalue() 

930 

931 @property 

932 def exception_formatter(self) -> ExceptionRenderer: 

933 """ 

934 The exception formatter used by this console renderer. 

935 

936 .. versionadded:: 25.5.0 

937 """ 

938 return self._exception_formatter 

939 

940 @exception_formatter.setter 

941 def exception_formatter(self, value: ExceptionRenderer) -> None: 

942 """ 

943 .. versionadded:: 25.5.0 

944 """ 

945 self._exception_formatter = value 

946 

947 @property 

948 def sort_keys(self) -> bool: 

949 """ 

950 Whether to sort keys when formatting. 

951 

952 .. versionadded:: 25.5.0 

953 """ 

954 return self._sort_keys 

955 

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 

963 

964 @property 

965 def columns(self) -> list[Column]: 

966 """ 

967 The columns configuration for this console renderer. 

968 

969 Warning: 

970 Just like with passing *columns* argument, many of the other 

971 arguments you may have passed are ignored. 

972 

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. 

977 

978 **Must** contain a column with ``key=''`` that defines the 

979 default formatter. 

980 

981 Raises: 

982 ValueError: If there's not exactly one default column formatter. 

983 

984 .. versionadded:: 25.5.0 

985 """ 

986 return [Column("", self._default_column_formatter), *self._columns] 

987 

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.") 

1000 

1001 self._default_column_formatter = defaults[0].formatter 

1002 self._columns = [col for col in value if col.key] 

1003 

1004 @property 

1005 def colors(self) -> bool: 

1006 """ 

1007 Whether to use colorful output styles. 

1008 

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. 

1012 

1013 .. versionadded:: 25.5.0 

1014 """ 

1015 return self._colors 

1016 

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) 

1027 

1028 self._configure_columns() 

1029 

1030 @property 

1031 def force_colors(self) -> bool: 

1032 """ 

1033 Force colorful output even in non-interactive environments. 

1034 

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. 

1038 

1039 .. versionadded:: 25.5.0 

1040 """ 

1041 return self._force_colors 

1042 

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) 

1051 

1052 self._configure_columns() 

1053 

1054 @property 

1055 def level_styles(self) -> dict[str, str]: 

1056 """ 

1057 The level styles mapping for this console renderer. 

1058 

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. 

1062 

1063 .. versionadded:: 25.5.0 

1064 """ 

1065 return self._level_styles 

1066 

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() 

1078 

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. 

1084 

1085 Setting this will rebuild columns to reflect the change. 

1086 

1087 .. versionadded:: 25.5.0 

1088 """ 

1089 return self._pad_level 

1090 

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() 

1098 

1099 @property 

1100 def pad_event_to(self) -> int: 

1101 """ 

1102 The number of characters to pad the event to. 

1103 

1104 Setting this will rebuild columns to reflect the change. 

1105 

1106 .. versionadded:: 25.5.0 

1107 """ 

1108 return self._pad_event_to 

1109 

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() 

1117 

1118 @property 

1119 def event_key(self) -> str: 

1120 """ 

1121 The key to look for the main log message. 

1122 

1123 Setting this will rebuild columns to reflect the change. 

1124 

1125 .. versionadded:: 25.5.0 

1126 """ 

1127 return self._event_key 

1128 

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() 

1136 

1137 @property 

1138 def timestamp_key(self) -> str: 

1139 """ 

1140 The key to look for the timestamp of the log message. 

1141 

1142 Setting this will rebuild columns to reflect the change. 

1143 

1144 .. versionadded:: 25.5.0 

1145 """ 

1146 return self._timestamp_key 

1147 

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() 

1155 

1156 @property 

1157 def repr_native_str(self) -> bool: 

1158 """ 

1159 Whether native strings are passed through repr() in non-event values. 

1160 

1161 .. versionadded:: 25.5.0 

1162 """ 

1163 return self._repr_native_str 

1164 

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 

1171 

1172 

1173_SENTINEL = object() 

1174 

1175 

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"``. 

1181 

1182 Do nothing if the name is different or ``exc_info`` is already set. 

1183 

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 

1191 

1192 event_dict["exc_info"] = True 

1193 

1194 return event_dict