A fast, minimal Prometheus middleware for FastAPI β no multiprocessing, no env config, no body reads. Just clean, efficient metrics.
FastAPI Prometheus Lite is a lightweight and high-performance Prometheus middleware for FastAPI applications. It provides essential request-level metrics such as request counts and durations, while avoiding the overhead of more feature-rich instrumentors. This library is ideal for developers who want precise, clean metrics without pulling in unnecessary complexity.
β οΈ Limitations & Caveats: This project was built for personal use and has certain constraints that are acceptable for my setup:
- Does not support multi-worker (prefork) deployments. If you run multiple Gunicorn/Uvicorn workers in the same process, metrics will not aggregate correctly.
- Works correctly when you deploy multiple replicas (separate processes or containers) behind a loadβbalancer, since each replica maintains its own registry.
- Live collectors cannot access the route template (
matched_path_template) in the ASGI scope; only post-request collectors can utilize it for labeling.- Instrumentation currently supports only base HTTP and WebSocket routes patched via Starlette.
This project was created with a clear goal in mind: provide just enough instrumentation for FastAPI performance and visibility β and nothing more.
Compared to full-fledged solutions, this package intentionally avoids:
- β Multiprocessing support (unnecessary for most simple deployments)
- β Environment-based configuration (explicit is better than implicit)
- β Automatic registration of default or system-level metrics
- β Double routing (avoiding processing the same request twice through middleware)
- β Request and response body parsing (to keep things fast and safe)
Instead, it focuses on:
- β
Precise, labeled Prometheus metrics (
http_requests_total,http_request_duration_seconds) - β Zero-config setup with FastAPIβs native middleware interface
- β
Minimal dependency tree (just
fastapiandprometheus-client) - β ASGI-native implementation with optional Starlette type support
Itβs perfect for lean microservices, observability-conscious APIs, and production-like setups where performance and control matter.
β οΈ Note: This project was developed for personal use and as a showcase of FastAPI middleware design. It is not intended to be a production-ready replacement for full-featured solutions. Feel free to use it as inspiration, reference, or a learning tool β not as a drop-in replacement unless you've reviewed and tailored it to your own needs.
from fastapi import FastAPI
from fastapi_prometheus_lite import Instrumentor
from fastapi_prometheus_lite.metrics.post_metrics import TotalRequests
from fastapi_prometheus_lite.metrics.live_metrics import GlobalActiveRequests
app = FastAPI()
instrumentor = Instrumentor(
metrics_collectors=[TotalRequests()],
live_metrics_collectors=[GlobalActiveRequests()],
excluded_paths=["^/docs"]
).instrument(app).expose(app)The Instrumentor (alias for FastApiPrometheusLite) provides a simple, fluent API for integrating Prometheus metrics into your FastAPI application.
Instantiate the instrumentor with optional configuration:
instrumentor = Instrumentor(
registry=None, # optional CollectorRegistry, defaults to global
metrics_collectors=[], # post-request collectors (list of CollectorBase; e.g., TotalRequests())
live_metrics_collectors=[], # in-request collectors (list of LiveCollectorBase; e.g., GlobalActiveRequests())
excluded_paths=["^/health$"], # regex patterns of paths to skip
static_labels = None, # a dictionary[str, str] containing static labels
)registry: PrometheusCollectorRegistry(uses global registry ifNone).metrics_collectors: list ofCollectorBaseinstances executed after each request (counters, histograms, etc.).live_metrics_collectors: list ofLiveCollectorBaseinstances wrapping each request (during execution, e.g., in-flight gauges, timers).excluded_paths: regex patterns matching request paths to skip instrumentation.static_labels: a dictionary[str, str] containing static labels.
Attach the Prometheus middleware to your FastAPI application:
instrumentor.instrument(app)This call adds the ASGI middleware that records metrics on every HTTP request (except excluded paths).
Register a scrape endpoint that serves collected metrics:
instrumentor.expose(
app,
endpoint="/metrics", # URL path (default: "/metrics")
include_in_schema=True, # include in OpenAPI schema
tags=["Metrics"], # optional OpenAPI tags
)endpoint: path at which metrics are exposed.include_in_schema: toggle inclusion in OpenAPI docs.tags: list of tags for documentation grouping.**kwargs: additional parameters forwarded toapp.get().
All methods return the instrumentor instance, allowing chaining:
Instrumentor(...)
.instrument(app)
.expose(app)From 0.4.0 static labels are supported. This feature is intended to support those use-cases where you want to collect data with a fine level of detail and you do not want to deal with aggregation at this level. Ex pod, replica, etc. Keep in mind anyway that this could affect cardinality and should be used only for specific well known cases.
Note: In the current implementation the static labels are supported only under the usage of generate_latest offered by the current lib.
from fastapi_prometheus_lite.registry.utils import generate_latestNow this is the default function used in the /metric endpoint. Static Labels are attached to all metrics only at generation time and are not part of any collector in any way.
For more advanced collectors, you can extend the provided typed-base abstractions:
CounterCollectorBase,GaugeCollectorBase,HistogramCollectorBase,SummaryCollectorBasefor post-request collectors.LiveCounterCollectorBase,LiveGaugeCollectorBase,LiveHistogramCollectorBase,LiveSummaryCollectorBasefor in-request (live) collectors.
from fastapi_prometheus_lite.collectors.typed_collector_bases import CounterCollectorBase
from fastapi_prometheus_lite.collectors.base import MetricsContext
class MyRequestCounter(CounterCollectorBase):
def __call__(self, ctx: MetricsContext):
matched, path_format = ctx.matched_path_template
labels = {
"method": ctx.request_method,
"path": path_format,
"status": str(ctx.response.status_code),
}
# Increment the counter with custom labels
self.metric.labels(**labels).inc()from fastapi_prometheus_lite.collectors.typed_live_collector_bases import LiveGaugeCollectorBase
class InFlightRequestsGauge(LiveGaugeCollectorBase):
def __enter__(self):
labels = {"method": self._scope["method"], "path": self._scope["path"]}
self.metric.labels(**labels).inc()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
labels = {"method": self._scope["method"], "path": self._scope["path"]}
self.metric.labels(**labels).dec()Use these base classes when initializing your instrumentor:
instrumentor = Instrumentor(
metrics_collectors=[MyRequestCounter()],
live_metrics_collectors=[InFlightRequestsGauge()],
excluded_paths=["^/docs$"],
).instrument(app).expose(app)This project is heavily inspired by prometheus-fastapi-instrumentator by @trallnag.
It borrows the overall design philosophy of exposing route-aware, labeled Prometheus metrics for FastAPI applications.