summaryrefslogtreecommitdiffstats
path: root/Doc
diff options
context:
space:
mode:
authorVinay Sajip <vinay_sajip@yahoo.co.uk>2016-04-01 22:13:01 (GMT)
committerVinay Sajip <vinay_sajip@yahoo.co.uk>2016-04-01 22:13:01 (GMT)
commitd93a60149c6e91a2423d9b6447cd8490885919bf (patch)
tree8129357e5a76692b5beb9da85629d1f05a748f72 /Doc
parentc94a93aecbca31939086bc65f09f683bd521a2f6 (diff)
downloadcpython-d93a60149c6e91a2423d9b6447cd8490885919bf.zip
cpython-d93a60149c6e91a2423d9b6447cd8490885919bf.tar.gz
cpython-d93a60149c6e91a2423d9b6447cd8490885919bf.tar.bz2
Added a cookbook recipe for a logging context manager.
Diffstat (limited to 'Doc')
-rw-r--r--Doc/howto/logging-cookbook.rst102
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.