From 15d223c7f2fe3de6a76359bef169b6443adf4438 Mon Sep 17 00:00:00 2001 From: Ash Berlin-Taylor Date: Tue, 2 Dec 2025 17:50:49 +0000 Subject: [PATCH] [v3-1-test] Fix exception when logging stdout with a custom %-fmt string. (#58959) For instance, if you set this config ```ini [logging] log_format={%%(filename)s:%%(lineno)d} --- %%(message)s ``` this would blow up with an error like this on printing anything from a trigger, killing the triggerer process: ``` 2025-11-03 14:13:27 File "/usr/local/lib/python3.12/site-packages/airflow/_shared/logging/percent_formatter.py", line 149, in __call__ 2025-11-03 14:13:27 sio.write(self._fmt % params) 2025-11-03 14:13:27 ~~~~~~~~~~^~~~~~~~ 2025-11-03 14:13:27 TypeError: %d format: a real number is required, not NoneType ``` (cherry picked from commit 586fe930791fd4980fc36433e95965d1e10d4306) Co-authored-by: Ash Berlin-Taylor --- .../airflow_shared/logging/percent_formatter.py | 10 ++++++++++ .../tests/logging/test_percent_formatter.py | 14 ++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 shared/logging/tests/logging/test_percent_formatter.py diff --git a/shared/logging/src/airflow_shared/logging/percent_formatter.py b/shared/logging/src/airflow_shared/logging/percent_formatter.py index b08024621e69d..78e4b82a81034 100644 --- a/shared/logging/src/airflow_shared/logging/percent_formatter.py +++ b/shared/logging/src/airflow_shared/logging/percent_formatter.py @@ -50,6 +50,16 @@ def __getitem__(self, key): # Roughly compatible with names from https://github.com/python/cpython/blob/v3.13.7/Lib/logging/__init__.py#L571 # Plus with ColoredLog added in + # If there is no callsite info (often for stdout/stderr), show the same sort of thing that stdlib + # logging would + # https://github.com/python/cpython/blob/d3c888b4ec15dbd7d6b6ef4f15b558af77c228af/Lib/logging/__init__.py#L1652C34-L1652C48 + if key == "lineno": + return self.event.get("lineno", 0) + if key == "filename": + return self.event.get("filename", "(unknown file)") + if key == "funcName": + return self.event.get("funcName", "(unknown function)") + if key in PercentFormatRender.callsite_parameters: return self.event.get(PercentFormatRender.callsite_parameters[key].value) if key == "name": diff --git a/shared/logging/tests/logging/test_percent_formatter.py b/shared/logging/tests/logging/test_percent_formatter.py new file mode 100644 index 0000000000000..efcafd1353a17 --- /dev/null +++ b/shared/logging/tests/logging/test_percent_formatter.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +from unittest import mock + +from airflow_shared.logging.percent_formatter import PercentFormatRender + + +class TestPercentFormatRender: + def test_no_callsite(self): + fmter = PercentFormatRender("%(filename)s:%(lineno)d %(message)s") + + formatted = fmter(mock.Mock(name="Logger"), "info", {"event": "our msg"}) + + assert formatted == "(unknown file):0 our msg"