Bläddra i källkod

Merge branch 'fix-log-rotation-for-shared-files' of https://github.com/whitmo/supervisor into whitmo-fix-log-rotation-for-shared-files

Mike Naberezny 13 år sedan
förälder
incheckning
fc082ba54b
2 ändrade filer med 67 tillägg och 3 borttagningar
  1. 31 3
      supervisor/loggers.py
  2. 36 0
      supervisor/tests/test_loggers.py

+ 31 - 3
supervisor/loggers.py

@@ -44,7 +44,7 @@ def getLevelNumByDescription(description):
     num = getattr(LevelsByDescription, description, None)
     return num
 
-class Handler:
+class Handler(object):
     fmt = '%(message)s'
     level = LevelsByName.INFO
     def setFormat(self, fmt):
@@ -87,7 +87,6 @@ class Handler:
 class FileHandler(Handler):
     """File handler which supports reopening of logs.
     """
-
     def __init__(self, filename, mode="a"):
         self.stream = open(filename, mode)
         self.baseFilename = filename
@@ -139,6 +138,9 @@ class BoundIO:
         self.buf = ''
             
 class RotatingFileHandler(FileHandler):
+
+    open_streams = {}
+
     def __init__(self, filename, mode='a', maxBytes=512*1024*1024,
                  backupCount=10):
         """
@@ -163,12 +165,38 @@ class RotatingFileHandler(FileHandler):
         """
         if maxBytes > 0:
             mode = 'a' # doesn't make sense otherwise!
-        FileHandler.__init__(self, filename, mode)
+        self.mode = mode
+        self.baseFilename = filename
+        self.stream = self.stream or open(filename, mode)
+
         self.maxBytes = maxBytes
         self.backupCount = backupCount
         self.counter = 0
         self.every = 10
 
+    class _stream(object):
+        """
+        Descriptor for managing open filehandles so that only one
+        filehandle per file path ever receives logging.
+        """
+        def __get__(self, obj, objtype):
+            """
+            Return open filehandle or None
+            """
+            return objtype.open_streams.get(obj.baseFilename)
+
+        def __set__(self, obj, stream):
+            """
+            Set open filehandle for filename defined on the
+            RotatingFileHandler
+            """
+            obj.open_streams[obj.baseFilename] = stream        
+
+    stream = _stream()
+
+    def close(self):
+        if self.stream: self.stream.close()
+
     def emit(self, record):
         """
         Emit a record.

+ 36 - 0
supervisor/tests/test_loggers.py

@@ -124,6 +124,7 @@ class FileHandlerTests(HandlerTests, unittest.TestCase):
                         dummy_stderr.written)
 
 class RotatingFileHandlerTests(FileHandlerTests):
+    
     def _getTargetClass(self):
         from supervisor.loggers import RotatingFileHandler
         return RotatingFileHandler
@@ -134,6 +135,41 @@ class RotatingFileHandlerTests(FileHandlerTests):
         self.assertEqual(handler.maxBytes, 512*1024*1024)
         self.assertEqual(handler.backupCount, 10)
 
+    def test_emit_tracks_correct_file_for_multiple_handlers(self):
+        """
+        Rollovers should roll for all handlers of the same file.
+        
+        When more than one process logs to a singlefile, we want to
+        make sure that files get rotated properly.
+
+        When the file rotates, all handlers should start writing to
+        the file specified by handler.baseFilename.
+        """
+        handler1 = self._makeOne(self.filename, maxBytes=10, backupCount=2)
+        handler2 = self._makeOne(self.filename, maxBytes=10, backupCount=2)
+        record = self._makeLogRecord('a' * 4)
+        handler1.emit(record) #4 bytes
+        handler2.emit(record) #8 bytes
+        self.assertFalse(os.path.exists(self.filename + '.1'))
+        handler1.emit(record) #12 bytes
+        self.assertTrue(os.path.exists(self.filename + '.1'))
+        self.assertTrue(handler1.stream == handler2.stream)
+        new_record = self._makeLogRecord("NEW") 
+        handler2.emit(new_record) 
+        self.assertTrue(open(self.filename).read().endswith("NEW"))
+        handler1.emit(record)
+        self.assertTrue(open(self.filename).read().endswith("aaaa"))
+        handler2.emit(new_record)
+        self.assertTrue(open(self.filename).read().endswith(""))
+
+    def test_reopen_raises(self):
+        handler = self._makeOne(self.filename)
+        stream = DummyStream()
+        handler.baseFilename = os.path.join(self.basedir, 'notthere', 'a.log')
+        handler.open_streams[handler.baseFilename] = stream
+        self.assertRaises(IOError, handler.reopen)
+        self.assertEqual(stream.closed, True)
+
     def test_emit_does_rollover(self):
         handler = self._makeOne(self.filename, maxBytes=10, backupCount=2)
         record = self._makeLogRecord('a' * 4)