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

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

324 statements  

1# This file is dual licensed under the terms of the Apache License, Version 

2# 2.0, and the MIT License. See the LICENSE file in the root of this 

3# repository for complete details. 

4 

5""" 

6Processors and helpers specific to the :mod:`logging` module from the `Python 

7standard library <https://docs.python.org/>`_. 

8 

9See also :doc:`structlog's standard library support <standard-library>`. 

10""" 

11 

12from __future__ import annotations 

13 

14import asyncio 

15import contextvars 

16import functools 

17import logging 

18import sys 

19import threading 

20import warnings 

21 

22from collections.abc import Callable, Collection, Iterable, Sequence 

23from functools import partial 

24from typing import Any, cast 

25 

26 

27if sys.version_info >= (3, 11): 

28 from typing import Self 

29else: 

30 from typing_extensions import Self 

31 

32 

33from . import _config 

34from ._base import BoundLoggerBase 

35from ._frames import _find_first_app_frame_and_name, _format_stack 

36from ._log_levels import LEVEL_TO_NAME, NAME_TO_LEVEL, add_log_level 

37from .contextvars import ( 

38 _ASYNC_CALLING_STACK, 

39 _ASYNC_CALLING_THREAD, 

40 merge_contextvars, 

41) 

42from .exceptions import DropEvent 

43from .processors import StackInfoRenderer 

44from .typing import ( 

45 Context, 

46 EventDict, 

47 ExcInfo, 

48 Processor, 

49 ProcessorReturnValue, 

50 WrappedLogger, 

51) 

52 

53 

54__all__ = [ 

55 "BoundLogger", 

56 "ExtraAdder", 

57 "LoggerFactory", 

58 "PositionalArgumentsFormatter", 

59 "ProcessorFormatter", 

60 "add_log_level", 

61 "add_log_level_number", 

62 "add_logger_name", 

63 "filter_by_level", 

64 "get_logger", 

65 "recreate_defaults", 

66 "render_to_log_args_and_kwargs", 

67 "render_to_log_kwargs", 

68] 

69 

70 

71def recreate_defaults(*, log_level: int | None = logging.NOTSET) -> None: 

72 """ 

73 Recreate defaults on top of standard library's logging. 

74 

75 The output looks the same, but goes through `logging`. 

76 

77 As with vanilla defaults, the backwards-compatibility guarantees don't 

78 apply to the settings applied here. 

79 

80 Args: 

81 log_level: 

82 If `None`, don't configure standard library logging **at all**. 

83 

84 Otherwise configure it to log to `sys.stdout` at *log_level* 

85 (``logging.NOTSET`` being the default). 

86 

87 If you need more control over `logging`, pass `None` here and 

88 configure it yourself. 

89 

90 .. versionadded:: 22.1.0 

91 .. versionchanged:: 23.3.0 Added `add_logger_name`. 

92 .. versionchanged:: 25.1.0 Added `PositionalArgumentsFormatter`. 

93 """ 

94 if log_level is not None: 

95 kw = {"force": True} 

96 

97 logging.basicConfig( 

98 format="%(message)s", 

99 stream=sys.stdout, 

100 level=log_level, 

101 **kw, # type: ignore[call-overload] 

102 ) 

103 

104 _config.reset_defaults() 

105 _config.configure( 

106 processors=[ 

107 PositionalArgumentsFormatter(), # handled by native loggers 

108 merge_contextvars, 

109 add_log_level, 

110 add_logger_name, 

111 StackInfoRenderer(), 

112 _config._BUILTIN_DEFAULT_PROCESSORS[-2], # TimeStamper 

113 _config._BUILTIN_DEFAULT_PROCESSORS[-1], # ConsoleRenderer 

114 ], 

115 wrapper_class=BoundLogger, 

116 logger_factory=LoggerFactory(), 

117 ) 

118 

119 

120_SENTINEL = object() 

121 

122 

123class _FixedFindCallerLogger(logging.Logger): 

124 """ 

125 Change the behavior of `logging.Logger.findCaller` to cope with 

126 *structlog*'s extra frames. 

127 """ 

128 

129 def findCaller( 

130 self, stack_info: bool = False, stacklevel: int = 1 

131 ) -> tuple[str, int, str, str | None]: 

132 """ 

133 Finds the first caller frame outside of structlog so that the caller 

134 info is populated for wrapping stdlib. 

135 

136 This logger gets set as the default one when using LoggerFactory. 

137 """ 

138 sinfo: str | None 

139 # stdlib logging passes stacklevel=1 from log methods like .warning(), 

140 # but we've already skipped those frames by ignoring "logging", so we 

141 # need to adjust stacklevel down by 1. We need to manually drop 

142 # logging frames, because there's cases where we call logging methods 

143 # from within structlog and the stacklevel offsets don't work anymore. 

144 adjusted_stacklevel = max(0, stacklevel - 1) if stacklevel else None 

145 f, _name = _find_first_app_frame_and_name( 

146 ["logging"], stacklevel=adjusted_stacklevel 

147 ) 

148 sinfo = _format_stack(f) if stack_info else None 

149 

150 return f.f_code.co_filename, f.f_lineno, f.f_code.co_name, sinfo 

151 

152 

153class BoundLogger(BoundLoggerBase): 

154 """ 

155 Python Standard Library version of `structlog.BoundLogger`. 

156 

157 Works exactly like the generic one except that it takes advantage of 

158 knowing the logging methods in advance. 

159 

160 Use it like:: 

161 

162 structlog.configure( 

163 wrapper_class=structlog.stdlib.BoundLogger, 

164 ) 

165 

166 It also contains a bunch of properties that pass-through to the wrapped 

167 `logging.Logger` which should make it work as a drop-in replacement. 

168 

169 .. versionadded:: 23.1.0 

170 Async variants `alog()`, `adebug()`, `ainfo()`, and so forth. 

171 

172 .. versionchanged:: 24.2.0 

173 Callsite parameters are now also collected by 

174 `structlog.processors.CallsiteParameterAdder` for async log methods. 

175 """ 

176 

177 _logger: logging.Logger 

178 

179 def bind(self, **new_values: Any) -> Self: 

180 """ 

181 Return a new logger with *new_values* added to the existing ones. 

182 """ 

183 return super().bind(**new_values) 

184 

185 def unbind(self, *keys: str) -> Self: 

186 """ 

187 Return a new logger with *keys* removed from the context. 

188 

189 Raises: 

190 KeyError: If the key is not part of the context. 

191 """ 

192 return super().unbind(*keys) 

193 

194 def try_unbind(self, *keys: str) -> Self: 

195 """ 

196 Like :meth:`unbind`, but best effort: missing keys are ignored. 

197 

198 .. versionadded:: 18.2.0 

199 """ 

200 return super().try_unbind(*keys) 

201 

202 def new(self, **new_values: Any) -> Self: 

203 """ 

204 Clear context and binds *initial_values* using `bind`. 

205 

206 Only necessary with dict implementations that keep global state like 

207 those wrapped by `structlog.threadlocal.wrap_dict` when threads 

208 are reused. 

209 """ 

210 return super().new(**new_values) 

211 

212 def debug(self, event: str | None = None, *args: Any, **kw: Any) -> Any: 

213 """ 

214 Process event and call `logging.Logger.debug` with the result. 

215 """ 

216 return self._proxy_to_logger("debug", event, *args, **kw) 

217 

218 def info(self, event: str | None = None, *args: Any, **kw: Any) -> Any: 

219 """ 

220 Process event and call `logging.Logger.info` with the result. 

221 """ 

222 return self._proxy_to_logger("info", event, *args, **kw) 

223 

224 def warning(self, event: str | None = None, *args: Any, **kw: Any) -> Any: 

225 """ 

226 Process event and call `logging.Logger.warning` with the result. 

227 """ 

228 return self._proxy_to_logger("warning", event, *args, **kw) 

229 

230 warn = warning 

231 

232 def error(self, event: str | None = None, *args: Any, **kw: Any) -> Any: 

233 """ 

234 Process event and call `logging.Logger.error` with the result. 

235 """ 

236 return self._proxy_to_logger("error", event, *args, **kw) 

237 

238 def critical(self, event: str | None = None, *args: Any, **kw: Any) -> Any: 

239 """ 

240 Process event and call `logging.Logger.critical` with the result. 

241 """ 

242 return self._proxy_to_logger("critical", event, *args, **kw) 

243 

244 def fatal(self, event: str | None = None, *args: Any, **kw: Any) -> Any: 

245 """ 

246 Process event and call `logging.Logger.critical` with the result. 

247 """ 

248 return self._proxy_to_logger("critical", event, *args, **kw) 

249 

250 def exception( 

251 self, event: str | None = None, *args: Any, **kw: Any 

252 ) -> Any: 

253 """ 

254 Process event and call `logging.Logger.exception` with the result, 

255 after setting ``exc_info`` to `True` if it's not already set. 

256 """ 

257 kw.setdefault("exc_info", True) 

258 return self._proxy_to_logger("exception", event, *args, **kw) 

259 

260 def log( 

261 self, level: int, event: str | None = None, *args: Any, **kw: Any 

262 ) -> Any: 

263 """ 

264 Process *event* and call the appropriate logging method depending on 

265 *level*. 

266 """ 

267 return self._proxy_to_logger(LEVEL_TO_NAME[level], event, *args, **kw) 

268 

269 def _proxy_to_logger( 

270 self, 

271 method_name: str, 

272 event: str | None = None, 

273 *event_args: str, 

274 **event_kw: Any, 

275 ) -> Any: 

276 """ 

277 Propagate a method call to the wrapped logger. 

278 

279 This is the same as the superclass implementation, except that 

280 it also preserves positional arguments in the ``event_dict`` so 

281 that the stdlib's support for format strings can be used. 

282 """ 

283 if event_args: 

284 event_kw["positional_args"] = event_args 

285 

286 return super()._proxy_to_logger(method_name, event=event, **event_kw) 

287 

288 # Pass-through attributes and methods to mimic the stdlib's logger 

289 # interface. 

290 

291 @property 

292 def name(self) -> str: 

293 """ 

294 Returns :attr:`logging.Logger.name` 

295 """ 

296 return self._logger.name 

297 

298 @property 

299 def level(self) -> int: 

300 """ 

301 Returns :attr:`logging.Logger.level` 

302 """ 

303 return self._logger.level 

304 

305 @property 

306 def parent(self) -> Any: 

307 """ 

308 Returns :attr:`logging.Logger.parent` 

309 """ 

310 return self._logger.parent 

311 

312 @property 

313 def propagate(self) -> bool: 

314 """ 

315 Returns :attr:`logging.Logger.propagate` 

316 """ 

317 return self._logger.propagate 

318 

319 @property 

320 def handlers(self) -> Any: 

321 """ 

322 Returns :attr:`logging.Logger.handlers` 

323 """ 

324 return self._logger.handlers 

325 

326 @property 

327 def disabled(self) -> int: 

328 """ 

329 Returns :attr:`logging.Logger.disabled` 

330 """ 

331 return self._logger.disabled 

332 

333 def setLevel(self, level: int) -> None: 

334 """ 

335 Calls :meth:`logging.Logger.setLevel` with unmodified arguments. 

336 """ 

337 self._logger.setLevel(level) 

338 

339 def findCaller( 

340 self, stack_info: bool = False, stacklevel: int = 1 

341 ) -> tuple[str, int, str, str | None]: 

342 """ 

343 Calls :meth:`logging.Logger.findCaller` with unmodified arguments. 

344 """ 

345 # No need for stacklevel-adjustments since we're within structlog and 

346 # our frames are ignored unconditionally. 

347 return self._logger.findCaller( 

348 stack_info=stack_info, stacklevel=stacklevel 

349 ) 

350 

351 def makeRecord( 

352 self, 

353 name: str, 

354 level: int, 

355 fn: str, 

356 lno: int, 

357 msg: str, 

358 args: tuple[Any, ...], 

359 exc_info: ExcInfo, 

360 func: str | None = None, 

361 extra: Any = None, 

362 ) -> logging.LogRecord: 

363 """ 

364 Calls :meth:`logging.Logger.makeRecord` with unmodified arguments. 

365 """ 

366 return self._logger.makeRecord( 

367 name, level, fn, lno, msg, args, exc_info, func=func, extra=extra 

368 ) 

369 

370 def handle(self, record: logging.LogRecord) -> None: 

371 """ 

372 Calls :meth:`logging.Logger.handle` with unmodified arguments. 

373 """ 

374 self._logger.handle(record) 

375 

376 def addHandler(self, hdlr: logging.Handler) -> None: 

377 """ 

378 Calls :meth:`logging.Logger.addHandler` with unmodified arguments. 

379 """ 

380 self._logger.addHandler(hdlr) 

381 

382 def removeHandler(self, hdlr: logging.Handler) -> None: 

383 """ 

384 Calls :meth:`logging.Logger.removeHandler` with unmodified arguments. 

385 """ 

386 self._logger.removeHandler(hdlr) 

387 

388 def hasHandlers(self) -> bool: 

389 """ 

390 Calls :meth:`logging.Logger.hasHandlers` with unmodified arguments. 

391 

392 Exists only in Python 3. 

393 """ 

394 return self._logger.hasHandlers() 

395 

396 def callHandlers(self, record: logging.LogRecord) -> None: 

397 """ 

398 Calls :meth:`logging.Logger.callHandlers` with unmodified arguments. 

399 """ 

400 self._logger.callHandlers(record) 

401 

402 def getEffectiveLevel(self) -> int: 

403 """ 

404 Calls :meth:`logging.Logger.getEffectiveLevel` with unmodified 

405 arguments. 

406 """ 

407 return self._logger.getEffectiveLevel() 

408 

409 def isEnabledFor(self, level: int) -> bool: 

410 """ 

411 Calls :meth:`logging.Logger.isEnabledFor` with unmodified arguments. 

412 """ 

413 return self._logger.isEnabledFor(level) 

414 

415 def is_enabled_for(self, level: int) -> bool: 

416 """ 

417 A snake_case alias of `isEnabledFor` for compatibility with 

418 `structlog.typing.FilteringBoundLogger`. 

419 

420 .. note:: 

421 

422 This method is more complex than the native `is_enabled_for` since 

423 it supports standard library-only features like 

424 :attr:`logging.Logger.disabled` while the native one only compares 

425 log levels. 

426 

427 .. versionadded:: 26.1.0 

428 """ 

429 return self._logger.isEnabledFor(level) 

430 

431 def get_effective_level(self) -> int: 

432 """ 

433 A snake_case alias of `getEffectiveLevel` for compatibility with 

434 `structlog.typing.FilteringBoundLogger`. 

435 

436 .. versionadded:: 26.1.0 

437 """ 

438 return self._logger.getEffectiveLevel() 

439 

440 def getChild(self, suffix: str) -> logging.Logger: 

441 """ 

442 Calls :meth:`logging.Logger.getChild` with unmodified arguments. 

443 """ 

444 return self._logger.getChild(suffix) 

445 

446 # Non-Standard Async 

447 async def _dispatch_to_sync( 

448 self, 

449 meth: Callable[..., Any], 

450 event: str, 

451 args: tuple[Any, ...], 

452 kw: dict[str, Any], 

453 ) -> None: 

454 """ 

455 Merge contextvars and log using the sync logger in a thread pool. 

456 """ 

457 # Capture thread-specific info before handing off to the executor. 

458 thread_token = _ASYNC_CALLING_THREAD.set( 

459 (threading.get_ident(), threading.current_thread().name) 

460 ) 

461 scs_token = _ASYNC_CALLING_STACK.set(sys._getframe().f_back.f_back) # type: ignore[union-attr, arg-type, unused-ignore] 

462 ctx = contextvars.copy_context() 

463 

464 try: 

465 await asyncio.get_running_loop().run_in_executor( 

466 None, 

467 lambda: ctx.run(lambda: meth(event, *args, **kw)), 

468 ) 

469 finally: 

470 _ASYNC_CALLING_STACK.reset(scs_token) 

471 _ASYNC_CALLING_THREAD.reset(thread_token) 

472 

473 async def adebug(self, event: str, *args: Any, **kw: Any) -> None: 

474 """ 

475 Log using `debug()`, but asynchronously in a separate thread. 

476 

477 .. versionadded:: 23.1.0 

478 """ 

479 await self._dispatch_to_sync(self.debug, event, args, kw) 

480 

481 async def ainfo(self, event: str, *args: Any, **kw: Any) -> None: 

482 """ 

483 Log using `info()`, but asynchronously in a separate thread. 

484 

485 .. versionadded:: 23.1.0 

486 """ 

487 await self._dispatch_to_sync(self.info, event, args, kw) 

488 

489 async def awarning(self, event: str, *args: Any, **kw: Any) -> None: 

490 """ 

491 Log using `warning()`, but asynchronously in a separate thread. 

492 

493 .. versionadded:: 23.1.0 

494 """ 

495 await self._dispatch_to_sync(self.warning, event, args, kw) 

496 

497 async def aerror(self, event: str, *args: Any, **kw: Any) -> None: 

498 """ 

499 Log using `error()`, but asynchronously in a separate thread. 

500 

501 .. versionadded:: 23.1.0 

502 """ 

503 await self._dispatch_to_sync(self.error, event, args, kw) 

504 

505 async def acritical(self, event: str, *args: Any, **kw: Any) -> None: 

506 """ 

507 Log using `critical()`, but asynchronously in a separate thread. 

508 

509 .. versionadded:: 23.1.0 

510 """ 

511 await self._dispatch_to_sync(self.critical, event, args, kw) 

512 

513 async def afatal(self, event: str, *args: Any, **kw: Any) -> None: 

514 """ 

515 Log using `critical()`, but asynchronously in a separate thread. 

516 

517 .. versionadded:: 23.1.0 

518 """ 

519 await self._dispatch_to_sync(self.critical, event, args, kw) 

520 

521 async def aexception(self, event: str, *args: Any, **kw: Any) -> None: 

522 """ 

523 Log using `exception()`, but asynchronously in a separate thread. 

524 

525 .. versionadded:: 23.1.0 

526 """ 

527 # To make `log.exception("foo") work, we have to check if the user 

528 # passed an explicit exc_info and if not, supply our own. 

529 if kw.get("exc_info", True) is True and kw.get("exception") is None: 

530 kw["exc_info"] = sys.exc_info() 

531 

532 await self._dispatch_to_sync(self.exception, event, args, kw) 

533 

534 async def alog( 

535 self, level: Any, event: str, *args: Any, **kw: Any 

536 ) -> None: 

537 """ 

538 Log using `log()`, but asynchronously in a separate thread. 

539 

540 .. versionadded:: 23.1.0 

541 """ 

542 await self._dispatch_to_sync(partial(self.log, level), event, args, kw) 

543 

544 

545def get_logger(*args: Any, **initial_values: Any) -> BoundLogger: 

546 """ 

547 Only calls `structlog.get_logger`, but has the correct type hints. 

548 

549 .. warning:: 

550 

551 Does **not** check whether -- or ensure that -- you've configured 

552 *structlog* for standard library :mod:`logging`! 

553 

554 See :doc:`standard-library` for details. 

555 

556 .. versionadded:: 20.2.0 

557 """ 

558 return _config.get_logger(*args, **initial_values) 

559 

560 

561class AsyncBoundLogger: 

562 """ 

563 Wraps a `BoundLogger` & exposes its logging methods as ``async`` versions. 

564 

565 This approach has turned out to be a mistake and the class has been 

566 deprecated in 23.1.0. Use the regular `BoundLogger` with its a-prefixed 

567 methods instead. 

568 

569 .. versionadded:: 20.2.0 

570 .. versionchanged:: 20.2.0 fix _dispatch_to_sync contextvars usage 

571 .. deprecated:: 23.1.0 

572 Use the regular `BoundLogger` with its a-prefixed methods instead. 

573 .. versionchanged:: 23.3.0 

574 Callsite parameters are now also collected for async log methods. 

575 """ 

576 

577 __slots__ = ("_loop", "sync_bl") 

578 

579 #: The wrapped synchronous logger. It is useful to be able to log 

580 #: synchronously occasionally. 

581 sync_bl: BoundLogger 

582 

583 _executor = None 

584 _bound_logger_factory = BoundLogger 

585 

586 def __init__( 

587 self, 

588 logger: logging.Logger, 

589 processors: Iterable[Processor], 

590 context: Context, 

591 *, 

592 # Only as an optimization for binding! 

593 _sync_bl: Any = None, # *vroom vroom* over purity. 

594 _loop: Any = None, 

595 ): 

596 if _sync_bl: 

597 self.sync_bl = _sync_bl 

598 self._loop = _loop 

599 

600 return 

601 

602 self.sync_bl = self._bound_logger_factory( 

603 logger=logger, processors=processors, context=context 

604 ) 

605 self._loop = asyncio.get_running_loop() 

606 

607 # Instances would've been correctly recognized as such, however the class 

608 # not and we need the class in `structlog.configure()`. 

609 @property 

610 def _context(self) -> Context: 

611 return self.sync_bl._context 

612 

613 def bind(self, **new_values: Any) -> Self: 

614 return self.__class__( 

615 # logger, processors and context are within sync_bl. These 

616 # arguments are ignored if _sync_bl is passed. *vroom vroom* over 

617 # purity. 

618 logger=None, # type: ignore[arg-type] 

619 processors=(), 

620 context={}, 

621 _sync_bl=self.sync_bl.bind(**new_values), 

622 _loop=self._loop, 

623 ) 

624 

625 def new(self, **new_values: Any) -> Self: 

626 return self.__class__( 

627 # c.f. comment in bind 

628 logger=None, # type: ignore[arg-type] 

629 processors=(), 

630 context={}, 

631 _sync_bl=self.sync_bl.new(**new_values), 

632 _loop=self._loop, 

633 ) 

634 

635 def unbind(self, *keys: str) -> Self: 

636 return self.__class__( 

637 # c.f. comment in bind 

638 logger=None, # type: ignore[arg-type] 

639 processors=(), 

640 context={}, 

641 _sync_bl=self.sync_bl.unbind(*keys), 

642 _loop=self._loop, 

643 ) 

644 

645 def try_unbind(self, *keys: str) -> Self: 

646 return self.__class__( 

647 # c.f. comment in bind 

648 logger=None, # type: ignore[arg-type] 

649 processors=(), 

650 context={}, 

651 _sync_bl=self.sync_bl.try_unbind(*keys), 

652 _loop=self._loop, 

653 ) 

654 

655 async def _dispatch_to_sync( 

656 self, 

657 meth: Callable[..., Any], 

658 event: str, 

659 args: tuple[Any, ...], 

660 kw: dict[str, Any], 

661 ) -> None: 

662 """ 

663 Merge contextvars and log using the sync logger in a thread pool. 

664 """ 

665 # Capture thread-specific info before handing off to the executor. 

666 thread_token = _ASYNC_CALLING_THREAD.set( 

667 (threading.get_ident(), threading.current_thread().name) 

668 ) 

669 scs_token = _ASYNC_CALLING_STACK.set(sys._getframe().f_back.f_back) # type: ignore[union-attr, arg-type, unused-ignore] 

670 ctx = contextvars.copy_context() 

671 

672 try: 

673 await asyncio.get_running_loop().run_in_executor( 

674 self._executor, 

675 lambda: ctx.run(lambda: meth(event, *args, **kw)), 

676 ) 

677 finally: 

678 _ASYNC_CALLING_STACK.reset(scs_token) 

679 _ASYNC_CALLING_THREAD.reset(thread_token) 

680 

681 async def debug(self, event: str, *args: Any, **kw: Any) -> None: 

682 await self._dispatch_to_sync(self.sync_bl.debug, event, args, kw) 

683 

684 async def info(self, event: str, *args: Any, **kw: Any) -> None: 

685 await self._dispatch_to_sync(self.sync_bl.info, event, args, kw) 

686 

687 async def warning(self, event: str, *args: Any, **kw: Any) -> None: 

688 await self._dispatch_to_sync(self.sync_bl.warning, event, args, kw) 

689 

690 async def warn(self, event: str, *args: Any, **kw: Any) -> None: 

691 await self._dispatch_to_sync(self.sync_bl.warning, event, args, kw) 

692 

693 async def error(self, event: str, *args: Any, **kw: Any) -> None: 

694 await self._dispatch_to_sync(self.sync_bl.error, event, args, kw) 

695 

696 async def critical(self, event: str, *args: Any, **kw: Any) -> None: 

697 await self._dispatch_to_sync(self.sync_bl.critical, event, args, kw) 

698 

699 async def fatal(self, event: str, *args: Any, **kw: Any) -> None: 

700 await self._dispatch_to_sync(self.sync_bl.critical, event, args, kw) 

701 

702 async def exception(self, event: str, *args: Any, **kw: Any) -> None: 

703 # To make `log.exception("foo") work, we have to check if the user 

704 # passed an explicit exc_info and if not, supply our own. 

705 ei = kw.pop("exc_info", None) 

706 if ei is None and kw.get("exception") is None: 

707 ei = sys.exc_info() 

708 

709 kw["exc_info"] = ei 

710 

711 await self._dispatch_to_sync(self.sync_bl.exception, event, args, kw) 

712 

713 async def log(self, level: Any, event: str, *args: Any, **kw: Any) -> None: 

714 await self._dispatch_to_sync( 

715 partial(self.sync_bl.log, level), event, args, kw 

716 ) 

717 

718 

719class LoggerFactory: 

720 """ 

721 Build a standard library logger when an *instance* is called. 

722 

723 Sets a custom logger using :func:`logging.setLoggerClass` so variables in 

724 log format are expanded properly. 

725 

726 >>> from structlog import configure 

727 >>> from structlog.stdlib import LoggerFactory 

728 >>> configure(logger_factory=LoggerFactory()) 

729 

730 Args: 

731 ignore_frame_names: 

732 When guessing the name of a logger, skip frames whose names *start* 

733 with one of these. For example, in pyramid applications you'll 

734 want to set it to ``["venusian", "pyramid.config"]``. This argument 

735 is called *additional_ignores* in other APIs throughout 

736 *structlog*. 

737 """ 

738 

739 def __init__(self, ignore_frame_names: list[str] | None = None): 

740 self._ignore = ignore_frame_names 

741 logging.setLoggerClass(_FixedFindCallerLogger) 

742 

743 def __call__(self, *args: Any) -> logging.Logger: 

744 """ 

745 Deduce the caller's module name and create a stdlib logger. 

746 

747 If an optional argument is passed, it will be used as the logger name 

748 instead of guesswork. This optional argument would be passed from the 

749 :func:`structlog.get_logger` call. For example 

750 ``structlog.get_logger("foo")`` would cause this method to be called 

751 with ``"foo"`` as its first positional argument. 

752 

753 .. versionchanged:: 0.4.0 

754 Added support for optional positional arguments. Using the first 

755 one for naming the constructed logger. 

756 """ 

757 if args: 

758 return logging.getLogger(args[0]) 

759 

760 # We skip all frames that originate from within structlog or one of the 

761 # configured names. 

762 _, name = _find_first_app_frame_and_name(self._ignore) 

763 

764 return logging.getLogger(name) 

765 

766 

767class PositionalArgumentsFormatter: 

768 """ 

769 Apply stdlib-like string formatting to the ``event`` key. 

770 

771 If the ``positional_args`` key in the event dict is set, it must 

772 contain a tuple that is used for formatting (using the ``%s`` string 

773 formatting operator) of the value from the ``event`` key. This works 

774 in the same way as the stdlib handles arguments to the various log 

775 methods: if the tuple contains only a single `dict` argument it is 

776 used for keyword placeholders in the ``event`` string, otherwise it 

777 will be used for positional placeholders. 

778 

779 ``positional_args`` is populated by `structlog.stdlib.BoundLogger` or 

780 can be set manually. 

781 

782 The *remove_positional_args* flag can be set to `False` to keep the 

783 ``positional_args`` key in the event dict; by default it will be 

784 removed from the event dict after formatting a message. 

785 """ 

786 

787 def __init__(self, remove_positional_args: bool = True) -> None: 

788 self.remove_positional_args = remove_positional_args 

789 

790 def __call__( 

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

792 ) -> EventDict: 

793 args = event_dict.get("positional_args") 

794 

795 # Mimic the formatting behaviour of the stdlib's logging module, which 

796 # accepts both positional arguments and a single dict argument. The 

797 # "single dict" check is the same one as the stdlib's logging module 

798 # performs in LogRecord.__init__(). 

799 if args: 

800 if len(args) == 1 and isinstance(args[0], dict) and args[0]: 

801 args = args[0] 

802 

803 event_dict["event"] %= args 

804 

805 if self.remove_positional_args and args is not None: 

806 del event_dict["positional_args"] 

807 

808 return event_dict 

809 

810 

811def filter_by_level( 

812 logger: logging.Logger, method_name: str, event_dict: EventDict 

813) -> EventDict: 

814 """ 

815 Check whether logging is configured to accept messages from this log level. 

816 

817 Should be the first processor if stdlib's filtering by level is used so 

818 possibly expensive processors like exception formatters are avoided in the 

819 first place. 

820 

821 >>> import logging 

822 >>> from structlog.stdlib import filter_by_level 

823 >>> logging.basicConfig(level=logging.WARN) 

824 >>> logger = logging.getLogger() 

825 >>> filter_by_level(logger, 'warn', {}) 

826 {} 

827 >>> filter_by_level(logger, 'debug', {}) 

828 Traceback (most recent call last): 

829 ... 

830 DropEvent 

831 """ 

832 if ( 

833 # We can't use logger.isEnabledFor() because it's always disabled when 

834 # a log entry is in flight on Python 3.14 and later, 

835 not logger.disabled 

836 and NAME_TO_LEVEL[method_name] >= logger.getEffectiveLevel() 

837 ): 

838 return event_dict 

839 

840 raise DropEvent 

841 

842 

843def add_log_level_number( 

844 logger: logging.Logger, method_name: str, event_dict: EventDict 

845) -> EventDict: 

846 """ 

847 Add the log level number to the event dict. 

848 

849 Log level numbers map to the log level names. The Python stdlib uses them 

850 for filtering logic. This adds the same numbers so users can leverage 

851 similar filtering. Compare:: 

852 

853 level in ("warning", "error", "critical") 

854 level_number >= 30 

855 

856 The mapping of names to numbers is in 

857 ``structlog.stdlib._log_levels._NAME_TO_LEVEL``. 

858 

859 .. versionadded:: 18.2.0 

860 """ 

861 event_dict["level_number"] = NAME_TO_LEVEL[method_name] 

862 

863 return event_dict 

864 

865 

866def add_logger_name( 

867 logger: logging.Logger, method_name: str, event_dict: EventDict 

868) -> EventDict: 

869 """ 

870 Add the logger name to the event dict. 

871 """ 

872 record = event_dict.get("_record") 

873 if record is None: 

874 event_dict["logger"] = logger.name 

875 else: 

876 event_dict["logger"] = record.name 

877 return event_dict 

878 

879 

880_LOG_RECORD_KEYS = logging.LogRecord( 

881 "name", 0, "pathname", 0, "msg", (), None 

882).__dict__.keys() 

883 

884 

885class ExtraAdder: 

886 """ 

887 Add extra attributes of `logging.LogRecord` objects to the event 

888 dictionary. 

889 

890 This processor can be used for adding data passed in the ``extra`` 

891 parameter of the `logging` module's log methods to the event dictionary. 

892 

893 Args: 

894 allow: 

895 An optional collection of attributes that, if present in 

896 `logging.LogRecord` objects, will be copied to event dictionaries. 

897 

898 If ``allow`` is None all attributes of `logging.LogRecord` objects 

899 that do not exist on a standard `logging.LogRecord` object will be 

900 copied to event dictionaries. 

901 

902 .. versionadded:: 21.5.0 

903 """ 

904 

905 __slots__ = ("_copier",) 

906 

907 def __init__(self, allow: Collection[str] | None = None) -> None: 

908 self._copier: Callable[[EventDict, logging.LogRecord], None] 

909 if allow is not None: 

910 # The contents of allow is copied to a new list so that changes to 

911 # the list passed into the constructor does not change the 

912 # behaviour of this processor. 

913 self._copier = functools.partial(self._copy_allowed, [*allow]) 

914 else: 

915 self._copier = self._copy_all 

916 

917 def __call__( 

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

919 ) -> EventDict: 

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

921 if record is not None: 

922 self._copier(event_dict, record) 

923 return event_dict 

924 

925 @classmethod 

926 def _copy_all( 

927 cls, event_dict: EventDict, record: logging.LogRecord 

928 ) -> None: 

929 for key, value in record.__dict__.items(): 

930 if key not in _LOG_RECORD_KEYS: 

931 event_dict[key] = value 

932 

933 @classmethod 

934 def _copy_allowed( 

935 cls, 

936 allow: Collection[str], 

937 event_dict: EventDict, 

938 record: logging.LogRecord, 

939 ) -> None: 

940 for key in allow: 

941 if key in record.__dict__: 

942 event_dict[key] = record.__dict__[key] 

943 

944 

945LOG_KWARG_NAMES = ("exc_info", "stack_info", "stacklevel") 

946 

947 

948def render_to_log_args_and_kwargs( 

949 _: logging.Logger, __: str, event_dict: EventDict 

950) -> tuple[tuple[Any, ...], dict[str, Any]]: 

951 """ 

952 Render ``event_dict`` into positional and keyword arguments for 

953 `logging.Logger` logging methods. 

954 See `logging.Logger.debug` method for keyword arguments reference. 

955 

956 The ``event`` field is passed in the first positional argument, positional 

957 arguments from ``positional_args`` field are passed in subsequent positional 

958 arguments, keyword arguments are extracted from the *event_dict* and the 

959 rest of the *event_dict* is added as ``extra``. 

960 

961 This allows you to defer formatting to `logging`. 

962 

963 .. versionadded:: 25.1.0 

964 """ 

965 args = (event_dict.pop("event"), *event_dict.pop("positional_args", ())) 

966 

967 kwargs = { 

968 kwarg_name: event_dict.pop(kwarg_name) 

969 for kwarg_name in LOG_KWARG_NAMES 

970 if kwarg_name in event_dict 

971 } 

972 if event_dict: 

973 kwargs["extra"] = event_dict 

974 

975 return args, kwargs 

976 

977 

978def render_to_log_kwargs( 

979 _: logging.Logger, __: str, event_dict: EventDict 

980) -> EventDict: 

981 """ 

982 Render ``event_dict`` into keyword arguments for `logging.Logger` logging 

983 methods. 

984 See `logging.Logger.debug` method for keyword arguments reference. 

985 

986 The ``event`` field is translated into ``msg``, keyword arguments are 

987 extracted from the *event_dict* and the rest of the *event_dict* is added as 

988 ``extra``. 

989 

990 This allows you to defer formatting to `logging`. 

991 

992 .. versionadded:: 17.1.0 

993 .. versionchanged:: 22.1.0 

994 ``exc_info``, ``stack_info``, and ``stacklevel`` are passed as proper 

995 kwargs and not put into ``extra``. 

996 .. versionchanged:: 24.2.0 

997 ``stackLevel`` corrected to ``stacklevel``. 

998 """ 

999 return { 

1000 "msg": event_dict.pop("event"), 

1001 "extra": event_dict, 

1002 **{ 

1003 kw: event_dict.pop(kw) 

1004 for kw in LOG_KWARG_NAMES 

1005 if kw in event_dict 

1006 }, 

1007 } 

1008 

1009 

1010class ProcessorFormatter(logging.Formatter): 

1011 r""" 

1012 Call *structlog* processors on `logging.LogRecord`\s. 

1013 

1014 This is an implementation of a `logging.Formatter` that can be used to 

1015 format log entries from both *structlog* and `logging`. 

1016 

1017 Its static method `wrap_for_formatter` must be the final processor in 

1018 *structlog*'s processor chain. 

1019 

1020 Please refer to :ref:`processor-formatter` for examples. 

1021 

1022 Args: 

1023 foreign_pre_chain: 

1024 If not `None`, it is used as a processor chain that is applied to 

1025 **non**-*structlog* log entries before the event dictionary is 

1026 passed to *processors*. (default: `None`) 

1027 

1028 processors: 

1029 A chain of *structlog* processors that is used to process **all** 

1030 log entries. The last one must render to a `str` which then gets 

1031 passed on to `logging` for output. 

1032 

1033 Compared to *structlog*'s regular processor chains, there's a few 

1034 differences: 

1035 

1036 - The event dictionary contains two additional keys: 

1037 

1038 #. ``_record``: a `logging.LogRecord` that either was created 

1039 using `logging` APIs, **or** is a wrapped *structlog* log 

1040 entry created by `wrap_for_formatter`. 

1041 

1042 #. ``_from_structlog``: a `bool` that indicates whether or not 

1043 ``_record`` was created by a *structlog* logger. 

1044 

1045 Since you most likely don't want ``_record`` and 

1046 ``_from_structlog`` in your log files, we've added the static 

1047 method `remove_processors_meta` to ``ProcessorFormatter`` that 

1048 you can add just before your renderer. 

1049 

1050 - Since this is a `logging` *formatter*, raising 

1051 `structlog.DropEvent` will crash your application. 

1052 

1053 keep_exc_info: 

1054 ``exc_info`` on `logging.LogRecord`\ s is added to the 

1055 ``event_dict`` and removed afterwards. Set this to ``True`` to keep 

1056 it on the `logging.LogRecord`. (default: False) 

1057 

1058 keep_stack_info: 

1059 Same as *keep_exc_info* except for ``stack_info``. (default: False) 

1060 

1061 logger: 

1062 Logger which we want to push through the *structlog* processor 

1063 chain. This parameter is necessary for some of the processors like 

1064 `filter_by_level`. (default: None) 

1065 

1066 pass_foreign_args: 

1067 If True, pass a foreign log record's ``args`` attribute to the 

1068 ``event_dict`` under ``positional_args`` key. (default: False) 

1069 

1070 processor: 

1071 A single *structlog* processor used for rendering the event 

1072 dictionary before passing it off to `logging`. Must return a `str`. 

1073 The event dictionary does **not** contain ``_record`` and 

1074 ``_from_structlog``. 

1075 

1076 This parameter exists for historic reasons. Please use *processors* 

1077 instead. 

1078 

1079 use_get_message: 

1080 If True, use ``record.getMessage`` to get a fully rendered log 

1081 message, otherwise use ``str(record.msg)``. (default: True) 

1082 

1083 Raises: 

1084 TypeError: If both or neither *processor* and *processors* are passed. 

1085 

1086 .. versionadded:: 17.1.0 

1087 .. versionadded:: 17.2.0 *keep_exc_info* and *keep_stack_info* 

1088 .. versionadded:: 19.2.0 *logger* 

1089 .. versionadded:: 19.2.0 *pass_foreign_args* 

1090 .. versionadded:: 21.3.0 *processors* 

1091 .. deprecated:: 21.3.0 

1092 *processor* (singular) in favor of *processors* (plural). Removal is not 

1093 planned. 

1094 .. versionadded:: 23.3.0 *use_get_message* 

1095 """ 

1096 

1097 def __init__( 

1098 self, 

1099 processor: Processor | None = None, 

1100 processors: Sequence[Processor] | None = (), 

1101 foreign_pre_chain: Sequence[Processor] | None = None, 

1102 keep_exc_info: bool = False, 

1103 keep_stack_info: bool = False, 

1104 logger: logging.Logger | None = None, 

1105 pass_foreign_args: bool = False, 

1106 use_get_message: bool = True, 

1107 *args: Any, 

1108 **kwargs: Any, 

1109 ) -> None: 

1110 fmt = kwargs.pop("fmt", "%(message)s") 

1111 super().__init__(*args, fmt=fmt, **kwargs) # type: ignore[misc] 

1112 

1113 if processor and processors: 

1114 msg = ( 

1115 "The `processor` and `processors` arguments are mutually" 

1116 " exclusive." 

1117 ) 

1118 raise TypeError(msg) 

1119 

1120 self.processors: Sequence[Processor] 

1121 if processor is not None: 

1122 self.processors = (self.remove_processors_meta, processor) 

1123 elif processors: 

1124 self.processors = processors 

1125 else: 

1126 msg = "Either `processor` or `processors` must be passed." 

1127 raise TypeError(msg) 

1128 

1129 self.foreign_pre_chain = foreign_pre_chain 

1130 self.keep_exc_info = keep_exc_info 

1131 self.keep_stack_info = keep_stack_info 

1132 self.logger = logger 

1133 self.pass_foreign_args = pass_foreign_args 

1134 self.use_get_message = use_get_message 

1135 

1136 def format(self, record: logging.LogRecord) -> str: 

1137 """ 

1138 Extract *structlog*'s `event_dict` from ``record.msg`` and format it. 

1139 

1140 *record* has been patched by `wrap_for_formatter` first though, so the 

1141 type isn't quite right. 

1142 """ 

1143 # Make a shallow copy of the record to let other handlers/formatters 

1144 # process the original one 

1145 record = logging.makeLogRecord(record.__dict__) 

1146 

1147 logger = getattr(record, "_logger", _SENTINEL) 

1148 meth_name = getattr(record, "_name", "__structlog_sentinel__") 

1149 

1150 ed: ProcessorReturnValue 

1151 if logger is not _SENTINEL and meth_name != "__structlog_sentinel__": 

1152 # Both attached by wrap_for_formatter 

1153 if self.logger is not None: 

1154 logger = self.logger 

1155 meth_name = cast(str, record._name) # type:ignore[attr-defined] 

1156 

1157 # We need to copy because it's possible that the same record gets 

1158 # processed by multiple logging formatters. LogRecord.getMessage 

1159 # would transform our dict into a str. 

1160 ed = cast(dict[str, Any], record.msg).copy() 

1161 ed["_record"] = record 

1162 ed["_from_structlog"] = True 

1163 else: 

1164 logger = self.logger 

1165 meth_name = record.levelname.lower() 

1166 ed = { 

1167 "event": ( 

1168 record.getMessage() 

1169 if self.use_get_message 

1170 else str(record.msg) 

1171 ), 

1172 "_record": record, 

1173 "_from_structlog": False, 

1174 } 

1175 

1176 if self.pass_foreign_args: 

1177 ed["positional_args"] = record.args 

1178 

1179 record.args = () 

1180 

1181 # Add stack-related attributes to the event dict 

1182 if record.exc_info: 

1183 ed["exc_info"] = record.exc_info 

1184 if record.stack_info: 

1185 ed["stack_info"] = record.stack_info 

1186 

1187 # Non-structlog allows to run through a chain to prepare it for the 

1188 # final processor (e.g. adding timestamps and log levels). 

1189 for proc in self.foreign_pre_chain or (): 

1190 ed = cast(EventDict, proc(logger, meth_name, ed)) 

1191 

1192 # If required, unset stack-related attributes on the record copy so 

1193 # that the base implementation doesn't append stacktraces to the 

1194 # output. 

1195 if not self.keep_exc_info: 

1196 record.exc_text = None 

1197 record.exc_info = None 

1198 if not self.keep_stack_info: 

1199 record.stack_info = None 

1200 

1201 for p in self.processors: 

1202 ed = p(logger, meth_name, ed) # type: ignore[arg-type] 

1203 

1204 if not isinstance(ed, str): 

1205 warnings.warn( 

1206 "The last processor in ProcessorFormatter.processors must " 

1207 f"return a string, but {self.processors[-1]} returned a " 

1208 f"{type(ed)} instead.", 

1209 category=RuntimeWarning, 

1210 stacklevel=1, 

1211 ) 

1212 ed = cast(str, ed) 

1213 

1214 record.msg = ed 

1215 

1216 return super().format(record) 

1217 

1218 @staticmethod 

1219 def wrap_for_formatter( 

1220 logger: logging.Logger, name: str, event_dict: EventDict 

1221 ) -> tuple[tuple[EventDict], dict[str, dict[str, Any]]]: 

1222 """ 

1223 Wrap *logger*, *name*, and *event_dict*. 

1224 

1225 The result is later unpacked by `ProcessorFormatter` when formatting 

1226 log entries. 

1227 

1228 Use this static method as the renderer (in other words, final 

1229 processor) if you want to use `ProcessorFormatter` in your `logging` 

1230 configuration. 

1231 """ 

1232 return (event_dict,), {"extra": {"_logger": logger, "_name": name}} 

1233 

1234 @staticmethod 

1235 def remove_processors_meta( 

1236 _: WrappedLogger, __: str, event_dict: EventDict 

1237 ) -> EventDict: 

1238 """ 

1239 Remove ``_record`` and ``_from_structlog`` from *event_dict*. 

1240 

1241 These keys are added to the event dictionary, before 

1242 `ProcessorFormatter`'s *processors* are run. 

1243 

1244 .. versionadded:: 21.3.0 

1245 """ 

1246 del event_dict["_record"] 

1247 del event_dict["_from_structlog"] 

1248 

1249 return event_dict