Skip to content

Commit

Permalink
Merge pull request #56 from Ptrskay3/backend-agnostic-layer
Browse files Browse the repository at this point in the history
A backend-agnostic layer
  • Loading branch information
Ptrskay3 authored Jul 20, 2024
2 parents 0bcc34f + 407b774 commit 0b83fb7
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ All notable changes to this project will be documented in this file.
### Added

- `GenericMetricLayer::pair_from` to initialize from a concrete struct. `GenericMetricLayer::pair` now requires that the handle type implements `Default`. [\#49]
- `BaseMetricLayer` that serves a more lightweight alternative to `GenericMetricLayer`. [\#56]

# [0.6.1] - 2024-01-23

Expand Down
2 changes: 2 additions & 0 deletions examples/base-metric-layer-example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target
/Cargo.lock
13 changes: 13 additions & 0 deletions examples/base-metric-layer-example/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "base-metric-layer-example"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
axum = "0.7.1"
tokio = { version = "1.0", features = ["full"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
axum-prometheus = { path = "../../", features = ["push-gateway"] }

43 changes: 43 additions & 0 deletions examples/base-metric-layer-example/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//! This example uses the `BaseMetricLayer`, which only emits metrics using the `metrics` crate's macros,
//! and the global exporter/recorder is fully up to user to initialize and configure.
//!
//! Run with
//!
//! ```not_rust
//! cd examples && cargo run -p base-metric-layer-example
//! ```
//!
use axum::{routing::get, Router};
use axum_prometheus::{metrics_exporter_prometheus::PrometheusBuilder, BaseMetricLayer};
use std::{net::SocketAddr, time::Duration};

#[tokio::main]
async fn main() {
// Initialize the recorder as you like. This example uses push gateway mode instead of a http listener.
// To use this, don't forget to enable the "push-gateway" feature in `axum-prometheus`.
PrometheusBuilder::new()
.with_push_gateway(
"http://127.0.0.1:9091/metrics/job/example",
Duration::from_secs(10),
None,
None,
)
.expect("push gateway endpoint should be valid")
.install()
.expect("failed to install Prometheus recorder");

let app = Router::<()>::new()
.route("/fast", get(|| async {}))
.route(
"/slow",
get(|| async {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}),
)
// Only need to add this layer at the end.
.layer(BaseMetricLayer::new());
let listener = tokio::net::TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 3000)))
.await
.unwrap();
axum::serve(listener, app).await.unwrap()
}
91 changes: 91 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,97 @@ impl<'a, FailureClass> Callbacks<FailureClass> for Traffic<'a> {
}
}

/// The tower middleware layer for recording HTTP metrics.
///
/// Unlike [`GenericMetricLayer`], this struct __does not__ know about the metrics exporter, or the recorder. It will only emit
/// metrics via the `metrics` crate's macros. It's entirely up to the user to set the global metrics recorder/exporter before using this.
///
/// You may use this if `GenericMetricLayer`'s requirements are too strict for your use case.
#[derive(Clone)]
pub struct BaseMetricLayer<'a> {
pub(crate) inner_layer: LifeCycleLayer<
SharedClassifier<StatusInRangeAsFailures>,
Traffic<'a>,
Option<BodySizeRecorder>,
>,
}

impl<'a> BaseMetricLayer<'a> {
/// Construct a new `BaseMetricLayer`.
///
/// # Example
/// ```
/// use axum::{routing::get, Router};
/// use axum_prometheus::{AXUM_HTTP_REQUESTS_DURATION_SECONDS, utils::SECONDS_DURATION_BUCKETS, BaseMetricLayer};
/// use metrics_exporter_prometheus::{Matcher, PrometheusBuilder};
/// use std::net::SocketAddr;
///
/// #[tokio::main]
/// async fn main() {
/// // Initialize the recorder as you like.
/// let metric_handle = PrometheusBuilder::new()
/// .set_buckets_for_metric(
/// Matcher::Full(AXUM_HTTP_REQUESTS_DURATION_SECONDS.to_string()),
/// SECONDS_DURATION_BUCKETS,
/// )
/// .unwrap()
/// .install_recorder()
/// .unwrap();
///
/// let app = Router::<()>::new()
/// .route("/fast", get(|| async {}))
/// .route(
/// "/slow",
/// get(|| async {
/// tokio::time::sleep(std::time::Duration::from_secs(1)).await;
/// }),
/// )
/// // Expose the metrics somehow to the outer world.
/// .route("/metrics", get(|| async move { metric_handle.render() }))
/// // Only need to add this layer at the end.
/// .layer(BaseMetricLayer::new());
///
/// // Run the server as usual:
/// // let listener = tokio::net::TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 3000)))
/// // .await
/// // .unwrap();
/// // axum::serve(listener, app).await.unwrap()
/// }
/// ```
pub fn new() -> Self {
let make_classifier =
StatusInRangeAsFailures::new_for_client_and_server_errors().into_make_classifier();
let inner_layer = LifeCycleLayer::new(make_classifier, Traffic::new(), None);
Self { inner_layer }
}

/// Construct a new `BaseMetricLayer` with response body size tracking enabled.
pub fn with_response_body_size() -> Self {
let mut this = Self::new();
this.inner_layer.on_body_chunk(Some(BodySizeRecorder));
this
}
}

impl<'a> Default for BaseMetricLayer<'a> {
fn default() -> Self {
Self::new()
}
}

impl<'a, S> Layer<S> for BaseMetricLayer<'a> {
type Service = LifeCycle<
S,
SharedClassifier<StatusInRangeAsFailures>,
Traffic<'a>,
Option<BodySizeRecorder>,
>;

fn layer(&self, inner: S) -> Self::Service {
self.inner_layer.layer(inner)
}
}

/// The tower middleware layer for recording http metrics with different exporters.
pub struct GenericMetricLayer<'a, T, M> {
pub(crate) inner_layer: LifeCycleLayer<
Expand Down

0 comments on commit 0b83fb7

Please sign in to comment.