]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-84649: Use statx() in TimedRotatingFileHandler if available (ПР-150968)
authorSerhiy Storchaka <storchaka@gmail.com>
Sat, 6 Jun 2026 08:49:07 +0000 (11:49 +0300)
committerGitHub <noreply@github.com>
Sat, 6 Jun 2026 08:49:07 +0000 (11:49 +0300)
This allows to support rotation based on the file birth time on Linux.

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

index 714db5fa12af0aaa099e30e96571db7d929ea492..5152c7561fa1f26c9981785aae25c622e88be0c1 100644 (file)
@@ -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.
index a055113dec0494cec35dc1771dc0aefc9d041923..5a7da26bb5b95a6ce00af12bc9c4ab76ee63d478 100644 (file)
@@ -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
 --
 
index 73782f53041008c19554360aea07c31f7d8ee814..a5394d2dbea6494d58d7855d2649e1d5c52eb45f 100644 (file)
@@ -284,14 +284,28 @@ class TimedRotatingFileHandler(BaseRotatingHandler):
         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)
index 84f735c1537efa798df8af0b59de5a7658e47567..cd85ef60a80f4bff8ae4ba52d0d506188a3d68aa 100644 (file)
@@ -40,7 +40,6 @@ __all__ = [
     "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 @@ has_fork_support = hasattr(os, "fork") and not (
     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()")
 
index 9f29fe8a5b3c9bcc8561ee5b70c80ae455f31ff3..cc2e9b782a3502265080cec7640df7d00c4fdb7e 100644 (file)
@@ -6652,8 +6652,8 @@ class TimedRotatingFileHandlerTest(BaseFileTest):
                     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:
index eab474dfd2ea82a1d169dd17a60d2112cc5e1265..239f856bb04c0ae6d3e3f7a76207f097a3f4f369 100644 (file)
@@ -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.