Context manager that prepares the whole logging machinery properly.
| 337 | |
| 338 | # Not using @contextmanager for performance reasons. |
| 339 | class catching_logs(Generic[_HandlerType]): |
| 340 | """Context manager that prepares the whole logging machinery properly.""" |
| 341 | |
| 342 | __slots__ = ("attached_loggers", "handler", "level", "orig_level") |
| 343 | |
| 344 | def __init__(self, handler: _HandlerType, level: int | None = None) -> None: |
| 345 | self.handler = handler |
| 346 | self.level = level |
| 347 | self.attached_loggers: list[logging.Logger] = [] |
| 348 | |
| 349 | def __enter__(self) -> _HandlerType: |
| 350 | root_logger = logging.getLogger() |
| 351 | if self.level is not None: |
| 352 | self.handler.setLevel(self.level) |
| 353 | # Attach to root logger. |
| 354 | root_logger.addHandler(self.handler) |
| 355 | self.attached_loggers.append(root_logger) |
| 356 | # Attach to all non-propagating loggers (won't reach root). |
| 357 | # Note that will miss loggers that *become* non-propagating |
| 358 | # after the `__enter__`. Not worth the trouble for now. |
| 359 | for logger in root_logger.manager.loggerDict.values(): |
| 360 | if ( |
| 361 | isinstance(logger, logging.Logger) |
| 362 | and not logger.propagate |
| 363 | and logger is not root_logger |
| 364 | ): |
| 365 | logger.addHandler(self.handler) |
| 366 | self.attached_loggers.append(logger) |
| 367 | if self.level is not None: |
| 368 | # Non-propagating loggers still inherit the level (unless a logger |
| 369 | # explicitly set level), so only do this on the root logger. |
| 370 | self.orig_level = root_logger.level |
| 371 | root_logger.setLevel(min(self.orig_level, self.level)) |
| 372 | return self.handler |
| 373 | |
| 374 | def __exit__( |
| 375 | self, |
| 376 | exc_type: type[BaseException] | None, |
| 377 | exc_val: BaseException | None, |
| 378 | exc_tb: TracebackType | None, |
| 379 | ) -> None: |
| 380 | root_logger = logging.getLogger() |
| 381 | if self.level is not None: |
| 382 | root_logger.setLevel(self.orig_level) |
| 383 | for logger in self.attached_loggers: |
| 384 | logger.removeHandler(self.handler) |
| 385 | self.attached_loggers.clear() |
| 386 | |
| 387 | |
| 388 | class LogCaptureHandler(logging_StreamHandler): |
no outgoing calls