guts_node/observability/
middleware.rs1use axum::{
9 body::Body,
10 extract::Request,
11 http::{header::HeaderName, HeaderValue},
12 middleware::Next,
13 response::Response,
14};
15use std::future::Future;
16use std::pin::Pin;
17use std::time::Instant;
18use uuid::Uuid;
19
20use super::metrics::METRICS;
21
22pub const REQUEST_ID_HEADER: &str = "x-request-id";
24
25type MiddlewareFuture = Pin<Box<dyn Future<Output = Response> + Send>>;
27
28type MiddlewareFn = fn(Request, Next) -> MiddlewareFuture;
30
31pub type MiddlewareLayer = axum::middleware::FromFnLayer<MiddlewareFn, (), Request>;
33
34pub fn request_id_layer() -> MiddlewareLayer {
36 axum::middleware::from_fn(request_id_middleware_fn)
37}
38
39fn request_id_middleware_fn(request: Request, next: Next) -> MiddlewareFuture {
40 Box::pin(async move {
41 let request_id = request
42 .headers()
43 .get(REQUEST_ID_HEADER)
44 .and_then(|v| v.to_str().ok())
45 .map(String::from)
46 .unwrap_or_else(|| Uuid::new_v4().to_string());
47
48 let span = tracing::info_span!(
50 "request",
51 request_id = %request_id,
52 method = %request.method(),
53 uri = %request.uri(),
54 );
55
56 let _guard = span.enter();
57
58 let mut response = next.run(request).await;
59
60 if let Ok(header_value) = HeaderValue::from_str(&request_id) {
62 response
63 .headers_mut()
64 .insert(HeaderName::from_static("x-request-id"), header_value);
65 }
66
67 response
68 })
69}
70
71pub async fn request_id_middleware(mut request: Request, next: Next) -> Response {
73 let request_id = request
75 .headers()
76 .get(REQUEST_ID_HEADER)
77 .and_then(|v| v.to_str().ok())
78 .map(String::from)
79 .unwrap_or_else(|| Uuid::new_v4().to_string());
80
81 request
83 .extensions_mut()
84 .insert(RequestId(request_id.clone()));
85
86 let span = tracing::info_span!(
88 "request",
89 request_id = %request_id,
90 method = %request.method(),
91 uri = %request.uri(),
92 );
93
94 let _guard = span.enter();
95
96 let mut response = next.run(request).await;
97
98 if let Ok(header_value) = HeaderValue::from_str(&request_id) {
100 response
101 .headers_mut()
102 .insert(HeaderName::from_static("x-request-id"), header_value);
103 }
104
105 response
106}
107
108#[derive(Clone, Debug)]
110pub struct RequestId(pub String);
111
112pub async fn metrics_middleware(request: Request, next: Next) -> Response {
114 let start = Instant::now();
115 let method = request.method().to_string();
116 let path = request.uri().path().to_string();
117
118 METRICS.http_active_connections.inc();
120
121 let response = next.run(request).await;
122
123 METRICS.http_active_connections.dec();
125
126 let duration = start.elapsed().as_secs_f64();
128 let status = response.status().as_u16();
129
130 METRICS.record_http_request(&method, &path, status, duration);
131
132 tracing::debug!(
133 method = %method,
134 path = %path,
135 status = %status,
136 duration_ms = %format!("{:.2}", duration * 1000.0),
137 "Request completed"
138 );
139
140 response
141}
142
143pub fn metrics_layer() -> MiddlewareLayer {
145 axum::middleware::from_fn(metrics_middleware_fn)
146}
147
148fn metrics_middleware_fn(request: Request, next: Next) -> MiddlewareFuture {
149 Box::pin(async move {
150 let start = Instant::now();
151 let method = request.method().to_string();
152 let path = request.uri().path().to_string();
153
154 METRICS.http_active_connections.inc();
155
156 let response = next.run(request).await;
157
158 METRICS.http_active_connections.dec();
159
160 let duration = start.elapsed().as_secs_f64();
161 let status = response.status().as_u16();
162
163 METRICS.record_http_request(&method, &path, status, duration);
164
165 tracing::debug!(
166 method = %method,
167 path = %path,
168 status = %status,
169 duration_ms = %format!("{:.2}", duration * 1000.0),
170 "Request completed"
171 );
172
173 response
174 })
175}
176
177pub type RequestIdLayer = axum::middleware::FromFnLayer<
179 fn(Request, Next) -> std::pin::Pin<Box<dyn std::future::Future<Output = Response> + Send>>,
180 (),
181 Request,
182>;
183
184pub type MetricsLayer = axum::middleware::FromFnLayer<
186 fn(Request, Next) -> std::pin::Pin<Box<dyn std::future::Future<Output = Response> + Send>>,
187 (),
188 Request,
189>;
190
191pub async fn metrics_handler() -> Response<Body> {
193 let metrics_output = METRICS.encode();
194
195 Response::builder()
196 .status(200)
197 .header("content-type", "text/plain; version=0.0.4; charset=utf-8")
198 .body(Body::from(metrics_output))
199 .unwrap_or_else(|_| {
200 Response::builder()
201 .status(500)
202 .body(Body::from("Failed to encode metrics"))
203 .expect("Failed to build error response")
204 })
205}