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

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


The following commit(s) were added to refs/heads/v3-2-test by this push:
     new b36783c2ba4 [v3-2-test] Bring back pre/post execute log groups for 
task logs (#66037) (#66049)
b36783c2ba4 is described below

commit b36783c2ba456544c7d68a218c4ce77881c64086
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Fri May 1 17:22:40 2026 +0200

    [v3-2-test] Bring back pre/post execute log groups for task logs (#66037) 
(#66049)
    
    In Airflow 2, task startup and finalization logs were wrapped in
    collapsible ::group:: markers so the UI could hide them, letting users
    focus on the actual task output. This was lost in the move to structlog.
    
    Re-emit the markers in the task runner:
    - ::group::Pre Execute before startup through pre_execute/callbacks
    - ::endgroup:: just before execute()
    - ::group::Post Execute / ::endgroup:: around finalize()
    (cherry picked from commit 0b0271b74c52ad9cc5da5183495e3fb003a02faa)
    
    Co-authored-by: Ash Berlin-Taylor <[email protected]>
---
 .../src/airflow/sdk/execution_time/task_runner.py  |  7 +++
 .../task_sdk/execution_time/test_task_runner.py    | 63 ++++++++++++++++++++++
 2 files changed, 70 insertions(+)

diff --git a/task-sdk/src/airflow/sdk/execution_time/task_runner.py 
b/task-sdk/src/airflow/sdk/execution_time/task_runner.py
index 5a2624def02..0f89596cda2 100644
--- a/task-sdk/src/airflow/sdk/execution_time/task_runner.py
+++ b/task-sdk/src/airflow/sdk/execution_time/task_runner.py
@@ -1290,6 +1290,8 @@ def run(
                 # Send update only if value changed (e.g., user set context 
variables during execution)
                 if ti.rendered_map_index and ti.rendered_map_index != 
previous_rendered_map_index:
                     
SUPERVISOR_COMMS.send(msg=SetRenderedMapIndex(rendered_map_index=ti.rendered_map_index))
+            finally:
+                log.info("::group::Post Execute")
 
         _push_xcom_if_needed(result, ti, log)
 
@@ -1679,6 +1681,8 @@ def _execute_task(context: Context, ti: 
RuntimeTaskInstance, log: Logger):
 
     _run_task_state_change_callbacks(task, "on_execute_callback", context, log)
 
+    log.info("::endgroup::")
+
     if task.execution_timeout:
         from airflow.sdk.execution_time.timeout import timeout
 
@@ -1846,6 +1850,8 @@ def finalize(
     except Exception:
         log.exception("error calling listener")
 
+    log.info("::endgroup::")
+
 
 @contextmanager
 def flush_spans():
@@ -1874,6 +1880,7 @@ def main():
     with stack:
         try:
             try:
+                log.info("::group::Pre Execute")
                 startup_details = get_startup_details()
                 span = _make_task_span(msg=startup_details)
                 stack.enter_context(span)
diff --git a/task-sdk/tests/task_sdk/execution_time/test_task_runner.py 
b/task-sdk/tests/task_sdk/execution_time/test_task_runner.py
index 9ceedc8ecaf..36084c21db6 100644
--- a/task-sdk/tests/task_sdk/execution_time/test_task_runner.py
+++ b/task-sdk/tests/task_sdk/execution_time/test_task_runner.py
@@ -802,6 +802,69 @@ def test_run_downstream_skipped(mocked_parse, 
create_runtime_ti, mock_supervisor
     )
 
 
+def test_run_emits_endgroup_before_execute(create_runtime_ti, 
mock_supervisor_comms):
+    """::endgroup:: is emitted to close the pre-execute log group just before 
execute() runs."""
+    call_order: list[str] = []
+
+    class TrackingOperator(BaseOperator):
+        def execute(self, context):
+            call_order.append("execute")
+
+    task = TrackingOperator(task_id="tracking_task")
+    ti = create_runtime_ti(task=task)
+    log = mock.MagicMock(spec=["info", "debug", "warning", "error", 
"exception", "bind"])
+
+    def tracking_info(msg, *args, **kwargs):
+        if msg == "::endgroup::":
+            call_order.append("::endgroup::")
+
+    log.info.side_effect = tracking_info
+
+    run(ti, context=ti.get_template_context(), log=log)
+
+    assert "::endgroup::" in call_order, "::endgroup:: was never emitted"
+    assert "execute" in call_order, "execute() was never called"
+    assert call_order.index("::endgroup::") < call_order.index("execute")
+
+
+def test_run_emits_post_execute_group_before_xcom_push(create_runtime_ti, 
mock_supervisor_comms):
+    """::group::Post Execute is opened before 'Pushing xcom' so the push 
appears inside the group."""
+    call_order: list[str] = []
+
+    class ReturningOperator(BaseOperator):
+        def execute(self, context):
+            return "some_value"
+
+    task = ReturningOperator(task_id="returning_task")
+    ti = create_runtime_ti(task=task)
+    log = mock.MagicMock(spec=["info", "debug", "warning", "error", 
"exception", "bind"])
+
+    def tracking_info(msg, *args, **kwargs):
+        if msg in ("::group::Post Execute", "Pushing xcom"):
+            call_order.append(msg)
+
+    log.info.side_effect = tracking_info
+
+    run(ti, context=ti.get_template_context(), log=log)
+
+    assert "::group::Post Execute" in call_order
+    assert "Pushing xcom" in call_order
+    assert call_order.index("::group::Post Execute") < 
call_order.index("Pushing xcom")
+
+
+def test_finalize_emits_endgroup(create_runtime_ti, mock_supervisor_comms):
+    """finalize() closes the post-execute log group but does not open it."""
+    task = BaseOperator(task_id="some_task")
+    ti = create_runtime_ti(task=task)
+    log = mock.MagicMock()
+
+    finalize(ti, state=TaskInstanceState.SUCCESS, 
context=ti.get_template_context(), log=log)
+
+    info_calls = log.info.call_args_list
+    assert call("::group::Post Execute") not in info_calls
+    assert info_calls[-1] == call("::endgroup::")
+
+
 def test_resume_from_deferred(time_machine, create_runtime_ti, 
mock_supervisor_comms, spy_agency: SpyAgency):
     from airflow.providers.standard.sensors.date_time import 
DateTimeSensorAsync
 

Reply via email to