Crate axum_prometheus

Source
Expand description

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 backend to interact with Prometheus.

§Metrics

By default three HTTP metrics are tracked

  • axum_http_requests_total (labels: endpoint, method, status): the total number of HTTP requests handled (counter)
  • axum_http_requests_duration_seconds (labels: endpoint, method, status): the request duration for all HTTP requests handled (histogram)
  • axum_http_requests_pending (labels: endpoint, method): the number of currently in-flight requests (gauge)

This crate also allows to track response body sizes as a histogram — see PrometheusMetricLayerBuilder::enable_response_body_size.

§Renaming Metrics

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

  • AXUM_HTTP_REQUESTS_TOTAL
  • AXUM_HTTP_REQUESTS_DURATION_SECONDS
  • AXUM_HTTP_REQUESTS_PENDING
  • AXUM_HTTP_RESPONSE_BODY_SIZE (if body size tracking is enabled)

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

[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"
AXUM_HTTP_RESPONSE_BODY_SIZE = "my_app_response_body_size"

..or optionally use PrometheusMetricLayerBuilder::with_prefix function.

§Usage

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

Add axum-prometheus to your Cargo.toml.

[dependencies]
axum-prometheus = "0.8.0"

Then you instantiate the prometheus middleware:

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

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

    let listener = tokio::net::TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 3000)))
        .await
        .unwrap();
    axum::serve(listener, app).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:

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

§Prometheus push gateway feature

This crate currently has no higher level API for the push-gateway feature. If you plan to use it, enable the push-gateway feature in axum-prometheus, use BaseMetricLayer, and setup your recorder manually, similar to the base-metric-layer-example.

§Using a different exporter than Prometheus

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

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

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

use metrics_exporter_statsd::StatsdBuilder;
use axum_prometheus::{MakeDefaultHandle, GenericMetricLayer};

// The custom StatsD exporter struct. It may take fields as well.
struct Recorder { port: u16 }

// In order to use this with `axum_prometheus`, we must implement `MakeDefaultHandle`.
impl MakeDefaultHandle for Recorder {
    // We don't need to return anything meaningful from here (unlike PrometheusHandle)
    // Let's just return an empty tuple.
    type Out = ();

    fn make_default_handle(self) -> Self::Out {
        // The regular setup for StatsD. Notice that `self` is passed in by value.
        let recorder = StatsdBuilder::from("127.0.0.1", self.port)
            .with_queue_size(5000)
            .with_buffer_size(1024)
            .build(Some("prefix"))
            .expect("Could not create StatsdRecorder");

        metrics::set_boxed_recorder(Box::new(recorder)).unwrap();
    }
}

fn main() {
    // Use `GenericMetricLayer` instead of `PrometheusMetricLayer`.
    // Generally `GenericMetricLayer::pair_from` is what you're looking for.
    // It lets you pass in a concrete initialized `Recorder`.
    let (metric_layer, _handle) = GenericMetricLayer::pair_from(Recorder { port: 8125 });
}

It’s also possible to use GenericMetricLayer::pair, however it’s only callable if the recorder struct implements Default as well.

use metrics_exporter_statsd::StatsdBuilder;
use axum_prometheus::{MakeDefaultHandle, GenericMetricLayer};

#[derive(Default)]
struct Recorder { port: u16 }

impl MakeDefaultHandle for Recorder {
   /* .. same as before .. */
}

fn main() {
    // This will internally call `Recorder::make_default_handle(Recorder::default)`.
    let (metric_layer, _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.

Re-exports§

pub use metrics;
pub use metrics_exporter_prometheus;

Modules§

lifecycle
Request lifecycle hooks that can be used to further customize how and what callbacks to run on events.
utils
Utilities for getting metric names at runtime, and other helpers.

Structs§

BaseMetricLayer
The tower middleware layer for recording HTTP metrics.
BodySizeRecorder
A marker struct that implements lifecycle::OnBodyChunk, so it can be used to track response body sizes.
GenericMetricLayer
The tower middleware layer for recording http metrics with different exporters.
Handle
The default handle for the Prometheus exporter.
MetricLayerBuilder
A builder for GenericMetricLayer that enables further customizations.
MetricsData
Struct used for storing and calculating information about the current request.
Traffic
A marker struct that implements the lifecycle::Callbacks trait.

Enums§

EndpointLabel
Determines how endpoints are reported.

Constants§

AXUM_HTTP_REQUESTS_DURATION_SECONDS
Identifies the histogram/summary used for request latency. Defaults to axum_http_requests_duration_seconds, but can be changed by setting the AXUM_HTTP_REQUESTS_DURATION_SECONDS env at compile time.
AXUM_HTTP_REQUESTS_PENDING
Identifies the gauge used for the requests pending metric. Defaults to axum_http_requests_pending, but can be changed by setting the AXUM_HTTP_REQUESTS_PENDING env at compile time.
AXUM_HTTP_REQUESTS_TOTAL
Identifies the counter used for requests total. Defaults to axum_http_requests_total, but can be changed by setting the AXUM_HTTP_REQUESTS_TOTAL env at compile time.
AXUM_HTTP_RESPONSE_BODY_SIZE
Identifies the histogram/summary used for response body size. Defaults to axum_http_response_body_size, but can be changed by setting the AXUM_HTTP_RESPONSE_BODY_SIZE env at compile time.

Traits§

MakeDefaultHandle
The trait that allows to use a metrics exporter in GenericMetricLayer.

Type Aliases§

PrometheusMetricLayer
The tower middleware layer for recording http metrics with Prometheus.
PrometheusMetricLayerBuilder
A builder for crate::PrometheusMetricLayer that enables further customizations.