From 5698fb8be97103eb09563d0491de5cdd2c7e85ce Mon Sep 17 00:00:00 2001 From: David Pedersen <david.pdrsn@gmail.com> Date: Tue, 28 Dec 2021 15:58:48 +0100 Subject: [PATCH] Add metrics example (#671) --- .github/workflows/CI.yml | 9 ++- examples/prometheus-metrics/Cargo.toml | 14 ++++ examples/prometheus-metrics/src/main.rs | 91 +++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 examples/prometheus-metrics/Cargo.toml create mode 100644 examples/prometheus-metrics/src/main.rs diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 18e6ab1f..4251b29a 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -166,4 +166,11 @@ jobs: CC: clang with: command: check - args: --all --all-targets --all-features --target armv5te-unknown-linux-musleabi + args: > + --all-targets + --all-features + -p axum + -p axum-core + -p axum-extra + -p axum-debug + --target armv5te-unknown-linux-musleabi diff --git a/examples/prometheus-metrics/Cargo.toml b/examples/prometheus-metrics/Cargo.toml new file mode 100644 index 00000000..ef436edb --- /dev/null +++ b/examples/prometheus-metrics/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "example-prometheus-metrics" +version = "0.1.0" +edition = "2018" +publish = false + +[dependencies] +axum = { path = "../../axum" } +axum-extra = { path = "../../axum-extra" } +metrics = "0.17" +metrics-exporter-prometheus = "0.7" +tokio = { version = "1.0", features = ["full"] } +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/examples/prometheus-metrics/src/main.rs b/examples/prometheus-metrics/src/main.rs new file mode 100644 index 00000000..d8e8ec21 --- /dev/null +++ b/examples/prometheus-metrics/src/main.rs @@ -0,0 +1,91 @@ +//! Someday tower-http will hopefully have a metrics middleware, until then you can track +//! metrics like this. +//! +//! Run with +//! +//! ```not_rust +//! cargo run -p example-prometheus-metrics +//! ``` + +use axum::{extract::MatchedPath, http::Request, response::IntoResponse, routing::get, Router}; +use axum_extra::middleware::{self, Next}; +use metrics_exporter_prometheus::{Matcher, PrometheusBuilder, PrometheusHandle}; +use std::{ + future::ready, + net::SocketAddr, + time::{Duration, Instant}, +}; + +#[tokio::main] +async fn main() { + // Set the RUST_LOG, if it hasn't been explicitly defined + if std::env::var_os("RUST_LOG").is_none() { + std::env::set_var("RUST_LOG", "example_todos=debug,tower_http=debug") + } + tracing_subscriber::fmt::init(); + + let recorder_handle = setup_metrics_recorder(); + + let app = Router::new() + .route("/fast", get(|| async {})) + .route( + "/slow", + get(|| async { + tokio::time::sleep(Duration::from_secs(1)).await; + }), + ) + .route("/metrics", get(move || ready(recorder_handle.render()))) + .route_layer(middleware::from_fn(track_metrics)); + + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + tracing::debug!("listening on {}", addr); + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .unwrap(); +} + +fn setup_metrics_recorder() -> PrometheusHandle { + const EXPONENTIAL_SECONDS: &[f64] = &[ + 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, + ]; + + let recorder = PrometheusBuilder::new() + .set_buckets_for_metric( + Matcher::Full("http_requests_duration_seconds".to_string()), + EXPONENTIAL_SECONDS, + ) + .build(); + + let recorder_handle = recorder.handle(); + + metrics::set_boxed_recorder(Box::new(recorder)).unwrap(); + + recorder_handle +} + +async fn track_metrics<B>(req: Request<B>, next: Next<B>) -> impl IntoResponse { + let start = Instant::now(); + let path = if let Some(matched_path) = req.extensions().get::<MatchedPath>() { + matched_path.as_str().to_owned() + } else { + req.uri().path().to_owned() + }; + let method = req.method().clone(); + + let response = next.run(req).await; + + let latency = start.elapsed().as_secs_f64(); + let status = response.status().as_u16().to_string(); + + let labels = [ + ("method", method.to_string()), + ("path", path), + ("status", status), + ]; + + metrics::increment_counter!("http_requests_total", &labels); + metrics::histogram!("http_requests_duration_seconds", latency, &labels); + + response +}