diff --git a/src/supervisor/loggers.py b/src/supervisor/loggers.py index 7042a7e26..0f2009535 100644 --- a/src/supervisor/loggers.py +++ b/src/supervisor/loggers.py @@ -58,7 +58,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): @@ -151,8 +151,15 @@ def getvalue(self): def clear(self): self.buf = '' + + + + class RotatingFileHandler(FileHandler): + + open_streams = {} + def __init__(self, filename, mode='a', maxBytes=512*1024*1024, backupCount=10): """ @@ -177,12 +184,39 @@ def __init__(self, filename, mode='a', maxBytes=512*1024*1024, """ 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. diff --git a/src/supervisor/tests/test_loggers.py b/src/supervisor/tests/test_loggers.py index 8766655d6..fcbe60232 100644 --- a/src/supervisor/tests/test_loggers.py +++ b/src/supervisor/tests/test_loggers.py @@ -134,6 +134,39 @@ def test_ctor(self): self.assertEqual(handler.maxBytes, 512*1024*1024) self.assertEqual(handler.backupCount, 10) + def test_emit_tracks_correct_file_for_multiple_handlers(self): + """ + 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)