Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/structlog/dev.py: 77%

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

261 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 shutil 

15import sys 

16import warnings 

17 

18from dataclasses import dataclass 

19from io import StringIO 

20from types import ModuleType 

21from typing import ( 

22 Any, 

23 Callable, 

24 Literal, 

25 Protocol, 

26 Sequence, 

27 TextIO, 

28 Type, 

29 Union, 

30 cast, 

31) 

32 

33from ._frames import _format_exception 

34from .processors import _figure_out_exc_info 

35from .typing import EventDict, ExceptionRenderer, ExcInfo, WrappedLogger 

36 

37 

38try: 

39 import colorama 

40except ImportError: 

41 colorama = None 

42 

43try: 

44 import better_exceptions 

45except ImportError: 

46 better_exceptions = None 

47 

48try: 

49 import rich 

50 

51 from rich.console import Console 

52 from rich.traceback import Traceback 

53except ImportError: 

54 rich = None # type: ignore[assignment] 

55 

56__all__ = [ 

57 "ConsoleRenderer", 

58 "RichTracebackFormatter", 

59 "better_traceback", 

60 "plain_traceback", 

61 "rich_traceback", 

62] 

63 

64_IS_WINDOWS = sys.platform == "win32" 

65 

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

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

68 

69 

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

71 """ 

72 Pads *s* to length *length*. 

73 """ 

74 missing = length - len(s) 

75 

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

77 

78 

79if colorama is not None: 

80 RESET_ALL = colorama.Style.RESET_ALL 

81 BRIGHT = colorama.Style.BRIGHT 

82 DIM = colorama.Style.DIM 

83 RED = colorama.Fore.RED 

84 BLUE = colorama.Fore.BLUE 

85 CYAN = colorama.Fore.CYAN 

86 MAGENTA = colorama.Fore.MAGENTA 

87 YELLOW = colorama.Fore.YELLOW 

88 GREEN = colorama.Fore.GREEN 

89 RED_BACK = colorama.Back.RED 

90else: 

91 # These are the same values as the Colorama color codes. Redefining them 

92 # here allows users to specify that they want color without having to 

93 # install Colorama, which is only supposed to be necessary in Windows. 

94 RESET_ALL = "\033[0m" 

95 BRIGHT = "\033[1m" 

96 DIM = "\033[2m" 

97 RED = "\033[31m" 

98 BLUE = "\033[34m" 

99 CYAN = "\033[36m" 

100 MAGENTA = "\033[35m" 

101 YELLOW = "\033[33m" 

102 GREEN = "\033[32m" 

103 RED_BACK = "\033[41m" 

104 

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

106_has_colors = not _IS_WINDOWS or colorama is not None 

107 

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

109_use_colors = _has_colors 

110 

111 

112class _Styles(Protocol): 

113 reset: str 

114 bright: str 

115 level_critical: str 

116 level_exception: str 

117 level_error: str 

118 level_warn: str 

119 level_info: str 

120 level_debug: str 

121 level_notset: str 

122 

123 timestamp: str 

124 logger_name: str 

125 kv_key: str 

126 kv_value: str 

127 

128 

129Styles = Union[_Styles, Type[_Styles]] 

130 

131 

132class _ColorfulStyles: 

133 reset = RESET_ALL 

134 bright = BRIGHT 

135 

136 level_critical = RED 

137 level_exception = RED 

138 level_error = RED 

139 level_warn = YELLOW 

140 level_info = GREEN 

141 level_debug = GREEN 

142 level_notset = RED_BACK 

143 

144 timestamp = DIM 

145 logger_name = BLUE 

146 kv_key = CYAN 

147 kv_value = MAGENTA 

148 

149 

150class _PlainStyles: 

151 reset = "" 

152 bright = "" 

153 

154 level_critical = "" 

155 level_exception = "" 

156 level_error = "" 

157 level_warn = "" 

158 level_info = "" 

159 level_debug = "" 

160 level_notset = "" 

161 

162 timestamp = "" 

163 logger_name = "" 

164 kv_key = "" 

165 kv_value = "" 

166 

167 

168class ColumnFormatter(Protocol): 

169 """ 

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

171 

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

173 

174 .. versionadded:: 23.3.0 

175 """ 

176 

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

178 """ 

179 Format *value* for *key*. 

180 

181 This method is responsible for formatting, *key*, the ``=``, and the 

182 *value*. That means that it can use any string instead of the ``=`` and 

183 it can leave out both the *key* or the *value*. 

184 

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

186 """ 

187 

188 

189@dataclass 

190class Column: 

191 """ 

192 A column defines the way a key-value pair is formatted, and, by it's 

193 position to the *columns* argument of `ConsoleRenderer`, the order in which 

194 it is rendered. 

195 

196 Args: 

197 key: 

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

199 it as the default formatter. 

200 

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

202 

203 .. versionadded:: 23.3.0 

204 """ 

205 

206 key: str 

207 formatter: ColumnFormatter 

208 

209 

210@dataclass 

211class KeyValueColumnFormatter: 

212 """ 

213 Format a key-value pair. 

214 

215 Args: 

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

217 

218 value_style: The style to apply to the value. 

219 

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

221 

222 value_repr: 

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

224 

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

226 

227 prefix: 

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

229 styles. 

230 

231 postfix: 

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

233 styles. 

234 

235 .. versionadded:: 23.3.0 

236 """ 

237 

238 key_style: str | None 

239 value_style: str 

240 reset_style: str 

241 value_repr: Callable[[object], str] 

242 width: int = 0 

243 prefix: str = "" 

244 postfix: str = "" 

245 

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

247 sio = StringIO() 

248 

249 if self.prefix: 

250 sio.write(self.prefix) 

251 sio.write(self.reset_style) 

252 

253 if self.key_style is not None: 

254 sio.write(self.key_style) 

255 sio.write(key) 

256 sio.write(self.reset_style) 

257 sio.write("=") 

258 

259 sio.write(self.value_style) 

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

261 sio.write(self.reset_style) 

262 

263 if self.postfix: 

264 sio.write(self.postfix) 

265 sio.write(self.reset_style) 

266 

267 return sio.getvalue() 

268 

269 

270class LogLevelColumnFormatter: 

271 """ 

272 Format a log level according to *level_styles*. 

273 

274 The width is padded to the longest level name (if *level_styles* is passed 

275 -- otherwise there's no way to know the lengths of all levels). 

276 

277 Args: 

278 level_styles: 

279 A dictionary of level names to styles that are applied to it. If 

280 None, the level is formatted as a plain ``[level]``. 

281 

282 reset_style: 

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

284 if *level_styles* is None. 

285 

286 width: 

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

288 

289 .. versionadded:: 23.3.0 

290 .. versionadded:: 24.2.0 *width* 

291 """ 

292 

293 level_styles: dict[str, str] | None 

294 reset_style: str 

295 width: int 

296 

297 def __init__( 

298 self, 

299 level_styles: dict[str, str], 

300 reset_style: str, 

301 width: int | None = None, 

302 ) -> None: 

303 self.level_styles = level_styles 

304 if level_styles: 

305 self.width = ( 

306 0 

307 if width == 0 

308 else len(max(self.level_styles.keys(), key=lambda e: len(e))) 

309 ) 

310 self.reset_style = reset_style 

311 else: 

312 self.width = 0 

313 self.reset_style = "" 

314 

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

316 level = cast(str, value) 

317 style = ( 

318 "" 

319 if self.level_styles is None 

320 else self.level_styles.get(level, "") 

321 ) 

322 

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

324 

325 

326_NOTHING = object() 

327 

328 

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

330 """ 

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

332 

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

334 

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

336 

337 .. versionadded:: 21.2.0 

338 """ 

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

340 

341 

342@dataclass 

343class RichTracebackFormatter: 

344 """ 

345 A Rich traceback renderer with the given options. 

346 

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

348 

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

350 

351 If a *width* of -1 is passed, the terminal width is used. If the width 

352 can't be determined, fall back to 80. 

353 

354 .. versionadded:: 23.2.0 

355 """ 

356 

357 color_system: Literal[ 

358 "auto", "standard", "256", "truecolor", "windows" 

359 ] = "truecolor" 

360 show_locals: bool = True 

361 max_frames: int = 100 

362 theme: str | None = None 

363 word_wrap: bool = False 

364 extra_lines: int = 3 

365 width: int = 100 

366 indent_guides: bool = True 

367 locals_max_length: int = 10 

368 locals_max_string: int = 80 

369 locals_hide_dunder: bool = True 

370 locals_hide_sunder: bool = False 

371 suppress: Sequence[str | ModuleType] = () 

372 

373 def __call__(self, sio: TextIO, exc_info: ExcInfo) -> None: 

374 if self.width == -1: 

375 self.width, _ = shutil.get_terminal_size((80, 0)) 

376 

377 sio.write("\n") 

378 

379 Console( 

380 file=sio, color_system=self.color_system, width=self.width 

381 ).print( 

382 Traceback.from_exception( 

383 *exc_info, 

384 show_locals=self.show_locals, 

385 max_frames=self.max_frames, 

386 theme=self.theme, 

387 word_wrap=self.word_wrap, 

388 extra_lines=self.extra_lines, 

389 width=self.width, 

390 indent_guides=self.indent_guides, 

391 locals_max_length=self.locals_max_length, 

392 locals_max_string=self.locals_max_string, 

393 locals_hide_dunder=self.locals_hide_dunder, 

394 locals_hide_sunder=self.locals_hide_sunder, 

395 suppress=self.suppress, 

396 ) 

397 ) 

398 

399 

400rich_traceback = RichTracebackFormatter() 

401""" 

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

403 

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

405 

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

407if Rich is installed. 

408 

409.. versionadded:: 21.2.0 

410""" 

411 

412 

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

414 """ 

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

416 

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

418 

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

420 

421 .. versionadded:: 21.2.0 

422 """ 

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

424 

425 

426if rich is not None: 

427 default_exception_formatter = rich_traceback 

428elif better_exceptions is not None: 

429 default_exception_formatter = better_traceback 

430else: 

431 default_exception_formatter = plain_traceback 

432 

433 

434class ConsoleRenderer: 

435 r""" 

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

437 

438 If ``event_dict`` contains a true-ish ``exc_info`` key, it will be rendered 

439 *after* the log line. If Rich_ or better-exceptions_ are present, in colors 

440 and with extra context. 

441 

442 Args: 

443 columns: 

444 A list of `Column` objects defining both the order and format of 

445 the key-value pairs in the output. If passed, most other arguments 

446 become meaningless. 

447 

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

449 formatter. 

450 

451 .. seealso:: `columns-config` 

452 

453 pad_event: 

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

455 passed. 

456 

457 colors: 

458 Use colors for a nicer output. `True` by default. On Windows only 

459 if Colorama_ is installed. Ignored if *columns* are passed. 

460 

461 force_colors: 

462 Force colors even for non-tty destinations. Use this option if your 

463 logs are stored in a file that is meant to be streamed to the 

464 console. Only meaningful on Windows. Ignored if *columns* are 

465 passed. 

466 

467 repr_native_str: 

468 When `True`, `repr` is also applied to ``str``\ s. The ``event`` 

469 key is *never* `repr` -ed. Ignored if *columns* are passed. 

470 

471 level_styles: 

472 When present, use these styles for colors. This must be a dict from 

473 level names (strings) to terminal sequences (for example, Colorama) 

474 styles. The default can be obtained by calling 

475 `ConsoleRenderer.get_default_level_styles`. Ignored when *columns* 

476 are passed. 

477 

478 exception_formatter: 

479 A callable to render ``exc_infos``. If Rich_ or better-exceptions_ 

480 are installed, they are used for pretty-printing by default (rich_ 

481 taking precedence). You can also manually set it to 

482 `plain_traceback`, `better_traceback`, an instance of 

483 `RichTracebackFormatter` like `rich_traceback`, or implement your 

484 own. 

485 

486 sort_keys: 

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

488 *columns* are passed. 

489 

490 event_key: 

491 The key to look for the main log message. Needed when you rename it 

492 e.g. using `structlog.processors.EventRenamer`. Ignored if 

493 *columns* are passed. 

494 

495 timestamp_key: 

496 The key to look for timestamp of the log message. Needed when you 

497 rename it e.g. using `structlog.processors.EventRenamer`. Ignored 

498 if *columns* are passed. 

499 

500 pad_level: 

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

502 level label. 

503 

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

505 

506 Raises: 

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

508 

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

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

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

512 

513 .. versionadded:: 16.0.0 

514 .. versionadded:: 16.1.0 *colors* 

515 .. versionadded:: 17.1.0 *repr_native_str* 

516 .. versionadded:: 18.1.0 *force_colors* 

517 .. versionadded:: 18.1.0 *level_styles* 

518 .. versionchanged:: 19.2.0 

519 Colorama now initializes lazily to avoid unwanted initializations as 

520 ``ConsoleRenderer`` is used by default. 

521 .. versionchanged:: 19.2.0 Can be pickled now. 

522 .. versionchanged:: 20.1.0 

523 Colorama does not initialize lazily on Windows anymore because it breaks 

524 rendering. 

525 .. versionchanged:: 21.1.0 

526 It is additionally possible to set the logger name using the 

527 ``logger_name`` key in the ``event_dict``. 

528 .. versionadded:: 21.2.0 *exception_formatter* 

529 .. versionchanged:: 21.2.0 

530 `ConsoleRenderer` now handles the ``exc_info`` event dict key itself. Do 

531 **not** use the `structlog.processors.format_exc_info` processor 

532 together with `ConsoleRenderer` anymore! It will keep working, but you 

533 can't have customize exception formatting and a warning will be raised 

534 if you ask for it. 

535 .. versionchanged:: 21.2.0 

536 The colors keyword now defaults to True on non-Windows systems, and 

537 either True or False in Windows depending on whether Colorama is 

538 installed. 

539 .. versionadded:: 21.3.0 *sort_keys* 

540 .. versionadded:: 22.1.0 *event_key* 

541 .. versionadded:: 23.2.0 *timestamp_key* 

542 .. versionadded:: 23.3.0 *columns* 

543 .. versionadded:: 24.2.0 *pad_level* 

544 """ 

545 

546 def __init__( # noqa: PLR0912, PLR0915 

547 self, 

548 pad_event: int = _EVENT_WIDTH, 

549 colors: bool = _has_colors, 

550 force_colors: bool = False, 

551 repr_native_str: bool = False, 

552 level_styles: dict[str, str] | None = None, 

553 exception_formatter: ExceptionRenderer = default_exception_formatter, 

554 sort_keys: bool = True, 

555 event_key: str = "event", 

556 timestamp_key: str = "timestamp", 

557 columns: list[Column] | None = None, 

558 pad_level: bool = True, 

559 ): 

560 self._exception_formatter = exception_formatter 

561 self._sort_keys = sort_keys 

562 

563 if columns is not None: 

564 to_warn = [] 

565 

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

567 to_warn.append( 

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

569 ) 

570 

571 if pad_event != _EVENT_WIDTH: 

572 add_meaningless_arg("pad_event") 

573 

574 if colors != _has_colors: 

575 add_meaningless_arg("colors") 

576 

577 if force_colors is not False: 

578 add_meaningless_arg("force_colors") 

579 

580 if repr_native_str is not False: 

581 add_meaningless_arg("repr_native_str") 

582 

583 if level_styles is not None: 

584 add_meaningless_arg("level_styles") 

585 

586 if event_key != "event": 

587 add_meaningless_arg("event_key") 

588 

589 if timestamp_key != "timestamp": 

590 add_meaningless_arg("timestamp_key") 

591 

592 for w in to_warn: 

593 warnings.warn(w, stacklevel=2) 

594 

595 defaults = [col for col in columns if col.key == ""] 

596 if not defaults: 

597 raise ValueError( 

598 "Must pass a default column formatter (a column with `key=''`)." 

599 ) 

600 if len(defaults) > 1: 

601 raise ValueError("Only one default column formatter allowed.") 

602 

603 self._default_column_formatter = defaults[0].formatter 

604 self._columns = [col for col in columns if col.key] 

605 

606 return 

607 

608 # Create default columns configuration. 

609 styles: Styles 

610 if colors: 

611 if _IS_WINDOWS: # pragma: no cover 

612 # On Windows, we can't do colorful output without colorama. 

613 if colorama is None: 

614 classname = self.__class__.__name__ 

615 raise SystemError( 

616 _MISSING.format( 

617 who=classname + " with `colors=True`", 

618 package="colorama", 

619 ) 

620 ) 

621 # Colorama must be init'd on Windows, but must NOT be 

622 # init'd on other OSes, because it can break colors. 

623 if force_colors: 

624 colorama.deinit() 

625 colorama.init(strip=False) 

626 else: 

627 colorama.init() 

628 

629 styles = _ColorfulStyles 

630 else: 

631 styles = _PlainStyles 

632 

633 self._styles = styles 

634 

635 level_to_color = ( 

636 self.get_default_level_styles(colors) 

637 if level_styles is None 

638 else level_styles 

639 ).copy() 

640 

641 for key in level_to_color: 

642 level_to_color[key] += styles.bright 

643 self._longest_level = len( 

644 max(level_to_color.keys(), key=lambda e: len(e)) 

645 ) 

646 

647 self._repr_native_str = repr_native_str 

648 

649 self._default_column_formatter = KeyValueColumnFormatter( 

650 styles.kv_key, 

651 styles.kv_value, 

652 styles.reset, 

653 value_repr=self._repr, 

654 width=0, 

655 ) 

656 

657 logger_name_formatter = KeyValueColumnFormatter( 

658 key_style=None, 

659 value_style=styles.bright + styles.logger_name, 

660 reset_style=styles.reset, 

661 value_repr=str, 

662 prefix="[", 

663 postfix="]", 

664 ) 

665 

666 level_width = 0 if not pad_level else None 

667 

668 self._columns = [ 

669 Column( 

670 timestamp_key, 

671 KeyValueColumnFormatter( 

672 key_style=None, 

673 value_style=styles.timestamp, 

674 reset_style=styles.reset, 

675 value_repr=str, 

676 ), 

677 ), 

678 Column( 

679 "level", 

680 LogLevelColumnFormatter( 

681 level_to_color, reset_style=styles.reset, width=level_width 

682 ), 

683 ), 

684 Column( 

685 event_key, 

686 KeyValueColumnFormatter( 

687 key_style=None, 

688 value_style=styles.bright, 

689 reset_style=styles.reset, 

690 value_repr=str, 

691 width=pad_event, 

692 ), 

693 ), 

694 Column("logger", logger_name_formatter), 

695 Column("logger_name", logger_name_formatter), 

696 ] 

697 

698 def _repr(self, val: Any) -> str: 

699 """ 

700 Determine representation of *val* depending on its type & 

701 self._repr_native_str. 

702 """ 

703 if self._repr_native_str is True: 

704 return repr(val) 

705 

706 if isinstance(val, str): 

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

708 return repr(val) 

709 return val 

710 

711 return repr(val) 

712 

713 def __call__( 

714 self, logger: WrappedLogger, name: str, event_dict: EventDict 

715 ) -> str: 

716 stack = event_dict.pop("stack", None) 

717 exc = event_dict.pop("exception", None) 

718 exc_info = event_dict.pop("exc_info", None) 

719 

720 kvs = [ 

721 col.formatter(col.key, val) 

722 for col in self._columns 

723 if (val := event_dict.pop(col.key, _NOTHING)) is not _NOTHING 

724 ] + [ 

725 self._default_column_formatter(key, event_dict[key]) 

726 for key in (sorted(event_dict) if self._sort_keys else event_dict) 

727 ] 

728 

729 sio = StringIO() 

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

731 

732 if stack is not None: 

733 sio.write("\n" + stack) 

734 if exc_info or exc is not None: 

735 sio.write("\n\n" + "=" * 79 + "\n") 

736 

737 exc_info = _figure_out_exc_info(exc_info) 

738 if exc_info: 

739 self._exception_formatter(sio, exc_info) 

740 elif exc is not None: 

741 if self._exception_formatter is not plain_traceback: 

742 warnings.warn( 

743 "Remove `format_exc_info` from your processor chain " 

744 "if you want pretty exceptions.", 

745 stacklevel=2, 

746 ) 

747 

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

749 

750 return sio.getvalue() 

751 

752 @staticmethod 

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

754 """ 

755 Get the default styles for log levels 

756 

757 This is intended to be used with `ConsoleRenderer`'s ``level_styles`` 

758 parameter. For example, if you are adding custom levels in your 

759 home-grown :func:`~structlog.stdlib.add_log_level` you could do:: 

760 

761 my_styles = ConsoleRenderer.get_default_level_styles() 

762 my_styles["EVERYTHING_IS_ON_FIRE"] = my_styles["critical"] 

763 renderer = ConsoleRenderer(level_styles=my_styles) 

764 

765 Args: 

766 colors: 

767 Whether to use colorful styles. This must match the *colors* 

768 parameter to `ConsoleRenderer`. Default: `True`. 

769 """ 

770 styles: Styles 

771 styles = _ColorfulStyles if colors else _PlainStyles 

772 return { 

773 "critical": styles.level_critical, 

774 "exception": styles.level_exception, 

775 "error": styles.level_error, 

776 "warn": styles.level_warn, 

777 "warning": styles.level_warn, 

778 "info": styles.level_info, 

779 "debug": styles.level_debug, 

780 "notset": styles.level_notset, 

781 } 

782 

783 

784_SENTINEL = object() 

785 

786 

787def set_exc_info( 

788 logger: WrappedLogger, method_name: str, event_dict: EventDict 

789) -> EventDict: 

790 """ 

791 Set ``event_dict["exc_info"] = True`` if *method_name* is ``"exception"``. 

792 

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

794 """ 

795 if ( 

796 method_name != "exception" 

797 or event_dict.get("exc_info", _SENTINEL) is not _SENTINEL 

798 ): 

799 return event_dict 

800 

801 event_dict["exc_info"] = True 

802 

803 return event_dict