use axum::{
body::Body,
extract::MatchedPath,
http::{Request, Response},
middleware::Next,
};
use std::time::Instant;
use tracing::info;
pub async fn metrics_middleware(
req: Request<Body>,
next: Next,
) -> Response<Body> {
let start = Instant::now();
let method = req.method().to_string();
let path = req
.extensions()
.get::<MatchedPath>()
.map(|mp| mp.as_str().to_string())
.unwrap_or_else(|| req.uri().path().to_string());
let response = next.run(req).await;
let duration = start.elapsed();
let status = response.status().as_u16();
info!(
method = %method,
path = %path,
status = status,
duration_ms = duration.as_millis() as u64,
"http_request_completed"
);
response
}
pub async fn create_request_span(
req: Request<Body>,
next: Next,
) -> Response<Body> {
let method = req.method().to_string();
let uri = req.uri().to_string();
let version = format!("{:?}", req.version());
let path = req
.extensions()
.get::<MatchedPath>()
.map(|mp| mp.as_str().to_string())
.unwrap_or_else(|| req.uri().path().to_string());
let span = tracing::info_span!(
"http_request",
http.method = %method,
http.target = %uri,
http.route = %path,
http.version = %version,
otel.kind = "server",
otel.status_code = tracing::field::Empty,
);
let _enter = span.enter();
let response = next.run(req).await;
span.record("otel.status_code", response.status().as_u16());
response
}
#[cfg(test)]
mod tests {
use super::*;
use axum::{
body::Body,
http::{Request, StatusCode},
middleware,
response::Response,
routing::get,
Router,
};
use tower::ServiceExt;
async fn test_handler() -> &'static str {
"OK"
}
#[tokio::test]
async fn test_metrics_middleware() {
let app = Router::new()
.route("/test", get(test_handler))
.layer(middleware::from_fn(metrics_middleware));
let request = Request::builder()
.uri("/test")
.body(Body::empty())
.unwrap();
let response = app.oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
}
#[tokio::test]
async fn test_request_span_middleware() {
let app = Router::new()
.route("/test", get(test_handler))
.layer(middleware::from_fn(create_request_span));
let request = Request::builder()
.uri("/test")
.body(Body::empty())
.unwrap();
let response = app.oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
}
}