+class LoggingContext(contextlib.ContextDecorator):
+ def __init__(
+ self,
+ logger: logging.Logger,
+ *,
+ handlers: Optional[List[logging.Handler]] = None,
+ prefix: Optional[str] = None,
+ suffix: Optional[str] = None,
+ ):
+ """This is a logging context that can be used to temporarily change logging:
+
+ * Change the destination of log messages (by adding temporary handlers)
+ * Add a prefix string to log messages
+ * Add a suffix string to log messages
+
+ .. warning::
+
+ Unfortunately this can't be used to dynamically change the
+ defaut logging level because of a conflict with
+ :class:`DynamicPerScopeLoggingLevelFilter` which, to work,
+ must see every logging message. I love the ability to set
+ logging per module from the commandline and am not willing
+ to lose it in return for the ability to dynamically change
+ the logging level in code.
+
+ Sample usage:
+
+ >>> logging.root.setLevel(0)
+ >>> logger = logging.getLogger(__name__)
+ >>> logger.addHandler(logging.StreamHandler(sys.stdout))
+ >>> logger.info("Hello!")
+ Hello!
+
+ >>> with LoggingContext(logger, prefix="REQUEST#12345>") as log:
+ ... log.info("This is a test %d", 123)
+ REQUEST#12345>This is a test 123
+
+ """
+ self.logger = logger
+ self.handlers = handlers
+ self.prefix = prefix
+ self.suffix = suffix
+
+ def __enter__(self) -> Union[logging.Logger, logging.LoggerAdapter]:
+ assert self.logger
+ self.log: Union[logging.Logger, logging.LoggerAdapter] = self.logger
+ if self.handlers:
+ for handler in self.handlers:
+ self.log.addHandler(handler)
+ if self.prefix:
+ self.log = PrependingLogAdapter(self.log, {"prefix": self.prefix})
+ if self.suffix:
+ self.log = AppendingLogAdapter(self.log, {"suffix": self.suffix})
+ return self.log
+
+ def __exit__(self, et, ev, tb) -> None:
+ if self.handlers:
+ for handler in self.handlers:
+ self.logger.removeHandler(handler)
+ handler.close()
+ return None # propagate exceptions out
+
+