tower_http_metrics/server/
body.rs1use 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}