mirror of https://github.com/python/cpython
Added a cookbook recipe for a logging context manager.
This commit is contained in:
parent
c94a93aecb
commit
d93a60149c
|
@ -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
|
showing how the time is formatted both as local time and UTC, one for each
|
||||||
handler.
|
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.
|
||||||
|
|
Loading…
Reference in New Issue