From: Serhiy Storchaka Date: Sat, 6 Jun 2026 08:49:07 +0000 (+0300) Subject: gh-84649: Use statx() in TimedRotatingFileHandler if available (ПР-150968) X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=b18b6a49c6f8c2172cf2bed366302064db1298b1;p=thirdparty%2FPython%2Fcpython.git gh-84649: Use statx() in TimedRotatingFileHandler if available (ПР-150968) This allows to support rotation based on the file birth time on Linux. --- diff --git a/Doc/library/logging.handlers.rst b/Doc/library/logging.handlers.rst index 714db5fa12af..5152c7561fa1 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 a055113dec04..5a7da26bb5b9 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 73782f530410..a5394d2dbea6 100644 --- a/Lib/logging/handlers.py +++ b/Lib/logging/handlers.py @@ -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) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 84f735c1537e..cd85ef60a80f 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -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()") diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 9f29fe8a5b3c..cc2e9b782a35 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -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: 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 eab474dfd2ea..239f856bb04c 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.