Skip to content

Commit

Permalink
Add prometheus metrics via metrics-rs
Browse files Browse the repository at this point in the history
  • Loading branch information
damszew committed Feb 20, 2024
1 parent b02cc31 commit c4caaee
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 0 deletions.
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ swagger = ["paperclip"]
rabbit = ["amiquip", "crossbeam-channel"]
prometheus = []
tracing = ["dep:tracing", "tracing-actix-web", "tracing-subscriber", "tracing-bunyan-formatter"]
metrics = ["dep:metrics", "metrics-exporter-prometheus", "lazy_static", "futures-util"]

[dependencies]
actix = "0.13"
Expand Down Expand Up @@ -65,3 +66,8 @@ tracing-actix-web = { version = "0.7.8", optional = true }
tracing = { version = "0.1.40", features = ["log"], optional = true }
tracing-subscriber = { version = "0.3.16", features = ["env-filter", "tracing-log"], optional = true }
tracing-bunyan-formatter = { version = "0.3", optional = true }

metrics = { version = "0.22.1", optional = true }
metrics-exporter-prometheus = { version = "0.13.1", optional = true }
lazy_static = { version = "1.4.0", optional = true }
futures-util = { version = "0.3.30", optional = true }
6 changes: 6 additions & 0 deletions src/server/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,12 @@ impl<'a> Serwus<'a> {
actix_web::web::get().to(super::prometheus::prometheus_stats_handler::<T, D>),
);

#[cfg(feature = "metrics")]
let app = app.wrap(super::metrics::middleware::Metrics).route(
"metrics",
actix_web::web::get().to(super::metrics::handler::metrics),
);

#[cfg(feature = "swagger")]
let app = if prod_env {
app.wrap_api()
Expand Down
14 changes: 14 additions & 0 deletions src/server/metrics/handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use actix_web::HttpRequest;
use lazy_static::lazy_static;
use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusHandle};
use paperclip::actix::api_v2_operation;

Check warning on line 4 in src/server/metrics/handler.rs

View workflow job for this annotation

GitHub Actions / Nightly tests

unused import: `paperclip::actix::api_v2_operation`

Check failure on line 4 in src/server/metrics/handler.rs

View workflow job for this annotation

GitHub Actions / Serwus Clippy Output

unused import: `paperclip::actix::api_v2_operation`

error: unused import: `paperclip::actix::api_v2_operation` --> src/server/metrics/handler.rs:4:5 | 4 | use paperclip::actix::api_v2_operation; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

lazy_static! {
pub static ref PROM_HANDLER: PrometheusHandle = PrometheusBuilder::new()
.install_recorder()
.expect("failed to install recorder");
}

pub async fn metrics(_: HttpRequest) -> String {
PROM_HANDLER.render()
}
74 changes: 74 additions & 0 deletions src/server/metrics/middleware.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use std::{
future::{ready, Ready},
time::Instant,
};

use actix_web::{
dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
Error,
};
use futures_util::future::LocalBoxFuture;

#[derive(Debug, Clone)]
pub struct Metrics;

impl<S, B> Transform<S, ServiceRequest> for Metrics
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = MetricsMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;

fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(MetricsMiddleware { service }))
}
}

pub struct MetricsMiddleware<S> {
service: S,
}

impl<S, B> Service<ServiceRequest> for MetricsMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;

forward_ready!(service);

fn call(&self, req: ServiceRequest) -> Self::Future {
let start = Instant::now();
let path = req.path().to_owned();
let method = req.method().to_string();

let fut = self.service.call(req);

Box::pin(async move {
let response = fut.await;

let latency = start.elapsed().as_secs_f64();
let status = response
.as_ref()
.map(|r| r.status())
.unwrap_or_else(|e| e.as_response_error().status_code())
.as_u16()
.to_string();

let labels = [("method", method), ("path", path), ("status", status)];

metrics::counter!("http_requests_total", &labels).increment(1);
metrics::histogram!("http_requests_duration_seconds", &labels).record(latency);

response
})
}
}
2 changes: 2 additions & 0 deletions src/server/metrics/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod handler;
pub mod middleware;
2 changes: 2 additions & 0 deletions src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ mod builder;
pub mod json_error;
#[cfg(feature = "prometheus")]
pub mod prometheus;
#[cfg(feature = "metrics")]
pub mod metrics;
pub mod stats;

use actix_cors::Cors;
Expand Down

0 comments on commit c4caaee

Please sign in to comment.