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

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

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

7Processors useful regardless of the logging framework. 

8""" 

9 

10from __future__ import annotations 

11 

12import datetime 

13import enum 

14import json 

15import logging 

16import operator 

17import os 

18import sys 

19import threading 

20import time 

21 

22from collections.abc import Callable, Collection, Sequence 

23from types import FrameType, TracebackType 

24from typing import ( 

25 Any, 

26 ClassVar, 

27 NamedTuple, 

28 TextIO, 

29 cast, 

30) 

31 

32from ._frames import ( 

33 _find_first_app_frame_and_name, 

34 _format_exception, 

35 _format_stack, 

36) 

37from ._log_levels import NAME_TO_LEVEL, add_log_level 

38from ._utils import get_processname 

39from .contextvars import _ASYNC_CALLING_THREAD 

40from .tracebacks import ExceptionDictTransformer 

41from .typing import ( 

42 EventDict, 

43 ExceptionTransformer, 

44 ExcInfo, 

45 WrappedLogger, 

46) 

47 

48 

49__all__ = [ 

50 "NAME_TO_LEVEL", # some people rely on it being here 

51 "CallsiteParameter", 

52 "CallsiteParameterAdder", 

53 "EventRenamer", 

54 "ExceptionPrettyPrinter", 

55 "JSONRenderer", 

56 "KeyValueRenderer", 

57 "LogfmtRenderer", 

58 "StackInfoRenderer", 

59 "TimeStamper", 

60 "UnicodeDecoder", 

61 "UnicodeEncoder", 

62 "add_log_level", 

63 "dict_tracebacks", 

64 "format_exc_info", 

65] 

66 

67 

68class KeyValueRenderer: 

69 """ 

70 Render ``event_dict`` as a list of ``Key=repr(Value)`` pairs. 

71 

72 Args: 

73 sort_keys: Whether to sort keys when formatting. 

74 

75 key_order: 

76 List of keys that should be rendered in this exact order. Missing 

77 keys will be rendered as ``None``, extra keys depending on 

78 *sort_keys* and the dict class. 

79 

80 drop_missing: 

81 When ``True``, extra keys in *key_order* will be dropped rather 

82 than rendered as ``None``. 

83 

84 repr_native_str: 

85 When ``True``, :func:`repr()` is also applied to native strings. 

86 

87 .. versionadded:: 0.2.0 *key_order* 

88 .. versionadded:: 16.1.0 *drop_missing* 

89 .. versionadded:: 17.1.0 *repr_native_str* 

90 """ 

91 

92 def __init__( 

93 self, 

94 sort_keys: bool = False, 

95 key_order: Sequence[str] | None = None, 

96 drop_missing: bool = False, 

97 repr_native_str: bool = True, 

98 ): 

99 self._ordered_items = _items_sorter(sort_keys, key_order, drop_missing) 

100 

101 if repr_native_str is True: 

102 self._repr = repr 

103 else: 

104 

105 def _repr(inst: Any) -> str: 

106 if isinstance(inst, str): 

107 return inst 

108 

109 return repr(inst) 

110 

111 self._repr = _repr 

112 

113 def __call__( 

114 self, _: WrappedLogger, __: str, event_dict: EventDict 

115 ) -> str: 

116 return " ".join( 

117 k + "=" + self._repr(v) for k, v in self._ordered_items(event_dict) 

118 ) 

119 

120 

121class LogfmtRenderer: 

122 """ 

123 Render ``event_dict`` using the logfmt_ format. 

124 

125 .. _logfmt: https://brandur.org/logfmt 

126 

127 Args: 

128 sort_keys: Whether to sort keys when formatting. 

129 

130 key_order: 

131 List of keys that should be rendered in this exact order. Missing 

132 keys are rendered with empty values, extra keys depending on 

133 *sort_keys* and the dict class. 

134 

135 drop_missing: 

136 When ``True``, extra keys in *key_order* will be dropped rather 

137 than rendered with empty values. 

138 

139 bool_as_flag: 

140 When ``True``, render ``{"flag": True}`` as ``flag``, instead of 

141 ``flag=true``. ``{"flag": False}`` is always rendered as 

142 ``flag=false``. 

143 

144 Raises: 

145 ValueError: If a key contains non-printable or whitespace characters. 

146 

147 .. versionadded:: 21.5.0 

148 """ 

149 

150 def __init__( 

151 self, 

152 sort_keys: bool = False, 

153 key_order: Sequence[str] | None = None, 

154 drop_missing: bool = False, 

155 bool_as_flag: bool = True, 

156 ): 

157 self._ordered_items = _items_sorter(sort_keys, key_order, drop_missing) 

158 self.bool_as_flag = bool_as_flag 

159 

160 def __call__( 

161 self, _: WrappedLogger, __: str, event_dict: EventDict 

162 ) -> str: 

163 elements: list[str] = [] 

164 for key, value in self._ordered_items(event_dict): 

165 if any(c <= " " for c in key): 

166 msg = f'Invalid key: "{key}"' 

167 raise ValueError(msg) 

168 

169 if value is None: 

170 elements.append(f"{key}=") 

171 continue 

172 

173 if isinstance(value, bool): 

174 if self.bool_as_flag and value: 

175 elements.append(f"{key}") 

176 continue 

177 value = "true" if value else "false" 

178 

179 value = str(value) 

180 backslashes_need_escaping = ( 

181 " " in value or "=" in value or '"' in value 

182 ) 

183 if backslashes_need_escaping and "\\" in value: 

184 value = value.replace("\\", "\\\\") 

185 

186 value = value.replace('"', '\\"').replace("\n", "\\n") 

187 

188 if backslashes_need_escaping: 

189 value = f'"{value}"' 

190 

191 elements.append(f"{key}={value}") 

192 

193 return " ".join(elements) 

194 

195 

196def _items_sorter( 

197 sort_keys: bool, 

198 key_order: Sequence[str] | None, 

199 drop_missing: bool, 

200) -> Callable[[EventDict], list[tuple[str, object]]]: 

201 """ 

202 Return a function to sort items from an ``event_dict``. 

203 

204 See `KeyValueRenderer` for an explanation of the parameters. 

205 """ 

206 # Use an optimized version for each case. 

207 if key_order and sort_keys: 

208 

209 def ordered_items(event_dict: EventDict) -> list[tuple[str, Any]]: 

210 items = [] 

211 for key in key_order: 

212 value = event_dict.pop(key, None) 

213 if value is not None or not drop_missing: 

214 items.append((key, value)) 

215 

216 items += sorted(event_dict.items()) 

217 

218 return items 

219 

220 elif key_order: 

221 

222 def ordered_items(event_dict: EventDict) -> list[tuple[str, Any]]: 

223 items = [] 

224 for key in key_order: 

225 value = event_dict.pop(key, None) 

226 if value is not None or not drop_missing: 

227 items.append((key, value)) 

228 

229 items += event_dict.items() 

230 

231 return items 

232 

233 elif sort_keys: 

234 

235 def ordered_items(event_dict: EventDict) -> list[tuple[str, Any]]: 

236 return sorted(event_dict.items()) 

237 

238 else: 

239 ordered_items = operator.methodcaller( # type: ignore[assignment] 

240 "items" 

241 ) 

242 

243 return ordered_items 

244 

245 

246class UnicodeEncoder: 

247 """ 

248 Encode unicode values in ``event_dict``. 

249 

250 Args: 

251 encoding: Encoding to encode to (default: ``"utf-8"``). 

252 

253 errors: 

254 How to cope with encoding errors (default ``"backslashreplace"``). 

255 

256 Just put it in the processor chain before the renderer. 

257 

258 .. note:: Not very useful in a Python 3-only world. 

259 """ 

260 

261 _encoding: str 

262 _errors: str 

263 

264 def __init__( 

265 self, encoding: str = "utf-8", errors: str = "backslashreplace" 

266 ) -> None: 

267 self._encoding = encoding 

268 self._errors = errors 

269 

270 def __call__( 

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

272 ) -> EventDict: 

273 for key, value in event_dict.items(): 

274 if isinstance(value, str): 

275 event_dict[key] = value.encode(self._encoding, self._errors) 

276 

277 return event_dict 

278 

279 

280class UnicodeDecoder: 

281 """ 

282 Decode byte string values in ``event_dict``. 

283 

284 Args: 

285 encoding: Encoding to decode from (default: ``"utf-8"``). 

286 

287 errors: How to cope with encoding errors (default: ``"replace"``). 

288 

289 Useful to prevent ``b"abc"`` being rendered as as ``'b"abc"'``. 

290 

291 Just put it in the processor chain before the renderer. 

292 

293 .. versionadded:: 15.4.0 

294 """ 

295 

296 _encoding: str 

297 _errors: str 

298 

299 def __init__( 

300 self, encoding: str = "utf-8", errors: str = "replace" 

301 ) -> None: 

302 self._encoding = encoding 

303 self._errors = errors 

304 

305 def __call__( 

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

307 ) -> EventDict: 

308 for key, value in event_dict.items(): 

309 if isinstance(value, bytes): 

310 event_dict[key] = value.decode(self._encoding, self._errors) 

311 

312 return event_dict 

313 

314 

315class JSONRenderer: 

316 """ 

317 Render the ``event_dict`` using ``serializer(event_dict, **dumps_kw)``. 

318 

319 Args: 

320 dumps_kw: 

321 Are passed unmodified to *serializer*. If *default* is passed, it 

322 will disable support for ``__structlog__``-based serialization. 

323 

324 serializer: 

325 A :func:`json.dumps`-compatible callable that will be used to 

326 format the string. This can be used to use alternative JSON 

327 encoders (default: :func:`json.dumps`). 

328 

329 .. seealso:: :doc:`performance` for examples. 

330 

331 .. versionadded:: 0.2.0 Support for ``__structlog__`` serialization method. 

332 .. versionadded:: 15.4.0 *serializer* parameter. 

333 .. versionadded:: 18.2.0 

334 Serializer's *default* parameter can be overwritten now. 

335 """ 

336 

337 def __init__( 

338 self, 

339 serializer: Callable[..., str | bytes] = json.dumps, 

340 **dumps_kw: Any, 

341 ) -> None: 

342 dumps_kw.setdefault("default", _json_fallback_handler) 

343 self._dumps_kw = dumps_kw 

344 self._dumps = serializer 

345 

346 def __call__( 

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

348 ) -> str | bytes: 

349 """ 

350 The return type of this depends on the return type of self._dumps. 

351 """ 

352 return self._dumps(event_dict, **self._dumps_kw) 

353 

354 

355def _json_fallback_handler(obj: Any) -> Any: 

356 """ 

357 Serialize custom datatypes and pass the rest to __structlog__ & repr(). 

358 """ 

359 # circular imports :( 

360 from structlog.threadlocal import _ThreadLocalDictWrapper 

361 

362 if isinstance(obj, _ThreadLocalDictWrapper): 

363 return obj._dict 

364 

365 try: 

366 return obj.__structlog__() 

367 except AttributeError: 

368 return repr(obj) 

369 

370 

371class ExceptionRenderer: 

372 """ 

373 Replace an ``exc_info`` field with an ``exception`` field which is rendered 

374 by *exception_formatter*. 

375 

376 The contents of the ``exception`` field depends on the return value of the 

377 *exception_formatter* that is passed: 

378 

379 - The default produces a formatted string via Python's built-in traceback 

380 formatting (this is :obj:`.format_exc_info`). 

381 - If you pass a :class:`~structlog.tracebacks.ExceptionDictTransformer`, it 

382 becomes a list of stack dicts that can be serialized to JSON. 

383 

384 If *event_dict* contains the key ``exc_info``, there are three possible 

385 behaviors: 

386 

387 1. If the value is a tuple, render it into the key ``exception``. 

388 2. If the value is an Exception render it into the key ``exception``. 

389 3. If the value true but no tuple, obtain exc_info ourselves and render 

390 that. 

391 

392 If there is no ``exc_info`` key, the *event_dict* is not touched. This 

393 behavior is analog to the one of the stdlib's logging. 

394 

395 Args: 

396 exception_formatter: 

397 A callable that is used to format the exception from the 

398 ``exc_info`` field into the ``exception`` field. 

399 

400 .. seealso:: 

401 :doc:`exceptions` for a broader explanation of *structlog*'s exception 

402 features. 

403 

404 .. versionadded:: 22.1.0 

405 """ 

406 

407 def __init__( 

408 self, 

409 exception_formatter: ExceptionTransformer = _format_exception, 

410 ) -> None: 

411 self.format_exception = exception_formatter 

412 

413 def __call__( 

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

415 ) -> EventDict: 

416 exc_info = _figure_out_exc_info(event_dict.pop("exc_info", None)) 

417 if exc_info: 

418 event_dict["exception"] = self.format_exception(exc_info) 

419 

420 return event_dict 

421 

422 

423format_exc_info = ExceptionRenderer() 

424""" 

425Replace an ``exc_info`` field with an ``exception`` string field using Python's 

426built-in traceback formatting. 

427 

428If *event_dict* contains the key ``exc_info``, there are three possible 

429behaviors: 

430 

4311. If the value is a tuple, render it into the key ``exception``. 

4322. If the value is an Exception render it into the key ``exception``. 

4333. If the value is true but no tuple, obtain exc_info ourselves and render 

434 that. 

435 

436If there is no ``exc_info`` key, the *event_dict* is not touched. This behavior 

437is analog to the one of the stdlib's logging. 

438 

439.. seealso:: 

440 :doc:`exceptions` for a broader explanation of *structlog*'s exception 

441 features. 

442""" 

443 

444dict_tracebacks = ExceptionRenderer(ExceptionDictTransformer()) 

445""" 

446Replace an ``exc_info`` field with an ``exception`` field containing structured 

447tracebacks suitable for, e.g., JSON output. 

448 

449It is a shortcut for :class:`ExceptionRenderer` with a 

450:class:`~structlog.tracebacks.ExceptionDictTransformer`. 

451 

452The treatment of the ``exc_info`` key is identical to `format_exc_info`. 

453 

454.. versionadded:: 22.1.0 

455 

456.. seealso:: 

457 :doc:`exceptions` for a broader explanation of *structlog*'s exception 

458 features. 

459""" 

460 

461 

462class TimeStamper: 

463 """ 

464 Add a timestamp to ``event_dict``. 

465 

466 Args: 

467 fmt: 

468 strftime format string, or ``"iso"`` for `ISO 8601 

469 <https://en.wikipedia.org/wiki/ISO_8601>`_, or `None` for a `UNIX 

470 timestamp <https://en.wikipedia.org/wiki/Unix_time>`_. 

471 

472 utc: Whether timestamp should be in UTC or local time. 

473 

474 key: Target key in *event_dict* for added timestamps. 

475 

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

477 """ 

478 

479 __slots__ = ("_stamper", "fmt", "key", "utc") 

480 

481 def __init__( 

482 self, 

483 fmt: str | None = None, 

484 utc: bool = True, 

485 key: str = "timestamp", 

486 ) -> None: 

487 self.fmt, self.utc, self.key = fmt, utc, key 

488 

489 self._stamper = _make_stamper(fmt, utc, key) 

490 

491 def __call__( 

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

493 ) -> EventDict: 

494 return self._stamper(event_dict) 

495 

496 def __getstate__(self) -> dict[str, Any]: 

497 return {"fmt": self.fmt, "utc": self.utc, "key": self.key} 

498 

499 def __setstate__(self, state: dict[str, Any]) -> None: 

500 self.fmt = state["fmt"] 

501 self.utc = state["utc"] 

502 self.key = state["key"] 

503 

504 self._stamper = _make_stamper(**state) 

505 

506 

507def _make_stamper( 

508 fmt: str | None, utc: bool, key: str 

509) -> Callable[[EventDict], EventDict]: 

510 """ 

511 Create a stamper function. 

512 """ 

513 if fmt is None and not utc: 

514 msg = "UNIX timestamps are always UTC." 

515 raise ValueError(msg) 

516 

517 now: Callable[[], datetime.datetime] 

518 

519 if utc: 

520 

521 def now() -> datetime.datetime: 

522 return datetime.datetime.now(tz=datetime.timezone.utc) 

523 

524 else: 

525 

526 def now() -> datetime.datetime: 

527 # We don't need the TZ for our own formatting. We add it only for 

528 # user-defined formats later. 

529 return datetime.datetime.now() # noqa: DTZ005 

530 

531 if fmt is None: 

532 

533 def stamper_unix(event_dict: EventDict) -> EventDict: 

534 event_dict[key] = time.time() 

535 

536 return event_dict 

537 

538 return stamper_unix 

539 

540 if fmt.upper() == "ISO": 

541 

542 def stamper_iso_local(event_dict: EventDict) -> EventDict: 

543 event_dict[key] = now().isoformat() 

544 return event_dict 

545 

546 def stamper_iso_utc(event_dict: EventDict) -> EventDict: 

547 event_dict[key] = now().isoformat().replace("+00:00", "Z") 

548 return event_dict 

549 

550 if utc: 

551 return stamper_iso_utc 

552 

553 return stamper_iso_local 

554 

555 def stamper_fmt_local(event_dict: EventDict) -> EventDict: 

556 event_dict[key] = now().astimezone().strftime(fmt) 

557 return event_dict 

558 

559 def stamper_fmt_utc(event_dict: EventDict) -> EventDict: 

560 event_dict[key] = now().strftime(fmt) 

561 return event_dict 

562 

563 if utc: 

564 return stamper_fmt_utc 

565 

566 return stamper_fmt_local 

567 

568 

569class MaybeTimeStamper: 

570 """ 

571 A timestamper that only adds a timestamp if there is none. 

572 

573 This allows you to overwrite the ``timestamp`` key in the event dict for 

574 example when the event is coming from another system. 

575 

576 It takes the same arguments as `TimeStamper`. 

577 

578 .. versionadded:: 23.2.0 

579 """ 

580 

581 __slots__ = ("stamper",) 

582 

583 def __init__( 

584 self, 

585 fmt: str | None = None, 

586 utc: bool = True, 

587 key: str = "timestamp", 

588 ): 

589 self.stamper = TimeStamper(fmt=fmt, utc=utc, key=key) 

590 

591 def __call__( 

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

593 ) -> EventDict: 

594 if self.stamper.key not in event_dict: 

595 return self.stamper(logger, name, event_dict) 

596 

597 return event_dict 

598 

599 

600def _figure_out_exc_info(v: Any) -> ExcInfo | None: 

601 """ 

602 Try to convert *v* into an ``exc_info`` tuple. 

603 

604 Return ``None`` if *v* does not represent an exception or if there is no 

605 current exception. 

606 """ 

607 if isinstance(v, BaseException): 

608 return (v.__class__, v, v.__traceback__) 

609 

610 if isinstance(v, tuple) and len(v) == 3: 

611 has_type = isinstance(v[0], type) and issubclass(v[0], BaseException) 

612 has_exc = isinstance(v[1], BaseException) 

613 has_tb = v[2] is None or isinstance(v[2], TracebackType) 

614 if has_type and has_exc and has_tb: 

615 return v 

616 

617 if v: 

618 result = sys.exc_info() 

619 if result == (None, None, None): 

620 return None 

621 return cast(ExcInfo, result) 

622 

623 return None 

624 

625 

626class ExceptionPrettyPrinter: 

627 """ 

628 Pretty print exceptions rendered by *exception_formatter* and remove them 

629 from the ``event_dict``. 

630 

631 Args: 

632 file: Target file for output (default: ``sys.stdout``). 

633 exception_formatter: 

634 A callable that is used to format the exception from the 

635 ``exc_info`` field into the ``exception`` field. 

636 

637 This processor is mostly for development and testing so you can read 

638 exceptions properly formatted. 

639 

640 It behaves like `format_exc_info`, except that it removes the exception data 

641 from the event dictionary after printing it using the passed 

642 *exception_formatter*, which defaults to Python's built-in traceback formatting. 

643 

644 It's tolerant to having `format_exc_info` in front of itself in the 

645 processor chain but doesn't require it. In other words, it handles both 

646 ``exception`` as well as ``exc_info`` keys. 

647 

648 .. versionadded:: 0.4.0 

649 

650 .. versionchanged:: 16.0.0 

651 Added support for passing exceptions as ``exc_info`` on Python 3. 

652 

653 .. versionchanged:: 25.4.0 

654 Fixed *exception_formatter* so that it overrides the default if set. 

655 """ 

656 

657 def __init__( 

658 self, 

659 file: TextIO | None = None, 

660 exception_formatter: ExceptionTransformer = _format_exception, 

661 ) -> None: 

662 self.format_exception = exception_formatter 

663 if file is not None: 

664 self._file = file 

665 else: 

666 self._file = sys.stdout 

667 

668 def __call__( 

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

670 ) -> EventDict: 

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

672 if exc is None: 

673 exc_info = _figure_out_exc_info(event_dict.pop("exc_info", None)) 

674 if exc_info: 

675 exc = self.format_exception(exc_info) 

676 

677 if exc: 

678 print(exc, file=self._file) 

679 

680 return event_dict 

681 

682 

683class StackInfoRenderer: 

684 """ 

685 Add stack information with key ``stack`` if ``stack_info`` is `True`. 

686 

687 Useful when you want to attach a stack dump to a log entry without 

688 involving an exception and works analogously to the *stack_info* argument 

689 of the Python standard library logging. 

690 

691 Args: 

692 additional_ignores: 

693 By default, stack frames coming from *structlog* are ignored. With 

694 this argument you can add additional names that are ignored, before 

695 the stack starts being rendered. They are matched using 

696 ``startswith()``, so they don't have to match exactly. The names 

697 are used to find the first relevant name, therefore once a frame is 

698 found that doesn't start with *structlog* or one of 

699 *additional_ignores*, **no filtering** is applied to subsequent 

700 frames. 

701 

702 .. versionadded:: 0.4.0 

703 .. versionadded:: 22.1.0 *additional_ignores* 

704 """ 

705 

706 __slots__ = ("_additional_ignores",) 

707 

708 def __init__(self, additional_ignores: list[str] | None = None) -> None: 

709 self._additional_ignores = additional_ignores 

710 

711 def __call__( 

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

713 ) -> EventDict: 

714 if event_dict.pop("stack_info", None): 

715 event_dict["stack"] = _format_stack( 

716 _find_first_app_frame_and_name(self._additional_ignores)[0] 

717 ) 

718 

719 return event_dict 

720 

721 

722class CallsiteParameter(enum.Enum): 

723 """ 

724 Callsite parameters that can be added to an event dictionary with the 

725 `structlog.processors.CallsiteParameterAdder` processor class. 

726 

727 The string values of the members of this enum will be used as the keys for 

728 the callsite parameters in the event dictionary. 

729 

730 .. versionadded:: 21.5.0 

731 

732 .. versionadded:: 25.5.0 

733 `QUAL_NAME` parameter. 

734 

735 .. versionadded:: 26.1.0 

736 `QUAL_MODULE` parameter. 

737 """ 

738 

739 #: The full path to the python source file of the callsite. 

740 PATHNAME = "pathname" 

741 #: The basename part of the full path to the python source file of the 

742 #: callsite. 

743 FILENAME = "filename" 

744 #: The python module the callsite was in. This mimics the module attribute 

745 #: of `logging.LogRecord` objects and will be the basename, without 

746 #: extension, of the full path to the python source file of the callsite. 

747 MODULE = "module" 

748 #: The fully qualified import name of the module of the callsite. 

749 QUAL_MODULE = "qual_module" 

750 #: The name of the function that the callsite was in. 

751 FUNC_NAME = "func_name" 

752 #: The qualified name of the callsite (includes scope and class names). 

753 #: Requires Python 3.11+. 

754 QUAL_NAME = "qual_name" 

755 #: The line number of the callsite. 

756 LINENO = "lineno" 

757 #: The ID of the thread the callsite was executed in. 

758 THREAD = "thread" 

759 #: The name of the thread the callsite was executed in. 

760 THREAD_NAME = "thread_name" 

761 #: The ID of the process the callsite was executed in. 

762 PROCESS = "process" 

763 #: The name of the process the callsite was executed in. 

764 PROCESS_NAME = "process_name" 

765 

766 

767def _get_callsite_pathname(module: str, frame: FrameType) -> Any: 

768 return frame.f_code.co_filename 

769 

770 

771def _get_callsite_filename(module: str, frame: FrameType) -> Any: 

772 return os.path.basename(frame.f_code.co_filename) 

773 

774 

775def _get_callsite_module(module: str, frame: FrameType) -> Any: 

776 return os.path.splitext(os.path.basename(frame.f_code.co_filename))[0] 

777 

778 

779def _get_callsite_func_name(module: str, frame: FrameType) -> Any: 

780 return frame.f_code.co_name 

781 

782 

783def _get_callsite_qual_name(module: str, frame: FrameType) -> Any: 

784 return frame.f_code.co_qualname # will crash on Python <3.11 

785 

786 

787def _get_callsite_qual_module(module: str, frame: FrameType) -> Any: 

788 return module 

789 

790 

791def _get_callsite_lineno(module: str, frame: FrameType) -> Any: 

792 return frame.f_lineno 

793 

794 

795def _get_callsite_thread(module: str, frame: FrameType) -> Any: 

796 thread_info = _ASYNC_CALLING_THREAD.get(None) 

797 if thread_info is None: 

798 return threading.get_ident() 

799 

800 return thread_info[0] 

801 

802 

803def _get_callsite_thread_name(module: str, frame: FrameType) -> Any: 

804 thread_info = _ASYNC_CALLING_THREAD.get(None) 

805 if thread_info is None: 

806 return threading.current_thread().name 

807 

808 return thread_info[1] 

809 

810 

811def _get_callsite_process(module: str, frame: FrameType) -> Any: 

812 return os.getpid() 

813 

814 

815def _get_callsite_process_name(module: str, frame: FrameType) -> Any: 

816 return get_processname() 

817 

818 

819class CallsiteParameterAdder: 

820 """ 

821 Adds parameters of the callsite that an event dictionary originated from to 

822 the event dictionary. This processor can be used to enrich events 

823 dictionaries with information such as the function name, line number and 

824 filename that an event dictionary originated from. 

825 

826 If the event dictionary has an embedded `logging.LogRecord` object and did 

827 not originate from *structlog* then the callsite information will be 

828 determined from the `logging.LogRecord` object. For event dictionaries 

829 without an embedded `logging.LogRecord` object the callsite will be 

830 determined from the stack trace, ignoring all intra-structlog calls, calls 

831 from the `logging` module, and stack frames from modules with names that 

832 start with values in ``additional_ignores``, if it is specified. 

833 

834 The keys used for callsite parameters in the event dictionary are the 

835 string values of `CallsiteParameter` enum members. 

836 

837 Args: 

838 parameters: 

839 A collection of `CallsiteParameter` values that should be added to 

840 the event dictionary. 

841 

842 additional_ignores: 

843 Additional names with which a stack frame's module name must not 

844 start for it to be considered when determening the callsite. 

845 

846 .. note:: 

847 

848 When used with `structlog.stdlib.ProcessorFormatter` the most efficient 

849 configuration is to either use this processor in ``foreign_pre_chain`` 

850 of `structlog.stdlib.ProcessorFormatter` and in ``processors`` of 

851 `structlog.configure`, or to use it in ``processors`` of 

852 `structlog.stdlib.ProcessorFormatter` without using it in 

853 ``processors`` of `structlog.configure` and ``foreign_pre_chain`` of 

854 `structlog.stdlib.ProcessorFormatter`. 

855 

856 .. versionadded:: 21.5.0 

857 """ 

858 

859 _handlers: ClassVar[ 

860 dict[CallsiteParameter, Callable[[str, FrameType], Any]] 

861 ] = { 

862 # We can't use lambda functions here because they are not pickleable. 

863 CallsiteParameter.PATHNAME: _get_callsite_pathname, 

864 CallsiteParameter.FILENAME: _get_callsite_filename, 

865 CallsiteParameter.MODULE: _get_callsite_module, 

866 CallsiteParameter.QUAL_MODULE: _get_callsite_qual_module, 

867 CallsiteParameter.FUNC_NAME: _get_callsite_func_name, 

868 CallsiteParameter.QUAL_NAME: _get_callsite_qual_name, 

869 CallsiteParameter.LINENO: _get_callsite_lineno, 

870 CallsiteParameter.THREAD: _get_callsite_thread, 

871 CallsiteParameter.THREAD_NAME: _get_callsite_thread_name, 

872 CallsiteParameter.PROCESS: _get_callsite_process, 

873 CallsiteParameter.PROCESS_NAME: _get_callsite_process_name, 

874 } 

875 _record_attribute_map: ClassVar[dict[CallsiteParameter, str]] = { 

876 CallsiteParameter.PATHNAME: "pathname", 

877 CallsiteParameter.FILENAME: "filename", 

878 CallsiteParameter.MODULE: "module", 

879 CallsiteParameter.FUNC_NAME: "funcName", 

880 CallsiteParameter.LINENO: "lineno", 

881 CallsiteParameter.THREAD: "thread", 

882 CallsiteParameter.THREAD_NAME: "threadName", 

883 CallsiteParameter.PROCESS: "process", 

884 CallsiteParameter.PROCESS_NAME: "processName", 

885 } 

886 

887 _all_parameters: ClassVar[set[CallsiteParameter]] = set(CallsiteParameter) 

888 

889 class _RecordMapping(NamedTuple): 

890 event_dict_key: str 

891 record_attribute: str 

892 

893 __slots__ = ("_active_handlers", "_additional_ignores", "_record_mappings") 

894 

895 def __init__( 

896 self, 

897 parameters: Collection[CallsiteParameter] = _all_parameters, 

898 additional_ignores: list[str] | None = None, 

899 ) -> None: 

900 if additional_ignores is None: 

901 additional_ignores = [] 

902 # Ignore stack frames from the logging module. They will occur if this 

903 # processor is used in ProcessorFormatter, and additionally the logging 

904 # module should not be logging using structlog. 

905 self._additional_ignores = ["logging", *additional_ignores] 

906 self._active_handlers: list[ 

907 tuple[CallsiteParameter, Callable[[str, FrameType], Any]] 

908 ] = [] 

909 self._record_mappings: list[CallsiteParameterAdder._RecordMapping] = [] 

910 for parameter in parameters: 

911 self._active_handlers.append( 

912 (parameter, self._handlers[parameter]) 

913 ) 

914 if ( 

915 record_attr := self._record_attribute_map.get(parameter) 

916 ) is not None: 

917 self._record_mappings.append( 

918 self._RecordMapping( 

919 parameter.value, 

920 record_attr, 

921 ) 

922 ) 

923 

924 def __call__( 

925 self, logger: logging.Logger, name: str, event_dict: EventDict 

926 ) -> EventDict: 

927 record: logging.LogRecord | None = event_dict.get("_record") 

928 from_structlog: bool = event_dict.get("_from_structlog", False) 

929 

930 # If the event dictionary has a record, but it comes from structlog, 

931 # then the callsite parameters of the record will not be correct. 

932 if record is not None and not from_structlog: 

933 for mapping in self._record_mappings: 

934 event_dict[mapping.event_dict_key] = record.__dict__[ 

935 mapping.record_attribute 

936 ] 

937 

938 return event_dict 

939 

940 frame, module = _find_first_app_frame_and_name( 

941 additional_ignores=self._additional_ignores 

942 ) 

943 for parameter, handler in self._active_handlers: 

944 event_dict[parameter.value] = handler(module, frame) 

945 

946 return event_dict 

947 

948 

949class EventRenamer: 

950 r""" 

951 Rename the ``event`` key in event dicts. 

952 

953 This is useful if you want to use consistent log message keys across 

954 platforms and/or use the ``event`` key for something custom. 

955 

956 .. warning:: 

957 

958 It's recommended to put this processor right before the renderer, since 

959 some processors may rely on the presence and meaning of the ``event`` 

960 key. 

961 

962 Args: 

963 to: Rename ``event_dict["event"]`` to ``event_dict[to]`` 

964 

965 replace_by: 

966 Rename ``event_dict[replace_by]`` to ``event_dict["event"]``. 

967 *replace_by* missing from ``event_dict`` is handled gracefully. 

968 

969 .. versionadded:: 22.1.0 

970 

971 See also the :ref:`rename-event` recipe. 

972 """ 

973 

974 def __init__(self, to: str, replace_by: str | None = None): 

975 self.to = to 

976 self.replace_by = replace_by 

977 

978 def __call__( 

979 self, logger: logging.Logger, name: str, event_dict: EventDict 

980 ) -> EventDict: 

981 event = event_dict.pop("event") 

982 event_dict[self.to] = event 

983 

984 if self.replace_by is not None: 

985 replace_by = event_dict.pop(self.replace_by, None) 

986 if replace_by is not None: 

987 event_dict["event"] = replace_by 

988 

989 return event_dict