servers/http/
client_ip.rs1use std::net::SocketAddr;
16
17use axum::body::Body;
18use axum::extract::{ConnectInfo, MatchedPath};
19use axum::http::Request;
20use axum::middleware::Next;
21use axum::response::Response;
22use common_telemetry::warn;
23
24pub async fn log_error_with_client_ip(req: Request<Body>, next: Next) -> Response {
28 let request_info = if is_public_http_api_path(req.uri().path()) {
29 req.extensions()
30 .get::<ConnectInfo<SocketAddr>>()
31 .map(|c| c.0)
32 .map(|addr| {
33 let method = req.method().clone();
34 let uri = req.uri().clone();
35 let matched_path = req.extensions().get::<MatchedPath>().cloned();
36 (addr, method, uri, matched_path)
37 })
38 } else {
39 None
40 };
41
42 let response = next.run(req).await;
43
44 if (response.status().is_client_error() || response.status().is_server_error())
45 && let Some((addr, method, uri, matched_path)) = request_info
46 {
47 warn!(
48 "HTTP error response {} for {} {} (matched: {}) from client {}",
49 response.status(),
50 method,
51 uri,
52 matched_path
53 .as_ref()
54 .map(|p| p.as_str())
55 .unwrap_or("<unknown>"),
56 addr
57 );
58 }
59
60 response
61}
62
63fn is_public_http_api_path(path: &str) -> bool {
64 super::is_namespace(path, super::HTTP_API_PREFIX_WITHOUT_TRAILING_SLASH)
65}
66
67#[cfg(test)]
68mod tests {
69 use axum::Router;
70 use axum::routing::get;
71 use http::StatusCode;
72 use tower::ServiceExt;
73
74 use super::*;
75
76 #[test]
77 fn test_public_http_api_path_matches_v1_prefix() {
78 assert!(is_public_http_api_path("/v1"));
79 assert!(is_public_http_api_path("/v1/sql"));
80 assert!(is_public_http_api_path("/v1/prometheus/api/v1/query"));
81
82 assert!(!is_public_http_api_path("/"));
83 assert!(!is_public_http_api_path("/health"));
84 assert!(!is_public_http_api_path("/status"));
85 assert!(!is_public_http_api_path("/metrics"));
86 assert!(!is_public_http_api_path("/v10/sql"));
87 }
88
89 #[tokio::test]
90 async fn test_middleware_passes_error_response() {
91 async fn not_found_handler() -> StatusCode {
92 StatusCode::NOT_FOUND
93 }
94
95 let app = Router::new()
96 .route("/not-found", get(not_found_handler))
97 .layer(axum::middleware::from_fn(log_error_with_client_ip));
98
99 let response = app
100 .oneshot(
101 Request::builder()
102 .uri("/not-found")
103 .body(Body::empty())
104 .unwrap(),
105 )
106 .await
107 .unwrap();
108
109 assert_eq!(response.status(), StatusCode::NOT_FOUND);
110 }
111
112 #[tokio::test]
113 async fn test_middleware_passes_success_response() {
114 async fn ok_handler() -> StatusCode {
115 StatusCode::OK
116 }
117
118 let app = Router::new()
119 .route("/ok", get(ok_handler))
120 .layer(axum::middleware::from_fn(log_error_with_client_ip));
121
122 let response = app
123 .oneshot(Request::builder().uri("/ok").body(Body::empty()).unwrap())
124 .await
125 .unwrap();
126
127 assert_eq!(response.status(), StatusCode::OK);
128 }
129}