Skip to content

Fix missing HTTP access logs when api-server omits the core app - #72026

Merged
vatsrahul1001 merged 3 commits into
apache:mainfrom
manish1337:fix-execution-api-access-logs
Sep 9, 2026
Merged

vatsrahul1001 merged 3 commits into
apache:mainfrom
manish1337:fix-execution-api-access-logs

Conversation

@manish1337

Copy link
Copy Markdown
Contributor

An api-server started without the core app — for example --apps execution, the shape used to run
the Task Execution API as its own deployment — serves requests normally but emits no access log
lines at all.

Three separate places disable the built-in access loggers, all resting on the premise that
HttpAccessLogMiddleware handles access logging:

  • AirflowUvicornWorker.CONFIG_KWARGS — access_log: False
  • the uvicorn kwargs built in api_server_command
  • uvicorn.access / gunicorn.access muted (handlers: [], propagate: False) in logging_config

That premise only held while the middleware was installed inside the "core" in apps_list branch of
create_app, so an --apps selection without core had no access-log producer at all — and because
the loggers are muted at the logging-config level too, no configuration could bring the records back.

This installs the middleware for every apps selection, which makes the premise true again rather
than adding a fourth conditional. The alternative — enabling uvicorn's own access log when core is
absent — needs three coordinated conditionals across three files and would give execution-only
deployments a different log format from every other deployment.

For anyone running the Task Execution API as its own deployment, this restores per-request telemetry
(latency, status codes, endpoint counts) for the component every running task depends on for
heartbeats, state transitions and XComs.

Verification

Started a real api-server and issued a marked request, before and after the change:

--apps uvicorn gunicorn
execution no record → logged no record → logged
core logged logged
all logged logged
  • Middleware order for core/all is unchanged: [HttpAccessLogMiddleware, GZipMiddleware, JWTRefreshMiddleware].
  • The generated OpenAPI specs are byte-identical with and without the change.
  • The added test fails on [execution] without the fix and passes with it.
  • airflow-core/tests/unit/api_fastapi (3750), providers/fab (536) and providers/common/compat
    (197) pass. The test_hitl.py and example-Dag failures seen locally also fail on unmodified
    main, so they are unrelated.

Note for reviewers: #64523 (open) also edits init_middlewares to add a metrics middleware, so the
two may conflict textually.


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

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

@ColtenOuO ColtenOuO 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.

LGTM overall.

Just a small suggestion: If you want to update your branch with the latest main, consider using rebase instead of merge.

This helps keep the commit history cleaner and easier to follow.

@manish1337
manish1337 force-pushed the fix-execution-api-access-logs branch from 4314b3b to a433742 Compare August 25, 2026 05:30
@manish1337

Copy link
Copy Markdown
Contributor Author

Thanks for the review!

Rebased onto latest main — the branch is a single commit now. The merge commits came from GitHub's "Update branch" button, which merges rather than rebases; rewritten with --force-with-lease, net diff unchanged. Also added the newsfragment that was missing.

@manish1337
manish1337 force-pushed the fix-execution-api-access-logs branch from a433742 to 0921739 Compare August 27, 2026 05:16

@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.

I'm not sure whether this is a bug or a intentional purpose?

Since the Execution API is more sensitive (it's directly related to the Task Execution), so it makes sense to have all the access logging. For the case of core API, I personally think it's fine to not have the access logging for all the routes.

cc @pierrejeambrun What do you think? Thanks.

@manish1337

manish1337 commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks for the review!

It only affects --apps selections without core — core's stack is unchanged. --apps execution goes from no access-log middleware to having it; core and all are untouched.

all three places that disable the built-in access loggers (AirflowUvicornWorker.CONFIG_KWARGS, the uvicorn kwargs in api_server_command, logging_config) say in their comments that HttpAccessLogMiddleware handles access logging but it was only installed in the "core" in apps_list branch. And since the loggers are muted at the logging-config level too, no config can bring the records back.

@manish1337
manish1337 force-pushed the fix-execution-api-access-logs branch from 0921739 to 5c35105 Compare August 27, 2026 13:54

@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.

LGTM, just few nits.

@manish1337
manish1337 force-pushed the fix-execution-api-access-logs branch 2 times, most recently from 2aae5c1 to afc7c94 Compare August 29, 2026 04:11
@manish1337

Copy link
Copy Markdown
Contributor Author

Thanks for the review! Made the changes.

@manish1337
manish1337 force-pushed the fix-execution-api-access-logs branch 2 times, most recently from 7feebc6 to aa2d4db Compare September 1, 2026 04:58
An api-server started without the core app -- for example `--apps execution`,
the shape used to run the Task Execution API as its own deployment -- served
requests but recorded none of them. That leaves the component every running task
depends on for heartbeats, state transitions and XComs with no per-request
telemetry, which is the primary signal for diagnosing heartbeat stalls.

Both server backends disable their own access logger, and the built-in access
loggers are muted in the logging config, all on the premise that
HttpAccessLogMiddleware handles access logging. That premise only held while the
middleware was installed by the core app branch, so no configuration could
recover the records. Installing it for every apps selection makes the premise
true again instead of adding a fourth conditional that would give execution-only
deployments a different log format from every other deployment.
@manish1337
manish1337 force-pushed the fix-execution-api-access-logs branch from aa2d4db to 28b773a Compare September 8, 2026 04:43
@eladkal eladkal added this to the Airflow 3.3.2 milestone Sep 8, 2026

@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.

LGTM, thanks

@vatsrahul1001
vatsrahul1001 merged commit 860233f into apache:main Sep 9, 2026
79 checks passed
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Backport successfully created: v3-3-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.

Status Branch Result
✅ v3-3-test PR Link

vatsrahul1001 pushed a commit that referenced this pull request Sep 10, 2026
…re app (#72026) (#72808)

* Fix missing HTTP access logs when api-server omits the core app

An api-server started without the core app -- for example `--apps execution`,
the shape used to run the Task Execution API as its own deployment -- served
requests but recorded none of them. That leaves the component every running task
depends on for heartbeats, state transitions and XComs with no per-request
telemetry, which is the primary signal for diagnosing heartbeat stalls.

Both server backends disable their own access logger, and the built-in access
loggers are muted in the logging config, all on the premise that
HttpAccessLogMiddleware handles access logging. That premise only held while the
middleware was installed by the core app branch, so no configuration could
recover the records. Installing it for every apps selection makes the premise
true again instead of adding a fourth conditional that would give execution-only
deployments a different log format from every other deployment.

* Lock in HttpAccessLog outermost ordering with a positional assertion

* Trim redundant caveat from init_middlewares ordering note

---------
(cherry picked from commit 860233f)

Co-authored-by: manish1337 <[email protected]>
Co-authored-by: pierrejeambrun <[email protected]>
imrichardwu pushed a commit to imrichardwu/airflow that referenced this pull request Sep 11, 2026
…he#72026)

* Fix missing HTTP access logs when api-server omits the core app

An api-server started without the core app -- for example `--apps execution`,
the shape used to run the Task Execution API as its own deployment -- served
requests but recorded none of them. That leaves the component every running task
depends on for heartbeats, state transitions and XComs with no per-request
telemetry, which is the primary signal for diagnosing heartbeat stalls.

Both server backends disable their own access logger, and the built-in access
loggers are muted in the logging config, all on the premise that
HttpAccessLogMiddleware handles access logging. That premise only held while the
middleware was installed by the core app branch, so no configuration could
recover the records. Installing it for every apps selection makes the premise
true again instead of adding a fourth conditional that would give execution-only
deployments a different log format from every other deployment.

* Lock in HttpAccessLog outermost ordering with a positional assertion

* Trim redundant caveat from init_middlewares ordering note

---------

Co-authored-by: pierrejeambrun <[email protected]>
henry3260 added a commit to henry3260/airflow that referenced this pull request Oct 8, 2026
`/execution` carries every task heartbeat, state transition and XCom, making it
the busiest surface the API server has, and it emitted no metrics at all.

Two things kept it out. The middleware was registered from `init_middlewares`,
which `create_app` only calls for the core app, so `airflow api-server --apps
execution` -- the shape used to run the execution API as its own deployment --
installed nothing, and no configuration could recover the metrics. The path
prefix allowlist also covered only `/api/v2` and `/ui`.

Install the middleware for every `--apps` selection from a new
`init_api_metrics`, the shape apache#72026 settled on after the identical gap left
execution-only deployments with no access logs, and add `/execution` to the
allowlist. Registering it on the outer app rather than inside the execution
sub-app keeps a single instance, so a request cannot be counted twice, and
keeps it outside `GZipMiddleware` so execution API durations include
compression time the way core API durations already do.

A mounted sub-app declares its routes relative to the mount point, so the route
tag now carries `root_path`. Without it, `/execution/task-instances/{id}/run`
would be tagged `/task-instances/{id}/run` and collide with any core route
sharing that template.
henry3260 added a commit to henry3260/airflow that referenced this pull request Oct 8, 2026
`/execution` carries every task heartbeat, state transition and XCom, making it
the busiest surface the API server has, and it emitted no metrics at all.

Two things kept it out. The middleware was registered from `init_middlewares`,
which `create_app` only calls for the core app, so `airflow api-server --apps
execution` -- the shape used to run the execution API as its own deployment --
installed nothing, and no configuration could recover the metrics. The path
prefix allowlist also covered only `/api/v2` and `/ui`.

Install the middleware for every `--apps` selection from a new
`init_api_metrics`, the shape apache#72026 settled on after the identical gap left
execution-only deployments with no access logs, and add `/execution` to the
allowlist. Registering it on the outer app rather than inside the execution
sub-app keeps a single instance, so a request cannot be counted twice, and
keeps it outside `GZipMiddleware` so execution API durations include
compression time the way core API durations already do.

A mounted sub-app declares its routes relative to the mount point, so the route
tag now carries `root_path`. Without it, `/execution/task-instances/{id}/run`
would be tagged `/task-instances/{id}/run` and collide with any core route
sharing that template.
@henry3260 henry3260 mentioned this pull request Oct 8, 2026
1 task done
pierrejeambrun pushed a commit that referenced this pull request Oct 13, 2026
* add metrics

* allow user define the mapping themself

* Move API metrics config validation into configuration parser

* Simplify REST API metrics tagging

Remove the configurable path-to-surface mapping and emit metrics only
for core API and UI routes. Use normalized route templates with method
and status family tags to keep metric cardinality bounded.

* Report REST API request duration under a milliseconds metric name

Stats.timing records milliseconds on every supported backend, so publishing the
timer as http_request_duration_seconds would have left every consumer of the
metric wrong by a factor of 1000.

Route tagging also kept a fallback that reverse-looked-up the endpoint in
scope["router"].routes whenever scope["route"] was absent. Only FastAPI's
APIRoute populates scope["route"], and all 151 routes under /api/v2 and /ui are
APIRoute, so that branch was unreachable. For a mounted sub-app -- the one case
it was meant to cover -- scope["router"] stays the root router, so the lookup
could not have succeeded there either. The tests built a plain Starlette app,
which never sets scope["route"], and so covered only that dead branch instead of
the path the API server actually takes.

* Bound the REST API metrics method tag to methods the API serves

The router rejects a method mismatch with 405 before any authentication
dependency runs, and uvicorn's h11 parser accepts any token as a method. That
let an anonymous client create a new metric series per request just by
inventing method names, defeating the bounded-cardinality goal of these
metrics. Only the five methods registered on API routes now get their own tag
value; everything else is recorded as OTHER, so the series count is fixed by
the route table rather than by whatever clients send.

* Cover the Task SDK execution API in the REST API metrics

`/execution` carries every task heartbeat, state transition and XCom, making it
the busiest surface the API server has, and it emitted no metrics at all.

Two things kept it out. The middleware was registered from `init_middlewares`,
which `create_app` only calls for the core app, so `airflow api-server --apps
execution` -- the shape used to run the execution API as its own deployment --
installed nothing, and no configuration could recover the metrics. The path
prefix allowlist also covered only `/api/v2` and `/ui`.

Install the middleware for every `--apps` selection from a new
`init_api_metrics`, the shape #72026 settled on after the identical gap left
execution-only deployments with no access logs, and add `/execution` to the
allowlist. Registering it on the outer app rather than inside the execution
sub-app keeps a single instance, so a request cannot be counted twice, and
keeps it outside `GZipMiddleware` so execution API durations include
compression time the way core API durations already do.

A mounted sub-app declares its routes relative to the mount point, so the route
tag now carries `root_path`. Without it, `/execution/task-instances/{id}/run`
would be tagged `/task-instances/{id}/run` and collide with any core route
sharing that template.

* Build the API metrics test app once per module

Review feedback: every test rebuilt the whole FastAPI app just to issue one
request, so the app construction cost was paid once per test case for no
benefit. A module-scoped fixture is enough -- the middleware under test holds
no state, and each test still gets its own client.

* Document that REST API metric tags need a tagging backend

Review feedback: the metric tables promised a breakdown by method, route and
status_family without saying that classic StatsD drops tags, so an operator
reading the docs would configure statsd_on and then wonder why they only ever
see one aggregate series.
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 type:bug-fix Changelog: Bug Fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants