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}