1use crate::service::web::response::IntoResponse;
6use crate::{
7 Request, Response, StreamingBody,
8 body::{Frame, SizeHint},
9};
10use pin_project_lite::pin_project;
11use rama_core::bytes::Bytes;
12use rama_core::error::BoxError;
13use rama_core::extensions::ExtensionsRef;
14use rama_core::telemetry::opentelemetry::metrics::UpDownCounter;
15use rama_core::telemetry::opentelemetry::semantic_conventions::metric::{
16 HTTP_SERVER_ACTIVE_REQUESTS, HTTP_SERVER_REQUEST_BODY_SIZE,
17};
18use rama_core::telemetry::opentelemetry::{
19 AttributesFactory, InstrumentationScope, KeyValue, MeterOptions, global,
20 metrics::{Counter, Histogram, Meter},
21 semantic_conventions,
22};
23use rama_core::{Layer, Service};
24use rama_net::{AuthorityInputExt, HttpVersionInputExt, ProtocolInputExt};
25use rama_utils::macros::define_inner_service_accessors;
26use std::sync::atomic::{self, AtomicUsize};
27use std::{borrow::Cow, fmt, sync::Arc};
28use tokio::time::Instant;
29
30use semantic_conventions::attribute::{
34 HTTP_REQUEST_METHOD, HTTP_RESPONSE_STATUS_CODE, NETWORK_PROTOCOL_VERSION, SERVER_PORT,
35 URL_SCHEME,
36};
37
38const HTTP_SERVER_DURATION: &str = "http.requests.duration";
39const HTTP_SERVER_TOTAL_REQUESTS: &str = "http.requests.total";
40const HTTP_SERVER_TOTAL_FAILURES: &str = "http.failures.total";
41const HTTP_SERVER_TOTAL_RESPONSES: &str = "http.responses.total";
42
43const HTTP_REQUEST_HOST: &str = "http.request.host";
44
45#[derive(Clone, Debug)]
51struct Metrics {
52 http_server_duration: Histogram<f64>,
53 http_server_total_requests: Counter<u64>,
54 http_server_total_responses: Counter<u64>,
55 http_server_total_failures: Counter<u64>,
56 http_server_active_requests: UpDownCounter<i64>,
57 http_server_request_body_size: Histogram<u64>,
58}
59
60impl Metrics {
61 #[must_use]
63 fn new(meter: &Meter, prefix: Option<&str>) -> Self {
64 let http_server_duration = meter
65 .f64_histogram(match &prefix {
66 Some(prefix) => Cow::Owned(format!("{prefix}.{HTTP_SERVER_DURATION}")),
67 None => Cow::Borrowed(HTTP_SERVER_DURATION),
68 })
69 .with_description("Measures the duration of inbound HTTP requests.")
70 .with_unit("s")
71 .build();
72
73 let http_server_total_requests = meter
74 .u64_counter(match &prefix {
75 Some(prefix) => Cow::Owned(format!("{prefix}.{HTTP_SERVER_TOTAL_REQUESTS}")),
76 None => Cow::Borrowed(HTTP_SERVER_TOTAL_REQUESTS),
77 })
78 .with_description("Measures the total number of HTTP requests have been seen.")
79 .build();
80
81 let http_server_total_responses = meter
82 .u64_counter(match &prefix {
83 Some(prefix) => Cow::Owned(format!("{prefix}.{HTTP_SERVER_TOTAL_RESPONSES}")),
84 None => Cow::Borrowed(HTTP_SERVER_TOTAL_RESPONSES),
85 })
86 .with_description("Measures the total number of HTTP responses have been seen.")
87 .build();
88
89 let http_server_total_failures = meter
90 .u64_counter(match &prefix {
91 Some(prefix) => Cow::Owned(format!("{prefix}.{HTTP_SERVER_TOTAL_FAILURES}")),
92 None => Cow::Borrowed(HTTP_SERVER_TOTAL_FAILURES),
93 })
94 .with_description(
95 "Measures the total number of failed HTTP requests that have been seen.",
96 )
97 .build();
98
99 let http_server_active_requests = meter
100 .i64_up_down_counter(match &prefix {
101 Some(prefix) => Cow::Owned(format!("{prefix}.{HTTP_SERVER_ACTIVE_REQUESTS}")),
102 None => Cow::Borrowed(HTTP_SERVER_ACTIVE_REQUESTS),
103 })
104 .with_description("Measures the number of active HTTP server requests.")
105 .build();
106
107 let http_server_request_body_size = meter
108 .u64_histogram(match &prefix {
109 Some(prefix) => Cow::Owned(format!("{prefix}.{HTTP_SERVER_REQUEST_BODY_SIZE}")),
110 None => Cow::Borrowed(HTTP_SERVER_REQUEST_BODY_SIZE),
111 })
112 .with_description("Measures the HTTP request body size.")
113 .with_unit("B")
114 .build();
115
116 Self {
117 http_server_total_requests,
118 http_server_total_responses,
119 http_server_total_failures,
120 http_server_duration,
121 http_server_active_requests,
122 http_server_request_body_size,
123 }
124 }
125}
126
127#[derive(Debug, Clone)]
129pub struct RequestMetricsLayer<F = ()> {
130 metrics: Arc<Metrics>,
131 base_attributes: Vec<KeyValue>,
132 attributes_factory: F,
133}
134
135impl RequestMetricsLayer<()> {
136 #[must_use]
139 pub fn new() -> Self {
140 Self::custom(MeterOptions::default())
141 }
142
143 #[must_use]
146 pub fn custom(opts: MeterOptions) -> Self {
147 let attributes = opts.attributes.unwrap_or_default();
148 let meter = get_versioned_meter();
149 let metrics = Metrics::new(&meter, opts.metric_prefix.as_deref());
150
151 Self {
152 metrics: Arc::new(metrics),
153 base_attributes: attributes,
154 attributes_factory: (),
155 }
156 }
157
158 pub fn with_attributes<F>(self, attributes: F) -> RequestMetricsLayer<F> {
161 RequestMetricsLayer {
162 metrics: self.metrics,
163 base_attributes: self.base_attributes,
164 attributes_factory: attributes,
165 }
166 }
167}
168
169impl Default for RequestMetricsLayer {
170 fn default() -> Self {
171 Self::new()
172 }
173}
174
175fn get_versioned_meter() -> Meter {
176 global::meter_with_scope(
177 InstrumentationScope::builder(const_format::formatcp!(
178 "{}-network-http",
179 rama_utils::info::NAME
180 ))
181 .with_version(rama_utils::info::VERSION)
182 .with_schema_url(semantic_conventions::SCHEMA_URL)
183 .build(),
184 )
185}
186
187impl<S, F: Clone> Layer<S> for RequestMetricsLayer<F> {
188 type Service = RequestMetricsService<S, F>;
189
190 fn layer(&self, inner: S) -> Self::Service {
191 RequestMetricsService {
192 inner,
193 metrics: self.metrics.clone(),
194 base_attributes: self.base_attributes.clone(),
195 attributes_factory: self.attributes_factory.clone(),
196 }
197 }
198
199 fn into_layer(self, inner: S) -> Self::Service {
200 RequestMetricsService {
201 inner,
202 metrics: self.metrics,
203 base_attributes: self.base_attributes,
204 attributes_factory: self.attributes_factory,
205 }
206 }
207}
208
209#[derive(Debug, Clone)]
211pub struct RequestMetricsService<S, F = ()> {
212 inner: S,
213 metrics: Arc<Metrics>,
214 base_attributes: Vec<KeyValue>,
215 attributes_factory: F,
216}
217
218impl<S> RequestMetricsService<S, ()> {
219 pub fn new(inner: S) -> Self {
221 RequestMetricsLayer::new().into_layer(inner)
222 }
223
224 define_inner_service_accessors!();
225}
226
227impl<S, F> RequestMetricsService<S, F> {
228 fn compute_attributes<Body>(&self, req: &Request<Body>) -> Vec<KeyValue>
229 where
230 F: AttributesFactory,
231 {
232 let mut attributes = self
233 .attributes_factory
234 .attributes(5 + self.base_attributes.len(), req.extensions());
235 attributes.extend(self.base_attributes.iter().cloned());
236
237 let authority = req.authority();
239 let protocol = req.protocol();
240 let http_version = req.http_version();
241
242 if let Some(authority) = authority.as_ref() {
243 attributes.push(KeyValue::new(HTTP_REQUEST_HOST, authority.host.to_string()));
244 if let Some(port) = authority.port.as_u16() {
245 attributes.push(KeyValue::new(SERVER_PORT, port as i64));
246 }
247 }
248
249 if let Some(protocol) = protocol.as_ref() {
251 attributes.push(KeyValue::new(URL_SCHEME, protocol.to_string()));
252 }
253
254 attributes.push(KeyValue::new(HTTP_REQUEST_METHOD, req.method().to_string()));
255 if let Some(http_version) = http_version.map(|v| match v {
256 rama_http_types::Version::HTTP_09 => "0.9",
257 rama_http_types::Version::HTTP_10 => "1.0",
258 rama_http_types::Version::HTTP_11 => "1.1",
259 rama_http_types::Version::HTTP_2 => "2",
260 rama_http_types::Version::HTTP_3 => "3",
261 }) {
262 attributes.push(KeyValue::new(NETWORK_PROTOCOL_VERSION, http_version));
263 }
264
265 attributes
266 }
267}
268
269impl<S, F, Body> Service<Request<Body>> for RequestMetricsService<S, F>
270where
271 S: Service<Request, Output: IntoResponse>,
272 F: AttributesFactory,
273 Body: StreamingBody<Data = Bytes, Error: Into<BoxError>> + Send + Sync + 'static,
274{
275 type Output = Response;
276 type Error = S::Error;
277
278 async fn serve(&self, req: Request<Body>) -> Result<Self::Output, Self::Error> {
279 let mut attributes: Vec<KeyValue> = self.compute_attributes(&req);
280
281 self.metrics.http_server_total_requests.add(1, &attributes);
282 self.metrics.http_server_active_requests.add(1, &attributes);
283
284 let timer = Instant::now();
286
287 let polled_body_size: Arc<AtomicUsize> = Default::default();
288 let req = req.map(|body| {
289 crate::Body::new(BodyTracker {
290 inner: body,
291 polled_size: polled_body_size.clone(),
292 })
293 });
294
295 let result = self.inner.serve(req).await;
296
297 self.metrics
298 .http_server_active_requests
299 .add(-1, &attributes);
300 self.metrics.http_server_request_body_size.record(
301 polled_body_size.load(atomic::Ordering::Relaxed) as u64,
302 &attributes,
303 );
304
305 match result {
306 Ok(res) => {
307 let res = res.into_response();
308
309 attributes.push(KeyValue::new(
310 HTTP_RESPONSE_STATUS_CODE,
311 res.status().as_u16() as i64,
312 ));
313
314 self.metrics.http_server_total_responses.add(1, &attributes);
315 self.metrics
316 .http_server_duration
317 .record(timer.elapsed().as_secs_f64(), &attributes);
318
319 Ok(res)
320 }
321 Err(err) => {
322 self.metrics.http_server_total_failures.add(1, &attributes);
323
324 Err(err)
325 }
326 }
327 }
328}
329
330pin_project! {
331 pub struct BodyTracker<B> {
334 #[pin]
335 inner: B,
336 polled_size: Arc<AtomicUsize>,
337 }
338}
339
340impl<B: fmt::Debug> fmt::Debug for BodyTracker<B> {
341 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
342 f.debug_struct("BodyTracker")
343 .field("inner", &self.inner)
344 .field("polled_size", &self.polled_size)
345 .finish()
346 }
347}
348
349impl<B> StreamingBody for BodyTracker<B>
350where
351 B: StreamingBody<Data = Bytes, Error: Into<BoxError>> + Send + Sync + 'static,
352{
353 type Data = B::Data;
354 type Error = B::Error;
355
356 fn poll_frame(
357 self: std::pin::Pin<&mut Self>,
358 cx: &mut std::task::Context<'_>,
359 ) -> std::task::Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
360 let this = self.project();
361 match this.inner.poll_frame(cx) {
362 std::task::Poll::Ready(opt) => {
363 if let Some(Ok(frame)) = &opt
364 && let Some(data) = frame.data_ref()
365 {
366 this.polled_size
367 .fetch_add(data.len(), atomic::Ordering::Relaxed);
368 }
369 std::task::Poll::Ready(opt)
370 }
371 std::task::Poll::Pending => std::task::Poll::Pending,
372 }
373 }
374
375 fn is_end_stream(&self) -> bool {
376 self.inner.is_end_stream()
377 }
378
379 fn size_hint(&self) -> SizeHint {
380 self.inner.size_hint()
381 }
382}
383
384#[cfg(test)]
385mod tests {
386 use rama_core::extensions::Extensions;
387
388 use super::*;
389
390 #[test]
391 fn test_default_svc_compute_attributes_default() {
392 let svc = RequestMetricsService::new(());
393 let req = Request::builder()
394 .uri("http://www.example.com")
395 .body(())
396 .unwrap();
397
398 let attributes = svc.compute_attributes(&req);
399
400 assert!(
401 attributes
402 .iter()
403 .any(|attr| attr.key.as_str() == HTTP_REQUEST_HOST)
404 );
405 }
406
407 #[test]
408 fn test_custom_svc_compute_attributes_default() {
409 let svc = RequestMetricsLayer::custom(MeterOptions {
410 metric_prefix: Some("foo".to_owned()),
411 ..Default::default()
412 })
413 .into_layer(());
414 let req = Request::builder()
415 .uri("http://www.example.com")
416 .body(())
417 .unwrap();
418
419 let attributes = svc.compute_attributes(&req);
420
421 assert!(
422 attributes
423 .iter()
424 .any(|attr| attr.key.as_str() == HTTP_REQUEST_HOST)
425 );
426 }
427
428 #[test]
429 fn test_custom_svc_compute_attributes_attributes_vec() {
430 let svc = RequestMetricsLayer::custom(MeterOptions {
431 metric_prefix: Some("foo".to_owned()),
432 ..Default::default()
433 })
434 .with_attributes(vec![KeyValue::new("test", "attribute_fn")])
435 .into_layer(());
436 let req = Request::builder()
437 .uri("http://www.example.com")
438 .body(())
439 .unwrap();
440
441 let attributes = svc.compute_attributes(&req);
442 assert!(
443 attributes
444 .iter()
445 .any(|attr| attr.key.as_str() == HTTP_REQUEST_HOST)
446 );
447 assert!(
448 attributes
449 .iter()
450 .any(|attr| attr.key.as_str() == "test" && attr.value.as_str() == "attribute_fn")
451 );
452 }
453
454 #[test]
455 fn test_custom_svc_compute_attributes_attribute_fn() {
456 let svc = RequestMetricsLayer::custom(MeterOptions {
457 metric_prefix: Some("foo".to_owned()),
458 ..Default::default()
459 })
460 .with_attributes(|size_hint: usize, _extensions: &Extensions| {
461 let mut attributes = Vec::with_capacity(size_hint + 1);
462 attributes.push(KeyValue::new("test", "attribute_fn"));
463 attributes
464 })
465 .into_layer(());
466 let req = Request::builder()
467 .uri("http://www.example.com")
468 .body(())
469 .unwrap();
470
471 let attributes = svc.compute_attributes(&req);
472
473 assert!(
474 attributes
475 .iter()
476 .any(|attr| attr.key.as_str() == HTTP_REQUEST_HOST)
477 );
478 assert!(
479 attributes
480 .iter()
481 .any(|attr| attr.key.as_str() == "test" && attr.value.as_str() == "attribute_fn")
482 );
483 }
484}