Skip to main content

Drop-in Prometheus metrics for FastAPI: HTTP requests, latency, event-loop lag, threadpool tokens, background tasks. One line of setup, no per-route changes.

Project description

transparent-fastapi

CI Python License: MIT

Drop-in Prometheus metrics for FastAPI. One line of setup, no per-route changes, disciplined cardinality.

Why

FastAPI doesn't ship metrics, and the usual options leave gaps:

  • Hand-rolled middleware is easy to start and easy to get wrong — raw URLs as labels turn /users/123 and /users/124 into separate time series, and a port scanner can balloon series count overnight.
  • prometheus-fastapi-instrumentator covers HTTP nicely, but route-template grouping is opt-in and process-level signals (event-loop lag, threadpool saturation, BackgroundTasks) aren't in scope.

transparent-fastapi is the tool you reach for when you want one install(app) to give you HTTP metrics with safe-by-default cardinality, plus the runtime signals you need to debug "why is my async app slow" — event-loop lag, threadpool tokens, and BackgroundTasks outcomes — without per-route changes.

Install

pip install transparent-fastapi

Use

from fastapi import FastAPI
from transparent_fastapi import install

app = FastAPI()
install(app)

That's it. A /metrics endpoint is now exposed in Prometheus exposition format.

Call install(app) once, as early as possible after app = FastAPI(...). Any middleware you add afterwards (auth, CORS) wraps the metrics middleware, so route-handler latency isn't skewed by upstream concerns.

Compatibility: Python 3.10+, FastAPI 0.75+, asyncio (anyio default backend). Fully type-annotated — ships a py.typed marker.

Cardinality discipline

This is the whole point of the library — make it safe to leave running in production.

  • Routes are FastAPI's path templates (/users/{id}), never raw URLs. /users/123, /users/124, /users/125 collapse into one series.
  • Unmatched paths (port scans, vulnerability probes) collapse to route="<unmatched>" rather than leaking the raw path as a label.
  • Methods are allowlisted (GET/POST/PUT/PATCH/DELETE/HEAD/OPTIONS); anything else becomes OTHER.
  • Status is the full numeric code ("200", "404"); fine-grained enough for "alert when 401 spikes vs 500 spikes" without exploding cardinality.

What it exports

Metric Type Labels
http_requests_total Counter method, route, status
http_request_duration_seconds Histogram method, route
http_requests_in_flight Gauge
event_loop_lag_seconds Gauge
threadpool_tokens Gauge state ∈ {total, borrowed, available}
threadpool_tasks_waiting Gauge
background_task_scheduled_total Counter mode ∈ {async, threadpool}
background_task_total Counter mode ∈ {async, threadpool}, outcome ∈ {ok, error}
background_task_duration_seconds Histogram mode
Plus, free from prometheus_client's default registry

prometheus_client auto-registers a process collector and a Python collector. Because transparent-fastapi's /metrics endpoint calls generate_latest() on the default registry, these are exposed too — you don't need to do anything:

Metric Type Labels What it tells you
process_cpu_seconds_total Counter CPU time consumed; rate() → cores in use
process_resident_memory_bytes Gauge RSS (physical memory)
process_virtual_memory_bytes Gauge VSZ
process_open_fds Gauge FDs in use; creep toward process_max_fds = leak
process_max_fds Gauge Soft FD limit
process_start_time_seconds Gauge Unix epoch of process start; time() - this = uptime
python_gc_collections_total Counter generation ∈ {0, 1, 2} GC collections; sustained gen-2 = long-lived churn
python_gc_objects_collected_total Counter generation Objects reclaimed per generation
python_gc_objects_uncollectable_total Counter generation Objects GC could not free (cycles with __del__)
python_info Info version, implementation, major, minor, patchlevel Python build details

Configuration

Two optional kwargs:

install(
    app,
    excluded_paths=["/health", "/readiness"],   # noisy probes don't pollute metrics
    background_task_metrics=True,                # default; opt-out of the BackgroundTasks patch
)

Useful queries

# Validation-failure rate by route — Pydantic 422s already live in the status label
sum by (route) (rate(http_requests_total{status="422"}[5m]))

# 5xx error ratio
sum(rate(http_requests_total{status=~"5.."}[5m])) /
  sum(rate(http_requests_total[5m]))

# p95 latency by route
histogram_quantile(0.95,
  sum by (route, le) (rate(http_request_duration_seconds_bucket[5m])))

# Threadpool saturation — non-zero means sync work is queuing
sum(threadpool_tasks_waiting)

# Background task backlog growth (positive = scheduling outpaces completion)
sum(rate(background_task_scheduled_total[1m]))
  - sum(rate(background_task_total[1m]))

# Process uptime in seconds
time() - process_start_time_seconds

Limitations

FastAPI(root_path=...) and app.include_router(router, prefix=...) are handled correctly — both produce clean, prefixed templates in the route label. A couple of app.mount(...) cases are worth knowing about:

  • StaticFiles (and any Starlette Mount) records as route="<unmatched>". Starlette's Mount doesn't populate request.scope["route"] the way FastAPI's APIRoute does, so static-file requests share a single series with unmatched paths (port scans, typos). This is a feature for cardinality — a 100k-asset directory can't explode the time series — but it does mean static traffic isn't separately observable here. Per-asset metrics belong at the CDN / edge proxy.
  • Mounted FastAPI sub-applications share series with top-level routes. app.mount("/api", sub_app) records traffic as route="/users/{user_id}", without the /api prefix. This is fine when you have one mount; two mounts of the same sub-app at different paths would collide on the same series. Prefer app.include_router(router, prefix="/api") for FastAPI-internal composition — its templates are baked with the prefix and stay distinct.
How it works
  • HTTP metrics are recorded by two ASGI middlewares wired up by install(app). Route templates are read from request.scope["route"].path after dispatch, so labels are always the matched template — never a raw URL.
  • Event-loop lag is sampled by a background task install(app) adds to your lifespan. It schedules a 1-second sleep and reports the delta between expected and actual wake-up.
  • Threadpool gauges are refreshed at scrape time from anyio's default CapacityLimiter, so the values are always live, not polled.
  • Background task metrics come from a one-time monkey-patch of starlette.background.BackgroundTasks.add_task. Your route code keeps using background_tasks.add_task(...) as normal; the patch wraps the func to record ok/error and duration, and labels by mode using starlette's own async-vs-threadpool detection.

Local demo stack

The repo ships a docker-compose stack (app + Prometheus + Grafana with a provisioned dashboard) and locust load profiles under deploy/local/. From the project root:

docker compose -f deploy/local/docker-compose.yml up -d --build
deploy/local/scripts/deploy.sh status        # curl all three services
deploy/local/scripts/deploy.sh load-medium   # locust, realistic traffic mix

Grafana lands on http://localhost:3000 (admin/admin) with transparent-fastapi as the default dashboard. See deploy/local/CLAUDE.md for the full reference.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

transparent_fastapi-0.1.1.tar.gz (155.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

transparent_fastapi-0.1.1-py3-none-any.whl (11.2 kB view details)

Uploaded Python 3

File details

Details for the file transparent_fastapi-0.1.1.tar.gz.

File metadata

  • Download URL: transparent_fastapi-0.1.1.tar.gz
  • Upload date:
  • Size: 155.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for transparent_fastapi-0.1.1.tar.gz
Algorithm Hash digest
SHA256 d9ab3d5aaf4d80e6ab3403ffb357d4554c2adee690030efc84922d3017c9e7f7
MD5 e257374ccacbbe28dd2fec3e5cfce710
BLAKE2b-256 70cf15cb964e1d355d8716c089c3ec8d2113ca3926edfcfd1a9042a9d3983394

See more details on using hashes here.

Provenance

The following attestation bundles were made for transparent_fastapi-0.1.1.tar.gz:

Publisher: release.yml on Ashish-Github193/transparent-fastapi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file transparent_fastapi-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for transparent_fastapi-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 e37d0657f91c14ba2785761bdbbf9ffc39de9073a56659f252f8a784aa3855e3
MD5 63ffbc1ed55c527da7db97e95fdfb97f
BLAKE2b-256 d4da305c18fa767ee965365567eabf5023512bac499d43c674d4865ce38b8646

See more details on using hashes here.

Provenance

The following attestation bundles were made for transparent_fastapi-0.1.1-py3-none-any.whl:

Publisher: release.yml on Ashish-Github193/transparent-fastapi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page