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."""

Reply via email to