This is an automated email from the ASF dual-hosted git repository.

vatsrahul1001 pushed a commit to branch v3-4-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-4-test by this push:
     new 87577b644b8 [v3-4-test] Add dag_id and task_id tags to resumable_job 
metrics (#75721) (#75732)
87577b644b8 is described below

commit 87577b644b8ea94c7a414105ef26677fc94af60e
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Oct 27 10:08:19 2026 +0000

    [v3-4-test] Add dag_id and task_id tags to resumable_job metrics (#75721) 
(#75732)
    
    (cherry picked from commit 5243901c8186423b0add017bf3c9e000da0f77e7)
    
    Co-authored-by: Amogh Desai <[email protected]>
---
 .../observability/metrics/metrics_template.yaml    | 12 +++---
 .../src/airflow/sdk/bases/resumablejobmixin.py     |  7 +---
 .../tests/task_sdk/bases/test_resumablejobmixin.py | 47 +++++++++++++++++-----
 3 files changed, 45 insertions(+), 21 deletions(-)

diff --git 
a/shared/observability/src/airflow_shared/observability/metrics/metrics_template.yaml
 
b/shared/observability/src/airflow_shared/observability/metrics/metrics_template.yaml
index b432f40773f..1b9bf737377 100644
--- 
a/shared/observability/src/airflow_shared/observability/metrics/metrics_template.yaml
+++ 
b/shared/observability/src/airflow_shared/observability/metrics/metrics_template.yaml
@@ -53,35 +53,37 @@ metrics:
 
   - name: "resumable_job.fresh_submit"
     description: "Number of times a ResumableJobMixin operator submitted a 
fresh job
-    with no prior run ID stored (first run). Metric with operator tagging."
+    with no prior run ID stored (first run). Metric with operator, dag_id and 
task_id tagging."
     type: "counter"
     legacy_name: "-"
     name_variables: []
 
   - name: "resumable_job.already_succeeded"
     description: "Number of times a ResumableJobMixin operator found a stored 
run ID
-    whose job had already completed successfully and skipped resubmission. 
Metric with operator tagging."
+    whose job had already completed successfully and skipped resubmission. 
Metric with operator,
+    dag_id and task_id tagging."
     type: "counter"
     legacy_name: "-"
     name_variables: []
 
   - name: "resumable_job.terminal_resubmit"
     description: "Number of times a ResumableJobMixin operator found a stored 
run ID
-    whose job was in a terminal (failed) state and submitted a fresh job. 
Metric with operator tagging."
+    whose job was in a terminal (failed) state and submitted a fresh job. 
Metric with operator,
+    dag_id and task_id tagging."
     type: "counter"
     legacy_name: "-"
     name_variables: []
 
   - name: "resumable_job.reconnect_attempt"
     description: "Number of times a ResumableJobMixin operator found a stored 
run ID
-    on retry and attempted to reconnect. Metric with operator tagging."
+    on retry and attempted to reconnect. Metric with operator, dag_id and 
task_id tagging."
     type: "counter"
     legacy_name: "-"
     name_variables: []
 
   - name: "resumable_job.reconnect_success"
     description: "Number of times a ResumableJobMixin operator successfully 
reconnected
-    to an active job on retry. Metric with operator tagging."
+    to an active job on retry. Metric with operator, dag_id and task_id 
tagging."
     type: "counter"
     legacy_name: "-"
     name_variables: []
diff --git a/task-sdk/src/airflow/sdk/bases/resumablejobmixin.py 
b/task-sdk/src/airflow/sdk/bases/resumablejobmixin.py
index ef1629da975..347a700a349 100644
--- a/task-sdk/src/airflow/sdk/bases/resumablejobmixin.py
+++ b/task-sdk/src/airflow/sdk/bases/resumablejobmixin.py
@@ -122,13 +122,8 @@ class ResumableJobMixin(ABC):
             self.poll_until_complete(external_id, context)
             return self.get_job_result(external_id, context)
 
-        stats_tags = {"operator": type(self).__name__}
-        # The task is team-scoped in multi-team deployments; surface team_name 
on the
-        # resumable_job metrics via the running task instance's stats tags 
(omitted when
-        # not multi-team or the task has no team).
         ti = context.get("ti")
-        if ti is not None and (team_name := ti.stats_tags.get("team_name")):
-            stats_tags["team_name"] = team_name
+        stats_tags = {**(ti.stats_tags if ti is not None else {}), "operator": 
type(self).__name__}
 
         reconnect_to: Any = None
         already_succeeded_id: Any = None
diff --git a/task-sdk/tests/task_sdk/bases/test_resumablejobmixin.py 
b/task-sdk/tests/task_sdk/bases/test_resumablejobmixin.py
index 1f84baa2d89..4d9487cb3f8 100644
--- a/task-sdk/tests/task_sdk/bases/test_resumablejobmixin.py
+++ b/task-sdk/tests/task_sdk/bases/test_resumablejobmixin.py
@@ -86,14 +86,14 @@ def make_context(task_store: FakeTaskState | None = None) 
-> dict:
 
 
 class FakeTI:
-    """Minimal stand-in for RuntimeTaskInstance exposing stats_tags with an 
optional team_name."""
+    """Minimal stand-in for RuntimeTaskInstance exposing stats_tags."""
 
     def __init__(self, team_name: str | None = None):
         self._team_name = team_name
 
     @property
     def stats_tags(self) -> dict[str, str]:
-        tags = {"dag_id": "d", "task_id": "t"}
+        tags = {"dag_id": "d", "task_id": "t", "run_type": "scheduled"}
         if self._team_name:
             tags["team_name"] = self._team_name
         return tags
@@ -251,6 +251,8 @@ class TestExternalIdKey:
 class TestMetrics:
     _PATCH = "airflow.sdk._shared.observability.metrics.stats.incr"
     _TAG = {"operator": "ConcreteResumableOperator"}
+    _TI_TAGS = {"dag_id": "d", "task_id": "t", "run_type": "scheduled"}
+    _FULL_TAGS = {**_TI_TAGS, "team_name": "team_alpha", **_TAG}
 
     def test_fresh_submit_fires_only_fresh_submit_counter(self):
         op = ConcreteResumableOperator(task_id="test_task")
@@ -297,24 +299,49 @@ class TestMetrics:
         assert "resumable_job.fresh_submit" not in called_names
 
     @pytest.mark.parametrize(
-        ("team_name", "expected_tag"),
+        ("stored_status", "expected_names"),
         [
+            pytest.param(None, ["resumable_job.fresh_submit"], 
id="fresh_submit"),
             pytest.param(
-                "team_alpha",
-                {"operator": "ConcreteResumableOperator", "team_name": 
"team_alpha"},
-                id="with_team",
+                "RUNNING",
+                ["resumable_job.reconnect_attempt", 
"resumable_job.reconnect_success"],
+                id="reconnect",
+            ),
+            pytest.param(
+                "SUCCEEDED",
+                ["resumable_job.reconnect_attempt", 
"resumable_job.already_succeeded"],
+                id="already_succeeded",
+            ),
+            pytest.param(
+                "FAILED",
+                ["resumable_job.reconnect_attempt", 
"resumable_job.terminal_resubmit"],
+                id="terminal_resubmit",
             ),
-            pytest.param(None, {"operator": "ConcreteResumableOperator"}, 
id="without_team"),
         ],
     )
-    def test_team_name_added_to_metric_tags(self, team_name, expected_tag):
+    def test_every_counter_carries_task_instance_stats_tags(self, 
stored_status, expected_names):
+        op = ConcreteResumableOperator(task_id="test_task")
+        if stored_status is None:
+            task_state = FakeTaskState()
+        else:
+            op._status_map["job-001"] = stored_status
+            task_state = FakeTaskState({"test_job_id": "job-001"})
+        ctx = make_context(task_state)
+        ctx["ti"] = FakeTI("team_alpha")
+        mock_incr = MagicMock()
+        with patch(self._PATCH, mock_incr):
+            op.execute_resumable(ctx)
+        assert [call.args[0] for call in mock_incr.call_args_list] == 
expected_names
+        assert all(call.kwargs["tags"] == self._FULL_TAGS for call in 
mock_incr.call_args_list)
+
+    def test_team_name_omitted_when_task_has_no_team(self):
         op = ConcreteResumableOperator(task_id="test_task")
         ctx = make_context(FakeTaskState())
-        ctx["ti"] = FakeTI(team_name)
+        ctx["ti"] = FakeTI()
         mock_incr = MagicMock()
         with patch(self._PATCH, mock_incr):
             op.execute_resumable(ctx)
-        mock_incr.assert_called_once_with("resumable_job.fresh_submit", 
tags=expected_tag)
+        mock_incr.assert_called_once_with("resumable_job.fresh_submit", 
tags={**self._TI_TAGS, **self._TAG})
 
 
 class TestTracing:

Reply via email to