Skip to content

Commit b18b6a4

Browse files
gh-84649: Use statx() in TimedRotatingFileHandler if available (ПР-150968)
This allows to support rotation based on the file birth time on Linux.
1 parent 97dea30 commit b18b6a4

6 files changed

Lines changed: 46 additions & 22 deletions

File tree

Doc/library/logging.handlers.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,8 @@ timed intervals.
402402
rollover interval.
403403

404404
When computing the next rollover time for the first time (when the handler
405-
is created), the last modification time of an existing log file, or else
405+
is created), the creation time (if supported by the OS and file system)
406+
or the last modification of an existing log file, or else
406407
the current time, is used to compute when the next rotation will occur.
407408

408409
If the *utc* argument is true, times in UTC will be used; otherwise
@@ -449,6 +450,10 @@ timed intervals.
449450
.. versionchanged:: 3.9
450451
The *errors* parameter was added.
451452

453+
.. versionchanged:: next
454+
Use the creation time instead of the last modification time, if supported by the OS and file system.
455+
456+
452457
.. method:: doRollover()
453458

454459
Does a rollover, as described above.

Doc/whatsnew/3.16.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,17 @@ lzma
102102
requires ``lzma`` 5.4.0 or newer while RISC-V requires 5.6.0 or newer.
103103
(Contributed by Chien Wong in :gh:`115988`.)
104104

105+
logging
106+
-------
107+
108+
* :class:`~logging.handlers.TimedRotatingFileHandler` now uses the creation
109+
time instead of the last modification time of an existing log file as
110+
the basis for the first rotation after handler creation, if supported by
111+
the OS and file system.
112+
This allows it to be used in short-running programs that start and end
113+
before the rotation interval expires.
114+
(Contributed by Iván Márton and Serhiy Storchaka in :gh:`84649`.)
115+
105116
os
106117
--
107118

Lib/logging/handlers.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -284,14 +284,28 @@ def __init__(self, filename, when='h', interval=1, backupCount=0,
284284
if os.path.exists(filename):
285285
# Use the minimum of file creation and modification time as
286286
# the base of the rollover calculation
287-
stat_result = os.stat(filename)
288-
# Use st_birthtime whenever it is available or use st_ctime
289-
# instead otherwise
290-
try:
291-
creation_time = stat_result.st_birthtime
292-
except AttributeError:
293-
creation_time = stat_result.st_ctime
294-
t = int(min(creation_time, stat_result.st_mtime))
287+
creation_time = modification_time = None
288+
if hasattr(os, 'statx'):
289+
statx_result = os.statx(filename,
290+
os.STATX_BTIME|os.STATX_CTIME|os.STATX_MTIME)
291+
# Use stx_btime whenever it is available or use stx_ctime
292+
# instead otherwise
293+
creation_time = statx_result.stx_btime
294+
if creation_time is None:
295+
creation_time = statx_result.stx_ctime
296+
modification_time = statx_result.stx_mtime
297+
if creation_time is None or modification_time is None:
298+
stat_result = os.stat(filename)
299+
# Use st_birthtime whenever it is available or use st_ctime
300+
# instead otherwise
301+
if creation_time is None:
302+
try:
303+
creation_time = stat_result.st_birthtime
304+
except AttributeError:
305+
creation_time = stat_result.st_ctime
306+
if modification_time is None:
307+
modification_time = stat_result.st_mtime
308+
t = int(min(creation_time, modification_time))
295309
else:
296310
t = int(time.time())
297311
self.rolloverAt = self.computeRollover(t)

Lib/test/support/__init__.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
"has_fork_support", "requires_fork",
4141
"has_subprocess_support", "requires_subprocess",
4242
"has_socket_support", "requires_working_socket",
43-
"has_st_birthtime",
4443
"has_remote_subprocess_debugging", "requires_remote_subprocess_debugging",
4544
"anticipate_failure", "load_package_tests", "detect_api_mismatch",
4645
"check__all__", "skip_if_buggy_ucrt_strfptime",
@@ -621,10 +620,6 @@ def skip_wasi_stack_overflow():
621620
or is_android
622621
)
623622

624-
# At the moment, st_birthtime attribute is only supported on Windows,
625-
# MacOS and FreeBSD.
626-
has_st_birthtime = sys.platform.startswith(("win", "freebsd", "darwin"))
627-
628623
def requires_fork():
629624
return unittest.skipUnless(has_fork_support, "requires working os.fork()")
630625

Lib/test/test_logging.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6652,8 +6652,8 @@ def test_rollover(self):
66526652
print(tf.read())
66536653
self.assertTrue(found, msg=msg)
66546654

6655-
@unittest.skipUnless(support.has_st_birthtime,
6656-
"st_birthtime not available or supported by Python on this OS")
6655+
@unittest.skipUnless(hasattr(os.stat_result, 'st_birthtime') or hasattr(os, 'statx'),
6656+
"st_birthtime and statx() not available or supported by Python on this OS")
66576657
@support.requires_resource('walltime')
66586658
def test_rollover_based_on_st_birthtime_only(self):
66596659
def add_record(message: str) -> None:
Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
A bug has been fixed that made the ``TimedRotatingFileHandler`` use the
2-
MTIME attribute of the configured log file to to detect whether it has to be
3-
rotated yet or not. In cases when the file was changed within the rotation
4-
period the value of the MTIME was also updated to the current time and as a
5-
result the rotation never happened. The file creation time (CTIME) is used
6-
instead that makes the rotation file modification independent.
1+
:class:`~logging.handlers.TimedRotatingFileHandler` now uses the creation time
2+
instead of the last modification time of an existing log file as the basis
3+
for the first rotation after handler creation, if supported by the OS and file system.
4+
This allows it to be used in short-running programs that start and end before
5+
the rotation interval expires.

0 commit comments

Comments
 (0)