Skip to content

Add API endpoint and UI for deadline callback log visibility - #66610

Closed
seanghaeli wants to merge 14 commits into
apache:mainfrom
aws-mwaa:ghaeli/callback-log-ui-visibility
Closed

Add API endpoint and UI for deadline callback log visibility#66610
seanghaeli wants to merge 14 commits into
apache:mainfrom
aws-mwaa:ghaeli/callback-log-ui-visibility

Conversation

@seanghaeli

@seanghaeliseanghaeli commented May 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Make deadline callback logs visible in the Airflow UI. When a deadline is missed and a callback fires, users can now view the callback's execution logs directly from the web interface.

Changes

Backend:

  • New CallbackLogReader utility (airflow-core/src/airflow/utils/log/callback_log_reader.py) — reads callback logs from remote or local storage using the same RemoteLogIO infrastructure as task logs
  • New API endpoint GET /ui/dags/{dag_id}/dagRuns/{dag_run_id}/callbacks/{callback_id}/logs on the deadlines UI router
  • Updated DeadlineResponse datamodel — added callback_id and callback_state fields

Frontend:

  • CallbackLogViewer.tsx component — fetches and displays callback execution logs in a modal dialog
  • Simplified deadline status display: inline "Missed" badge + "Callback Logs" button + expected/actual times (no success badge, no "Finished late" text, no alert name)

Motivation

Kaxil requested this: "Some way to know if a deadline didn't fire and it failed: something on UI would be great, like Task log. If deadlines are visible, it would be even better experience than previous SLAs."

Dependencies


Was generative AI tooling used to co-author this PR?
  • Yes — Claude Code (Opus 4.6)

Generated-by: Claude Code (Opus 4.6) following the guidelines

End-to-End Verification

Verified in breeze with full Airflow stack (scheduler + dag-processor + triggerer + API server + LocalExecutor). A test DAG with a SyncCallback deadline was parsed, unpaused, scheduled, and the deadline missed naturally. The callback executed through supervise_callback() and produced real structured logs.

Dag Run Page — Deadline Status with Callback

Deadline Status in Dag Run

Callback Logs Modal

Callback Logs Modal

Verified:

  • 6/6 API unit tests pass
  • 12/12 log reader unit tests pass
  • Path traversal protection via regex validation + os.path.commonpath containment check
  • Stress tested with 10K-100K line logs: 10K lines renders in 0.23s, acceptable for realistic callback output

@boring-cyborgboring-cyborgBot added area:API Airflow's REST/HTTP API area:logging labels May 8, 2026
Comment threadairflow-core/src/airflow/utils/log/callback_log_reader.py Fixed
@seanghaeli
seanghaeliforce-pushed the ghaeli/callback-log-ui-visibility branch from 2b1e618 to 9102967CompareMay 13, 2026 22:37
Comment threadairflow-core/src/airflow/utils/log/callback_log_reader.py Fixed
@seanghaeli
seanghaeliforce-pushed the ghaeli/callback-log-ui-visibility branch 9 times, most recently from e303ead to f6103b3CompareMay 23, 2026 17:36
Comment threadairflow-core/src/airflow/utils/log/callback_log_reader.py Fixed
@vincbeck

Copy link
Copy Markdown
Contributor

I like the interface but I would like to have @bbovenzi opinion on this one

@guan404mingguan404ming left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks nice for ui wise. Just left some comments

Comment threadairflow-core/src/airflow/ui/src/pages/Run/CallbackLogViewer.tsx Outdated
Comment threadairflow-core/src/airflow/ui/src/pages/Run/CallbackLogViewer.tsx Outdated

@jason810496jason810496 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice feature! LGTM overall.

Comment threadairflow-core/src/airflow/api_fastapi/core_api/routes/ui/deadlines.py Outdated
Comment threadairflow-core/src/airflow/jobs/triggerer_job_runner.py
Comment threadairflow-core/src/airflow/utils/log/callback_log_reader.py Outdated
Comment threadairflow-core/src/airflow/utils/log/callback_log_reader.py Outdated
Comment threadairflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/deadline.py Outdated
Comment threadairflow-core/src/airflow/utils/log/callback_log_reader.py
@seanghaeli

Copy link
Copy Markdown
ContributorAuthor

Thanks for the review @jason810496 Fixed what I think is mission critical / low-risk, left off some others for follow on PRs. Let me know if you disagree with any of the points I've chosen to defer

@seanghaeli
seanghaeliforce-pushed the ghaeli/callback-log-ui-visibility branch 2 times, most recently from d0fd5c3 to 131fe8aCompareJune 22, 2026 20:27

@ferruzziferruzzi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like main may be down, but other than that I think this has run its course and can be approved/merged?

@seanghaeli
seanghaeliforce-pushed the ghaeli/callback-log-ui-visibility branch 3 times, most recently from 432e0c4 to f7cefa0CompareJune 23, 2026 07:13

@pierrejeambrunpierrejeambrun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a UI perspective this looks unpolished.

  • Competing card title with TI / Run state (not specific to this PR can be a follow up)
  • Callback button style is off compared to other buttons in the header. (borderless, icons, position dropped mid card)
  • Deadline section in the header (not spefific to this PR can be a follow up)

Comment on lines +46 to +58
const parseCallbackLogs = (
data: TaskInstancesLogResponse["content"],
translate: TFunction,
): Array<ParsedLogEntry> => {
let lineNumber = 0;
const lineNumbers = data.map((datum) => {
const text = typeof datum === "string" ? datum : datum.event;

if (text.includes("::group::") || text.includes("::endgroup::")) {
return undefined;
}
const current = lineNumber;

@pierrejeambrunpierrejeambrunJun 23, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks duplicated from the current parseLog function we can probably re-use some pieces instead.

Comment on lines +156 to 158
// Single deadline — show inline with Expected / Actual times.
const [dl] = deadlines;

@pierrejeambrunpierrejeambrunJun 23, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't an entity have multiple missed dealines?

I don't think the way we display this information should have 3 branches based on items count. It's making things confusing.

But that's not related to this PR, can be a follow up.

Can you add screenshot for the log button when there are multiple callbacks/deadlines to show.

Sean Ghaeli added 14 commits June 23, 2026 16:50
Add a callback-logs API endpoint and UI viewer so users can see the logs
produced during async (triggerer) and executor deadline callback execution,
matching the task-log UX. Includes the callback log reader, the
CallbackLogViewer component, and triggerer-side callback log capture/upload.
…anceState
Address review feedback on the deadline callback-log viewer:
- Replace the hand-rolled stateColorMap + Badge with the shared StateBadge
component (maps state to colorPalette and adds the state icon), matching
the usage in Run/Details.
- Type the callbackState prop as TaskInstanceState instead of a bare string;
cast dl.callback_state at the two call sites since the generated API schema
types it as string.
…ructuredLog)
The prettier import-sort is case-sensitive (uppercase before lowercase),
so StateBadge must precede renderStructuredLog. Fixes the ts-compile-lint-ui
static check.
…e as CallbackState
- read_callback_log is a generator, so path-validation ValueError fires during
iteration; move list() inside the try so the 400 is actually raised (was dead code).
- Annotate DeadlineResponse.callback_state as CallbackState instead of bare str.
(Skipped the StreamingLogResponse alias suggestion — that alias isn't present on this
branch's base; the explicit tuple type is correct here.)
Address review: annotate _read_callback_remote_logs and _read_callback_local_logs
with the existing StreamingLogResponse alias (= tuple[LogSourceInfo, list[RawLogStream]])
from airflow._shared.logging.remote, instead of the inlined tuple type. Imported under
TYPE_CHECKING (annotation-only, no runtime cost).
… spec
- _create_workload: read trigger.callback via getattr (it's a Trigger-model attr, not on
BaseEventTrigger) so asset-only triggers / spec'd test mocks don't AttributeError.
- Regenerate _private_ui.yaml for the callback_state CallbackState enum change.
The callback_state field on DeadlineResponse is now typed as the
CallbackState enum (str enum) rather than a bare string. Regenerate the
UI OpenAPI client so schemas.gen.ts/types.gen.ts include the CallbackState
schema + type and reference it from DeadlineResponse, matching what
'pnpm codegen' produces. Fixes the ts-compile-lint-ui static check
('files were modified by this hook').
datamodel-codegen emits the CalendarTimeRangeResponse 'state' enum alias
immediately after the response type, then CallbackState. Match that order
in types.gen.ts so ts-compile-lint-ui's codegen step produces no diff.
…anges
apache#66608 (now in main) added skip-on-missing-DagRun logic to _create_workload
that returns None before the callback logging setup ran, so logger_cache was
never populated. Move the TriggerLoggingFactory setup ahead of the context
fetch + skip check, so callback logs are captured even when context fetch
fails. Update the test to stub _fetch_callback_dag_run_data (via
mocker.patch.object, since the supervisor is attrs-slotted) so a workload is
built.
Verified locally: 1 passed.
Rebase conflict resolution kept the completionRule(alert) usage from apache#66608
but dropped the DeadlineAlertResponse type import, so eslint flagged
.interval/.reference_type as unsafe member access on an unresolved type
(20 errors in ts-compile-lint-ui). Add the missing import to both
DeadlineStatus.tsx and DeadlineStatusModal.tsx.
@seanghaeli
seanghaeliforce-pushed the ghaeli/callback-log-ui-visibility branch from e5e6615 to a8409baCompareJune 23, 2026 16:50
@seanghaeli

Copy link
Copy Markdown
ContributorAuthor

Closing this for now, it has a data-layer dependency on the work in #66608, which has been fully reverted (#68909). Will circle back when the dependencies are merged

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:APIAirflow's REST/HTTP APIarea:loggingready for maintainer reviewSet after triaging when all criteria pass.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

11 participants

@seanghaeli@vincbeck@potiuk@ferruzzi@ashb@SameerMesiah97@pierrejeambrun@github-advanced-security@jason810496@guan404ming@bbovenzi