This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new fd7d535c3ff Prevent scheduler crash when process/thread are missing
from log format (#69402)
fd7d535c3ff is described below
commit fd7d535c3ff5d3618c2a4a83a634fdec09c911e9
Author: Anas Khan <[email protected]>
AuthorDate: Mon Jul 13 02:56:03 2026 +0530
Prevent scheduler crash when process/thread are missing from log format
(#69402)
When a record reaches the percent formatter without callsite information,
for example a stdlib warning routed through the logging bridge, the process
and thread fields are absent. The formatter fell back to the "(unknown)"
string for them, so a format string using the numeric "%(process)d" or
"%(thread)d" specifiers raised "TypeError: %d format: a real number is
required" and could take down the scheduler at startup.
Give those two numeric callsite parameters a numeric fallback of 0, the same
way lineno is already handled, so the format never receives a string where a
number is expected.
---
.../src/airflow_shared/logging/percent_formatter.py | 6 ++++++
shared/logging/tests/logging/test_percent_formatter.py | 18 ++++++++++++++++++
2 files changed, 24 insertions(+)
diff --git a/shared/logging/src/airflow_shared/logging/percent_formatter.py
b/shared/logging/src/airflow_shared/logging/percent_formatter.py
index ee9195e59b5..0efbb78d992 100644
--- a/shared/logging/src/airflow_shared/logging/percent_formatter.py
+++ b/shared/logging/src/airflow_shared/logging/percent_formatter.py
@@ -55,6 +55,12 @@ class _LazyLogRecordDict(collections.abc.Mapping):
#
https://github.com/python/cpython/blob/d3c888b4ec15dbd7d6b6ef4f15b558af77c228af/Lib/logging/__init__.py#L1652C34-L1652C48
if key == "lineno":
return self.event.get("lineno") or 0
+ # process and thread are numeric callsite params formatted with %d, so
fall back to a
+ # number (like lineno above) rather than the "(unknown)" string used
for text params;
+ # otherwise "%(process)d"/"%(thread)d" raises TypeError when the
callsite info is absent
+ # (e.g. warnings routed through the logging bridge).
+ if key == "process" or key == "thread":
+ return self.event.get(key) or 0
if key == "filename":
return self.event.get("filename", "(unknown file)")
if key == "funcName":
diff --git a/shared/logging/tests/logging/test_percent_formatter.py
b/shared/logging/tests/logging/test_percent_formatter.py
index 3a3ae84f562..217c23708e5 100644
--- a/shared/logging/tests/logging/test_percent_formatter.py
+++ b/shared/logging/tests/logging/test_percent_formatter.py
@@ -19,6 +19,8 @@ from __future__ import annotations
from unittest import mock
+import pytest
+
from airflow_shared.logging.percent_formatter import PercentFormatRender
@@ -40,3 +42,19 @@ class TestPercentFormatRender:
)
assert formatted == "test.py:0 our msg"
+
+ @pytest.mark.parametrize(
+ "event",
+ [
+ pytest.param({"event": "our msg"}, id="missing"),
+ pytest.param({"event": "our msg", "process": None, "thread":
None}, id="none"),
+ ],
+ )
+ def test_numeric_callsite_without_process_or_thread(self, event):
+ # Regression for a scheduler crash: a %d specifier for process/thread
with no callsite
+ # info (e.g. a warning routed through the logging bridge) must not
raise TypeError.
+ fmter = PercentFormatRender("%(process)d:%(thread)d %(message)s")
+
+ formatted = fmter(mock.Mock(name="Logger"), "info", event)
+
+ assert formatted == "0:0 our msg"