tower_http_metrics/server/
body.rs

1use std::{
2    pin::Pin,
3    task::{Context, Poll},
4    time::Instant,
5};
6
7use http::HeaderMap;
8use http_body::Body;
9use metrics::{decrement_gauge, histogram};
10use pin_project::{pin_project, pinned_drop};
11
12use crate::{HTTP_SERVER_REQUESTS_PENDING, HTTP_SERVER_REQUEST_DURATION_SECONDS};
13
14#[pin_project(PinnedDrop)]
15pub struct InstrumentedBody<B> {
16    #[pin]
17    inner: B,
18    start: Instant,
19    method: &'static str,
20    status: u16,
21}
22
23impl<B> InstrumentedBody<B> {
24    pub(crate) fn new(inner: B, start: Instant, method: &'static str, status: u16) -> Self {
25        Self {
26            inner,
27            start,
28            method,
29            status,
30        }
31    }
32}
33
34impl<B: Body> Body for InstrumentedBody<B>
35where
36    B: Body,
37{
38    type Data = B::Data;
39
40    type Error = B::Error;
41
42    fn poll_data(
43        self: Pin<&mut Self>,
44        cx: &mut Context<'_>,
45    ) -> Poll<Option<Result<Self::Data, Self::Error>>> {
46        let this = self.project();
47        this.inner.poll_data(cx)
48    }
49
50    fn poll_trailers(
51        self: Pin<&mut Self>,
52        cx: &mut Context<'_>,
53    ) -> Poll<Result<Option<HeaderMap>, Self::Error>> {
54        let this = self.project();
55        this.inner.poll_trailers(cx)
56    }
57}
58
59#[pinned_drop]
60impl<B> PinnedDrop for InstrumentedBody<B> {
61    fn drop(self: Pin<&mut Self>) {
62        let duration_seconds = self.start.elapsed().as_secs_f64();
63
64        decrement_gauge!(
65            HTTP_SERVER_REQUESTS_PENDING,
66            1.0,
67            &[("method", self.method)]
68        );
69        histogram!(
70            HTTP_SERVER_REQUEST_DURATION_SECONDS,
71            duration_seconds,
72            &[
73                ("method", self.method.to_string()),
74                ("status", self.status.to_string())
75            ]
76        );
77    }
78}