https://github.com/python/cpython/commit/b18b6a49c6f8c2172cf2bed366302064db1298b1
commit: b18b6a49c6f8c2172cf2bed366302064db1298b1
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-06-06T11:49:07+03:00
summary:

gh-84649: Use statx() in TimedRotatingFileHandler if available (ПР-150968)

This allows to support rotation based on the file birth time on Linux.

files:
M Doc/library/logging.handlers.rst
M Doc/whatsnew/3.16.rst
M Lib/logging/handlers.py
M Lib/test/support/__init__.py
M Lib/test/test_logging.py
M Misc/NEWS.d/next/Library/2021-02-26-13-17-57.bpo-40469.yJHeQg.rst

diff --git a/Doc/library/logging.handlers.rst b/Doc/library/logging.handlers.rst
index 714db5fa12af0aa..5152c7561fa1f26 100644
--- a/Doc/library/logging.handlers.rst
+++ b/Doc/library/logging.handlers.rst
@@ -402,7 +402,8 @@ timed intervals.
    rollover interval.
 
    When computing the next rollover time for the first time (when the handler
-   is created), the last modification time of an existing log file, or else
+   is created), the creation time (if supported by the OS and file system)
+   or the last modification of an existing log file, or else
    the current time, is used to compute when the next rotation will occur.
 
    If the *utc* argument is true, times in UTC will be used; otherwise
@@ -449,6 +450,10 @@ timed intervals.
    .. versionchanged:: 3.9
       The *errors* parameter was added.
 
+   .. versionchanged:: next
+      Use the creation time instead of the last modification time, if 
supported by the OS and file system.
+
+
    .. method:: doRollover()
 
       Does a rollover, as described above.
diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst
index a055113dec0494c..5a7da26bb5b95a6 100644
--- a/Doc/whatsnew/3.16.rst
+++ b/Doc/whatsnew/3.16.rst
@@ -102,6 +102,17 @@ lzma
   requires ``lzma`` 5.4.0 or newer while RISC-V requires 5.6.0 or newer.
   (Contributed by Chien Wong in :gh:`115988`.)
 
+logging
+-------
+
+* :class:`~logging.handlers.TimedRotatingFileHandler` now uses the creation
+  time instead of the last modification time of an existing log file as
+  the basis for the first rotation after handler creation, if supported by
+  the OS and file system.
+  This allows it to be used in short-running programs that start and end
+  before the rotation interval expires.
+  (Contributed by Iván Márton and Serhiy Storchaka in :gh:`84649`.)
+
 os
 --
 
diff --git a/Lib/logging/handlers.py b/Lib/logging/handlers.py
index 73782f53041008c..a5394d2dbea6494 100644
--- a/Lib/logging/handlers.py
+++ b/Lib/logging/handlers.py
@@ -284,14 +284,28 @@ def __init__(self, filename, when='h', interval=1, 
backupCount=0,
         if os.path.exists(filename):
             # Use the minimum of file creation and modification time as
             # the base of the rollover calculation
-            stat_result = os.stat(filename)
-            # Use st_birthtime whenever it is available or use st_ctime
-            # instead otherwise
-            try:
-                creation_time = stat_result.st_birthtime
-            except AttributeError:
-                creation_time = stat_result.st_ctime
-            t = int(min(creation_time, stat_result.st_mtime))
+            creation_time = modification_time = None
+            if hasattr(os, 'statx'):
+                statx_result = os.statx(filename,
+                        os.STATX_BTIME|os.STATX_CTIME|os.STATX_MTIME)
+                # Use stx_btime whenever it is available or use stx_ctime
+                # instead otherwise
+                creation_time = statx_result.stx_btime
+                if creation_time is None:
+                    creation_time = statx_result.stx_ctime
+                modification_time = statx_result.stx_mtime
+            if creation_time is None or modification_time is None:
+                stat_result = os.stat(filename)
+                # Use st_birthtime whenever it is available or use st_ctime
+                # instead otherwise
+                if creation_time is None:
+                    try:
+                        creation_time = stat_result.st_birthtime
+                    except AttributeError:
+                        creation_time = stat_result.st_ctime
+                if modification_time is None:
+                    modification_time = stat_result.st_mtime
+            t = int(min(creation_time, modification_time))
         else:
             t = int(time.time())
         self.rolloverAt = self.computeRollover(t)
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index 84f735c1537efa7..cd85ef60a80f4bf 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -40,7 +40,6 @@
     "has_fork_support", "requires_fork",
     "has_subprocess_support", "requires_subprocess",
     "has_socket_support", "requires_working_socket",
-    "has_st_birthtime",
     "has_remote_subprocess_debugging", "requires_remote_subprocess_debugging",
     "anticipate_failure", "load_package_tests", "detect_api_mismatch",
     "check__all__", "skip_if_buggy_ucrt_strfptime",
@@ -621,10 +620,6 @@ def skip_wasi_stack_overflow():
     or is_android
 )
 
-# At the moment, st_birthtime attribute is only supported on Windows,
-# MacOS and FreeBSD.
-has_st_birthtime = sys.platform.startswith(("win", "freebsd", "darwin"))
-
 def requires_fork():
     return unittest.skipUnless(has_fork_support, "requires working os.fork()")
 
diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py
index 9f29fe8a5b3c9bc..cc2e9b782a35022 100644
--- a/Lib/test/test_logging.py
+++ b/Lib/test/test_logging.py
@@ -6652,8 +6652,8 @@ def test_rollover(self):
                     print(tf.read())
         self.assertTrue(found, msg=msg)
 
-    @unittest.skipUnless(support.has_st_birthtime,
-        "st_birthtime not available or supported by Python on this OS")
+    @unittest.skipUnless(hasattr(os.stat_result, 'st_birthtime') or 
hasattr(os, 'statx'),
+        "st_birthtime and statx() not available or supported by Python on this 
OS")
     @support.requires_resource('walltime')
     def test_rollover_based_on_st_birthtime_only(self):
         def add_record(message: str) -> None:
diff --git a/Misc/NEWS.d/next/Library/2021-02-26-13-17-57.bpo-40469.yJHeQg.rst 
b/Misc/NEWS.d/next/Library/2021-02-26-13-17-57.bpo-40469.yJHeQg.rst
index eab474dfd2ea82a..239f856bb04c0ae 100644
--- a/Misc/NEWS.d/next/Library/2021-02-26-13-17-57.bpo-40469.yJHeQg.rst
+++ b/Misc/NEWS.d/next/Library/2021-02-26-13-17-57.bpo-40469.yJHeQg.rst
@@ -1,6 +1,5 @@
-A bug has been fixed that made the ``TimedRotatingFileHandler`` use the
-MTIME attribute of the configured log file to to detect whether it has to be
-rotated yet or not. In cases when the file was changed within the rotation
-period the value of the MTIME was also updated to the current time and as a
-result the rotation never happened. The file creation time (CTIME) is used
-instead that makes the rotation file modification independent.
+:class:`~logging.handlers.TimedRotatingFileHandler` now uses the creation time
+instead of the last modification time of an existing log file as the basis
+for the first rotation after handler creation, if supported by the OS and file 
system.
+This allows it to be used in short-running programs that start and end before
+the rotation interval expires.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to