This is an automated email from the ASF dual-hosted git repository.
jason810496 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 7c3eacc3cfb Fix UI log fetch crash when ti.hostname is empty (#64285)
7c3eacc3cfb is described below
commit 7c3eacc3cfb9104128def18faed221fd4f4b2cd3
Author: Gourav N S S <[email protected]>
AuthorDate: Tue Apr 21 11:42:50 2026 +0530
Fix UI log fetch crash when ti.hostname is empty (#64285)
* Fix UI log fetch crash when ti.hostname is empty
When tasks fail to report their hostname (e.g., due to cellular worker
proxy overrides dropping the internal API patched payload), the TaskInstance
hostname defaults to empty. This causes _get_log_retrieval_url to construct an
invalid URL ('http://:8793/...') which completely crashes log serving with an
'Invalid URL... No host supplied' error from python requests.
closes: #64263
* Fix UI log fetch crash when ti.hostname is empty: address reviewer
feedback
* Fix mypy static checks and add hostname_callable config help
* Update airflow-core/src/airflow/utils/log/file_task_handler.py
Co-authored-by: Jason(Zhe-You) Liu
<[email protected]>
---------
Co-authored-by: Jason(Zhe-You) Liu
<[email protected]>
---
.../src/airflow/utils/log/file_task_handler.py | 13 ++++++++-
.../tests/unit/utils/log/test_file_task_handler.py | 31 ++++++++++++++++++++++
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/airflow-core/src/airflow/utils/log/file_task_handler.py
b/airflow-core/src/airflow/utils/log/file_task_handler.py
index 4492be368ab..31d3db8df4d 100644
--- a/airflow-core/src/airflow/utils/log/file_task_handler.py
+++ b/airflow-core/src/airflow/utils/log/file_task_handler.py
@@ -708,7 +708,7 @@ class FileTaskHandler(logging.Handler):
ti: TaskInstance | TaskInstanceHistory,
log_relative_path: str,
log_type: LogType | None = None,
- ) -> tuple[str, str]:
+ ) -> tuple[str | None, str | None]:
"""Given TI, generate URL with which to fetch logs from service log
server."""
if log_type == LogType.TRIGGER:
if not ti.triggerer_job:
@@ -721,6 +721,10 @@ class FileTaskHandler(logging.Handler):
hostname = ti.hostname
config_key = "worker_log_server_port"
config_default = 8793
+
+ if not hostname:
+ return None, None
+
return (
urljoin(
f"http://{hostname}:{conf.get('logging', config_key,
fallback=config_default)}/log/",
@@ -904,6 +908,13 @@ class FileTaskHandler(logging.Handler):
try:
log_type = LogType.TRIGGER if getattr(ti, "triggerer_job", False)
else LogType.WORKER
url, rel_path = self._get_log_retrieval_url(ti,
worker_log_rel_path, log_type=log_type)
+ if not url or not rel_path:
+ sources.append(
+ f"Could not read served logs: Hostname not available for "
+ f"{log_type.value}. "
+ f"Please check your `hostname_callable` configuration."
+ )
+ return sources, log_streams
response = _fetch_logs_from_service(url, rel_path)
if response.status_code == 403:
sources.append(
diff --git a/airflow-core/tests/unit/utils/log/test_file_task_handler.py
b/airflow-core/tests/unit/utils/log/test_file_task_handler.py
index 93606cffe1d..ceecffafa32 100644
--- a/airflow-core/tests/unit/utils/log/test_file_task_handler.py
+++ b/airflow-core/tests/unit/utils/log/test_file_task_handler.py
@@ -91,6 +91,37 @@ class TestFileTaskHandlerLogServer:
assert "secret_key" in sources[0]
assert streams == []
+ @patch("airflow.utils.log.file_task_handler._fetch_logs_from_service")
+ @patch.object(FileTaskHandler, "_get_log_retrieval_url")
+ def test_read_from_logs_server_no_hostname(self, mock_get_url, mock_fetch):
+ """When hostname is missing, show a clear message instead of
attempting log server fetch."""
+ mock_get_url.return_value = (None, None)
+
+ sources, streams = self.handler._read_from_logs_server(self.ti,
"dag/run/task/1.log")
+
+ assert len(sources) == 1
+ assert "Hostname not available" in sources[0]
+ assert "worker" in sources[0]
+ assert streams == []
+ mock_fetch.assert_not_called()
+
+ @patch("airflow.utils.log.file_task_handler._fetch_logs_from_service")
+ @patch.object(FileTaskHandler, "_get_log_retrieval_url")
+ def test_read_from_logs_server_no_hostname_triggerer(self, mock_get_url,
mock_fetch):
+ """When hostname is missing for triggerer, show a clear message
instead of attempting log server fetch."""
+ mock_get_url.return_value = (None, None)
+ self.ti.triggerer_job = MagicMock()
+ self.ti.triggerer_job.hostname = None
+ self.ti.triggerer_job.id = 123
+
+ sources, streams = self.handler._read_from_logs_server(self.ti,
"dag/run/task/1.log")
+
+ assert len(sources) == 1
+ assert "Hostname not available" in sources[0]
+ assert "trigger" in sources[0]
+ assert streams == []
+ mock_fetch.assert_not_called()
+
class TestFileTaskHandlerReadFromLocal:
"""Tests for ``FileTaskHandler._read_from_local`` path containment."""