tower_http/trace/on_response.rs
1use super::{Latency, DEFAULT_MESSAGE_LEVEL};
2use crate::LatencyUnit;
3use http::Response;
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> {
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::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: FnOnce(&Response<B>, Duration, &Span),
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::trace::DefaultMakeSpan::level
80 pub fn level(mut self, level: Level) -> Self {
81 self.level = level;
82 self
83 }
84
85 /// Set the [`LatencyUnit`] latencies will be reported in.
86 ///
87 /// Defaults to [`LatencyUnit::Millis`].
88 pub fn latency_unit(mut self, latency_unit: LatencyUnit) -> Self {
89 self.latency_unit = latency_unit;
90 self
91 }
92
93 /// Include response headers on the [`Event`].
94 ///
95 /// By default headers are not included.
96 ///
97 /// [`Event`]: tracing::Event
98 pub fn include_headers(mut self, include_headers: bool) -> Self {
99 self.include_headers = include_headers;
100 self
101 }
102}
103
104impl<B> OnResponse<B> for DefaultOnResponse {
105 fn on_response(self, response: &Response<B>, latency: Duration, _: &Span) {
106 let latency = Latency {
107 unit: self.latency_unit,
108 duration: latency,
109 };
110 let response_headers = self
111 .include_headers
112 .then(|| tracing::field::debug(response.headers()));
113
114 event_dynamic_lvl!(
115 self.level,
116 %latency,
117 status = status(response),
118 response_headers,
119 "finished processing request"
120 );
121 }
122}
123
124fn status<B>(res: &Response<B>) -> Option<i32> {
125 use crate::classify::grpc_errors_as_failures::ParsedGrpcStatus;
126
127 // gRPC-over-HTTP2 uses the "application/grpc[+format]" content type, and gRPC-Web uses
128 // "application/grpc-web[+format]" or "application/grpc-web-text[+format]", where "format" is
129 // the message format, e.g. +proto, +json.
130 //
131 // So, valid grpc content types include (but are not limited to):
132 // - application/grpc
133 // - application/grpc+proto
134 // - application/grpc-web+proto
135 // - application/grpc-web-text+proto
136 //
137 // For simplicity, we simply check that the content type starts with "application/grpc".
138 let is_grpc = res
139 .headers()
140 .get(http::header::CONTENT_TYPE)
141 .map_or(false, |value| {
142 value.as_bytes().starts_with("application/grpc".as_bytes())
143 });
144
145 if is_grpc {
146 match crate::classify::grpc_errors_as_failures::classify_grpc_metadata(
147 res.headers(),
148 crate::classify::GrpcCode::Ok.into_bitmask(),
149 ) {
150 ParsedGrpcStatus::Success
151 | ParsedGrpcStatus::HeaderNotString
152 | ParsedGrpcStatus::HeaderNotInt => Some(0),
153 ParsedGrpcStatus::NonSuccess(status) => Some(status.get()),
154 // if `grpc-status` is missing then its a streaming response and there is no status
155 // _yet_, so its neither success nor error
156 ParsedGrpcStatus::GrpcStatusHeaderMissing => None,
157 }
158 } else {
159 Some(res.status().as_u16().into())
160 }
161}