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

@seanghaeli seanghaeli 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-cyborg boring-cyborg Bot added area:API Airflow's REST/HTTP API area:logging labels May 8, 2026
Comment thread airflow-core/src/airflow/utils/log/callback_log_reader.py Fixed
@seanghaeli seanghaeli force-pushed the ghaeli/callback-log-ui-visibility branch from 2b1e618 to 9102967 Compare May 13, 2026 22:37
Comment thread airflow-core/src/airflow/utils/log/callback_log_reader.py Fixed
@seanghaeli seanghaeli force-pushed the ghaeli/callback-log-ui-visibility branch 9 times, most recently from e303ead to f6103b3 Compare May 23, 2026 17:36
Comment thread airflow-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

@seanghaeli

Copy link
Copy Markdown
Contributor Author

@vincbeck Screenshots in the PR description are up to date now

@guan404ming guan404ming 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 thread airflow-core/src/airflow/ui/src/pages/Run/CallbackLogViewer.tsx Outdated
Comment thread airflow-core/src/airflow/ui/src/pages/Run/CallbackLogViewer.tsx Outdated

@jason810496 jason810496 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 thread airflow-core/src/airflow/api_fastapi/core_api/routes/ui/deadlines.py Outdated
Comment thread airflow-core/src/airflow/jobs/triggerer_job_runner.py
Comment thread airflow-core/src/airflow/utils/log/callback_log_reader.py Outdated
Comment thread airflow-core/src/airflow/utils/log/callback_log_reader.py Outdated
Comment thread airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/deadline.py Outdated
Comment thread airflow-core/src/airflow/utils/log/callback_log_reader.py
@seanghaeli

Copy link
Copy Markdown
Contributor Author

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 seanghaeli force-pushed the ghaeli/callback-log-ui-visibility branch 2 times, most recently from d0fd5c3 to 131fe8a Compare June 22, 2026 20:27

@ferruzzi ferruzzi 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 seanghaeli force-pushed the ghaeli/callback-log-ui-visibility branch 3 times, most recently from 432e0c4 to f7cefa0 Compare June 23, 2026 07:13

@pierrejeambrun pierrejeambrun 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;

@pierrejeambrun pierrejeambrun Jun 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;

@pierrejeambrun pierrejeambrun Jun 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 seanghaeli force-pushed the ghaeli/callback-log-ui-visibility branch from e5e6615 to a8409ba Compare June 23, 2026 16:50
@seanghaeli

Copy link
Copy Markdown
Contributor Author

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

@seanghaeli seanghaeli closed this Jun 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:API Airflow's REST/HTTP API area:logging ready for maintainer review Set after triaging when all criteria pass.

Projects

None yet

Development

Successfully merging this pull request may close these issues.