1use 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
24pub 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 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
69pub fn trace<F>(func: F) -> Trace<F>
92where
93 F: Fn(Info<'_>) -> Span + Clone,
94{
95 Trace { func }
96}
97
98pub fn named(name: &'static str) -> Trace<impl Fn(Info<'_>) -> Span + Copy> {
123 trace(move |_| tracing::debug_span!("context", "{}", name,))
124}
125
126#[derive(Clone, Copy, Debug)]
132pub struct Trace<F> {
133 func: F,
134}
135
136#[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 pub fn remote_addr(&self) -> Option<SocketAddr> {
162 self.route.remote_addr()
163 }
164
165 pub fn method(&self) -> &http::Method {
167 self.route.method()
168 }
169
170 pub fn path(&self) -> &str {
172 self.route.full_path()
173 }
174
175 pub fn version(&self) -> http::Version {
177 self.route.version()
178 }
179
180 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 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 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 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 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}