[Checkins] SVN: ZConfig/trunk/ZConfig/components/logger/ add support for basic rotating log files

Fred L. Drake, Jr. fdrake at gmail.com
Tue Jul 17 12:23:34 EDT 2007


Log message for revision 78079:
  add support for basic rotating log files
  (rotating by size, not time-based scheduling)
  

Changed:
  U   ZConfig/trunk/ZConfig/components/logger/handlers.py
  U   ZConfig/trunk/ZConfig/components/logger/handlers.xml
  U   ZConfig/trunk/ZConfig/components/logger/loghandler.py
  U   ZConfig/trunk/ZConfig/components/logger/tests/test_logger.py

-=-
Modified: ZConfig/trunk/ZConfig/components/logger/handlers.py
===================================================================
--- ZConfig/trunk/ZConfig/components/logger/handlers.py	2007-07-17 15:53:01 UTC (rev 78078)
+++ ZConfig/trunk/ZConfig/components/logger/handlers.py	2007-07-17 16:23:34 UTC (rev 78079)
@@ -95,10 +95,23 @@
     def create_loghandler(self):
         from ZConfig.components.logger import loghandler
         path = self.section.path
+        max_bytes = self.section.max_size
+        old_files = self.section.old_files
         if path == "STDERR":
+            if max_bytes or old_files:
+                raise ValueError("cannot rotate STDERR")
             handler = loghandler.StreamHandler(sys.stderr)
         elif path == "STDOUT":
+            if max_bytes or old_files:
+                raise ValueError("cannot rotate STDOUT")
             handler = loghandler.StreamHandler(sys.stdout)
+        elif max_bytes or old_files:
+            if not max_bytes:
+                raise ValueError("max-bytes must be set for log rotation")
+            if not old_files:
+                raise ValueError("old-files must be set for log rotation")
+            handler = loghandler.RotatingFileHandler(
+                path, maxBytes=max_bytes, backupCount=old_files)
         else:
             handler = loghandler.FileHandler(path)
         return handler

Modified: ZConfig/trunk/ZConfig/components/logger/handlers.xml
===================================================================
--- ZConfig/trunk/ZConfig/components/logger/handlers.xml	2007-07-17 15:53:01 UTC (rev 78078)
+++ ZConfig/trunk/ZConfig/components/logger/handlers.xml	2007-07-17 16:23:34 UTC (rev 78079)
@@ -30,6 +30,8 @@
                implements="ZConfig.logger.handler"
                extends="ZConfig.logger.base-log-handler">
     <key name="path" required="yes"/>
+    <key name="old-files" required="no" default="0" datatype="integer"/>
+    <key name="max-size" required="no" default="0" datatype="byte-size"/>
     <key name="format"
          default="------\n%(asctime)s %(levelname)s %(name)s %(message)s"
          datatype=".log_format"/>

Modified: ZConfig/trunk/ZConfig/components/logger/loghandler.py
===================================================================
--- ZConfig/trunk/ZConfig/components/logger/loghandler.py	2007-07-17 15:53:01 UTC (rev 78078)
+++ ZConfig/trunk/ZConfig/components/logger/loghandler.py	2007-07-17 16:23:34 UTC (rev 78079)
@@ -18,13 +18,23 @@
 import weakref
 
 from logging import Handler, StreamHandler
+from logging.handlers import RotatingFileHandler as _RotatingFileHandler
 from logging.handlers import SysLogHandler, BufferingHandler
 from logging.handlers import HTTPHandler, SMTPHandler
 from logging.handlers import NTEventLogHandler as Win32EventLogHandler
 
 
+
 _reopenable_handlers = []
 
+def closeFiles():
+    """Reopen all logfiles managed by ZConfig configuration."""
+    while _reopenable_handlers:
+        wr = _reopenable_handlers.pop()
+        h = wr()
+        if h is not None:
+            h.close()
+
 def reopenFiles():
     """Reopen all logfiles managed by ZConfig configuration."""
     for wr in _reopenable_handlers[:]:
@@ -99,6 +109,21 @@
     FileHandler = Win32FileHandler
 
 
+class RotatingFileHandler(_RotatingFileHandler):
+
+    def __init__(self, *args, **kw):
+        _RotatingFileHandler.__init__(self, *args, **kw)
+        self._wr = weakref.ref(self, _remove_from_reopenable)
+        _reopenable_handlers.append(self._wr)
+
+    def close(self):
+        _RotatingFileHandler.close(self)
+        _remove_from_reopenable(self._wr)
+
+    def reopen(self):
+        self.doRollover()
+
+
 class NullHandler(Handler):
     """Handler that does nothing."""
 

Modified: ZConfig/trunk/ZConfig/components/logger/tests/test_logger.py
===================================================================
--- ZConfig/trunk/ZConfig/components/logger/tests/test_logger.py	2007-07-17 15:53:01 UTC (rev 78078)
+++ ZConfig/trunk/ZConfig/components/logger/tests/test_logger.py	2007-07-17 16:23:34 UTC (rev 78079)
@@ -69,6 +69,10 @@
         while self._created:
             os.unlink(self._created.pop())
 
+        assert loghandler._reopenable_handlers == []
+        loghandler.closeFiles()
+        loghandler._reopenable_handlers == []
+
     def mktemp(self):
         fd, fn = tempfile.mkstemp()
         os.close(fd)
@@ -147,7 +151,6 @@
                                 loghandler.NullHandler))
 
     def test_with_logfile(self):
-        import os
         fn = self.mktemp()
         logger = self.check_simple_logger("<eventlog>\n"
                                           "  <logfile>\n"
@@ -158,6 +161,8 @@
         logfile = logger.handlers[0]
         self.assertEqual(logfile.level, logging.DEBUG)
         self.assert_(isinstance(logfile, loghandler.FileHandler))
+        logger.removeHandler(logfile)
+        logfile.close()
 
     def test_with_stderr(self):
         self.check_standard_stream("stderr")
@@ -165,6 +170,24 @@
     def test_with_stdout(self):
         self.check_standard_stream("stdout")
 
+    def test_with_rotating_logfile(self):
+        fn = self.mktemp()
+        logger = self.check_simple_logger("<eventlog>\n"
+                                          "  <logfile>\n"
+                                          "    path %s\n"
+                                          "    level debug\n"
+                                          "    max-size 5mb\n"
+                                          "    old-files 10\n"
+                                          "  </logfile>\n"
+                                          "</eventlog>" % fn)
+        logfile = logger.handlers[0]
+        self.assertEqual(logfile.level, logging.DEBUG)
+        self.assertEqual(logfile.backupCount, 10)
+        self.assertEqual(logfile.maxBytes, 5*1024*1024)
+        self.assert_(isinstance(logfile, loghandler.RotatingFileHandler))
+        logger.removeHandler(logfile)
+        logfile.close()
+
     def check_standard_stream(self, name):
         old_stream = getattr(sys, name)
         conf = self.get_config("""
@@ -280,7 +303,7 @@
         return logger
 
 
-class TestReopeningLogfiles(LoggingTestBase):
+class TestReopeningLogfilesBase(LoggingTestBase):
 
     # These tests should not be run on Windows.
 
@@ -291,28 +314,6 @@
       </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):
@@ -324,7 +325,7 @@
         # time around.
 
         fn = self.mktemp()
-        h = loghandler.FileHandler(fn)
+        h = self.handler_factory(fn)
         h.handle(mkrecord("message 1"))
         nfn1 = self.move(fn)
         h.handle(mkrecord("message 2"))
@@ -346,9 +347,44 @@
         self.assert_("message 4" in text2)
         self.assert_("message 5" in text3)
 
+class TestReopeningLogfiles(TestReopeningLogfilesBase):
+
+    handler_factory = loghandler.FileHandler
+
+    _sampleconfig_template = """
+      <logger>
+        name  foo.bar
+        <logfile>
+          path  %(path0)s
+          level debug
+        </logfile>
+        <logfile>
+          path  %(path1)s
+          level info
+        </logfile>
+      </logger>
+
+      <logger>
+        name  bar.foo
+        <logfile>
+          path  %(path2)s
+          level info
+        </logfile>
+      </logger>
+    """
+
     def test_logfile_reopening(self):
+        #
+        # This test only applies to the simple logfile reopening; it
+        # doesn't work the same way as the rotating logfile handler.
+        #
         paths = self.mktemp(), self.mktemp(), self.mktemp()
-        text = self._sampleconfig_template % paths
+        d = {
+            "path0": paths[0],
+            "path1": paths[1],
+            "path2": paths[2],
+            }
+        text = self._sampleconfig_template % d
         conf = self.get_config(text)
         assert len(conf.loggers) == 2
         # Build the loggers from the configuration, and write to them:
@@ -382,13 +418,105 @@
             self.assert_(os.path.isfile(fn), "%r must exist" % fn)
         for fn in npaths2:
             self.assert_(os.path.isfile(fn), "%r must exist" % fn)
+        #
+        # Clean up:
+        for logger in conf.loggers:
+            logger = logger()
+            for handler in logger.handlers[:]:
+                logger.removeHandler(handler)
+                handler.close()
 
 
+class TestReopeningRotatingLogfiles(TestReopeningLogfilesBase):
+
+    _sampleconfig_template = """
+      <logger>
+        name  foo.bar
+        <logfile>
+          path  %(path0)s
+          level debug
+          max-size 1mb
+          old-files 10
+        </logfile>
+        <logfile>
+          path  %(path1)s
+          level info
+          max-size 1mb
+          old-files 3
+        </logfile>
+      </logger>
+
+      <logger>
+        name  bar.foo
+        <logfile>
+          path  %(path2)s
+          level info
+          max-size 10mb
+          old-files 10
+        </logfile>
+      </logger>
+    """
+
+    handler_factory = loghandler.RotatingFileHandler
+
+    def test_logfile_reopening(self):
+        #
+        # This test only applies to the simple logfile reopening; it
+        # doesn't work the same way as the rotating logfile handler.
+        #
+        paths = self.mktemp(), self.mktemp(), self.mktemp()
+        d = {
+            "path0": paths[0],
+            "path1": paths[1],
+            "path2": paths[2],
+            }
+        text = self._sampleconfig_template % d
+        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")
+        #
+        # We expect this to re-open the original filenames, 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 expect this to re-open the original filenames, so we'll
+        # have nine files instead of six.
+        #
+        loghandler.reopenFiles()
+        #
+        # Write to them again:
+        conf.loggers[0]().info("message 5")
+        conf.loggers[1]().info("message 6")
+        #
+        # We should now have all nine files:
+        for fn in paths:
+            fn1 = fn + ".1"
+            fn2 = fn + ".2"
+            self.assert_(os.path.isfile(fn), "%r must exist" % fn)
+            self.assert_(os.path.isfile(fn1), "%r must exist" % fn1)
+            self.assert_(os.path.isfile(fn2), "%r must exist" % fn2)
+        #
+        # Clean up:
+        for logger in conf.loggers:
+            logger = logger()
+            for handler in logger.handlers[:]:
+                logger.removeHandler(handler)
+                handler.close()
+
+
 def test_suite():
     suite = unittest.TestSuite()
     suite.addTest(unittest.makeSuite(TestConfig))
     if os.name != "nt":
         suite.addTest(unittest.makeSuite(TestReopeningLogfiles))
+    suite.addTest(unittest.makeSuite(TestReopeningRotatingLogfiles))
     return suite
 
 if __name__ == '__main__':



More information about the Checkins mailing list