Axum-Prometheus

A middleware to collect HTTP metrics for Axum applications.

axum-prometheus relies on metrics.rs and its ecosystem to collect and export metrics - for instance for Prometheus, metrics_exporter_prometheus is used as a backed to interact with Prometheus.

Metrics

By default three HTTP metrics are tracked

Note that in the future request size metric is also planned to be implemented.

Renaming Metrics

These metrics can be renamed by specifying environmental variables at compile time:

These environmental variables can be set in your .cargo/config.toml since Cargo 1.56:

toml [env] AXUM_HTTP_REQUESTS_TOTAL = "my_app_requests_total" AXUM_HTTP_REQUESTS_DURATION_SECONDS = "my_app_requests_duration_seconds" AXUM_HTTP_REQUESTS_PENDING = "my_app_requests_pending"

..or optionally use PrometheusMetricLayerBuilder::with_prefix function.

Compatibility

| Axum Version | Crate Version | | ------------ | ------------- | | 0.5 | 0.1 | | 0.6 | 0.2, 0.3 |

Usage

For more elaborate use-cases, see the builder example.

Add axum-prometheus to your Cargo.toml.

toml [dependencies] axum-prometheus = "0.4.0"

Then you instantiate the prometheus middleware:

```rust use std::{net::SocketAddr, time::Duration}; use axum::{routing::get, Router}; use axum_prometheus::PrometheusMetricLayer;

[tokio::main]

async fn main() { let (prometheuslayer, metrichandle) = PrometheusMetricLayer::pair(); let app = Router::new() .route("/fast", get(|| async {})) .route( "/slow", get(|| async { tokio::time::sleep(Duration::fromsecs(1)).await; }), ) .route("/metrics", get(|| async move { metrichandle.render() })) .layer(prometheus_layer);

let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
axum::Server::bind(&addr)
    .serve(app.into_make_service())
    .await
    .unwrap();

} ```

Note that the /metrics endpoint is not automatically exposed, so you need to add that as a route manually. Calling the /metrics endpoint will expose your metrics:

not_rust axum_http_requests_total{method="GET",endpoint="/metrics",status="200"} 5 axum_http_requests_pending{method="GET",endpoint="/metrics"} 1 axum_http_requests_duration_seconds_bucket{method="GET",status="200",endpoint="/metrics",le="0.005"} 4 axum_http_requests_duration_seconds_bucket{method="GET",status="200",endpoint="/metrics",le="0.01"} 4 axum_http_requests_duration_seconds_bucket{method="GET",status="200",endpoint="/metrics",le="0.025"} 4 axum_http_requests_duration_seconds_bucket{method="GET",status="200",endpoint="/metrics",le="0.05"} 4 axum_http_requests_duration_seconds_bucket{method="GET",status="200",endpoint="/metrics",le="0.1"} 4 axum_http_requests_duration_seconds_bucket{method="GET",status="200",endpoint="/metrics",le="0.25"} 4 axum_http_requests_duration_seconds_bucket{method="GET",status="200",endpoint="/metrics",le="0.5"} 4 axum_http_requests_duration_seconds_bucket{method="GET",status="200",endpoint="/metrics",le="1"} 4 axum_http_requests_duration_seconds_bucket{method="GET",status="200",endpoint="/metrics",le="2.5"} 4 axum_http_requests_duration_seconds_bucket{method="GET",status="200",endpoint="/metrics",le="5"} 4 axum_http_requests_duration_seconds_bucket{method="GET",status="200",endpoint="/metrics",le="10"} 4 axum_http_requests_duration_seconds_bucket{method="GET",status="200",endpoint="/metrics",le="+Inf"} 4 axum_http_requests_duration_seconds_sum{method="GET",status="200",endpoint="/metrics"} 0.001997171 axum_http_requests_duration_seconds_count{method="GET",status="200",endpoint="/metrics"} 4

Using a different exporter than Prometheus

This crate may be used with other exporters than Prometheus. First, disable the default features:

toml axum-prometheus = { version = "0.4.0", default-features = false }

Then implement the MakeDefaultHandle for the provider you'd like to use. For StatsD:

```rust use metricsexporterstatsd::StatsdBuilder; use axum_prometheus::{MakeDefaultHandle, GenericMetricLayer};

// A marker struct for the custom StatsD exporter. struct Recorder;

// In order to use this with axum_prometheus, we must implement MakeDefaultHandle. impl MakeDefaultHandle for Recorder { type Out = ();

fn make_default_handle() -> Self::Out {
    // The regular setup for StatsD..
    let recorder = StatsdBuilder::from("127.0.0.1", 8125)
        .with_queue_size(5000)
        .with_buffer_size(1024)
        .build(Some("prefix"))
        .expect("Could not create StatsdRecorder");

    metrics::set_boxed_recorder(Box::new(recorder)).unwrap();
    // We don't need to return anything meaningful from here (unlike PrometheusHandle)
    // Let's just return an empty tuple.
    ()
}

}

fn main() { // ... // Use GenericMetricLayer instead of PrometheusMetricLayer. let (metriclayer, _handle) = GenericMetricLayer::<', _, Recorder>::pair(); // ...

} ```


This crate is similar to (and takes inspiration from) actix-web-prom and rocket_prometheus, and also builds on top of davidpdrsn's earlier work with LifeCycleHooks in tower-http.