]> granicus.if.org Git - python/commitdiff
Issue #4384: Added logging integration with warnings module using captureWarnings...
authorVinay Sajip <vinay_sajip@yahoo.co.uk>
Wed, 3 Dec 2008 23:22:58 +0000 (23:22 +0000)
committerVinay Sajip <vinay_sajip@yahoo.co.uk>
Wed, 3 Dec 2008 23:22:58 +0000 (23:22 +0000)
Doc/library/logging.rst
Lib/logging/__init__.py
Lib/test/test_logging.py
Misc/NEWS

index dac6aeb173ac8ec7b28b6ad4118a7f203a8ea3ae..c5fde339363a74da2459b8b47287571d8284b160 100644 (file)
@@ -461,6 +461,12 @@ should have the desired effect. If an organisation produces a number of
 libraries, then the logger name specified can be "orgname.foo" rather than
 just "foo".
 
+.. versionadded:: 2.7
+
+The :class:`NullHandler` class was not present in previous versions, but is now
+included, so that it need not be defined in library code.
+
+
 
 Logging Levels
 --------------
@@ -553,6 +559,15 @@ provided:
 #. :class:`HTTPHandler` instances send error messages to an HTTP server using
    either ``GET`` or ``POST`` semantics.
 
+#. :class:`NullHandler` instances do nothing with error messages. They are used
+   by library developers who want to use logging, but want to avoid the "No
+   handlers could be found for logger XXX" message which can be displayed if
+   the library user has not configured logging.
+
+.. versionadded:: 2.7
+
+The :class:`NullHandler` class was not present in previous versions.
+
 The :class:`StreamHandler` and :class:`FileHandler` classes are defined in the
 core logging package. The other handlers are defined in a sub- module,
 :mod:`logging.handlers`. (There is also another sub-module,
index 7b790d2de12dd6f2d1548a824c945084cc1140f4..c28d7c8650d9f400cadb9455c0941a12f5fc0cc6 100644 (file)
@@ -46,8 +46,8 @@ except ImportError:
 
 __author__  = "Vinay Sajip <vinay_sajip@red-dove.com>"
 __status__  = "production"
-__version__ = "0.5.0.5"
-__date__    = "24 January 2008"
+__version__ = "0.5.0.6"
+__date__    = "03 December 2008"
 
 #---------------------------------------------------------------------------
 #   Miscellaneous module data
@@ -1488,3 +1488,58 @@ except ImportError: # for Python versions < 2.0
             old_exit(status)
 
     sys.exit = exithook
+
+# Null handler
+
+class NullHandler(Handler):
+    """
+    This handler does nothing. It's intended to be used to avoid the
+    "No handlers could be found for logger XXX" one-off warning. This is
+    important for library code, which may contain code to log events. If a user
+    of the library does not configure logging, the one-off warning might be
+    produced; to avoid this, the library developer simply needs to instantiate
+    a NullHandler and add it to the top-level logger of the library module or
+    package.
+    """
+    def emit(self, record):
+        pass
+
+# Warnings integration
+
+_warnings_showwarning = None
+
+def _showwarning(message, category, filename, lineno, file=None, line=None):
+    """
+    Implementation of showwarnings which redirects to logging, which will first
+    check to see if the file parameter is None. If a file is specified, it will
+    delegate to the original warnings implementation of showwarning. Otherwise,
+    it will call warnings.formatwarning and will log the resulting string to a
+    warnings logger named "py.warnings" with level logging.WARNING.
+    """
+    if file is not None:
+        if _warnings_showwarning is not None:
+            _warnings_showwarning(message, category, filename, lineno, file, line)
+    else:
+        import warnings
+        s = warnings.formatwarning(message, category, filename, lineno, line)
+        logger = getLogger("py.warnings")
+        if not logger.handlers:
+            logger.addHandler(NullHandler())
+        logger.warning("%s", s)
+
+def captureWarnings(capture):
+    """
+    If capture is true, redirect all warnings to the logging package.
+    If capture is False, ensure that warnings are not redirected to logging
+    but to their original destinations.
+    """
+    import warnings
+    global _warnings_showwarning
+    if capture:
+        if _warnings_showwarning is None:
+            _warnings_showwarning = warnings.showwarning
+            warnings.showwarning = _showwarning
+    else:
+        if _warnings_showwarning is not None:
+            warnings.showwarning = _warnings_showwarning
+            _warnings_showwarning = None
index e837d41037c2ebc5697b88e3ea345ccc8cb98ffc..09fdf4b3df6cbc8f07e443c7f659a7a9f227523c 100644 (file)
@@ -18,7 +18,7 @@
 
 """Test harness for the logging module. Run all tests.
 
-Copyright (C) 2001-2002 Vinay Sajip. All Rights Reserved.
+Copyright (C) 2001-2008 Vinay Sajip. All Rights Reserved.
 """
 
 import logging
@@ -44,6 +44,7 @@ import threading
 import time
 import types
 import unittest
+import warnings
 import weakref
 
 
@@ -885,6 +886,32 @@ class EncodingTest(BaseTest):
             if os.path.isfile(fn):
                 os.remove(fn)
 
+class WarningsTest(BaseTest):
+    def test_warnings(self):
+        logging.captureWarnings(True)
+        warnings.filterwarnings("always", category=UserWarning)
+        try:
+            file = cStringIO.StringIO()
+            h = logging.StreamHandler(file)
+            logger = logging.getLogger("py.warnings")
+            logger.addHandler(h)
+            warnings.warn("I'm warning you...")
+            logger.removeHandler(h)
+            s = file.getvalue()
+            h.close()
+            self.assertTrue(s.find("UserWarning: I'm warning you...\n") > 0)
+
+            #See if an explicit file uses the original implementation
+            file = cStringIO.StringIO()
+            warnings.showwarning("Explicit", UserWarning, "dummy.py", 42, file,
+                                 "Dummy line")
+            s = file.getvalue()
+            file.close()
+            self.assertEqual(s, "dummy.py:42: UserWarning: Explicit\n  Dummy line\n")
+        finally:
+            warnings.resetwarnings()
+            logging.captureWarnings(False)
+
 # Set the locale to the platform-dependent default.  I have no idea
 # why the test does this, but in any case we save the current locale
 # first and restore it at the end.
@@ -893,7 +920,7 @@ def test_main():
     run_unittest(BuiltinLevelsTest, BasicFilterTest,
                     CustomLevelsAndFiltersTest, MemoryHandlerTest,
                     ConfigFileTest, SocketHandlerTest, MemoryTest,
-                    EncodingTest)
+                    EncodingTest, WarningsTest)
 
 if __name__ == "__main__":
     test_main()
index 025ca4c281b2754c27622fd1abbd4b7af8e3d4b0..78578e30595c0d1e171720c8ba845e066363d1cd 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -22,7 +22,7 @@ Core and Builtins
 - Issue #4367: Python would segfault during compiling when the unicodedata
   module couldn't be imported and \N escapes were present.
 
-- Issue #4233: Changed semantic of ``_fileio.FileIO``'s ``close()`` 
+- Issue #4233: Changed semantic of ``_fileio.FileIO``'s ``close()``
   method on file objects with closefd=False. The file descriptor is still
   kept open but the file object behaves like a closed file. The ``FileIO``
   object also got a new readonly attribute ``closefd``.
@@ -60,6 +60,12 @@ Core and Builtins
 Library
 -------
 
+- Issue #4384: Added integration with warnings module using captureWarnings().
+  This change includes a NullHandler which does nothing; it will be of use to
+  library developers who want to avoid the "No handlers could be found for
+  logger XXX" message which can appear if the library user doesn't configure
+  logging.
+
 - Issue #3741: DISTUTILS_USE_SDK set causes msvc9compiler.py to raise an
   exception.