1use tracing::Span;
12
13use http::header;
14
15use crate::filter::{Filter, WrapSealed};
16use crate::reject::IsReject;
17use crate::reply::Reply;
18use crate::route::Route;
19
20use self::internal::WithTrace;
21
22pub fn request() -> Trace<impl Fn(Info<'_>) -> Span + Clone> {
41 use tracing::field::{display, Empty};
42 trace(|info: Info<'_>| {
43 let span = tracing::info_span!(
44 "request",
45 remote.addr = Empty,
46 method = %info.method(),
47 path = %info.path(),
48 version = ?info.route.version(),
49 referer = Empty,
50 );
51
52 if let Some(referer) = info.referer() {
54 span.record("referer", &display(referer));
55 }
56
57 tracing::debug!(parent: &span, "received request");
58
59 span
60 })
61}
62
63pub fn trace<F>(func: F) -> Trace<F>
86where
87 F: Fn(Info<'_>) -> Span + Clone,
88{
89 Trace { func }
90}
91
92pub fn named(name: &'static str) -> Trace<impl Fn(Info<'_>) -> Span + Copy> {
117 trace(move |_| tracing::debug_span!("context", "{}", name,))
118}
119
120#[derive(Clone, Copy, Debug)]
126pub struct Trace<F> {
127 func: F,
128}
129
130#[allow(missing_debug_implementations)]
132pub struct Info<'a> {
133 route: &'a Route,
134}
135
136impl<FN, F> WrapSealed<F> for Trace<FN>
137where
138 FN: Fn(Info<'_>) -> Span + Clone + Send,
139 F: Filter + Clone + Send,
140 F::Extract: Reply,
141 F::Error: IsReject,
142{
143 type Wrapped = WithTrace<FN, F>;
144
145 fn wrap(&self, filter: F) -> Self::Wrapped {
146 WithTrace {
147 filter,
148 trace: self.clone(),
149 }
150 }
151}
152
153impl<'a> Info<'a> {
154 pub fn method(&self) -> &http::Method {
156 self.route.method()
157 }
158
159 pub fn path(&self) -> &str {
161 self.route.full_path()
162 }
163
164 pub fn version(&self) -> http::Version {
166 self.route.version()
167 }
168
169 pub fn referer(&self) -> Option<&str> {
171 self.route
172 .headers()
173 .get(header::REFERER)
174 .and_then(|v| v.to_str().ok())
175 }
176
177 pub fn user_agent(&self) -> Option<&str> {
179 self.route
180 .headers()
181 .get(header::USER_AGENT)
182 .and_then(|v| v.to_str().ok())
183 }
184
185 pub fn host(&self) -> Option<&str> {
187 self.route
188 .headers()
189 .get(header::HOST)
190 .and_then(|v| v.to_str().ok())
191 }
192
193 pub fn request_headers(&self) -> &http::HeaderMap {
195 self.route.headers()
196 }
197}
198
199mod internal {
200 use futures_util::{future::Inspect, future::MapOk, FutureExt, TryFutureExt};
201
202 use super::{Info, Trace};
203 use crate::filter::{Filter, FilterBase, Internal};
204 use crate::reject::IsReject;
205 use crate::reply::Reply;
206 use crate::reply::Response;
207 use crate::route;
208
209 #[allow(missing_debug_implementations)]
210 pub struct Traced(pub(super) Response);
211
212 impl Reply for Traced {
213 #[inline]
214 fn into_response(self) -> Response {
215 self.0
216 }
217 }
218
219 #[allow(missing_debug_implementations)]
220 #[derive(Clone, Copy)]
221 pub struct WithTrace<FN, F> {
222 pub(super) filter: F,
223 pub(super) trace: Trace<FN>,
224 }
225
226 use tracing::instrument::{Instrument, Instrumented};
227 use tracing::Span;
228
229 fn finished_logger<E: IsReject>(reply: &Result<(Traced,), E>) {
230 let (status, error) = match reply {
231 Ok((Traced(resp),)) => (resp.status(), None),
232 Err(error) => (error.status(), Some(error)),
233 };
234
235 if status.is_success() {
236 tracing::info!(
237 target: "warp::filters::trace",
238 status = status.as_u16(),
239 "finished processing with success"
240 );
241 } else if status.is_server_error() {
242 tracing::error!(
243 target: "warp::filters::trace",
244 status = status.as_u16(),
245 error = ?error,
246 "unable to process request (internal error)"
247 );
248 } else if status.is_client_error() {
249 tracing::warn!(
250 target: "warp::filters::trace",
251 status = status.as_u16(),
252 error = ?error,
253 "unable to serve request (client error)"
254 );
255 } else {
256 tracing::info!(
258 target: "warp::filters::trace",
259 status = status.as_u16(),
260 error = ?error,
261 "finished processing with status"
262 );
263 }
264 }
265
266 fn convert_reply<R: Reply>(reply: R) -> (Traced,) {
267 (Traced(reply.into_response()),)
268 }
269
270 impl<FN, F> FilterBase for WithTrace<FN, F>
271 where
272 FN: Fn(Info<'_>) -> Span + Clone + Send,
273 F: Filter + Clone + Send,
274 F::Extract: Reply,
275 F::Error: IsReject,
276 {
277 type Extract = (Traced,);
278 type Error = F::Error;
279 type Future = Instrumented<
280 Inspect<
281 MapOk<F::Future, fn(F::Extract) -> Self::Extract>,
282 fn(&Result<Self::Extract, F::Error>),
283 >,
284 >;
285
286 fn filter(&self, _: Internal) -> Self::Future {
287 let span = route::with(|route| (self.trace.func)(Info { route }));
288 let _entered = span.enter();
289
290 tracing::info!(target: "warp::filters::trace", "processing request");
291 self.filter
292 .filter(Internal)
293 .map_ok(convert_reply as fn(F::Extract) -> Self::Extract)
294 .inspect(finished_logger as fn(&Result<Self::Extract, F::Error>))
295 .instrument(span.clone())
296 }
297 }
298}