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