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

292 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 types import FrameType, TracebackType 

23from typing import ( 

24 Any, 

25 Callable, 

26 ClassVar, 

27 Collection, 

28 NamedTuple, 

29 Sequence, 

30 TextIO, 

31 cast, 

32) 

33 

34from ._frames import ( 

35 _find_first_app_frame_and_name, 

36 _format_exception, 

37 _format_stack, 

38) 

39from ._log_levels import NAME_TO_LEVEL, add_log_level 

40from ._utils import get_processname 

41from .tracebacks import ExceptionDictTransformer 

42from .typing import ( 

43 EventDict, 

44 ExceptionTransformer, 

45 ExcInfo, 

46 WrappedLogger, 

47) 

48 

49 

50__all__ = [ 

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

52 "CallsiteParameter", 

53 "CallsiteParameterAdder", 

54 "EventRenamer", 

55 "ExceptionPrettyPrinter", 

56 "JSONRenderer", 

57 "KeyValueRenderer", 

58 "LogfmtRenderer", 

59 "StackInfoRenderer", 

60 "TimeStamper", 

61 "UnicodeDecoder", 

62 "UnicodeEncoder", 

63 "add_log_level", 

64 "dict_tracebacks", 

65 "format_exc_info", 

66] 

67 

68 

69class KeyValueRenderer: 

70 """ 

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

72 

73 Args: 

74 sort_keys: Whether to sort keys when formatting. 

75 

76 key_order: 

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

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

79 *sort_keys* and the dict class. 

80 

81 drop_missing: 

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

83 than rendered as ``None``. 

84 

85 repr_native_str: 

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

87 

88 .. versionadded:: 0.2.0 *key_order* 

89 .. versionadded:: 16.1.0 *drop_missing* 

90 .. versionadded:: 17.1.0 *repr_native_str* 

91 """ 

92 

93 def __init__( 

94 self, 

95 sort_keys: bool = False, 

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

97 drop_missing: bool = False, 

98 repr_native_str: bool = True, 

99 ): 

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

101 

102 if repr_native_str is True: 

103 self._repr = repr 

104 else: 

105 

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

107 if isinstance(inst, str): 

108 return inst 

109 

110 return repr(inst) 

111 

112 self._repr = _repr 

113 

114 def __call__( 

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

116 ) -> str: 

117 return " ".join( 

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

119 ) 

120 

121 

122class LogfmtRenderer: 

123 """ 

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

125 

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

127 

128 Args: 

129 sort_keys: Whether to sort keys when formatting. 

130 

131 key_order: 

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

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

134 *sort_keys* and the dict class. 

135 

136 drop_missing: 

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

138 than rendered with empty values. 

139 

140 bool_as_flag: 

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

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

143 ``flag=false``. 

144 

145 Raises: 

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

147 

148 .. versionadded:: 21.5.0 

149 """ 

150 

151 def __init__( 

152 self, 

153 sort_keys: bool = False, 

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

155 drop_missing: bool = False, 

156 bool_as_flag: bool = True, 

157 ): 

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

159 self.bool_as_flag = bool_as_flag 

160 

161 def __call__( 

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

163 ) -> str: 

164 elements: list[str] = [] 

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

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

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

168 raise ValueError(msg) 

169 

170 if value is None: 

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

172 continue 

173 

174 if isinstance(value, bool): 

175 if self.bool_as_flag and value: 

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

177 continue 

178 value = "true" if value else "false" 

179 

180 value = str(value) 

181 backslashes_need_escaping = ( 

182 " " in value or "=" in value or '"' in value 

183 ) 

184 if backslashes_need_escaping and "\\" in value: 

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

186 

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

188 

189 if backslashes_need_escaping: 

190 value = f'"{value}"' 

191 

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

193 

194 return " ".join(elements) 

195 

196 

197def _items_sorter( 

198 sort_keys: bool, 

199 key_order: Sequence[str] | None, 

200 drop_missing: bool, 

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

202 """ 

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

204 

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

206 """ 

207 # Use an optimized version for each case. 

208 if key_order and sort_keys: 

209 

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

211 items = [] 

212 for key in key_order: 

213 value = event_dict.pop(key, None) 

214 if value is not None or not drop_missing: 

215 items.append((key, value)) 

216 

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

218 

219 return items 

220 

221 elif key_order: 

222 

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

224 items = [] 

225 for key in key_order: 

226 value = event_dict.pop(key, None) 

227 if value is not None or not drop_missing: 

228 items.append((key, value)) 

229 

230 items += event_dict.items() 

231 

232 return items 

233 

234 elif sort_keys: 

235 

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

237 return sorted(event_dict.items()) 

238 

239 else: 

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

241 "items" 

242 ) 

243 

244 return ordered_items 

245 

246 

247class UnicodeEncoder: 

248 """ 

249 Encode unicode values in ``event_dict``. 

250 

251 Args: 

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

253 

254 errors: 

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

256 

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

258 

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

260 """ 

261 

262 _encoding: str 

263 _errors: str 

264 

265 def __init__( 

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

267 ) -> None: 

268 self._encoding = encoding 

269 self._errors = errors 

270 

271 def __call__( 

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

273 ) -> EventDict: 

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

275 if isinstance(value, str): 

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

277 

278 return event_dict 

279 

280 

281class UnicodeDecoder: 

282 """ 

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

284 

285 Args: 

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

287 

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

289 

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

291 

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

293 

294 .. versionadded:: 15.4.0 

295 """ 

296 

297 _encoding: str 

298 _errors: str 

299 

300 def __init__( 

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

302 ) -> None: 

303 self._encoding = encoding 

304 self._errors = errors 

305 

306 def __call__( 

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

308 ) -> EventDict: 

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

310 if isinstance(value, bytes): 

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

312 

313 return event_dict 

314 

315 

316class JSONRenderer: 

317 """ 

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

319 

320 Args: 

321 dumps_kw: 

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

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

324 

325 serializer: 

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

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

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

329 

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

331 

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

333 .. versionadded:: 15.4.0 *serializer* parameter. 

334 .. versionadded:: 18.2.0 

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

336 """ 

337 

338 def __init__( 

339 self, 

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

341 **dumps_kw: Any, 

342 ) -> None: 

343 dumps_kw.setdefault("default", _json_fallback_handler) 

344 self._dumps_kw = dumps_kw 

345 self._dumps = serializer 

346 

347 def __call__( 

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

349 ) -> str | bytes: 

350 """ 

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

352 """ 

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

354 

355 

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

357 """ 

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

359 """ 

360 # circular imports :( 

361 from structlog.threadlocal import _ThreadLocalDictWrapper 

362 

363 if isinstance(obj, _ThreadLocalDictWrapper): 

364 return obj._dict 

365 

366 try: 

367 return obj.__structlog__() 

368 except AttributeError: 

369 return repr(obj) 

370 

371 

372class ExceptionRenderer: 

373 """ 

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

375 by *exception_formatter*. 

376 

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

378 *exception_formatter* that is passed: 

379 

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

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

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

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

384 

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

386 behaviors: 

387 

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

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

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

391 that. 

392 

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

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

395 

396 Args: 

397 exception_formatter: 

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

399 ``exc_info`` field into the ``exception`` field. 

400 

401 .. seealso:: 

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

403 features. 

404 

405 .. versionadded:: 22.1.0 

406 """ 

407 

408 def __init__( 

409 self, 

410 exception_formatter: ExceptionTransformer = _format_exception, 

411 ) -> None: 

412 self.format_exception = exception_formatter 

413 

414 def __call__( 

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

416 ) -> EventDict: 

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

418 if exc_info: 

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

420 

421 return event_dict 

422 

423 

424format_exc_info = ExceptionRenderer() 

425""" 

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

427built-in traceback formatting. 

428 

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

430behaviors: 

431 

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

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

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

435 that. 

436 

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

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

439 

440.. seealso:: 

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

442 features. 

443""" 

444 

445dict_tracebacks = ExceptionRenderer(ExceptionDictTransformer()) 

446""" 

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

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

449 

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

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

452 

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

454 

455.. versionadded:: 22.1.0 

456 

457.. seealso:: 

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

459 features. 

460""" 

461 

462 

463class TimeStamper: 

464 """ 

465 Add a timestamp to ``event_dict``. 

466 

467 Args: 

468 fmt: 

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

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

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

472 

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

474 

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

476 

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

478 """ 

479 

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

481 

482 def __init__( 

483 self, 

484 fmt: str | None = None, 

485 utc: bool = True, 

486 key: str = "timestamp", 

487 ) -> None: 

488 self.fmt, self.utc, self.key = fmt, utc, key 

489 

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

491 

492 def __call__( 

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

494 ) -> EventDict: 

495 return self._stamper(event_dict) 

496 

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

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

499 

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

501 self.fmt = state["fmt"] 

502 self.utc = state["utc"] 

503 self.key = state["key"] 

504 

505 self._stamper = _make_stamper(**state) 

506 

507 

508def _make_stamper( 

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

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

511 """ 

512 Create a stamper function. 

513 """ 

514 if fmt is None and not utc: 

515 msg = "UNIX timestamps are always UTC." 

516 raise ValueError(msg) 

517 

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

519 

520 if utc: 

521 

522 def now() -> datetime.datetime: 

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

524 

525 else: 

526 

527 def now() -> datetime.datetime: 

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

529 # user-defined formats later. 

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

531 

532 if fmt is None: 

533 

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

535 event_dict[key] = time.time() 

536 

537 return event_dict 

538 

539 return stamper_unix 

540 

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

542 

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

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

545 return event_dict 

546 

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

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

549 return event_dict 

550 

551 if utc: 

552 return stamper_iso_utc 

553 

554 return stamper_iso_local 

555 

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

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

558 return event_dict 

559 

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

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

562 return event_dict 

563 

564 if utc: 

565 return stamper_fmt_utc 

566 

567 return stamper_fmt_local 

568 

569 

570class MaybeTimeStamper: 

571 """ 

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

573 

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

575 example when the event is coming from another system. 

576 

577 It takes the same arguments as `TimeStamper`. 

578 

579 .. versionadded:: 23.2.0 

580 """ 

581 

582 __slots__ = ("stamper",) 

583 

584 def __init__( 

585 self, 

586 fmt: str | None = None, 

587 utc: bool = True, 

588 key: str = "timestamp", 

589 ): 

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

591 

592 def __call__( 

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

594 ) -> EventDict: 

595 if "timestamp" not in event_dict: 

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

597 

598 return event_dict 

599 

600 

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

602 """ 

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

604 

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

606 current exception. 

607 """ 

608 if isinstance(v, BaseException): 

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

610 

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

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

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

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

615 if has_type and has_exc and has_tb: 

616 return v 

617 

618 if v: 

619 result = sys.exc_info() 

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

621 return None 

622 return cast(ExcInfo, result) 

623 

624 return None 

625 

626 

627class ExceptionPrettyPrinter: 

628 """ 

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

630 from the ``event_dict``. 

631 

632 Args: 

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

634 exception_formatter: 

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

636 ``exc_info`` field into the ``exception`` field. 

637 

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

639 exceptions properly formatted. 

640 

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

642 from the event dictionary after printing it using the passed 

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

644 

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

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

647 ``exception`` as well as ``exc_info`` keys. 

648 

649 .. versionadded:: 0.4.0 

650 

651 .. versionchanged:: 16.0.0 

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

653 

654 .. versionchanged:: 25.4.0 

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

656 """ 

657 

658 def __init__( 

659 self, 

660 file: TextIO | None = None, 

661 exception_formatter: ExceptionTransformer = _format_exception, 

662 ) -> None: 

663 self.format_exception = exception_formatter 

664 if file is not None: 

665 self._file = file 

666 else: 

667 self._file = sys.stdout 

668 

669 def __call__( 

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

671 ) -> EventDict: 

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

673 if exc is None: 

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

675 if exc_info: 

676 exc = self.format_exception(exc_info) 

677 

678 if exc: 

679 print(exc, file=self._file) 

680 

681 return event_dict 

682 

683 

684class StackInfoRenderer: 

685 """ 

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

687 

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

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

690 of the Python standard library logging. 

691 

692 Args: 

693 additional_ignores: 

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

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

696 the stack starts being rendered. They are matched using 

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

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

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

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

701 frames. 

702 

703 .. versionadded:: 0.4.0 

704 .. versionadded:: 22.1.0 *additional_ignores* 

705 """ 

706 

707 __slots__ = ("_additional_ignores",) 

708 

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

710 self._additional_ignores = additional_ignores 

711 

712 def __call__( 

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

714 ) -> EventDict: 

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

716 event_dict["stack"] = _format_stack( 

717 _find_first_app_frame_and_name(self._additional_ignores)[0] 

718 ) 

719 

720 return event_dict 

721 

722 

723class CallsiteParameter(enum.Enum): 

724 """ 

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

726 `structlog.processors.CallsiteParameterAdder` processor class. 

727 

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

729 the callsite parameters in the event dictionary. 

730 

731 .. versionadded:: 21.5.0 

732 """ 

733 

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

735 PATHNAME = "pathname" 

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

737 #: callsite. 

738 FILENAME = "filename" 

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

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

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

742 MODULE = "module" 

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

744 FUNC_NAME = "func_name" 

745 #: The line number of the callsite. 

746 LINENO = "lineno" 

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

748 THREAD = "thread" 

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

750 THREAD_NAME = "thread_name" 

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

752 PROCESS = "process" 

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

754 PROCESS_NAME = "process_name" 

755 

756 

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

758 return frame.f_code.co_filename 

759 

760 

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

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

763 

764 

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

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

767 

768 

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

770 return frame.f_code.co_name 

771 

772 

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

774 return frame.f_lineno 

775 

776 

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

778 return threading.get_ident() 

779 

780 

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

782 return threading.current_thread().name 

783 

784 

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

786 return os.getpid() 

787 

788 

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

790 return get_processname() 

791 

792 

793class CallsiteParameterAdder: 

794 """ 

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

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

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

798 filename that an event dictionary originated from. 

799 

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

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

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

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

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

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

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

807 

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

809 string values of `CallsiteParameter` enum members. 

810 

811 Args: 

812 parameters: 

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

814 the event dictionary. 

815 

816 additional_ignores: 

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

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

819 

820 .. note:: 

821 

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

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

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

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

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

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

828 `structlog.stdlib.ProcessorFormatter`. 

829 

830 .. versionadded:: 21.5.0 

831 """ 

832 

833 _handlers: ClassVar[ 

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

835 ] = { 

836 CallsiteParameter.PATHNAME: _get_callsite_pathname, 

837 CallsiteParameter.FILENAME: _get_callsite_filename, 

838 CallsiteParameter.MODULE: _get_callsite_module, 

839 CallsiteParameter.FUNC_NAME: _get_callsite_func_name, 

840 CallsiteParameter.LINENO: _get_callsite_lineno, 

841 CallsiteParameter.THREAD: _get_callsite_thread, 

842 CallsiteParameter.THREAD_NAME: _get_callsite_thread_name, 

843 CallsiteParameter.PROCESS: _get_callsite_process, 

844 CallsiteParameter.PROCESS_NAME: _get_callsite_process_name, 

845 } 

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

847 CallsiteParameter.PATHNAME: "pathname", 

848 CallsiteParameter.FILENAME: "filename", 

849 CallsiteParameter.MODULE: "module", 

850 CallsiteParameter.FUNC_NAME: "funcName", 

851 CallsiteParameter.LINENO: "lineno", 

852 CallsiteParameter.THREAD: "thread", 

853 CallsiteParameter.THREAD_NAME: "threadName", 

854 CallsiteParameter.PROCESS: "process", 

855 CallsiteParameter.PROCESS_NAME: "processName", 

856 } 

857 

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

859 

860 class _RecordMapping(NamedTuple): 

861 event_dict_key: str 

862 record_attribute: str 

863 

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

865 

866 def __init__( 

867 self, 

868 parameters: Collection[CallsiteParameter] = _all_parameters, 

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

870 ) -> None: 

871 if additional_ignores is None: 

872 additional_ignores = [] 

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

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

875 # module should not be logging using structlog. 

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

877 self._active_handlers: list[ 

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

879 ] = [] 

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

881 for parameter in parameters: 

882 self._active_handlers.append( 

883 (parameter, self._handlers[parameter]) 

884 ) 

885 self._record_mappings.append( 

886 self._RecordMapping( 

887 parameter.value, 

888 self._record_attribute_map[parameter], 

889 ) 

890 ) 

891 

892 def __call__( 

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

894 ) -> EventDict: 

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

896 from_structlog: bool | None = event_dict.get("_from_structlog") 

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

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

899 if record is not None and not from_structlog: 

900 for mapping in self._record_mappings: 

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

902 mapping.record_attribute 

903 ] 

904 else: 

905 frame, module = _find_first_app_frame_and_name( 

906 additional_ignores=self._additional_ignores 

907 ) 

908 for parameter, handler in self._active_handlers: 

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

910 return event_dict 

911 

912 

913class EventRenamer: 

914 r""" 

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

916 

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

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

919 

920 .. warning:: 

921 

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

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

924 key. 

925 

926 Args: 

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

928 

929 replace_by: 

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

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

932 

933 .. versionadded:: 22.1.0 

934 

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

936 """ 

937 

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

939 self.to = to 

940 self.replace_by = replace_by 

941 

942 def __call__( 

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

944 ) -> EventDict: 

945 event = event_dict.pop("event") 

946 event_dict[self.to] = event 

947 

948 if self.replace_by is not None: 

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

950 if replace_by is not None: 

951 event_dict["event"] = replace_by 

952 

953 return event_dict