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: &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 parent: span,
116 self.level,
117 %latency,
118 status = status(response),
119 response_headers,
120 "finished processing request"
121 );
122 }
123}
124
125fn status<B>(res: &Response<B>) -> Option<i32> {
126 use crate::classify::grpc_errors_as_failures::ParsedGrpcStatus;
127
128 // gRPC-over-HTTP2 uses the "application/grpc[+format]" content type, and gRPC-Web uses
129 // "application/grpc-web[+format]" or "application/grpc-web-text[+format]", where "format" is
130 // the message format, e.g. +proto, +json.
131 //
132 // So, valid grpc content types include (but are not limited to):
133 // - application/grpc
134 // - application/grpc+proto
135 // - application/grpc-web+proto
136 // - application/grpc-web-text+proto
137 //
138 // For simplicity, we simply check that the content type starts with "application/grpc".
139 let is_grpc = res
140 .headers()
141 .get(http::header::CONTENT_TYPE)
142 .map_or(false, |value| {
143 value.as_bytes().starts_with("application/grpc".as_bytes())
144 });
145
146 if is_grpc {
147 match crate::classify::grpc_errors_as_failures::classify_grpc_metadata(
148 res.headers(),
149 crate::classify::GrpcCode::Ok.into_bitmask(),
150 ) {
151 ParsedGrpcStatus::Success | ParsedGrpcStatus::HeaderNotGrpcCode => Some(0),
152 ParsedGrpcStatus::NonSuccess(status) => Some(status.code_raw()),
153 // if `grpc-status` is missing then its a streaming response and there is no status
154 // _yet_, so its neither success nor error
155 ParsedGrpcStatus::GrpcStatusHeaderMissing => None,
156 }
157 } else {
158 Some(res.status().as_u16().into())
159 }
160}