Include TI UUID in scheduler, DAG processor, triggerer, and worker logs - #65458

Merged
kaxil merged 4 commits into
apache:mainfrom
astronomer:kaxilnaik/ti-uuid-log-context-binding
Apr 19, 2026
Merged

Include TI UUID in scheduler, DAG processor, triggerer, and worker logs#65458
kaxil merged 4 commits into
apache:mainfrom
astronomer:kaxilnaik/ti-uuid-log-context-binding

Conversation

@kaxil

Copy link
Copy Markdown
Member

Engineers currently cannot grep a single ti_id and reconstruct a task's full lifecycle timeline. The Execution API binds ti_id to every log line via bind_contextvars, but scheduler, DAG processor, triggerer, and worker each emit it in some places and not others. This PR closes that gap.

What changed

ComponentChangeRationale
Worker (task-sdk/.../task_runner.py)bind_contextvars(ti_id=str(msg.ti.id), ...) at the top of startup()One worker process = one TI. No cross-task leak risk, so context binding is strictly correct and instruments every subsequent log line (including ones added in future changes).
Triggerer (jobs/triggerer_job_runner.py:1308)Extend the existing bind_log_contextvars(trigger_id=...) to also bind ti_id + composite TI keys when trigger.task_instance is presentrun_trigger runs inside asyncio.create_task, which snapshots the context at creation, so the binding stays scoped to that coroutine.
Scheduler (jobs/scheduler_job_runner.py)Add ti_id=%s as a positional arg to eight TI-touching log calls across _enqueue_task_instances_with_queued_state, process_executor_events, and _maybe_requeue_stuck_tiExplicit positional args (no bind_contextvars) because the scheduler is a long-running process and any unhandled exception mid-loop would leave a stale ti_id bound for the rest of the process lifetime, corrupting all subsequent log lines. Per-iteration with bound_contextvars(...) would be correct but re-indents ~200 lines of hot-path code. Positional args are both safer and less invasive.
DAG processor (dag_processing/processor.py)Add ti_id to _execute_callbacks and _execute_task_callbacks log calls (kwarg where the call is pure-structlog style, %-format positional where the call is stdlib style)Same reasoning as scheduler.

Design notes

  • Why not bind_contextvars everywhere? The earlier draft of this PR did exactly that. But we found a contextvar leak: if any exception propagates out of the per-TI loop body, the post-loop unbind_contextvars is skipped and the last TI's ti_id stays bound for the remainder of the scheduler process. Because merge_contextvars is wired into the stdlib logging foreign_pre_chain, this taints every subsequent log line -- including ones that have nothing to do with that TI. Inverts the goal of the PR. Per-iteration bound_contextvars context manager is correct but requires re-indenting long loop bodies. For the scheduler and DAG processor, explicit positional ti_id=%s args are both safer and less invasive.
  • Why is it OK to bind in the worker? A worker is a fresh process that runs exactly one TI. There is no cross-iteration or cross-TI state to leak into. The bind is strictly correct and instruments every log line for free.
  • Why is it OK to bind in the triggerer?run_trigger is launched via asyncio.create_task, which copies the current contextvars.Context at task creation. Any contextvar bound inside the coroutine is scoped to that task and cannot leak into another trigger's coroutine or the triggerer's supervisor loop.

Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
Comment threadairflow-core/src/airflow/jobs/scheduler_job_runner.py Outdated
kaxil added 3 commits April 18, 2026 22:00
…tions
Addresses review feedback from @jedcunningham on apache#65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
@kaxil
kaxil merged commit 1a0efe7 into apache:mainApr 19, 2026
107 checks passed
@kaxil
kaxil deleted the kaxilnaik/ti-uuid-log-context-binding branch April 19, 2026 00:44
@kaxilkaxil added this to the Airflow 3.2.2 milestone Apr 19, 2026
github-actionsBot pushed a commit that referenced this pull request Apr 19, 2026
…nd worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport successfully created: v3-2-test

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

StatusBranchResult
v3-2-testPR Link

vatsrahul1001 pushed a commit that referenced this pull request Apr 23, 2026
…nd worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
potiuk pushed a commit that referenced this pull request Apr 26, 2026
…nd worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
potiuk added a commit that referenced this pull request Apr 26, 2026
…nd worker logs (#65458) (#65476)
* [v3-2-test] Bump actions/github-script in the github-actions-updates group (#65150) (#65160)
Bumps the github-actions-updates group with 1 update: [actions/github-script](https://github.com/actions/github-script).
Updates `actions/github-script` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@ed59741...3a2844b)
(cherry picked from commit e5a047c)
---
updated-dependencies:
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* [v3-2-test] Added breeze generate issue content for airflow-ctl (#65042) (#65241)
* Add breeze generate issue content for airflow-ctl
* add new command to doc
(cherry picked from commit b24538b)
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
* [v3-2-test] Run release calendar verification on its own schedule (#65118) (#65242)
* Move release calendar verification to its own scheduled workflow
Run dev/verify_release_calendar.py from a dedicated daily scheduled
workflow instead of as a canary job in the main CI pipeline, and
notify the #release-management Slack channel when the check fails so
the issue is surfaced to release managers directly.
* Include wiki and calendar links in release calendar Slack alert
(cherry picked from commit 048e9a1)
* [v3-2-test] Include TI UUID in scheduler, DAG processor, triggerer, and worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
vatsrahul1001 pushed a commit that referenced this pull request Apr 27, 2026
…nd worker logs (#65458) (#65476)
* [v3-2-test] Bump actions/github-script in the github-actions-updates group (#65150) (#65160)
Bumps the github-actions-updates group with 1 update: [actions/github-script](https://github.com/actions/github-script).
Updates `actions/github-script` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@ed59741...3a2844b)
(cherry picked from commit e5a047c)
---
updated-dependencies:
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* [v3-2-test] Added breeze generate issue content for airflow-ctl (#65042) (#65241)
* Add breeze generate issue content for airflow-ctl
* add new command to doc
(cherry picked from commit b24538b)
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
* [v3-2-test] Run release calendar verification on its own schedule (#65118) (#65242)
* Move release calendar verification to its own scheduled workflow
Run dev/verify_release_calendar.py from a dedicated daily scheduled
workflow instead of as a canary job in the main CI pipeline, and
notify the #release-management Slack channel when the check fails so
the issue is surfaced to release managers directly.
* Include wiki and calendar links in release calendar Slack alert
(cherry picked from commit 048e9a1)
* [v3-2-test] Include TI UUID in scheduler, DAG processor, triggerer, and worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
vatsrahul1001 pushed a commit that referenced this pull request May 20, 2026
…nd worker logs (#65458) (#65476)
* [v3-2-test] Bump actions/github-script in the github-actions-updates group (#65150) (#65160)
Bumps the github-actions-updates group with 1 update: [actions/github-script](https://github.com/actions/github-script).
Updates `actions/github-script` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@ed59741...3a2844b)
(cherry picked from commit e5a047c)
---
updated-dependencies:
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* [v3-2-test] Added breeze generate issue content for airflow-ctl (#65042) (#65241)
* Add breeze generate issue content for airflow-ctl
* add new command to doc
(cherry picked from commit b24538b)
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
* [v3-2-test] Run release calendar verification on its own schedule (#65118) (#65242)
* Move release calendar verification to its own scheduled workflow
Run dev/verify_release_calendar.py from a dedicated daily scheduled
workflow instead of as a canary job in the main CI pipeline, and
notify the #release-management Slack channel when the check fails so
the issue is surfaced to release managers directly.
* Include wiki and calendar links in release calendar Slack alert
(cherry picked from commit 048e9a1)
* [v3-2-test] Include TI UUID in scheduler, DAG processor, triggerer, and worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:DAG-processingarea:Schedulerincluding HA (high availability) schedulerarea:task-sdkarea:Triggerer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@kaxil@jedcunningham
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Include TI UUID in scheduler, DAG processor, triggerer, and worker logs - #65458

Merged
kaxil merged 4 commits into
apache:mainfrom
astronomer:kaxilnaik/ti-uuid-log-context-binding
Apr 19, 2026
Merged

Include TI UUID in scheduler, DAG processor, triggerer, and worker logs#65458
kaxil merged 4 commits into
apache:mainfrom
astronomer:kaxilnaik/ti-uuid-log-context-binding

Conversation

@kaxil

Copy link
Copy Markdown
Member

Engineers currently cannot grep a single ti_id and reconstruct a task's full lifecycle timeline. The Execution API binds ti_id to every log line via bind_contextvars, but scheduler, DAG processor, triggerer, and worker each emit it in some places and not others. This PR closes that gap.

What changed

ComponentChangeRationale
Worker (task-sdk/.../task_runner.py)bind_contextvars(ti_id=str(msg.ti.id), ...) at the top of startup()One worker process = one TI. No cross-task leak risk, so context binding is strictly correct and instruments every subsequent log line (including ones added in future changes).
Triggerer (jobs/triggerer_job_runner.py:1308)Extend the existing bind_log_contextvars(trigger_id=...) to also bind ti_id + composite TI keys when trigger.task_instance is presentrun_trigger runs inside asyncio.create_task, which snapshots the context at creation, so the binding stays scoped to that coroutine.
Scheduler (jobs/scheduler_job_runner.py)Add ti_id=%s as a positional arg to eight TI-touching log calls across _enqueue_task_instances_with_queued_state, process_executor_events, and _maybe_requeue_stuck_tiExplicit positional args (no bind_contextvars) because the scheduler is a long-running process and any unhandled exception mid-loop would leave a stale ti_id bound for the rest of the process lifetime, corrupting all subsequent log lines. Per-iteration with bound_contextvars(...) would be correct but re-indents ~200 lines of hot-path code. Positional args are both safer and less invasive.
DAG processor (dag_processing/processor.py)Add ti_id to _execute_callbacks and _execute_task_callbacks log calls (kwarg where the call is pure-structlog style, %-format positional where the call is stdlib style)Same reasoning as scheduler.

Design notes

  • Why not bind_contextvars everywhere? The earlier draft of this PR did exactly that. But we found a contextvar leak: if any exception propagates out of the per-TI loop body, the post-loop unbind_contextvars is skipped and the last TI's ti_id stays bound for the remainder of the scheduler process. Because merge_contextvars is wired into the stdlib logging foreign_pre_chain, this taints every subsequent log line -- including ones that have nothing to do with that TI. Inverts the goal of the PR. Per-iteration bound_contextvars context manager is correct but requires re-indenting long loop bodies. For the scheduler and DAG processor, explicit positional ti_id=%s args are both safer and less invasive.
  • Why is it OK to bind in the worker? A worker is a fresh process that runs exactly one TI. There is no cross-iteration or cross-TI state to leak into. The bind is strictly correct and instruments every log line for free.
  • Why is it OK to bind in the triggerer?run_trigger is launched via asyncio.create_task, which copies the current contextvars.Context at task creation. Any contextvar bound inside the coroutine is scoped to that task and cannot leak into another trigger's coroutine or the triggerer's supervisor loop.

Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
Comment threadairflow-core/src/airflow/jobs/scheduler_job_runner.py Outdated
kaxil added 3 commits April 18, 2026 22:00
…tions
Addresses review feedback from @jedcunningham on apache#65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
@kaxil
kaxil merged commit 1a0efe7 into apache:mainApr 19, 2026
107 checks passed
@kaxil
kaxil deleted the kaxilnaik/ti-uuid-log-context-binding branch April 19, 2026 00:44
@kaxilkaxil added this to the Airflow 3.2.2 milestone Apr 19, 2026
github-actionsBot pushed a commit that referenced this pull request Apr 19, 2026
…nd worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport successfully created: v3-2-test

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

StatusBranchResult
v3-2-testPR Link

vatsrahul1001 pushed a commit that referenced this pull request Apr 23, 2026
…nd worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
potiuk pushed a commit that referenced this pull request Apr 26, 2026
…nd worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
potiuk added a commit that referenced this pull request Apr 26, 2026
…nd worker logs (#65458) (#65476)
* [v3-2-test] Bump actions/github-script in the github-actions-updates group (#65150) (#65160)
Bumps the github-actions-updates group with 1 update: [actions/github-script](https://github.com/actions/github-script).
Updates `actions/github-script` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@ed59741...3a2844b)
(cherry picked from commit e5a047c)
---
updated-dependencies:
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* [v3-2-test] Added breeze generate issue content for airflow-ctl (#65042) (#65241)
* Add breeze generate issue content for airflow-ctl
* add new command to doc
(cherry picked from commit b24538b)
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
* [v3-2-test] Run release calendar verification on its own schedule (#65118) (#65242)
* Move release calendar verification to its own scheduled workflow
Run dev/verify_release_calendar.py from a dedicated daily scheduled
workflow instead of as a canary job in the main CI pipeline, and
notify the #release-management Slack channel when the check fails so
the issue is surfaced to release managers directly.
* Include wiki and calendar links in release calendar Slack alert
(cherry picked from commit 048e9a1)
* [v3-2-test] Include TI UUID in scheduler, DAG processor, triggerer, and worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
vatsrahul1001 pushed a commit that referenced this pull request Apr 27, 2026
…nd worker logs (#65458) (#65476)
* [v3-2-test] Bump actions/github-script in the github-actions-updates group (#65150) (#65160)
Bumps the github-actions-updates group with 1 update: [actions/github-script](https://github.com/actions/github-script).
Updates `actions/github-script` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@ed59741...3a2844b)
(cherry picked from commit e5a047c)
---
updated-dependencies:
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* [v3-2-test] Added breeze generate issue content for airflow-ctl (#65042) (#65241)
* Add breeze generate issue content for airflow-ctl
* add new command to doc
(cherry picked from commit b24538b)
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
* [v3-2-test] Run release calendar verification on its own schedule (#65118) (#65242)
* Move release calendar verification to its own scheduled workflow
Run dev/verify_release_calendar.py from a dedicated daily scheduled
workflow instead of as a canary job in the main CI pipeline, and
notify the #release-management Slack channel when the check fails so
the issue is surfaced to release managers directly.
* Include wiki and calendar links in release calendar Slack alert
(cherry picked from commit 048e9a1)
* [v3-2-test] Include TI UUID in scheduler, DAG processor, triggerer, and worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
vatsrahul1001 pushed a commit that referenced this pull request May 20, 2026
…nd worker logs (#65458) (#65476)
* [v3-2-test] Bump actions/github-script in the github-actions-updates group (#65150) (#65160)
Bumps the github-actions-updates group with 1 update: [actions/github-script](https://github.com/actions/github-script).
Updates `actions/github-script` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@ed59741...3a2844b)
(cherry picked from commit e5a047c)
---
updated-dependencies:
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* [v3-2-test] Added breeze generate issue content for airflow-ctl (#65042) (#65241)
* Add breeze generate issue content for airflow-ctl
* add new command to doc
(cherry picked from commit b24538b)
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
* [v3-2-test] Run release calendar verification on its own schedule (#65118) (#65242)
* Move release calendar verification to its own scheduled workflow
Run dev/verify_release_calendar.py from a dedicated daily scheduled
workflow instead of as a canary job in the main CI pipeline, and
notify the #release-management Slack channel when the check fails so
the issue is surfaced to release managers directly.
* Include wiki and calendar links in release calendar Slack alert
(cherry picked from commit 048e9a1)
* [v3-2-test] Include TI UUID in scheduler, DAG processor, triggerer, and worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:DAG-processingarea:Schedulerincluding HA (high availability) schedulerarea:task-sdkarea:Triggerer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@kaxil@jedcunningham
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Include TI UUID in scheduler, DAG processor, triggerer, and worker logs - #65458

Merged
kaxil merged 4 commits into
apache:mainfrom
astronomer:kaxilnaik/ti-uuid-log-context-binding
Apr 19, 2026
Merged

Include TI UUID in scheduler, DAG processor, triggerer, and worker logs#65458
kaxil merged 4 commits into
apache:mainfrom
astronomer:kaxilnaik/ti-uuid-log-context-binding

Conversation

@kaxil

Copy link
Copy Markdown
Member

Engineers currently cannot grep a single ti_id and reconstruct a task's full lifecycle timeline. The Execution API binds ti_id to every log line via bind_contextvars, but scheduler, DAG processor, triggerer, and worker each emit it in some places and not others. This PR closes that gap.

What changed

ComponentChangeRationale
Worker (task-sdk/.../task_runner.py)bind_contextvars(ti_id=str(msg.ti.id), ...) at the top of startup()One worker process = one TI. No cross-task leak risk, so context binding is strictly correct and instruments every subsequent log line (including ones added in future changes).
Triggerer (jobs/triggerer_job_runner.py:1308)Extend the existing bind_log_contextvars(trigger_id=...) to also bind ti_id + composite TI keys when trigger.task_instance is presentrun_trigger runs inside asyncio.create_task, which snapshots the context at creation, so the binding stays scoped to that coroutine.
Scheduler (jobs/scheduler_job_runner.py)Add ti_id=%s as a positional arg to eight TI-touching log calls across _enqueue_task_instances_with_queued_state, process_executor_events, and _maybe_requeue_stuck_tiExplicit positional args (no bind_contextvars) because the scheduler is a long-running process and any unhandled exception mid-loop would leave a stale ti_id bound for the rest of the process lifetime, corrupting all subsequent log lines. Per-iteration with bound_contextvars(...) would be correct but re-indents ~200 lines of hot-path code. Positional args are both safer and less invasive.
DAG processor (dag_processing/processor.py)Add ti_id to _execute_callbacks and _execute_task_callbacks log calls (kwarg where the call is pure-structlog style, %-format positional where the call is stdlib style)Same reasoning as scheduler.

Design notes

  • Why not bind_contextvars everywhere? The earlier draft of this PR did exactly that. But we found a contextvar leak: if any exception propagates out of the per-TI loop body, the post-loop unbind_contextvars is skipped and the last TI's ti_id stays bound for the remainder of the scheduler process. Because merge_contextvars is wired into the stdlib logging foreign_pre_chain, this taints every subsequent log line -- including ones that have nothing to do with that TI. Inverts the goal of the PR. Per-iteration bound_contextvars context manager is correct but requires re-indenting long loop bodies. For the scheduler and DAG processor, explicit positional ti_id=%s args are both safer and less invasive.
  • Why is it OK to bind in the worker? A worker is a fresh process that runs exactly one TI. There is no cross-iteration or cross-TI state to leak into. The bind is strictly correct and instruments every log line for free.
  • Why is it OK to bind in the triggerer?run_trigger is launched via asyncio.create_task, which copies the current contextvars.Context at task creation. Any contextvar bound inside the coroutine is scoped to that task and cannot leak into another trigger's coroutine or the triggerer's supervisor loop.

Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
Comment threadairflow-core/src/airflow/jobs/scheduler_job_runner.py Outdated
kaxil added 3 commits April 18, 2026 22:00
…tions
Addresses review feedback from @jedcunningham on apache#65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
@kaxil
kaxil merged commit 1a0efe7 into apache:mainApr 19, 2026
107 checks passed
@kaxil
kaxil deleted the kaxilnaik/ti-uuid-log-context-binding branch April 19, 2026 00:44
@kaxilkaxil added this to the Airflow 3.2.2 milestone Apr 19, 2026
github-actionsBot pushed a commit that referenced this pull request Apr 19, 2026
…nd worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport successfully created: v3-2-test

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

StatusBranchResult
v3-2-testPR Link

vatsrahul1001 pushed a commit that referenced this pull request Apr 23, 2026
…nd worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
potiuk pushed a commit that referenced this pull request Apr 26, 2026
…nd worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
potiuk added a commit that referenced this pull request Apr 26, 2026
…nd worker logs (#65458) (#65476)
* [v3-2-test] Bump actions/github-script in the github-actions-updates group (#65150) (#65160)
Bumps the github-actions-updates group with 1 update: [actions/github-script](https://github.com/actions/github-script).
Updates `actions/github-script` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@ed59741...3a2844b)
(cherry picked from commit e5a047c)
---
updated-dependencies:
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* [v3-2-test] Added breeze generate issue content for airflow-ctl (#65042) (#65241)
* Add breeze generate issue content for airflow-ctl
* add new command to doc
(cherry picked from commit b24538b)
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
* [v3-2-test] Run release calendar verification on its own schedule (#65118) (#65242)
* Move release calendar verification to its own scheduled workflow
Run dev/verify_release_calendar.py from a dedicated daily scheduled
workflow instead of as a canary job in the main CI pipeline, and
notify the #release-management Slack channel when the check fails so
the issue is surfaced to release managers directly.
* Include wiki and calendar links in release calendar Slack alert
(cherry picked from commit 048e9a1)
* [v3-2-test] Include TI UUID in scheduler, DAG processor, triggerer, and worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
vatsrahul1001 pushed a commit that referenced this pull request Apr 27, 2026
…nd worker logs (#65458) (#65476)
* [v3-2-test] Bump actions/github-script in the github-actions-updates group (#65150) (#65160)
Bumps the github-actions-updates group with 1 update: [actions/github-script](https://github.com/actions/github-script).
Updates `actions/github-script` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@ed59741...3a2844b)
(cherry picked from commit e5a047c)
---
updated-dependencies:
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* [v3-2-test] Added breeze generate issue content for airflow-ctl (#65042) (#65241)
* Add breeze generate issue content for airflow-ctl
* add new command to doc
(cherry picked from commit b24538b)
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
* [v3-2-test] Run release calendar verification on its own schedule (#65118) (#65242)
* Move release calendar verification to its own scheduled workflow
Run dev/verify_release_calendar.py from a dedicated daily scheduled
workflow instead of as a canary job in the main CI pipeline, and
notify the #release-management Slack channel when the check fails so
the issue is surfaced to release managers directly.
* Include wiki and calendar links in release calendar Slack alert
(cherry picked from commit 048e9a1)
* [v3-2-test] Include TI UUID in scheduler, DAG processor, triggerer, and worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
vatsrahul1001 pushed a commit that referenced this pull request May 20, 2026
…nd worker logs (#65458) (#65476)
* [v3-2-test] Bump actions/github-script in the github-actions-updates group (#65150) (#65160)
Bumps the github-actions-updates group with 1 update: [actions/github-script](https://github.com/actions/github-script).
Updates `actions/github-script` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@ed59741...3a2844b)
(cherry picked from commit e5a047c)
---
updated-dependencies:
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* [v3-2-test] Added breeze generate issue content for airflow-ctl (#65042) (#65241)
* Add breeze generate issue content for airflow-ctl
* add new command to doc
(cherry picked from commit b24538b)
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
* [v3-2-test] Run release calendar verification on its own schedule (#65118) (#65242)
* Move release calendar verification to its own scheduled workflow
Run dev/verify_release_calendar.py from a dedicated daily scheduled
workflow instead of as a canary job in the main CI pipeline, and
notify the #release-management Slack channel when the check fails so
the issue is surfaced to release managers directly.
* Include wiki and calendar links in release calendar Slack alert
(cherry picked from commit 048e9a1)
* [v3-2-test] Include TI UUID in scheduler, DAG processor, triggerer, and worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:DAG-processingarea:Schedulerincluding HA (high availability) schedulerarea:task-sdkarea:Triggerer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@kaxil@jedcunningham
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Include TI UUID in scheduler, DAG processor, triggerer, and worker logs - #65458

Merged
kaxil merged 4 commits into
apache:mainfrom
astronomer:kaxilnaik/ti-uuid-log-context-binding
Apr 19, 2026
Merged

Include TI UUID in scheduler, DAG processor, triggerer, and worker logs#65458
kaxil merged 4 commits into
apache:mainfrom
astronomer:kaxilnaik/ti-uuid-log-context-binding

Conversation

@kaxil

Copy link
Copy Markdown
Member

Engineers currently cannot grep a single ti_id and reconstruct a task's full lifecycle timeline. The Execution API binds ti_id to every log line via bind_contextvars, but scheduler, DAG processor, triggerer, and worker each emit it in some places and not others. This PR closes that gap.

What changed

ComponentChangeRationale
Worker (task-sdk/.../task_runner.py)bind_contextvars(ti_id=str(msg.ti.id), ...) at the top of startup()One worker process = one TI. No cross-task leak risk, so context binding is strictly correct and instruments every subsequent log line (including ones added in future changes).
Triggerer (jobs/triggerer_job_runner.py:1308)Extend the existing bind_log_contextvars(trigger_id=...) to also bind ti_id + composite TI keys when trigger.task_instance is presentrun_trigger runs inside asyncio.create_task, which snapshots the context at creation, so the binding stays scoped to that coroutine.
Scheduler (jobs/scheduler_job_runner.py)Add ti_id=%s as a positional arg to eight TI-touching log calls across _enqueue_task_instances_with_queued_state, process_executor_events, and _maybe_requeue_stuck_tiExplicit positional args (no bind_contextvars) because the scheduler is a long-running process and any unhandled exception mid-loop would leave a stale ti_id bound for the rest of the process lifetime, corrupting all subsequent log lines. Per-iteration with bound_contextvars(...) would be correct but re-indents ~200 lines of hot-path code. Positional args are both safer and less invasive.
DAG processor (dag_processing/processor.py)Add ti_id to _execute_callbacks and _execute_task_callbacks log calls (kwarg where the call is pure-structlog style, %-format positional where the call is stdlib style)Same reasoning as scheduler.

Design notes

  • Why not bind_contextvars everywhere? The earlier draft of this PR did exactly that. But we found a contextvar leak: if any exception propagates out of the per-TI loop body, the post-loop unbind_contextvars is skipped and the last TI's ti_id stays bound for the remainder of the scheduler process. Because merge_contextvars is wired into the stdlib logging foreign_pre_chain, this taints every subsequent log line -- including ones that have nothing to do with that TI. Inverts the goal of the PR. Per-iteration bound_contextvars context manager is correct but requires re-indenting long loop bodies. For the scheduler and DAG processor, explicit positional ti_id=%s args are both safer and less invasive.
  • Why is it OK to bind in the worker? A worker is a fresh process that runs exactly one TI. There is no cross-iteration or cross-TI state to leak into. The bind is strictly correct and instruments every log line for free.
  • Why is it OK to bind in the triggerer?run_trigger is launched via asyncio.create_task, which copies the current contextvars.Context at task creation. Any contextvar bound inside the coroutine is scoped to that task and cannot leak into another trigger's coroutine or the triggerer's supervisor loop.

Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
Comment threadairflow-core/src/airflow/jobs/scheduler_job_runner.py Outdated
kaxil added 3 commits April 18, 2026 22:00
…tions
Addresses review feedback from @jedcunningham on apache#65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
@kaxil
kaxil merged commit 1a0efe7 into apache:mainApr 19, 2026
107 checks passed
@kaxil
kaxil deleted the kaxilnaik/ti-uuid-log-context-binding branch April 19, 2026 00:44
@kaxilkaxil added this to the Airflow 3.2.2 milestone Apr 19, 2026
github-actionsBot pushed a commit that referenced this pull request Apr 19, 2026
…nd worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport successfully created: v3-2-test

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

StatusBranchResult
v3-2-testPR Link

vatsrahul1001 pushed a commit that referenced this pull request Apr 23, 2026
…nd worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
potiuk pushed a commit that referenced this pull request Apr 26, 2026
…nd worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
potiuk added a commit that referenced this pull request Apr 26, 2026
…nd worker logs (#65458) (#65476)
* [v3-2-test] Bump actions/github-script in the github-actions-updates group (#65150) (#65160)
Bumps the github-actions-updates group with 1 update: [actions/github-script](https://github.com/actions/github-script).
Updates `actions/github-script` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@ed59741...3a2844b)
(cherry picked from commit e5a047c)
---
updated-dependencies:
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* [v3-2-test] Added breeze generate issue content for airflow-ctl (#65042) (#65241)
* Add breeze generate issue content for airflow-ctl
* add new command to doc
(cherry picked from commit b24538b)
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
* [v3-2-test] Run release calendar verification on its own schedule (#65118) (#65242)
* Move release calendar verification to its own scheduled workflow
Run dev/verify_release_calendar.py from a dedicated daily scheduled
workflow instead of as a canary job in the main CI pipeline, and
notify the #release-management Slack channel when the check fails so
the issue is surfaced to release managers directly.
* Include wiki and calendar links in release calendar Slack alert
(cherry picked from commit 048e9a1)
* [v3-2-test] Include TI UUID in scheduler, DAG processor, triggerer, and worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
vatsrahul1001 pushed a commit that referenced this pull request Apr 27, 2026
…nd worker logs (#65458) (#65476)
* [v3-2-test] Bump actions/github-script in the github-actions-updates group (#65150) (#65160)
Bumps the github-actions-updates group with 1 update: [actions/github-script](https://github.com/actions/github-script).
Updates `actions/github-script` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@ed59741...3a2844b)
(cherry picked from commit e5a047c)
---
updated-dependencies:
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* [v3-2-test] Added breeze generate issue content for airflow-ctl (#65042) (#65241)
* Add breeze generate issue content for airflow-ctl
* add new command to doc
(cherry picked from commit b24538b)
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
* [v3-2-test] Run release calendar verification on its own schedule (#65118) (#65242)
* Move release calendar verification to its own scheduled workflow
Run dev/verify_release_calendar.py from a dedicated daily scheduled
workflow instead of as a canary job in the main CI pipeline, and
notify the #release-management Slack channel when the check fails so
the issue is surfaced to release managers directly.
* Include wiki and calendar links in release calendar Slack alert
(cherry picked from commit 048e9a1)
* [v3-2-test] Include TI UUID in scheduler, DAG processor, triggerer, and worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
vatsrahul1001 pushed a commit that referenced this pull request May 20, 2026
…nd worker logs (#65458) (#65476)
* [v3-2-test] Bump actions/github-script in the github-actions-updates group (#65150) (#65160)
Bumps the github-actions-updates group with 1 update: [actions/github-script](https://github.com/actions/github-script).
Updates `actions/github-script` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@ed59741...3a2844b)
(cherry picked from commit e5a047c)
---
updated-dependencies:
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* [v3-2-test] Added breeze generate issue content for airflow-ctl (#65042) (#65241)
* Add breeze generate issue content for airflow-ctl
* add new command to doc
(cherry picked from commit b24538b)
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
* [v3-2-test] Run release calendar verification on its own schedule (#65118) (#65242)
* Move release calendar verification to its own scheduled workflow
Run dev/verify_release_calendar.py from a dedicated daily scheduled
workflow instead of as a canary job in the main CI pipeline, and
notify the #release-management Slack channel when the check fails so
the issue is surfaced to release managers directly.
* Include wiki and calendar links in release calendar Slack alert
(cherry picked from commit 048e9a1)
* [v3-2-test] Include TI UUID in scheduler, DAG processor, triggerer, and worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:DAG-processingarea:Schedulerincluding HA (high availability) schedulerarea:task-sdkarea:Triggerer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@kaxil@jedcunningham
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Include TI UUID in scheduler, DAG processor, triggerer, and worker logs - #65458

Merged
kaxil merged 4 commits into
apache:mainfrom
astronomer:kaxilnaik/ti-uuid-log-context-binding
Apr 19, 2026
Merged

Include TI UUID in scheduler, DAG processor, triggerer, and worker logs#65458
kaxil merged 4 commits into
apache:mainfrom
astronomer:kaxilnaik/ti-uuid-log-context-binding

Conversation

@kaxil

Copy link
Copy Markdown
Member

Engineers currently cannot grep a single ti_id and reconstruct a task's full lifecycle timeline. The Execution API binds ti_id to every log line via bind_contextvars, but scheduler, DAG processor, triggerer, and worker each emit it in some places and not others. This PR closes that gap.

What changed

ComponentChangeRationale
Worker (task-sdk/.../task_runner.py)bind_contextvars(ti_id=str(msg.ti.id), ...) at the top of startup()One worker process = one TI. No cross-task leak risk, so context binding is strictly correct and instruments every subsequent log line (including ones added in future changes).
Triggerer (jobs/triggerer_job_runner.py:1308)Extend the existing bind_log_contextvars(trigger_id=...) to also bind ti_id + composite TI keys when trigger.task_instance is presentrun_trigger runs inside asyncio.create_task, which snapshots the context at creation, so the binding stays scoped to that coroutine.
Scheduler (jobs/scheduler_job_runner.py)Add ti_id=%s as a positional arg to eight TI-touching log calls across _enqueue_task_instances_with_queued_state, process_executor_events, and _maybe_requeue_stuck_tiExplicit positional args (no bind_contextvars) because the scheduler is a long-running process and any unhandled exception mid-loop would leave a stale ti_id bound for the rest of the process lifetime, corrupting all subsequent log lines. Per-iteration with bound_contextvars(...) would be correct but re-indents ~200 lines of hot-path code. Positional args are both safer and less invasive.
DAG processor (dag_processing/processor.py)Add ti_id to _execute_callbacks and _execute_task_callbacks log calls (kwarg where the call is pure-structlog style, %-format positional where the call is stdlib style)Same reasoning as scheduler.

Design notes

  • Why not bind_contextvars everywhere? The earlier draft of this PR did exactly that. But we found a contextvar leak: if any exception propagates out of the per-TI loop body, the post-loop unbind_contextvars is skipped and the last TI's ti_id stays bound for the remainder of the scheduler process. Because merge_contextvars is wired into the stdlib logging foreign_pre_chain, this taints every subsequent log line -- including ones that have nothing to do with that TI. Inverts the goal of the PR. Per-iteration bound_contextvars context manager is correct but requires re-indenting long loop bodies. For the scheduler and DAG processor, explicit positional ti_id=%s args are both safer and less invasive.
  • Why is it OK to bind in the worker? A worker is a fresh process that runs exactly one TI. There is no cross-iteration or cross-TI state to leak into. The bind is strictly correct and instruments every log line for free.
  • Why is it OK to bind in the triggerer?run_trigger is launched via asyncio.create_task, which copies the current contextvars.Context at task creation. Any contextvar bound inside the coroutine is scoped to that task and cannot leak into another trigger's coroutine or the triggerer's supervisor loop.

Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
Comment threadairflow-core/src/airflow/jobs/scheduler_job_runner.py Outdated
kaxil added 3 commits April 18, 2026 22:00
…tions
Addresses review feedback from @jedcunningham on apache#65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
@kaxil
kaxil merged commit 1a0efe7 into apache:mainApr 19, 2026
107 checks passed
@kaxil
kaxil deleted the kaxilnaik/ti-uuid-log-context-binding branch April 19, 2026 00:44
@kaxilkaxil added this to the Airflow 3.2.2 milestone Apr 19, 2026
github-actionsBot pushed a commit that referenced this pull request Apr 19, 2026
…nd worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport successfully created: v3-2-test

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

StatusBranchResult
v3-2-testPR Link

vatsrahul1001 pushed a commit that referenced this pull request Apr 23, 2026
…nd worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
potiuk pushed a commit that referenced this pull request Apr 26, 2026
…nd worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
potiuk added a commit that referenced this pull request Apr 26, 2026
…nd worker logs (#65458) (#65476)
* [v3-2-test] Bump actions/github-script in the github-actions-updates group (#65150) (#65160)
Bumps the github-actions-updates group with 1 update: [actions/github-script](https://github.com/actions/github-script).
Updates `actions/github-script` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@ed59741...3a2844b)
(cherry picked from commit e5a047c)
---
updated-dependencies:
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* [v3-2-test] Added breeze generate issue content for airflow-ctl (#65042) (#65241)
* Add breeze generate issue content for airflow-ctl
* add new command to doc
(cherry picked from commit b24538b)
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
* [v3-2-test] Run release calendar verification on its own schedule (#65118) (#65242)
* Move release calendar verification to its own scheduled workflow
Run dev/verify_release_calendar.py from a dedicated daily scheduled
workflow instead of as a canary job in the main CI pipeline, and
notify the #release-management Slack channel when the check fails so
the issue is surfaced to release managers directly.
* Include wiki and calendar links in release calendar Slack alert
(cherry picked from commit 048e9a1)
* [v3-2-test] Include TI UUID in scheduler, DAG processor, triggerer, and worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
vatsrahul1001 pushed a commit that referenced this pull request Apr 27, 2026
…nd worker logs (#65458) (#65476)
* [v3-2-test] Bump actions/github-script in the github-actions-updates group (#65150) (#65160)
Bumps the github-actions-updates group with 1 update: [actions/github-script](https://github.com/actions/github-script).
Updates `actions/github-script` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@ed59741...3a2844b)
(cherry picked from commit e5a047c)
---
updated-dependencies:
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* [v3-2-test] Added breeze generate issue content for airflow-ctl (#65042) (#65241)
* Add breeze generate issue content for airflow-ctl
* add new command to doc
(cherry picked from commit b24538b)
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
* [v3-2-test] Run release calendar verification on its own schedule (#65118) (#65242)
* Move release calendar verification to its own scheduled workflow
Run dev/verify_release_calendar.py from a dedicated daily scheduled
workflow instead of as a canary job in the main CI pipeline, and
notify the #release-management Slack channel when the check fails so
the issue is surfaced to release managers directly.
* Include wiki and calendar links in release calendar Slack alert
(cherry picked from commit 048e9a1)
* [v3-2-test] Include TI UUID in scheduler, DAG processor, triggerer, and worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
vatsrahul1001 pushed a commit that referenced this pull request May 20, 2026
…nd worker logs (#65458) (#65476)
* [v3-2-test] Bump actions/github-script in the github-actions-updates group (#65150) (#65160)
Bumps the github-actions-updates group with 1 update: [actions/github-script](https://github.com/actions/github-script).
Updates `actions/github-script` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@ed59741...3a2844b)
(cherry picked from commit e5a047c)
---
updated-dependencies:
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* [v3-2-test] Added breeze generate issue content for airflow-ctl (#65042) (#65241)
* Add breeze generate issue content for airflow-ctl
* add new command to doc
(cherry picked from commit b24538b)
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
* [v3-2-test] Run release calendar verification on its own schedule (#65118) (#65242)
* Move release calendar verification to its own scheduled workflow
Run dev/verify_release_calendar.py from a dedicated daily scheduled
workflow instead of as a canary job in the main CI pipeline, and
notify the #release-management Slack channel when the check fails so
the issue is surfaced to release managers directly.
* Include wiki and calendar links in release calendar Slack alert
(cherry picked from commit 048e9a1)
* [v3-2-test] Include TI UUID in scheduler, DAG processor, triggerer, and worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:DAG-processingarea:Schedulerincluding HA (high availability) schedulerarea:task-sdkarea:Triggerer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@kaxil@jedcunningham
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Include TI UUID in scheduler, DAG processor, triggerer, and worker logs - #65458

Merged
kaxil merged 4 commits into
apache:mainfrom
astronomer:kaxilnaik/ti-uuid-log-context-binding
Apr 19, 2026
Merged

Include TI UUID in scheduler, DAG processor, triggerer, and worker logs#65458
kaxil merged 4 commits into
apache:mainfrom
astronomer:kaxilnaik/ti-uuid-log-context-binding

Conversation

@kaxil

Copy link
Copy Markdown
Member

Engineers currently cannot grep a single ti_id and reconstruct a task's full lifecycle timeline. The Execution API binds ti_id to every log line via bind_contextvars, but scheduler, DAG processor, triggerer, and worker each emit it in some places and not others. This PR closes that gap.

What changed

ComponentChangeRationale
Worker (task-sdk/.../task_runner.py)bind_contextvars(ti_id=str(msg.ti.id), ...) at the top of startup()One worker process = one TI. No cross-task leak risk, so context binding is strictly correct and instruments every subsequent log line (including ones added in future changes).
Triggerer (jobs/triggerer_job_runner.py:1308)Extend the existing bind_log_contextvars(trigger_id=...) to also bind ti_id + composite TI keys when trigger.task_instance is presentrun_trigger runs inside asyncio.create_task, which snapshots the context at creation, so the binding stays scoped to that coroutine.
Scheduler (jobs/scheduler_job_runner.py)Add ti_id=%s as a positional arg to eight TI-touching log calls across _enqueue_task_instances_with_queued_state, process_executor_events, and _maybe_requeue_stuck_tiExplicit positional args (no bind_contextvars) because the scheduler is a long-running process and any unhandled exception mid-loop would leave a stale ti_id bound for the rest of the process lifetime, corrupting all subsequent log lines. Per-iteration with bound_contextvars(...) would be correct but re-indents ~200 lines of hot-path code. Positional args are both safer and less invasive.
DAG processor (dag_processing/processor.py)Add ti_id to _execute_callbacks and _execute_task_callbacks log calls (kwarg where the call is pure-structlog style, %-format positional where the call is stdlib style)Same reasoning as scheduler.

Design notes

  • Why not bind_contextvars everywhere? The earlier draft of this PR did exactly that. But we found a contextvar leak: if any exception propagates out of the per-TI loop body, the post-loop unbind_contextvars is skipped and the last TI's ti_id stays bound for the remainder of the scheduler process. Because merge_contextvars is wired into the stdlib logging foreign_pre_chain, this taints every subsequent log line -- including ones that have nothing to do with that TI. Inverts the goal of the PR. Per-iteration bound_contextvars context manager is correct but requires re-indenting long loop bodies. For the scheduler and DAG processor, explicit positional ti_id=%s args are both safer and less invasive.
  • Why is it OK to bind in the worker? A worker is a fresh process that runs exactly one TI. There is no cross-iteration or cross-TI state to leak into. The bind is strictly correct and instruments every log line for free.
  • Why is it OK to bind in the triggerer?run_trigger is launched via asyncio.create_task, which copies the current contextvars.Context at task creation. Any contextvar bound inside the coroutine is scoped to that task and cannot leak into another trigger's coroutine or the triggerer's supervisor loop.

Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
Comment threadairflow-core/src/airflow/jobs/scheduler_job_runner.py Outdated
kaxil added 3 commits April 18, 2026 22:00
…tions
Addresses review feedback from @jedcunningham on apache#65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
@kaxil
kaxil merged commit 1a0efe7 into apache:mainApr 19, 2026
107 checks passed
@kaxil
kaxil deleted the kaxilnaik/ti-uuid-log-context-binding branch April 19, 2026 00:44
@kaxilkaxil added this to the Airflow 3.2.2 milestone Apr 19, 2026
github-actionsBot pushed a commit that referenced this pull request Apr 19, 2026
…nd worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport successfully created: v3-2-test

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

StatusBranchResult
v3-2-testPR Link

vatsrahul1001 pushed a commit that referenced this pull request Apr 23, 2026
…nd worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
potiuk pushed a commit that referenced this pull request Apr 26, 2026
…nd worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
potiuk added a commit that referenced this pull request Apr 26, 2026
…nd worker logs (#65458) (#65476)
* [v3-2-test] Bump actions/github-script in the github-actions-updates group (#65150) (#65160)
Bumps the github-actions-updates group with 1 update: [actions/github-script](https://github.com/actions/github-script).
Updates `actions/github-script` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@ed59741...3a2844b)
(cherry picked from commit e5a047c)
---
updated-dependencies:
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* [v3-2-test] Added breeze generate issue content for airflow-ctl (#65042) (#65241)
* Add breeze generate issue content for airflow-ctl
* add new command to doc
(cherry picked from commit b24538b)
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
* [v3-2-test] Run release calendar verification on its own schedule (#65118) (#65242)
* Move release calendar verification to its own scheduled workflow
Run dev/verify_release_calendar.py from a dedicated daily scheduled
workflow instead of as a canary job in the main CI pipeline, and
notify the #release-management Slack channel when the check fails so
the issue is surfaced to release managers directly.
* Include wiki and calendar links in release calendar Slack alert
(cherry picked from commit 048e9a1)
* [v3-2-test] Include TI UUID in scheduler, DAG processor, triggerer, and worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
vatsrahul1001 pushed a commit that referenced this pull request Apr 27, 2026
…nd worker logs (#65458) (#65476)
* [v3-2-test] Bump actions/github-script in the github-actions-updates group (#65150) (#65160)
Bumps the github-actions-updates group with 1 update: [actions/github-script](https://github.com/actions/github-script).
Updates `actions/github-script` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@ed59741...3a2844b)
(cherry picked from commit e5a047c)
---
updated-dependencies:
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* [v3-2-test] Added breeze generate issue content for airflow-ctl (#65042) (#65241)
* Add breeze generate issue content for airflow-ctl
* add new command to doc
(cherry picked from commit b24538b)
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
* [v3-2-test] Run release calendar verification on its own schedule (#65118) (#65242)
* Move release calendar verification to its own scheduled workflow
Run dev/verify_release_calendar.py from a dedicated daily scheduled
workflow instead of as a canary job in the main CI pipeline, and
notify the #release-management Slack channel when the check fails so
the issue is surfaced to release managers directly.
* Include wiki and calendar links in release calendar Slack alert
(cherry picked from commit 048e9a1)
* [v3-2-test] Include TI UUID in scheduler, DAG processor, triggerer, and worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
vatsrahul1001 pushed a commit that referenced this pull request May 20, 2026
…nd worker logs (#65458) (#65476)
* [v3-2-test] Bump actions/github-script in the github-actions-updates group (#65150) (#65160)
Bumps the github-actions-updates group with 1 update: [actions/github-script](https://github.com/actions/github-script).
Updates `actions/github-script` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@ed59741...3a2844b)
(cherry picked from commit e5a047c)
---
updated-dependencies:
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* [v3-2-test] Added breeze generate issue content for airflow-ctl (#65042) (#65241)
* Add breeze generate issue content for airflow-ctl
* add new command to doc
(cherry picked from commit b24538b)
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
* [v3-2-test] Run release calendar verification on its own schedule (#65118) (#65242)
* Move release calendar verification to its own scheduled workflow
Run dev/verify_release_calendar.py from a dedicated daily scheduled
workflow instead of as a canary job in the main CI pipeline, and
notify the #release-management Slack channel when the check fails so
the issue is surfaced to release managers directly.
* Include wiki and calendar links in release calendar Slack alert
(cherry picked from commit 048e9a1)
* [v3-2-test] Include TI UUID in scheduler, DAG processor, triggerer, and worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:DAG-processingarea:Schedulerincluding HA (high availability) schedulerarea:task-sdkarea:Triggerer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@kaxil@jedcunningham
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Include TI UUID in scheduler, DAG processor, triggerer, and worker logs - #65458

Merged
kaxil merged 4 commits into
apache:mainfrom
astronomer:kaxilnaik/ti-uuid-log-context-binding
Apr 19, 2026
Merged

Include TI UUID in scheduler, DAG processor, triggerer, and worker logs#65458
kaxil merged 4 commits into
apache:mainfrom
astronomer:kaxilnaik/ti-uuid-log-context-binding

Conversation

@kaxil

Copy link
Copy Markdown
Member

Engineers currently cannot grep a single ti_id and reconstruct a task's full lifecycle timeline. The Execution API binds ti_id to every log line via bind_contextvars, but scheduler, DAG processor, triggerer, and worker each emit it in some places and not others. This PR closes that gap.

What changed

ComponentChangeRationale
Worker (task-sdk/.../task_runner.py)bind_contextvars(ti_id=str(msg.ti.id), ...) at the top of startup()One worker process = one TI. No cross-task leak risk, so context binding is strictly correct and instruments every subsequent log line (including ones added in future changes).
Triggerer (jobs/triggerer_job_runner.py:1308)Extend the existing bind_log_contextvars(trigger_id=...) to also bind ti_id + composite TI keys when trigger.task_instance is presentrun_trigger runs inside asyncio.create_task, which snapshots the context at creation, so the binding stays scoped to that coroutine.
Scheduler (jobs/scheduler_job_runner.py)Add ti_id=%s as a positional arg to eight TI-touching log calls across _enqueue_task_instances_with_queued_state, process_executor_events, and _maybe_requeue_stuck_tiExplicit positional args (no bind_contextvars) because the scheduler is a long-running process and any unhandled exception mid-loop would leave a stale ti_id bound for the rest of the process lifetime, corrupting all subsequent log lines. Per-iteration with bound_contextvars(...) would be correct but re-indents ~200 lines of hot-path code. Positional args are both safer and less invasive.
DAG processor (dag_processing/processor.py)Add ti_id to _execute_callbacks and _execute_task_callbacks log calls (kwarg where the call is pure-structlog style, %-format positional where the call is stdlib style)Same reasoning as scheduler.

Design notes

  • Why not bind_contextvars everywhere? The earlier draft of this PR did exactly that. But we found a contextvar leak: if any exception propagates out of the per-TI loop body, the post-loop unbind_contextvars is skipped and the last TI's ti_id stays bound for the remainder of the scheduler process. Because merge_contextvars is wired into the stdlib logging foreign_pre_chain, this taints every subsequent log line -- including ones that have nothing to do with that TI. Inverts the goal of the PR. Per-iteration bound_contextvars context manager is correct but requires re-indenting long loop bodies. For the scheduler and DAG processor, explicit positional ti_id=%s args are both safer and less invasive.
  • Why is it OK to bind in the worker? A worker is a fresh process that runs exactly one TI. There is no cross-iteration or cross-TI state to leak into. The bind is strictly correct and instruments every log line for free.
  • Why is it OK to bind in the triggerer?run_trigger is launched via asyncio.create_task, which copies the current contextvars.Context at task creation. Any contextvar bound inside the coroutine is scoped to that task and cannot leak into another trigger's coroutine or the triggerer's supervisor loop.

Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
Comment threadairflow-core/src/airflow/jobs/scheduler_job_runner.py Outdated
kaxil added 3 commits April 18, 2026 22:00
…tions
Addresses review feedback from @jedcunningham on apache#65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
@kaxil
kaxil merged commit 1a0efe7 into apache:mainApr 19, 2026
107 checks passed
@kaxil
kaxil deleted the kaxilnaik/ti-uuid-log-context-binding branch April 19, 2026 00:44
@kaxilkaxil added this to the Airflow 3.2.2 milestone Apr 19, 2026
github-actionsBot pushed a commit that referenced this pull request Apr 19, 2026
…nd worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport successfully created: v3-2-test

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

StatusBranchResult
v3-2-testPR Link

vatsrahul1001 pushed a commit that referenced this pull request Apr 23, 2026
…nd worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
potiuk pushed a commit that referenced this pull request Apr 26, 2026
…nd worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
potiuk added a commit that referenced this pull request Apr 26, 2026
…nd worker logs (#65458) (#65476)
* [v3-2-test] Bump actions/github-script in the github-actions-updates group (#65150) (#65160)
Bumps the github-actions-updates group with 1 update: [actions/github-script](https://github.com/actions/github-script).
Updates `actions/github-script` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@ed59741...3a2844b)
(cherry picked from commit e5a047c)
---
updated-dependencies:
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* [v3-2-test] Added breeze generate issue content for airflow-ctl (#65042) (#65241)
* Add breeze generate issue content for airflow-ctl
* add new command to doc
(cherry picked from commit b24538b)
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
* [v3-2-test] Run release calendar verification on its own schedule (#65118) (#65242)
* Move release calendar verification to its own scheduled workflow
Run dev/verify_release_calendar.py from a dedicated daily scheduled
workflow instead of as a canary job in the main CI pipeline, and
notify the #release-management Slack channel when the check fails so
the issue is surfaced to release managers directly.
* Include wiki and calendar links in release calendar Slack alert
(cherry picked from commit 048e9a1)
* [v3-2-test] Include TI UUID in scheduler, DAG processor, triggerer, and worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
vatsrahul1001 pushed a commit that referenced this pull request Apr 27, 2026
…nd worker logs (#65458) (#65476)
* [v3-2-test] Bump actions/github-script in the github-actions-updates group (#65150) (#65160)
Bumps the github-actions-updates group with 1 update: [actions/github-script](https://github.com/actions/github-script).
Updates `actions/github-script` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@ed59741...3a2844b)
(cherry picked from commit e5a047c)
---
updated-dependencies:
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* [v3-2-test] Added breeze generate issue content for airflow-ctl (#65042) (#65241)
* Add breeze generate issue content for airflow-ctl
* add new command to doc
(cherry picked from commit b24538b)
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
* [v3-2-test] Run release calendar verification on its own schedule (#65118) (#65242)
* Move release calendar verification to its own scheduled workflow
Run dev/verify_release_calendar.py from a dedicated daily scheduled
workflow instead of as a canary job in the main CI pipeline, and
notify the #release-management Slack channel when the check fails so
the issue is surfaced to release managers directly.
* Include wiki and calendar links in release calendar Slack alert
(cherry picked from commit 048e9a1)
* [v3-2-test] Include TI UUID in scheduler, DAG processor, triggerer, and worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
vatsrahul1001 pushed a commit that referenced this pull request May 20, 2026
…nd worker logs (#65458) (#65476)
* [v3-2-test] Bump actions/github-script in the github-actions-updates group (#65150) (#65160)
Bumps the github-actions-updates group with 1 update: [actions/github-script](https://github.com/actions/github-script).
Updates `actions/github-script` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@ed59741...3a2844b)
(cherry picked from commit e5a047c)
---
updated-dependencies:
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* [v3-2-test] Added breeze generate issue content for airflow-ctl (#65042) (#65241)
* Add breeze generate issue content for airflow-ctl
* add new command to doc
(cherry picked from commit b24538b)
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
* [v3-2-test] Run release calendar verification on its own schedule (#65118) (#65242)
* Move release calendar verification to its own scheduled workflow
Run dev/verify_release_calendar.py from a dedicated daily scheduled
workflow instead of as a canary job in the main CI pipeline, and
notify the #release-management Slack channel when the check fails so
the issue is surfaced to release managers directly.
* Include wiki and calendar links in release calendar Slack alert
(cherry picked from commit 048e9a1)
* [v3-2-test] Include TI UUID in scheduler, DAG processor, triggerer, and worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:DAG-processingarea:Schedulerincluding HA (high availability) schedulerarea:task-sdkarea:Triggerer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@kaxil@jedcunningham
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Include TI UUID in scheduler, DAG processor, triggerer, and worker logs - #65458

Merged
kaxil merged 4 commits into
apache:mainfrom
astronomer:kaxilnaik/ti-uuid-log-context-binding
Apr 19, 2026
Merged

Include TI UUID in scheduler, DAG processor, triggerer, and worker logs#65458
kaxil merged 4 commits into
apache:mainfrom
astronomer:kaxilnaik/ti-uuid-log-context-binding

Conversation

@kaxil

Copy link
Copy Markdown
Member

Engineers currently cannot grep a single ti_id and reconstruct a task's full lifecycle timeline. The Execution API binds ti_id to every log line via bind_contextvars, but scheduler, DAG processor, triggerer, and worker each emit it in some places and not others. This PR closes that gap.

What changed

ComponentChangeRationale
Worker (task-sdk/.../task_runner.py)bind_contextvars(ti_id=str(msg.ti.id), ...) at the top of startup()One worker process = one TI. No cross-task leak risk, so context binding is strictly correct and instruments every subsequent log line (including ones added in future changes).
Triggerer (jobs/triggerer_job_runner.py:1308)Extend the existing bind_log_contextvars(trigger_id=...) to also bind ti_id + composite TI keys when trigger.task_instance is presentrun_trigger runs inside asyncio.create_task, which snapshots the context at creation, so the binding stays scoped to that coroutine.
Scheduler (jobs/scheduler_job_runner.py)Add ti_id=%s as a positional arg to eight TI-touching log calls across _enqueue_task_instances_with_queued_state, process_executor_events, and _maybe_requeue_stuck_tiExplicit positional args (no bind_contextvars) because the scheduler is a long-running process and any unhandled exception mid-loop would leave a stale ti_id bound for the rest of the process lifetime, corrupting all subsequent log lines. Per-iteration with bound_contextvars(...) would be correct but re-indents ~200 lines of hot-path code. Positional args are both safer and less invasive.
DAG processor (dag_processing/processor.py)Add ti_id to _execute_callbacks and _execute_task_callbacks log calls (kwarg where the call is pure-structlog style, %-format positional where the call is stdlib style)Same reasoning as scheduler.

Design notes

  • Why not bind_contextvars everywhere? The earlier draft of this PR did exactly that. But we found a contextvar leak: if any exception propagates out of the per-TI loop body, the post-loop unbind_contextvars is skipped and the last TI's ti_id stays bound for the remainder of the scheduler process. Because merge_contextvars is wired into the stdlib logging foreign_pre_chain, this taints every subsequent log line -- including ones that have nothing to do with that TI. Inverts the goal of the PR. Per-iteration bound_contextvars context manager is correct but requires re-indenting long loop bodies. For the scheduler and DAG processor, explicit positional ti_id=%s args are both safer and less invasive.
  • Why is it OK to bind in the worker? A worker is a fresh process that runs exactly one TI. There is no cross-iteration or cross-TI state to leak into. The bind is strictly correct and instruments every log line for free.
  • Why is it OK to bind in the triggerer?run_trigger is launched via asyncio.create_task, which copies the current contextvars.Context at task creation. Any contextvar bound inside the coroutine is scoped to that task and cannot leak into another trigger's coroutine or the triggerer's supervisor loop.

Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
Comment threadairflow-core/src/airflow/jobs/scheduler_job_runner.py Outdated
kaxil added 3 commits April 18, 2026 22:00
…tions
Addresses review feedback from @jedcunningham on apache#65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
@kaxil
kaxil merged commit 1a0efe7 into apache:mainApr 19, 2026
107 checks passed
@kaxil
kaxil deleted the kaxilnaik/ti-uuid-log-context-binding branch April 19, 2026 00:44
@kaxilkaxil added this to the Airflow 3.2.2 milestone Apr 19, 2026
github-actionsBot pushed a commit that referenced this pull request Apr 19, 2026
…nd worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport successfully created: v3-2-test

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

StatusBranchResult
v3-2-testPR Link

vatsrahul1001 pushed a commit that referenced this pull request Apr 23, 2026
…nd worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
potiuk pushed a commit that referenced this pull request Apr 26, 2026
…nd worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
potiuk added a commit that referenced this pull request Apr 26, 2026
…nd worker logs (#65458) (#65476)
* [v3-2-test] Bump actions/github-script in the github-actions-updates group (#65150) (#65160)
Bumps the github-actions-updates group with 1 update: [actions/github-script](https://github.com/actions/github-script).
Updates `actions/github-script` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@ed59741...3a2844b)
(cherry picked from commit e5a047c)
---
updated-dependencies:
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* [v3-2-test] Added breeze generate issue content for airflow-ctl (#65042) (#65241)
* Add breeze generate issue content for airflow-ctl
* add new command to doc
(cherry picked from commit b24538b)
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
* [v3-2-test] Run release calendar verification on its own schedule (#65118) (#65242)
* Move release calendar verification to its own scheduled workflow
Run dev/verify_release_calendar.py from a dedicated daily scheduled
workflow instead of as a canary job in the main CI pipeline, and
notify the #release-management Slack channel when the check fails so
the issue is surfaced to release managers directly.
* Include wiki and calendar links in release calendar Slack alert
(cherry picked from commit 048e9a1)
* [v3-2-test] Include TI UUID in scheduler, DAG processor, triggerer, and worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
vatsrahul1001 pushed a commit that referenced this pull request Apr 27, 2026
…nd worker logs (#65458) (#65476)
* [v3-2-test] Bump actions/github-script in the github-actions-updates group (#65150) (#65160)
Bumps the github-actions-updates group with 1 update: [actions/github-script](https://github.com/actions/github-script).
Updates `actions/github-script` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@ed59741...3a2844b)
(cherry picked from commit e5a047c)
---
updated-dependencies:
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* [v3-2-test] Added breeze generate issue content for airflow-ctl (#65042) (#65241)
* Add breeze generate issue content for airflow-ctl
* add new command to doc
(cherry picked from commit b24538b)
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
* [v3-2-test] Run release calendar verification on its own schedule (#65118) (#65242)
* Move release calendar verification to its own scheduled workflow
Run dev/verify_release_calendar.py from a dedicated daily scheduled
workflow instead of as a canary job in the main CI pipeline, and
notify the #release-management Slack channel when the check fails so
the issue is surfaced to release managers directly.
* Include wiki and calendar links in release calendar Slack alert
(cherry picked from commit 048e9a1)
* [v3-2-test] Include TI UUID in scheduler, DAG processor, triggerer, and worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
vatsrahul1001 pushed a commit that referenced this pull request May 20, 2026
…nd worker logs (#65458) (#65476)
* [v3-2-test] Bump actions/github-script in the github-actions-updates group (#65150) (#65160)
Bumps the github-actions-updates group with 1 update: [actions/github-script](https://github.com/actions/github-script).
Updates `actions/github-script` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@ed59741...3a2844b)
(cherry picked from commit e5a047c)
---
updated-dependencies:
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* [v3-2-test] Added breeze generate issue content for airflow-ctl (#65042) (#65241)
* Add breeze generate issue content for airflow-ctl
* add new command to doc
(cherry picked from commit b24538b)
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
* [v3-2-test] Run release calendar verification on its own schedule (#65118) (#65242)
* Move release calendar verification to its own scheduled workflow
Run dev/verify_release_calendar.py from a dedicated daily scheduled
workflow instead of as a canary job in the main CI pipeline, and
notify the #release-management Slack channel when the check fails so
the issue is surfaced to release managers directly.
* Include wiki and calendar links in release calendar Slack alert
(cherry picked from commit 048e9a1)
* [v3-2-test] Include TI UUID in scheduler, DAG processor, triggerer, and worker logs (#65458)
Support engineers could not reconstruct a task's full lifecycle from logs
because only the Execution API emitted the TaskInstance UUID consistently.
Adding ti_id to log lines across the other components makes 'grep ti_id=X'
surface every log touching that task, from scheduling through completion.
- Worker: bind ti_id to structlog context at startup(). Fresh process per
TI means no cross-task leak risk.
- Triggerer: extend existing bind_log_contextvars at trigger start. The
asyncio.create_task context copy scopes the binding per coroutine.
- Scheduler: add ti_id=%s to eight TI-touching log calls across
_enqueue_task_instances_with_queued_state, process_executor_events,
and _maybe_requeue_stuck_ti. Explicit positional args avoid the
contextvar leak a bind+unbind pattern would introduce on exception paths.
- DAG processor: add ti_id to callback-processing log lines in
_execute_callbacks and _execute_task_callbacks.
* Move ti_id into TaskInstance.__repr__; revert redundant log-line additions
Addresses review feedback from @jedcunningham on #65458: instead of
sprinkling ti_id=%s onto individual scheduler log lines, put the UUID in
TaskInstance.__repr__ once and let every %s-formatted TI log line inherit
it for free. Strictly better: covers log lines this PR didn't touch and
lines added by future PRs without further plumbing.
Net diff vs main goes from +61/-16 to +51/-14.
Changes:
- TaskInstance.__repr__ now appends `ti_id={self.id}` before the closing
bracket (matches the existing TaskInstanceNote repr precedent).
- Reverted 10 log-line ti_id additions in scheduler_job_runner.py where
the existing `%s` format arg was a TaskInstance; repr now supplies ti_id.
- Kept the explicit `ti_id=%s` in the "TaskInstance Finished" msg: it
formats individual fields (dag_id, task_id, etc.), not %s on the TI,
so the repr shortcut does not apply.
- Kept DAG processor structlog-kwargs ti_id additions: those go through
structlog's kwargs path, not __repr__.
- Updated one test assertion in test_scheduler_job.py that hardcoded the
exact TaskInstance repr string.
* Update test_not_enough_pool_slots for new TaskInstance repr
After adding ti_id to TaskInstance.__repr__, test_not_enough_pool_slots
needs to include ti_id in the expected log substring. Same fix pattern
as test_process_executor_events_with_callback at line 695.
* Fix test_not_enough_pool_slots ordering assumption on MySQL
dr.task_instances[0] can return can_run first on MySQL (alphabetical
default ordering) instead of cannot_run, so the expected ti_id used
in the "Not executing" assertion grabbed the wrong task's UUID and
the substring check failed on MySQL CI even though it passed on SQLite.
Look up the TI by task_id instead to make the assertion order-independent.
(cherry picked from commit 1a0efe7)
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Co-authored-by: Justin Pakzad <114518232+justinpakzad@users.noreply.github.com>
Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:DAG-processingarea:Schedulerincluding HA (high availability) schedulerarea:task-sdkarea:Triggerer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@kaxil@jedcunningham