Skip to content

Commit 9423422

Browse files
[3.9] bpo-46063: Improve algorithm for computing which rolled-over log file… (GH-30093) (GH-30095)
1 parent 9b68759 commit 9423422

File tree

3 files changed

+83
-5
lines changed

3 files changed

+83
-5
lines changed

Doc/library/logging.handlers.rst

+17
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,19 @@ need to override.
231231
return the same output every time for a given input, otherwise the
232232
rollover behaviour may not work as expected.
233233

234+
It's also worth noting that care should be taken when using a namer to
235+
preserve certain attributes in the filename which are used during rotation.
236+
For example, :class:`RotatingFileHandler` expects to have a set of log files
237+
whose names contain successive integers, so that rotation works as expected,
238+
and :class:`TimedRotatingFileHandler` deletes old log files (based on the
239+
``backupCount`` parameter passed to the handler's initializer) by determining
240+
the oldest files to delete. For this to happen, the filenames should be
241+
sortable using the date/time portion of the filename, and a namer needs to
242+
respect this. (If a namer is wanted that doesn't respect this scheme, it will
243+
need to be used in a subclass of :class:`TimedRotatingFileHandler` which
244+
overrides the :meth:`~TimedRotatingFileHandler.getFilesToDelete` method to
245+
fit in with the custom naming scheme.)
246+
234247
.. versionadded:: 3.3
235248

236249

@@ -440,6 +453,10 @@ timed intervals.
440453

441454
Outputs the record to the file, catering for rollover as described above.
442455

456+
.. method:: getFilesToDelete()
457+
458+
Returns a list of filenames which should be deleted as part of rollover. These
459+
are the absolute paths of the oldest backup log files written by the handler.
443460

444461
.. _socket-handler:
445462

Lib/logging/handlers.py

+16-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2001-2016 by Vinay Sajip. All Rights Reserved.
1+
# Copyright 2001-2021 by Vinay Sajip. All Rights Reserved.
22
#
33
# Permission to use, copy, modify, and distribute this software and its
44
# documentation for any purpose and without fee is hereby granted,
@@ -18,7 +18,7 @@
1818
Additional handlers for the logging package for Python. The core package is
1919
based on PEP 282 and comments thereto in comp.lang.python.
2020
21-
Copyright (C) 2001-2016 Vinay Sajip. All Rights Reserved.
21+
Copyright (C) 2001-2021 Vinay Sajip. All Rights Reserved.
2222
2323
To use, simply 'import logging.handlers' and log away!
2424
"""
@@ -363,9 +363,22 @@ def getFilesToDelete(self):
363363
fileNames = os.listdir(dirName)
364364
result = []
365365
# See bpo-44753: Don't use the extension when computing the prefix.
366-
prefix = os.path.splitext(baseName)[0] + "."
366+
n, e = os.path.splitext(baseName)
367+
prefix = n + '.'
367368
plen = len(prefix)
368369
for fileName in fileNames:
370+
if self.namer is None:
371+
# Our files will always start with baseName
372+
if not fileName.startswith(baseName):
373+
continue
374+
else:
375+
# Our files could be just about anything after custom naming, but
376+
# likely candidates are of the form
377+
# foo.log.DATETIME_SUFFIX or foo.DATETIME_SUFFIX.log
378+
if (not fileName.startswith(baseName) and fileName.endswith(e) and
379+
len(fileName) > (plen + 1) and not fileName[plen+1].isdigit()):
380+
continue
381+
369382
if fileName[:plen] == prefix:
370383
suffix = fileName[plen:]
371384
# See bpo-45628: The date/time suffix could be anywhere in the

Lib/test/test_logging.py

+50-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2001-2019 by Vinay Sajip. All Rights Reserved.
1+
# Copyright 2001-2021 by Vinay Sajip. All Rights Reserved.
22
#
33
# Permission to use, copy, modify, and distribute this software and its
44
# documentation for any purpose and without fee is hereby granted,
@@ -16,7 +16,7 @@
1616

1717
"""Test harness for the logging module. Run all tests.
1818
19-
Copyright (C) 2001-2019 Vinay Sajip. All Rights Reserved.
19+
Copyright (C) 2001-2021 Vinay Sajip. All Rights Reserved.
2020
"""
2121

2222
import logging
@@ -36,6 +36,7 @@
3636
import queue
3737
import random
3838
import re
39+
import shutil
3940
import socket
4041
import struct
4142
import sys
@@ -5284,6 +5285,53 @@ def test_compute_rollover_weekly_attime(self):
52845285
finally:
52855286
rh.close()
52865287

5288+
def test_compute_files_to_delete(self):
5289+
# See bpo-46063 for background
5290+
wd = tempfile.mkdtemp(prefix='test_logging_')
5291+
self.addCleanup(shutil.rmtree, wd)
5292+
times = []
5293+
dt = datetime.datetime.now()
5294+
for i in range(10):
5295+
times.append(dt.strftime('%Y-%m-%d_%H-%M-%S'))
5296+
dt += datetime.timedelta(seconds=5)
5297+
prefixes = ('a.b', 'a.b.c', 'd.e', 'd.e.f')
5298+
files = []
5299+
rotators = []
5300+
for prefix in prefixes:
5301+
p = os.path.join(wd, '%s.log' % prefix)
5302+
rotator = logging.handlers.TimedRotatingFileHandler(p, when='s',
5303+
interval=5,
5304+
backupCount=7)
5305+
rotators.append(rotator)
5306+
if prefix.startswith('a.b'):
5307+
for t in times:
5308+
files.append('%s.log.%s' % (prefix, t))
5309+
else:
5310+
rotator.namer = lambda name: name.replace('.log', '') + '.log'
5311+
for t in times:
5312+
files.append('%s.%s.log' % (prefix, t))
5313+
# Create empty files
5314+
for fn in files:
5315+
p = os.path.join(wd, fn)
5316+
with open(p, 'wb') as f:
5317+
pass
5318+
# Now the checks that only the correct files are offered up for deletion
5319+
for i, prefix in enumerate(prefixes):
5320+
rotator = rotators[i]
5321+
candidates = rotator.getFilesToDelete()
5322+
self.assertEqual(len(candidates), 3)
5323+
if prefix.startswith('a.b'):
5324+
p = '%s.log.' % prefix
5325+
for c in candidates:
5326+
d, fn = os.path.split(c)
5327+
self.assertTrue(fn.startswith(p))
5328+
else:
5329+
for c in candidates:
5330+
d, fn = os.path.split(c)
5331+
self.assertTrue(fn.endswith('.log'))
5332+
self.assertTrue(fn.startswith(prefix + '.') and
5333+
fn[len(prefix) + 2].isdigit())
5334+
52875335

52885336
def secs(**kw):
52895337
return datetime.timedelta(**kw) // datetime.timedelta(seconds=1)

0 commit comments

Comments
 (0)