From 81719a9dfd2379b36c1ac296ffed0f6c6a315000 Mon Sep 17 00:00:00 2001 From: lazerg Date: Mon, 19 Oct 2026 07:38:23 +0500 Subject: [PATCH 1/2] gh-159690: Fix TimedRotatingFileHandler stopping rollovers after a failed or skipped rollover --- Lib/logging/handlers.py | 2 +- Lib/test/test_logging.py | 26 +++++++++++++++++++ ...-10-19-02-38-17.gh-issue-159690.d1hW68.rst | 2 ++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-19-02-38-17.gh-issue-159690.d1hW68.rst diff --git a/Lib/logging/handlers.py b/Lib/logging/handlers.py index e225a21da408fad..9d31cd4bc3a1018 100644 --- a/Lib/logging/handlers.py +++ b/Lib/logging/handlers.py @@ -469,6 +469,7 @@ def doRollover(self): timeTuple = time.localtime(t + addend) dfn = self.rotation_filename(self.baseFilename + "." + time.strftime(self.suffix, timeTuple)) + self.rolloverAt = self.computeRollover(currentTime) if os.path.exists(dfn): # Already rolled over. return @@ -482,7 +483,6 @@ def doRollover(self): os.remove(s) if not self.delay: self.stream = self._open() - self.rolloverAt = self.computeRollover(currentTime) class WatchedFileHandler(logging.FileHandler): """ diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index b1852ee43b594fa..d53de2f3db4f000 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -6902,6 +6902,32 @@ def test_rollover_at_midnight(self, weekly=False): def test_rollover_at_weekday(self): self.test_rollover_at_midnight(weekly=True) + def test_rollover_backup_exists(self): + fh = logging.handlers.TimedRotatingFileHandler( + self.fn, 'S', encoding="utf-8", delay=True, utc=True) + self.addCleanup(fh.close) + rolloverAt = fh.rolloverAt + dfn = self.fn + time.strftime(".%Y-%m-%d_%H-%M-%S", + time.gmtime(rolloverAt - 1)) + open(dfn, 'w').close() + self.rmfiles.append(dfn) + with patch('time.time', return_value=rolloverAt): + fh.doRollover() + self.assertEqual(fh.rolloverAt, rolloverAt + 1) + + def test_rollover_error(self): + fh = logging.handlers.TimedRotatingFileHandler( + self.fn, 'S', encoding="utf-8", utc=True) + self.addCleanup(fh.close) + rolloverAt = fh.rolloverAt + dfn = self.fn + time.strftime(".%Y-%m-%d_%H-%M-%S", + time.gmtime(rolloverAt - 1)) + self.rmfiles.append(dfn) + with (patch('time.time', return_value=rolloverAt), + patch.object(fh, '_open', side_effect=OSError)): + self.assertRaises(OSError, fh.doRollover) + self.assertEqual(fh.rolloverAt, rolloverAt + 1) + def test_invalid(self): assertRaises = self.assertRaises assertRaises(ValueError, logging.handlers.TimedRotatingFileHandler, diff --git a/Misc/NEWS.d/next/Library/2026-10-19-02-38-17.gh-issue-159690.d1hW68.rst b/Misc/NEWS.d/next/Library/2026-10-19-02-38-17.gh-issue-159690.d1hW68.rst new file mode 100644 index 000000000000000..8da1aaf178a50f0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-19-02-38-17.gh-issue-159690.d1hW68.rst @@ -0,0 +1,2 @@ +Fix :class:`logging.handlers.TimedRotatingFileHandler` so that it no longer +stops rolling over when a rollover fails or its backup file already exists. From 0958ee08f81dcd83a53f23ebac4b36a3f2d3dd1c Mon Sep 17 00:00:00 2001 From: lazerg Date: Mon, 19 Oct 2026 07:43:05 +0500 Subject: [PATCH 2/2] Use os_helper.create_empty_file() in the test --- Lib/test/test_logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index d53de2f3db4f000..d94a79b3c5dd155 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -6909,7 +6909,7 @@ def test_rollover_backup_exists(self): rolloverAt = fh.rolloverAt dfn = self.fn + time.strftime(".%Y-%m-%d_%H-%M-%S", time.gmtime(rolloverAt - 1)) - open(dfn, 'w').close() + os_helper.create_empty_file(dfn) self.rmfiles.append(dfn) with patch('time.time', return_value=rolloverAt): fh.doRollover()