lmrc_http_common/middleware/
logging.rs1use axum::{
4 extract::Request,
5 middleware::Next,
6 response::Response,
7};
8use std::time::Instant;
9use tracing::{info, info_span, Instrument};
10
11pub struct LoggingLayer;
13
14impl LoggingLayer {
15 pub fn new() -> Self {
16 Self
17 }
18}
19
20impl Default for LoggingLayer {
21 fn default() -> Self {
22 Self::new()
23 }
24}
25
26pub async fn log_request(req: Request, next: Next) -> Response {
28 let method = req.method().clone();
29 let uri = req.uri().clone();
30 let version = req.version();
31
32 let request_id = req
34 .extensions()
35 .get::<super::request_id::RequestId>()
36 .map(|id| id.as_str().to_string())
37 .unwrap_or_else(|| "unknown".to_string());
38
39 let span = info_span!(
40 "http_request",
41 method = %method,
42 uri = %uri,
43 version = ?version,
44 request_id = %request_id,
45 );
46
47 async move {
48 let start = Instant::now();
49
50 info!("request started");
51
52 let response = next.run(req).await;
53
54 let latency = start.elapsed();
55 let status = response.status();
56
57 info!(
58 status = %status.as_u16(),
59 latency_ms = %latency.as_millis(),
60 "request completed"
61 );
62
63 response
64 }
65 .instrument(span)
66 .await
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72 use axum::{
73 body::Body,
74 http::{Request, StatusCode},
75 middleware,
76 routing::get,
77 Router,
78 };
79 use tower::ServiceExt;
80
81 #[tokio::test]
82 async fn test_logging_middleware() {
83 async fn handler() -> &'static str {
84 "ok"
85 }
86
87 let app = Router::new()
88 .route("/", get(handler))
89 .layer(middleware::from_fn(log_request));
90
91 let request = Request::builder().uri("/").body(Body::empty()).unwrap();
92
93 let response = app.oneshot(request).await.unwrap();
94
95 assert_eq!(response.status(), StatusCode::OK);
96 }
97}