warp/filters/
trace.rs

1//! [`tracing`] filters.
2//!
3//! [`tracing`] is a framework for instrumenting Rust programs to
4//! collect scoped, structured, and async-aware diagnostics. This module
5//! provides a set of filters for instrumenting Warp applications with `tracing`
6//! spans. [`Spans`] can be used to associate individual events  with a request,
7//! and track contexts through the application.
8//!
9//! [`tracing`]: https://crates.io/crates/tracing
10//! [`Spans`]: https://docs.rs/tracing/latest/tracing/#spans
11use tracing::Span;
12
13use std::net::SocketAddr;
14
15use http::header;
16
17use crate::filter::{Filter, WrapSealed};
18use crate::reject::IsReject;
19use crate::reply::Reply;
20use crate::route::Route;
21
22use self::internal::WithTrace;
23
24/// Create a wrapping filter that instruments every request with a `tracing`
25/// [`Span`] at the [`INFO`] level, containing a summary of the request.
26/// Additionally, if the [`DEBUG`] level is enabled, the span will contain an
27/// event recording the request's headers.
28///
29/// # Example
30///
31/// ```
32/// use warp::Filter;
33///
34/// let route = warp::any()
35///     .map(warp::reply)
36///     .with(warp::trace::request());
37/// ```
38///
39/// [`Span`]: https://docs.rs/tracing/latest/tracing/#spans
40/// [`INFO`]: https://docs.rs/tracing/0.1.16/tracing/struct.Level.html#associatedconstant.INFO
41/// [`DEBUG`]: https://docs.rs/tracing/0.1.16/tracing/struct.Level.html#associatedconstant.DEBUG
42pub fn request() -> Trace<impl Fn(Info<'_>) -> Span + Clone> {
43    use tracing::field::{display, Empty};
44    trace(|info: Info<'_>| {
45        let span = tracing::info_span!(
46            "request",
47            remote.addr = Empty,
48            method = %info.method(),
49            path = %info.path(),
50            version = ?info.route.version(),
51            referer = Empty,
52        );
53
54        // Record optional fields.
55        if let Some(remote_addr) = info.remote_addr() {
56            span.record("remote.addr", &display(remote_addr));
57        }
58
59        if let Some(referer) = info.referer() {
60            span.record("referer", &display(referer));
61        }
62
63        tracing::debug!(parent: &span, "received request");
64
65        span
66    })
67}
68
69/// Create a wrapping filter that instruments every request with a custom
70/// `tracing` [`Span`] provided by a function.
71///
72///
73/// # Example
74///
75/// ```
76/// use warp::Filter;
77///
78/// let route = warp::any()
79///     .map(warp::reply)
80///     .with(warp::trace(|info| {
81///         // Create a span using tracing macros
82///         tracing::info_span!(
83///             "request",
84///             method = %info.method(),
85///             path = %info.path(),
86///         )
87///     }));
88/// ```
89///
90/// [`Span`]: https://docs.rs/tracing/latest/tracing/#spans
91pub fn trace<F>(func: F) -> Trace<F>
92where
93    F: Fn(Info<'_>) -> Span + Clone,
94{
95    Trace { func }
96}
97
98/// Create a wrapping filter that instruments every request with a `tracing`
99/// [`Span`] at the [`DEBUG`] level representing a named context.
100///
101/// This can be used to instrument multiple routes with their own sub-spans in a
102/// per-request trace.
103///
104/// # Example
105///
106/// ```
107/// use warp::Filter;
108///
109/// let hello = warp::path("hello")
110///     .map(warp::reply)
111///     .with(warp::trace::named("hello"));
112///
113/// let goodbye = warp::path("goodbye")
114///     .map(warp::reply)
115///     .with(warp::trace::named("goodbye"));
116///
117/// let routes = hello.or(goodbye);
118/// ```
119///
120/// [`Span`]: https://docs.rs/tracing/latest/tracing/#spans
121/// [`DEBUG`]: https://docs.rs/tracing/0.1.16/tracing/struct.Level.html#associatedconstant.DEBUG
122pub fn named(name: &'static str) -> Trace<impl Fn(Info<'_>) -> Span + Copy> {
123    trace(move |_| tracing::debug_span!("context", "{}", name,))
124}
125
126/// Decorates a [`Filter`] to create a [`tracing`] [span] for
127/// requests and responses.
128///
129/// [`tracing`]: https://crates.io/crates/tracing
130/// [span]: https://docs.rs/tracing/latest/tracing/#spans
131#[derive(Clone, Copy, Debug)]
132pub struct Trace<F> {
133    func: F,
134}
135
136/// Information about the request/response that can be used to prepare log lines.
137#[allow(missing_debug_implementations)]
138pub struct Info<'a> {
139    route: &'a Route,
140}
141
142impl<FN, F> WrapSealed<F> for Trace<FN>
143where
144    FN: Fn(Info<'_>) -> Span + Clone + Send,
145    F: Filter + Clone + Send,
146    F::Extract: Reply,
147    F::Error: IsReject,
148{
149    type Wrapped = WithTrace<FN, F>;
150
151    fn wrap(&self, filter: F) -> Self::Wrapped {
152        WithTrace {
153            filter,
154            trace: self.clone(),
155        }
156    }
157}
158
159impl<'a> Info<'a> {
160    /// View the remote `SocketAddr` of the request.
161    pub fn remote_addr(&self) -> Option<SocketAddr> {
162        self.route.remote_addr()
163    }
164
165    /// View the `http::Method` of the request.
166    pub fn method(&self) -> &http::Method {
167        self.route.method()
168    }
169
170    /// View the URI path of the request.
171    pub fn path(&self) -> &str {
172        self.route.full_path()
173    }
174
175    /// View the `http::Version` of the request.
176    pub fn version(&self) -> http::Version {
177        self.route.version()
178    }
179
180    /// View the referer of the request.
181    pub fn referer(&self) -> Option<&str> {
182        self.route
183            .headers()
184            .get(header::REFERER)
185            .and_then(|v| v.to_str().ok())
186    }
187
188    /// View the user agent of the request.
189    pub fn user_agent(&self) -> Option<&str> {
190        self.route
191            .headers()
192            .get(header::USER_AGENT)
193            .and_then(|v| v.to_str().ok())
194    }
195
196    /// View the host of the request
197    pub fn host(&self) -> Option<&str> {
198        self.route
199            .headers()
200            .get(header::HOST)
201            .and_then(|v| v.to_str().ok())
202    }
203
204    /// View the request headers.
205    pub fn request_headers(&self) -> &http::HeaderMap {
206        self.route.headers()
207    }
208}
209
210mod internal {
211    use futures_util::{future::Inspect, future::MapOk, FutureExt, TryFutureExt};
212
213    use super::{Info, Trace};
214    use crate::filter::{Filter, FilterBase, Internal};
215    use crate::reject::IsReject;
216    use crate::reply::Reply;
217    use crate::reply::Response;
218    use crate::route;
219
220    #[allow(missing_debug_implementations)]
221    pub struct Traced(pub(super) Response);
222
223    impl Reply for Traced {
224        #[inline]
225        fn into_response(self) -> Response {
226            self.0
227        }
228    }
229
230    #[allow(missing_debug_implementations)]
231    #[derive(Clone, Copy)]
232    pub struct WithTrace<FN, F> {
233        pub(super) filter: F,
234        pub(super) trace: Trace<FN>,
235    }
236
237    use tracing::instrument::{Instrument, Instrumented};
238    use tracing::Span;
239
240    fn finished_logger<E: IsReject>(reply: &Result<(Traced,), E>) {
241        let (status, error) = match reply {
242            Ok((Traced(resp),)) => (resp.status(), None),
243            Err(error) => (error.status(), Some(error)),
244        };
245
246        if status.is_success() {
247            tracing::info!(
248                target: "warp::filters::trace",
249                status = status.as_u16(),
250                "finished processing with success"
251            );
252        } else if status.is_server_error() {
253            tracing::error!(
254                target: "warp::filters::trace",
255                status = status.as_u16(),
256                error = ?error,
257                "unable to process request (internal error)"
258            );
259        } else if status.is_client_error() {
260            tracing::warn!(
261                target: "warp::filters::trace",
262                status = status.as_u16(),
263                error = ?error,
264                "unable to serve request (client error)"
265            );
266        } else {
267            // Either informational or redirect
268            tracing::info!(
269                target: "warp::filters::trace",
270                status = status.as_u16(),
271                error = ?error,
272                    "finished processing with status"
273            );
274        }
275    }
276
277    fn convert_reply<R: Reply>(reply: R) -> (Traced,) {
278        (Traced(reply.into_response()),)
279    }
280
281    impl<FN, F> FilterBase for WithTrace<FN, F>
282    where
283        FN: Fn(Info<'_>) -> Span + Clone + Send,
284        F: Filter + Clone + Send,
285        F::Extract: Reply,
286        F::Error: IsReject,
287    {
288        type Extract = (Traced,);
289        type Error = F::Error;
290        type Future = Instrumented<
291            Inspect<
292                MapOk<F::Future, fn(F::Extract) -> Self::Extract>,
293                fn(&Result<Self::Extract, F::Error>),
294            >,
295        >;
296
297        fn filter(&self, _: Internal) -> Self::Future {
298            let span = route::with(|route| (self.trace.func)(Info { route }));
299            let _entered = span.enter();
300
301            tracing::info!(target: "warp::filters::trace", "processing request");
302            self.filter
303                .filter(Internal)
304                .map_ok(convert_reply as fn(F::Extract) -> Self::Extract)
305                .inspect(finished_logger as fn(&Result<Self::Extract, F::Error>))
306                .instrument(span.clone())
307        }
308    }
309}