diff --git a/shared/logging/src/airflow_shared/logging/percent_formatter.py b/shared/logging/src/airflow_shared/logging/percent_formatter.py index ee9195e59b594..0efbb78d9922e 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 @@ def __getitem__(self, key): # 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 3a3ae84f562eb..217c23708e5e2 100644 --- a/shared/logging/tests/logging/test_percent_formatter.py +++ b/shared/logging/tests/logging/test_percent_formatter.py @@ -19,6 +19,8 @@ from unittest import mock +import pytest + from airflow_shared.logging.percent_formatter import PercentFormatRender @@ -40,3 +42,19 @@ def test_lineno_is_none(self): ) 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"