datasynth_server/rest/
request_logging.rs1use axum::{body::Body, http::Request, middleware::Next, response::Response};
7use tracing::{info_span, Instrument};
8
9pub async fn request_logging_middleware(request: Request<Body>, next: Next) -> Response {
13 let method = request.method().clone();
14 let path = request.uri().path().to_string();
15 let start = std::time::Instant::now();
16
17 let request_id = request
19 .extensions()
20 .get::<crate::rest::request_id::RequestId>()
21 .map(|r| r.0.clone())
22 .unwrap_or_default();
23
24 let span = info_span!(
25 "http_request",
26 method = %method,
27 path = %path,
28 request_id = %request_id,
29 );
30
31 let response = next.run(request).instrument(span).await;
32
33 let duration_ms = start.elapsed().as_millis();
34 let status = response.status().as_u16();
35
36 tracing::info!(
37 method = %method,
38 path = %path,
39 status = status,
40 latency_ms = duration_ms,
41 request_id = %request_id,
42 "Request completed"
43 );
44
45 response
46}
47
48#[cfg(test)]
49#[allow(clippy::unwrap_used)]
50mod tests {
51 use super::*;
52 use axum::{routing::get, Router};
53 use tower::ServiceExt;
54
55 async fn ok_handler() -> &'static str {
56 "ok"
57 }
58
59 #[tokio::test]
60 async fn test_request_logging_passes_through() {
61 let router = Router::new()
62 .route("/test", get(ok_handler))
63 .layer(axum::middleware::from_fn(request_logging_middleware));
64
65 let request = Request::builder().uri("/test").body(Body::empty()).unwrap();
66
67 let response = router.oneshot(request).await.unwrap();
68 assert_eq!(response.status(), 200);
69 }
70}