[Zconfig] 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 ZConfig
mailing list