-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Get default metrics working with axum #8
Comments
Metrics in general is something I want to have built in to tower-http. Haven't quite found the right design yet though. If you wanna roll your own this does it (using metrics): use axum::{
extract::Extension,
handler::get,
http::{Request, Response},
response::IntoResponse,
AddExtensionLayer, Router,
};
use metrics_exporter_prometheus::{Matcher, PrometheusBuilder, PrometheusHandle};
use pin_project_lite::pin_project;
use std::{
convert::Infallible,
future::Future,
net::SocketAddr,
pin::Pin,
task::{Context, Poll},
time::{Duration, Instant},
};
use tower::{Service, ServiceBuilder};
#[tokio::main]
async fn main() {
let recorder = PrometheusBuilder::new()
.set_buckets_for_metric(
Matcher::Full("api_http_requests_duration_seconds".to_string()),
&[
0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0,
],
)
.build();
let recorder_handle = recorder.handle();
metrics::set_boxed_recorder(Box::new(recorder)).expect("failed to set metrics recorder");
let app = Router::new()
.route("/", get(handler))
.route("/metrics", get(metrics_handler))
.layer(
ServiceBuilder::new()
.layer_fn(|inner| RecordMetrics { inner })
.layer(AddExtensionLayer::new(recorder_handle)),
);
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
println!("listening on {}", addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
async fn handler() -> impl IntoResponse {
// simulate slowness
tokio::time::sleep(Duration::from_millis(100)).await;
}
async fn metrics_handler(Extension(handle): Extension<PrometheusHandle>) -> impl IntoResponse {
handle.render()
}
#[derive(Clone)]
struct RecordMetrics<S> {
inner: S,
}
impl<S, ReqBody, ResBody> Service<Request<ReqBody>> for RecordMetrics<S>
where
S: Service<Request<ReqBody>, Response = Response<ResBody>, Error = Infallible>,
{
type Response = S::Response;
type Error = Infallible;
type Future = RecordMetricsFuture<S::Future>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
let start = Instant::now();
let path = req.uri().path().to_string();
RecordMetricsFuture {
inner: self.inner.call(req),
path: Some(path),
start,
}
}
}
pin_project! {
struct RecordMetricsFuture<F> {
#[pin]
inner: F,
path: Option<String>,
start: Instant,
}
}
impl<F, B> Future for RecordMetricsFuture<F>
where
F: Future<Output = Result<Response<B>, Infallible>>,
{
type Output = Result<Response<B>, Infallible>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
match this.inner.poll(cx) {
Poll::Ready(Ok(res)) => {
let latency = this.start.elapsed().as_secs_f64();
let status = res.status().as_u16().to_string();
let path = this.path.take().expect("future polled after completion");
let labels = [("path", path), ("status", status)];
metrics::increment_counter!("api_http_requests_total", &labels);
metrics::histogram!("api_http_requests_duration_seconds", latency, &labels);
Poll::Ready(Ok(res))
}
Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
Poll::Pending => Poll::Pending,
}
}
} This results in these metrics:
|
1 task
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
We previously used
actix-web-prom
to give us good baseline metrics:but this had a lot of upgrade issues through the whole "required actix beta".
So, after the axum port in #6 we should see if there's a way to get good baseline default metrics added.
Probably requires a bit of a wait for metrics ecosystem to evolve though.
The text was updated successfully, but these errors were encountered: