1 //! Logger Filters
2 
3 use std::fmt;
4 use std::net::SocketAddr;
5 use std::time::{Duration, Instant};
6 
7 use http::{self, header, StatusCode};
8 
9 use crate::filter::{Filter, WrapSealed};
10 use crate::reject::IsReject;
11 use crate::reply::Reply;
12 use crate::route::Route;
13 
14 use self::internal::WithLog;
15 
16 /// Create a wrapping filter with the specified `name` as the `target`.
17 ///
18 /// This uses the default access logging format, and log records produced
19 /// will have their `target` set to `name`.
20 ///
21 /// # Example
22 ///
23 /// ```
24 /// use warp::Filter;
25 ///
26 /// // If using something like `pretty_env_logger`,
27 /// // view logs by setting `RUST_LOG=example::api`.
28 /// let log = warp::log("example::api");
29 /// let route = warp::any()
30 ///     .map(warp::reply)
31 ///     .with(log);
32 /// ```
log(name: &'static str) -> Log<impl Fn(Info) + Copy>33 pub fn log(name: &'static str) -> Log<impl Fn(Info) + Copy> {
34     let func = move |info: Info| {
35         // TODO?
36         // - response content length?
37         log::info!(
38             target: name,
39             "{} \"{} {} {:?}\" {} \"{}\" \"{}\" {:?}",
40             OptFmt(info.route.remote_addr()),
41             info.method(),
42             info.path(),
43             info.route.version(),
44             info.status().as_u16(),
45             OptFmt(info.referer()),
46             OptFmt(info.user_agent()),
47             info.elapsed(),
48         );
49     };
50     Log { func }
51 }
52 
53 /// Create a wrapping filter that receives `warp::log::Info`.
54 ///
55 /// # Example
56 ///
57 /// ```
58 /// use warp::Filter;
59 ///
60 /// let log = warp::log::custom(|info| {
61 ///     // Use a log macro, or slog, or println, or whatever!
62 ///     eprintln!(
63 ///         "{} {} {}",
64 ///         info.method(),
65 ///         info.path(),
66 ///         info.status(),
67 ///     );
68 /// });
69 /// let route = warp::any()
70 ///     .map(warp::reply)
71 ///     .with(log);
72 /// ```
custom<F>(func: F) -> Log<F> where F: Fn(Info),73 pub fn custom<F>(func: F) -> Log<F>
74 where
75     F: Fn(Info),
76 {
77     Log { func }
78 }
79 
80 /// Decorates a [`Filter`](crate::Filter) to log requests and responses.
81 #[derive(Clone, Copy, Debug)]
82 pub struct Log<F> {
83     func: F,
84 }
85 
86 /// Information about the request/response that can be used to prepare log lines.
87 #[allow(missing_debug_implementations)]
88 pub struct Info<'a> {
89     route: &'a Route,
90     start: Instant,
91     status: StatusCode,
92 }
93 
94 impl<FN, F> WrapSealed<F> for Log<FN>
95 where
96     FN: Fn(Info) + Clone + Send,
97     F: Filter + Clone + Send,
98     F::Extract: Reply,
99     F::Error: IsReject,
100 {
101     type Wrapped = WithLog<FN, F>;
102 
wrap(&self, filter: F) -> Self::Wrapped103     fn wrap(&self, filter: F) -> Self::Wrapped {
104         WithLog {
105             filter,
106             log: self.clone(),
107         }
108     }
109 }
110 
111 impl<'a> Info<'a> {
112     /// View the remote `SocketAddr` of the request.
remote_addr(&self) -> Option<SocketAddr>113     pub fn remote_addr(&self) -> Option<SocketAddr> {
114         self.route.remote_addr()
115     }
116 
117     /// View the `http::Method` of the request.
method(&self) -> &http::Method118     pub fn method(&self) -> &http::Method {
119         self.route.method()
120     }
121 
122     /// View the URI path of the request.
path(&self) -> &str123     pub fn path(&self) -> &str {
124         self.route.full_path()
125     }
126 
127     /// View the `http::Version` of the request.
version(&self) -> http::Version128     pub fn version(&self) -> http::Version {
129         self.route.version()
130     }
131 
132     /// View the `http::StatusCode` of the response.
status(&self) -> http::StatusCode133     pub fn status(&self) -> http::StatusCode {
134         self.status
135     }
136 
137     /// View the referer of the request.
referer(&self) -> Option<&str>138     pub fn referer(&self) -> Option<&str> {
139         self.route
140             .headers()
141             .get(header::REFERER)
142             .and_then(|v| v.to_str().ok())
143     }
144 
145     /// View the user agent of the request.
user_agent(&self) -> Option<&str>146     pub fn user_agent(&self) -> Option<&str> {
147         self.route
148             .headers()
149             .get(header::USER_AGENT)
150             .and_then(|v| v.to_str().ok())
151     }
152 
153     /// View the `Duration` that elapsed for the request.
elapsed(&self) -> Duration154     pub fn elapsed(&self) -> Duration {
155         tokio::time::Instant::now().into_std() - self.start
156     }
157 
158     /// View the host of the request
host(&self) -> Option<&str>159     pub fn host(&self) -> Option<&str> {
160         self.route
161             .headers()
162             .get(header::HOST)
163             .and_then(|v| v.to_str().ok())
164     }
165 }
166 
167 struct OptFmt<T>(Option<T>);
168 
169 impl<T: fmt::Display> fmt::Display for OptFmt<T> {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result170     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
171         if let Some(ref t) = self.0 {
172             fmt::Display::fmt(t, f)
173         } else {
174             f.write_str("-")
175         }
176     }
177 }
178 
179 mod internal {
180     use std::future::Future;
181     use std::pin::Pin;
182     use std::task::{Context, Poll};
183     use std::time::Instant;
184 
185     use futures::{ready, TryFuture};
186     use pin_project::pin_project;
187 
188     use super::{Info, Log};
189     use crate::filter::{Filter, FilterBase, Internal};
190     use crate::reject::IsReject;
191     use crate::reply::{Reply, Response};
192     use crate::route;
193 
194     #[allow(missing_debug_implementations)]
195     pub struct Logged(pub(super) Response);
196 
197     impl Reply for Logged {
198         #[inline]
into_response(self) -> Response199         fn into_response(self) -> Response {
200             self.0
201         }
202     }
203 
204     #[allow(missing_debug_implementations)]
205     #[derive(Clone, Copy)]
206     pub struct WithLog<FN, F> {
207         pub(super) filter: F,
208         pub(super) log: Log<FN>,
209     }
210 
211     impl<FN, F> FilterBase for WithLog<FN, F>
212     where
213         FN: Fn(Info) + Clone + Send,
214         F: Filter + Clone + Send,
215         F::Extract: Reply,
216         F::Error: IsReject,
217     {
218         type Extract = (Logged,);
219         type Error = F::Error;
220         type Future = WithLogFuture<FN, F::Future>;
221 
filter(&self, _: Internal) -> Self::Future222         fn filter(&self, _: Internal) -> Self::Future {
223             let started = tokio::time::Instant::now().into_std();
224             WithLogFuture {
225                 log: self.log.clone(),
226                 future: self.filter.filter(Internal),
227                 started,
228             }
229         }
230     }
231 
232     #[allow(missing_debug_implementations)]
233     #[pin_project]
234     pub struct WithLogFuture<FN, F> {
235         log: Log<FN>,
236         #[pin]
237         future: F,
238         started: Instant,
239     }
240 
241     impl<FN, F> Future for WithLogFuture<FN, F>
242     where
243         FN: Fn(Info),
244         F: TryFuture,
245         F::Ok: Reply,
246         F::Error: IsReject,
247     {
248         type Output = Result<(Logged,), F::Error>;
249 
poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output>250         fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
251             let pin = self.as_mut().project();
252             let (result, status) = match ready!(pin.future.try_poll(cx)) {
253                 Ok(reply) => {
254                     let resp = reply.into_response();
255                     let status = resp.status();
256                     (Poll::Ready(Ok((Logged(resp),))), status)
257                 }
258                 Err(reject) => {
259                     let status = reject.status();
260                     (Poll::Ready(Err(reject)), status)
261                 }
262             };
263 
264             route::with(|route| {
265                 (self.log.func)(Info {
266                     route,
267                     start: self.started,
268                     status,
269                 });
270             });
271 
272             result
273         }
274     }
275 }
276