diff options
author | Vinay Sajip <vinay_sajip@yahoo.co.uk> | 2016-04-01 22:13:01 (GMT) |
---|---|---|
committer | Vinay Sajip <vinay_sajip@yahoo.co.uk> | 2016-04-01 22:13:01 (GMT) |
commit | d93a60149c6e91a2423d9b6447cd8490885919bf (patch) | |
tree | 8129357e5a76692b5beb9da85629d1f05a748f72 /Doc/howto/logging-cookbook.rst | |
parent | c94a93aecbca31939086bc65f09f683bd521a2f6 (diff) | |
download | cpython-d93a60149c6e91a2423d9b6447cd8490885919bf.zip cpython-d93a60149c6e91a2423d9b6447cd8490885919bf.tar.gz cpython-d93a60149c6e91a2423d9b6447cd8490885919bf.tar.bz2 |
Added a cookbook recipe for a logging context manager.
Diffstat (limited to 'Doc/howto/logging-cookbook.rst')
-rw-r--r-- | Doc/howto/logging-cookbook.rst | 102 |
1 files changed, 102 insertions, 0 deletions
diff --git a/Doc/howto/logging-cookbook.rst b/Doc/howto/logging-cookbook.rst index 64f4c54..aee95a8 100644 --- a/Doc/howto/logging-cookbook.rst +++ b/Doc/howto/logging-cookbook.rst @@ -2409,3 +2409,105 @@ When this script is run, it should print something like:: showing how the time is formatted both as local time and UTC, one for each handler. + + +.. _context-manager: + +Using a context manager for selective logging +--------------------------------------------- + +There are times when it would be useful to temporarily change the logging +configuration and revert it back after doing something. For this, a context +manager is the most obvious way of saving and restoring the logging context. +Here is a simple example of such a context manager, which allows you to +optionally change the logging level and add a logging handler purely in the +scope of the context manager:: + + import logging + import sys + + class LoggingContext(object): + def __init__(self, logger, level=None, handler=None, close=True): + self.logger = logger + self.level = level + self.handler = handler + self.close = close + + def __enter__(self): + if self.level is not None: + self.old_level = self.logger.level + self.logger.setLevel(self.level) + if self.handler: + self.logger.addHandler(self.handler) + + def __exit__(self, et, ev, tb): + if self.level is not None: + self.logger.setLevel(self.old_level) + if self.handler: + self.logger.removeHandler(self.handler) + if self.handler and self.close: + self.handler.close() + # implicit return of None => don't swallow exceptions + +If you specify a level value, the logger's level is set to that value in the +scope of the with block covered by the context manager. If you specify a +handler, it is added to the logger on entry to the block and removed on exit +from the block. You can also ask the manager to close the handler for you on +block exit - you could do this if you don't need the handler any more. + +To illustrate how it works, we can add the following block of code to the +above:: + + if __name__ == '__main__': + logger = logging.getLogger('foo') + logger.addHandler(logging.StreamHandler()) + logger.setLevel(logging.INFO) + logger.info('1. This should appear just once on stderr.') + logger.debug('2. This should not appear.') + with LoggingContext(logger, level=logging.DEBUG): + logger.debug('3. This should appear once on stderr.') + logger.debug('4. This should not appear.') + h = logging.StreamHandler(sys.stdout) + with LoggingContext(logger, level=logging.DEBUG, handler=h, close=True): + logger.debug('5. This should appear twice - once on stderr and once on stdout.') + logger.info('6. This should appear just once on stderr.') + logger.debug('7. This should not appear.') + +We initially set the logger's level to ``INFO``, so message #1 appears and +message #2 doesn't. We then change the level to ``DEBUG`` temporarily in the +following ``with`` block, and so message #3 appears. After the block exits, the +logger's level is restored to ``INFO`` and so message #4 doesn't appear. In the +next ``with`` block, we set the level to ``DEBUG`` again but also add a handler +writing to ``sys.stdout``. Thus, message #5 appears twice on the console (once +via ``stderr`` and once via ``stdout``). After the ``with`` statement's +completion, the status is as it was before so message #6 appears (like message +#1) whereas message #7 doesn't (just like message #2). + +If we run the resulting script, the result is as follows:: + + $ python logctx.py + 1. This should appear just once on stderr. + 3. This should appear once on stderr. + 5. This should appear twice - once on stderr and once on stdout. + 5. This should appear twice - once on stderr and once on stdout. + 6. This should appear just once on stderr. + +If we run it again, but pipe ``stderr`` to ``/dev/null``, we see the following, +which is the only message written to ``stdout``:: + + $ python logctx.py 2>/dev/null + 5. This should appear twice - once on stderr and once on stdout. + +Once again, but piping ``stdout`` to ``/dev/null``, we get:: + + $ python logctx.py >/dev/null + 1. This should appear just once on stderr. + 3. This should appear once on stderr. + 5. This should appear twice - once on stderr and once on stdout. + 6. This should appear just once on stderr. + +In this case, the message #5 printed to ``stdout`` doesn't appear, as expected. + +Of course, the approach described here can be generalised, for example to attach +logging filters temporarily. Note that the above code works in Python 2 as well +as Python 3. |