[Checkins] SVN: ZConfig/trunk/ add support for reopening all log files opened using the ZConfig FileHandler

Fred L. Drake, Jr. fdrake at gmail.com
Wed Jun 20 15:12:32 EDT 2007


Log message for revision 76855:
  add support for reopening all log files opened using the ZConfig FileHandler

Changed:
  U   ZConfig/trunk/ZConfig/components/logger/loghandler.py
  U   ZConfig/trunk/ZConfig/components/logger/tests/test_logger.py
  U   ZConfig/trunk/doc/zconfig.pdf
  U   ZConfig/trunk/doc/zconfig.tex

-=-
Modified: ZConfig/trunk/ZConfig/components/logger/loghandler.py
===================================================================
--- ZConfig/trunk/ZConfig/components/logger/loghandler.py	2007-06-20 18:45:27 UTC (rev 76854)
+++ ZConfig/trunk/ZConfig/components/logger/loghandler.py	2007-06-20 19:12:32 UTC (rev 76855)
@@ -15,6 +15,7 @@
 
 import os
 import sys
+import weakref
 
 from logging import Handler, StreamHandler
 from logging.handlers import SysLogHandler, BufferingHandler
@@ -22,6 +23,24 @@
 from logging.handlers import NTEventLogHandler as Win32EventLogHandler
 
 
+_reopenable_handlers = []
+
+def reopenFiles():
+    """Reopen all logfiles managed by ZConfig configuration."""
+    for wr in _reopenable_handlers[:]:
+        h = wr()
+        if h is None:
+            try:
+                _reopenable_handlers.remove(wr)
+            except ValueError:
+                continue
+        else:
+            h.reopen()
+
+def _remove_from_reopenable(wr):
+    _reopenable_handlers.remove(wr)
+
+
 class FileHandler(StreamHandler):
     """File handler which supports reopening of logs.
 
@@ -34,6 +53,8 @@
         StreamHandler.__init__(self, open(filename, mode))
         self.baseFilename = filename
         self.mode = mode
+        self._wr = weakref.ref(self, _remove_from_reopenable)
+        _reopenable_handlers.append(self._wr)
 
     def close(self):
         self.stream.close()

Modified: ZConfig/trunk/ZConfig/components/logger/tests/test_logger.py
===================================================================
--- ZConfig/trunk/ZConfig/components/logger/tests/test_logger.py	2007-06-20 18:45:27 UTC (rev 76854)
+++ ZConfig/trunk/ZConfig/components/logger/tests/test_logger.py	2007-06-20 19:12:32 UTC (rev 76855)
@@ -45,22 +45,41 @@
     # XXX This tries to save and restore the state of logging around
     # the test.  Somewhat surgical; there may be a better way.
 
-    name = None
-
     def setUp(self):
-        self._old_logger = logging.getLogger(self.name)
+        self._created = []
+        self._old_logger = logging.getLogger()
         self._old_level = self._old_logger.level
         self._old_handlers = self._old_logger.handlers[:]
         self._old_logger.handlers[:] = []
         self._old_logger.setLevel(logging.WARN)
 
+        self._old_logger_dict = logging.root.manager.loggerDict.copy()
+        logging.root.manager.loggerDict.clear()
+
     def tearDown(self):
+        logging.root.manager.loggerDict.clear()
+        logging.root.manager.loggerDict.update(self._old_logger_dict)
+
         for h in self._old_logger.handlers:
             self._old_logger.removeHandler(h)
         for h in self._old_handlers:
             self._old_logger.addHandler(h)
         self._old_logger.setLevel(self._old_level)
 
+        while self._created:
+            os.unlink(self._created.pop())
+
+    def mktemp(self):
+        fd, fn = tempfile.mkstemp()
+        os.close(fd)
+        self._created.append(fn)
+        return fn
+
+    def move(self, fn):
+        nfn = self.mktemp()
+        os.rename(fn, nfn)
+        return nfn
+
     _schema = None
 
     def get_schema(self):
@@ -129,8 +148,7 @@
 
     def test_with_logfile(self):
         import os
-        fd, fn = tempfile.mkstemp()
-        os.close(fd)
+        fn = self.mktemp()
         logger = self.check_simple_logger("<eventlog>\n"
                                           "  <logfile>\n"
                                           "    path %s\n"
@@ -140,21 +158,6 @@
         logfile = logger.handlers[0]
         self.assertEqual(logfile.level, logging.DEBUG)
         self.assert_(isinstance(logfile, loghandler.FileHandler))
-        # Test the move-and-reopen behavior:
-        logger.propagate = False
-        logger.error("message 1")
-        os.rename(fn, fn + "-")
-        logger.error("message 2")
-        logfile.reopen()
-        logger.error("message 3")
-        logfile.close()
-        text1 = open(fn + "-").read()
-        text2 = open(fn).read()
-        self.assert_("message 1" in text1)
-        self.assert_("message 2" in text1)
-        self.assert_("message 3" in text2)
-        os.remove(fn)
-        os.remove(fn + "-")
 
     def test_with_stderr(self):
         self.check_standard_stream("stderr")
@@ -277,8 +280,93 @@
         return logger
 
 
+class TestReopeningLogfiles(LoggingTestBase):
+
+    # These tests should not be run on Windows.
+
+    _schematext = """
+      <schema>
+        <import package='ZConfig.components.logger'/>
+        <multisection type='logger' name='*' attribute='loggers'/>
+      </schema>
+    """
+
+    _sampleconfig_template = """
+      <logger>
+        name  foo.bar
+        <logfile>
+          path  %s
+          level debug
+        </logfile>
+        <logfile>
+          path  %s
+          level info
+        </logfile>
+      </logger>
+
+      <logger>
+        name  bar.foo
+        <logfile>
+          path  %s
+          level info
+        </logfile>
+      </logger>
+    """
+
+    def test_filehandler_reopen(self):
+
+        def mkrecord(msg):
+            return logging.LogRecord(
+                "foo.bar", logging.ERROR, __file__, 42, msg, (), ())
+
+        fn = self.mktemp()
+        h = loghandler.FileHandler(fn)
+        h.handle(mkrecord("message 1"))
+        nfn = self.move(fn)
+        h.handle(mkrecord("message 2"))
+        h.reopen()
+        h.handle(mkrecord("message 3"))
+        h.close()
+
+        # Check that the messages are in the right files::
+        text1 = open(nfn).read()
+        text2 = open(fn).read()
+        self.assert_("message 1" in text1)
+        self.assert_("message 2" in text1)
+        self.assert_("message 3" in text2)
+
+    def test_logfile_reopening(self):
+        paths = self.mktemp(), self.mktemp(), self.mktemp()
+        text = self._sampleconfig_template % paths
+        conf = self.get_config(text)
+        assert len(conf.loggers) == 2
+        # Build the loggers from the configuration, and write to them:
+        conf.loggers[0]().info("message 1")
+        conf.loggers[1]().info("message 2")
+        npaths = [self.move(fn) for fn in paths]
+        #
+        # We expect this to re-open the original files, so we'll have
+        # six files instead of three.
+        #
+        loghandler.reopenFiles()
+        #
+        # Write to them again:
+        conf.loggers[0]().info("message 3")
+        conf.loggers[1]().info("message 4")
+        #
+        # We should not have all six files:
+        for fn in paths:
+            self.assert_(os.path.isfile(fn), "%r must exist" % fn)
+        for fn in npaths:
+            self.assert_(os.path.isfile(fn), "%r must exist" % fn)
+
+
 def test_suite():
-    return unittest.makeSuite(TestConfig)
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(TestConfig))
+    if os.name != "nt":
+        suite.addTest(unittest.makeSuite(TestReopeningLogfiles))
+    return suite
 
 if __name__ == '__main__':
     unittest.main(defaultTest="test_suite")

Modified: ZConfig/trunk/doc/zconfig.pdf
===================================================================
(Binary files differ)

Modified: ZConfig/trunk/doc/zconfig.tex
===================================================================
--- ZConfig/trunk/doc/zconfig.tex	2007-06-20 18:45:27 UTC (rev 76854)
+++ ZConfig/trunk/doc/zconfig.tex	2007-06-20 19:12:32 UTC (rev 76855)
@@ -1178,8 +1178,17 @@
 method which may be called to close any log files and re-open them.
 This is useful when using a \UNIX{} signal to effect log file
 rotation: the signal handler can call this method, and not have to
-worry about what handlers have been registered for the logger.
+worry about what handlers have been registered for the logger.  There
+is also a function in the
+\module{ZConfig.components.logger.loghandler} module that re-opens all
+open log files created using ZConfig configuraiton:
 
+\begin{funcdesc}{reopenFiles}{}
+  Closes and re-opens all the log files held open by handlers created
+  by the factories for \code{logfile} sections.  This is intended to
+  help support log rotation for applications.
+\end{funcdesc}
+
 Building an application that uses the logging components is fairly
 straightforward.  The schema needs to import the relevant components
 and declare their use:



More information about the Checkins mailing list