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
|
||||
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