rama_http/layer/trace/
on_response.rs

1use super::{DEFAULT_MESSAGE_LEVEL, Latency};
2use crate::Response;
3use rama_utils::latency::LatencyUnit;
4use std::time::Duration;
5use tracing::Level;
6use tracing::Span;
7
8/// Trait used to tell [`Trace`] what to do when a response has been produced.
9///
10/// See the [module docs](../trace/index.html#on_response) for details on exactly when the
11/// `on_response` callback is called.
12///
13/// [`Trace`]: super::Trace
14pub trait OnResponse<B>: Send + Sync + 'static {
15    /// Do the thing.
16    ///
17    /// `latency` is the duration since the request was received.
18    ///
19    /// `span` is the `tracing` [`Span`], corresponding to this request, produced by the closure
20    /// passed to [`TraceLayer::make_span_with`]. It can be used to [record field values][record]
21    /// that weren't known when the span was created.
22    ///
23    /// [`Span`]: https://docs.rs/tracing/latest/tracing/span/index.html
24    /// [record]: https://docs.rs/tracing/latest/tracing/span/struct.Span.html#method.record
25    /// [`TraceLayer::make_span_with`]: crate::layer::trace::TraceLayer::make_span_with
26    fn on_response(self, response: &Response<B>, latency: Duration, span: &Span);
27}
28
29impl<B> OnResponse<B> for () {
30    #[inline]
31    fn on_response(self, _: &Response<B>, _: Duration, _: &Span) {}
32}
33
34impl<B, F> OnResponse<B> for F
35where
36    F: Fn(&Response<B>, Duration, &Span) + Send + Sync + 'static,
37{
38    fn on_response(self, response: &Response<B>, latency: Duration, span: &Span) {
39        self(response, latency, span)
40    }
41}
42
43/// The default [`OnResponse`] implementation used by [`Trace`].
44///
45/// [`Trace`]: super::Trace
46#[derive(Clone, Debug)]
47pub struct DefaultOnResponse {
48    level: Level,
49    latency_unit: LatencyUnit,
50    include_headers: bool,
51}
52
53impl Default for DefaultOnResponse {
54    fn default() -> Self {
55        Self {
56            level: DEFAULT_MESSAGE_LEVEL,
57            latency_unit: LatencyUnit::Millis,
58            include_headers: false,
59        }
60    }
61}
62
63impl DefaultOnResponse {
64    /// Create a new `DefaultOnResponse`.
65    pub fn new() -> Self {
66        Self::default()
67    }
68
69    /// Set the [`Level`] used for [tracing events].
70    ///
71    /// Please note that while this will set the level for the tracing events
72    /// themselves, it might cause them to lack expected information, like
73    /// request method or path. You can address this using
74    /// [`DefaultMakeSpan::level`].
75    ///
76    /// Defaults to [`Level::DEBUG`].
77    ///
78    /// [tracing events]: https://docs.rs/tracing/latest/tracing/#events
79    /// [`DefaultMakeSpan::level`]: crate::layer::trace::DefaultMakeSpan::level
80    pub fn level(mut self, level: Level) -> Self {
81        self.level = level;
82        self
83    }
84
85    /// Set the [`Level`] used for [tracing events].
86    ///
87    /// Please note that while this will set the level for the tracing events
88    /// themselves, it might cause them to lack expected information, like
89    /// request method or path. You can address this using
90    /// [`DefaultMakeSpan::level`].
91    ///
92    /// Defaults to [`Level::DEBUG`].
93    ///
94    /// [tracing events]: https://docs.rs/tracing/latest/tracing/#events
95    /// [`DefaultMakeSpan::level`]: crate::layer::trace::DefaultMakeSpan::level
96    pub fn set_level(&mut self, level: Level) -> &mut Self {
97        self.level = level;
98        self
99    }
100
101    /// Set the [`LatencyUnit`] latencies will be reported in.
102    ///
103    /// Defaults to [`LatencyUnit::Millis`].
104    pub fn latency_unit(mut self, latency_unit: LatencyUnit) -> Self {
105        self.latency_unit = latency_unit;
106        self
107    }
108
109    /// Set the [`LatencyUnit`] latencies will be reported in.
110    ///
111    /// Defaults to [`LatencyUnit::Millis`].
112    pub fn set_latency_unit(&mut self, latency_unit: LatencyUnit) -> &mut Self {
113        self.latency_unit = latency_unit;
114        self
115    }
116
117    /// Include response headers on the [`Event`].
118    ///
119    /// By default headers are not included.
120    ///
121    /// [`Event`]: tracing::Event
122    pub fn include_headers(mut self, include_headers: bool) -> Self {
123        self.include_headers = include_headers;
124        self
125    }
126
127    /// Include response headers on the [`Event`].
128    ///
129    /// By default headers are not included.
130    ///
131    /// [`Event`]: tracing::Event
132    pub fn set_include_headers(&mut self, include_headers: bool) -> &mut Self {
133        self.include_headers = include_headers;
134        self
135    }
136}
137
138impl<B> OnResponse<B> for DefaultOnResponse {
139    fn on_response(self, response: &Response<B>, latency: Duration, _: &Span) {
140        let latency = Latency {
141            unit: self.latency_unit,
142            duration: latency,
143        };
144        let response_headers = self
145            .include_headers
146            .then(|| tracing::field::debug(response.headers()));
147
148        event_dynamic_lvl!(
149            self.level,
150            %latency,
151            status = status(response),
152            response_headers,
153            "finished processing request"
154        );
155    }
156}
157
158fn status<B>(res: &Response<B>) -> Option<i32> {
159    use crate::layer::classify::grpc_errors_as_failures::ParsedGrpcStatus;
160
161    // gRPC-over-HTTP2 uses the "application/grpc[+format]" content type, and gRPC-Web uses
162    // "application/grpc-web[+format]" or "application/grpc-web-text[+format]", where "format" is
163    // the message format, e.g. +proto, +json.
164    //
165    // So, valid grpc content types include (but are not limited to):
166    //  - application/grpc
167    //  - application/grpc+proto
168    //  - application/grpc-web+proto
169    //  - application/grpc-web-text+proto
170    //
171    // For simplicity, we simply check that the content type starts with "application/grpc".
172    let is_grpc = res
173        .headers()
174        .get(rama_http_types::header::CONTENT_TYPE)
175        .is_some_and(|value| value.as_bytes().starts_with("application/grpc".as_bytes()));
176
177    if is_grpc {
178        match crate::layer::classify::grpc_errors_as_failures::classify_grpc_metadata(
179            res.headers(),
180            crate::layer::classify::GrpcCode::Ok.into_bitmask(),
181        ) {
182            ParsedGrpcStatus::Success
183            | ParsedGrpcStatus::HeaderNotString
184            | ParsedGrpcStatus::HeaderNotInt => Some(0),
185            ParsedGrpcStatus::NonSuccess(status) => Some(status.get()),
186            // if `grpc-status` is missing then its a streaming response and there is no status
187            // _yet_, so its neither success nor error
188            ParsedGrpcStatus::GrpcStatusHeaderMissing => None,
189        }
190    } else {
191        Some(res.status().as_u16().into())
192    }
193}