Skip to content

Ptrskay3/axum-prometheus

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

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

Compatibility

Axum Version Crate Version
0.5 0.1
0.6 0.2, 0.3, 0.4
0.7 0.5, 0.6, 0.7
0.8 0.8

MSRV

This crate's current MSRV is 1.75.

Usage

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

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

Let's note that since metrics-exporter-prometheus = "0.13" that crate introduced the push-gateway default feature, that requires openssl support. The axum_prometheus crate does not rely on, nor enable this feature by default β€” if you need it, you may enable it through the "push-gateway" feature in axum_prometheus.

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.

About

πŸ”Ž Prometheus metrics middleware for Axum

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages