#[cfg(all(feature = "response", feature = "telemetry"))] use axum::{ extract::{ConnectInfo, MatchedPath, Request}, middleware::Next, response::Response, }; #[cfg(all(feature = "response", feature = "telemetry"))] use std::net::SocketAddr; #[cfg(all(feature = "response", feature = "telemetry"))] use tracing::field; #[cfg(all(feature = "response", feature = "telemetry"))] fn first_forwarded_for(req: &Request) -> Option { let raw = req.headers().get("x-forwarded-for")?.to_str().ok()?; raw.split(',') .next() .map(|s| s.trim()) .filter(|s| !s.is_empty()) .map(|s| s.to_string()) } #[cfg(all(feature = "response", feature = "telemetry"))] fn connect_info_ip(req: &Request) -> Option { req.extensions() .get::>() .map(|ci| ci.0.ip().to_string()) } #[cfg(all(feature = "response", feature = "telemetry"))] fn matched_route(req: &Request) -> Option { req.extensions() .get::() .map(|m| m.as_str().to_string()) } #[cfg(all(feature = "response", feature = "telemetry"))] fn request_id(req: &Request) -> Option { req.headers() .get("x-request-id") .and_then(|v| v.to_str().ok()) .map(|s| s.to_string()) } #[cfg(all(feature = "response", feature = "telemetry"))] pub async fn trace_http_request(req: Request, next: Next) -> Response { let method = req.method().to_string(); let path = req.uri().path().to_string(); let route = matched_route(&req).unwrap_or_else(|| path.clone()); let client_ip = first_forwarded_for(&req).or_else(|| connect_info_ip(&req)); let req_id = request_id(&req); let span = tracing::info_span!( "http.request", http.method = %method, http.path = %path, http.route = %route, client.ip = %client_ip.clone().unwrap_or_else(|| "unknown".into()), request_id = %req_id.clone().unwrap_or_else(|| "unknown".into()), tenant_id = field::Empty, user_id = field::Empty, status = field::Empty, ); let _guard = span.enter(); let resp = next.run(req).await; let status = resp.status().as_u16(); tracing::Span::current().record("status", status); resp }