1use std::fmt;
4use std::net::SocketAddr;
5use std::time::{Duration, Instant};
6
7use http::{header, StatusCode};
8
9use crate::filter::{Filter, WrapSealed};
10use crate::reject::IsReject;
11use crate::reply::Reply;
12use crate::route::Route;
13
14use self::internal::WithLog;
15
16pub fn log(name: &'static str) -> Log<impl Fn(Info<'_>) + Copy> {
34 let func = move |info: Info<'_>| {
35 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
53pub fn custom<F>(func: F) -> Log<F>
74where
75 F: Fn(Info<'_>),
76{
77 Log { func }
78}
79
80#[derive(Clone, Copy, Debug)]
82pub struct Log<F> {
83 func: F,
84}
85
86#[allow(missing_debug_implementations)]
88pub struct Info<'a> {
89 route: &'a Route,
90 start: Instant,
91 status: StatusCode,
92}
93
94impl<FN, F> WrapSealed<F> for Log<FN>
95where
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
103 fn wrap(&self, filter: F) -> Self::Wrapped {
104 WithLog {
105 filter,
106 log: self.clone(),
107 }
108 }
109}
110
111impl<'a> Info<'a> {
112 pub fn remote_addr(&self) -> Option<SocketAddr> {
114 self.route.remote_addr()
115 }
116
117 pub fn method(&self) -> &http::Method {
119 self.route.method()
120 }
121
122 pub fn path(&self) -> &str {
124 self.route.full_path()
125 }
126
127 pub fn version(&self) -> http::Version {
129 self.route.version()
130 }
131
132 pub fn status(&self) -> http::StatusCode {
134 self.status
135 }
136
137 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 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 pub fn elapsed(&self) -> Duration {
155 tokio::time::Instant::now().into_std() - self.start
156 }
157
158 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 pub fn request_headers(&self) -> &http::HeaderMap {
168 self.route.headers()
169 }
170}
171
172struct OptFmt<T>(Option<T>);
173
174impl<T: fmt::Display> fmt::Display for OptFmt<T> {
175 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176 if let Some(ref t) = self.0 {
177 fmt::Display::fmt(t, f)
178 } else {
179 f.write_str("-")
180 }
181 }
182}
183
184mod internal {
185 use std::future::Future;
186 use std::pin::Pin;
187 use std::task::{Context, Poll};
188 use std::time::Instant;
189
190 use futures_util::{ready, TryFuture};
191 use pin_project::pin_project;
192
193 use super::{Info, Log};
194 use crate::filter::{Filter, FilterBase, Internal};
195 use crate::reject::IsReject;
196 use crate::reply::{Reply, Response};
197 use crate::route;
198
199 #[allow(missing_debug_implementations)]
200 pub struct Logged(pub(super) Response);
201
202 impl Reply for Logged {
203 #[inline]
204 fn into_response(self) -> Response {
205 self.0
206 }
207 }
208
209 #[allow(missing_debug_implementations)]
210 #[derive(Clone, Copy)]
211 pub struct WithLog<FN, F> {
212 pub(super) filter: F,
213 pub(super) log: Log<FN>,
214 }
215
216 impl<FN, F> FilterBase for WithLog<FN, F>
217 where
218 FN: Fn(Info<'_>) + Clone + Send,
219 F: Filter + Clone + Send,
220 F::Extract: Reply,
221 F::Error: IsReject,
222 {
223 type Extract = (Logged,);
224 type Error = F::Error;
225 type Future = WithLogFuture<FN, F::Future>;
226
227 fn filter(&self, _: Internal) -> Self::Future {
228 let started = tokio::time::Instant::now().into_std();
229 WithLogFuture {
230 log: self.log.clone(),
231 future: self.filter.filter(Internal),
232 started,
233 }
234 }
235 }
236
237 #[allow(missing_debug_implementations)]
238 #[pin_project]
239 pub struct WithLogFuture<FN, F> {
240 log: Log<FN>,
241 #[pin]
242 future: F,
243 started: Instant,
244 }
245
246 impl<FN, F> Future for WithLogFuture<FN, F>
247 where
248 FN: Fn(Info<'_>),
249 F: TryFuture,
250 F::Ok: Reply,
251 F::Error: IsReject,
252 {
253 type Output = Result<(Logged,), F::Error>;
254
255 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
256 let pin = self.as_mut().project();
257 let (result, status) = match ready!(pin.future.try_poll(cx)) {
258 Ok(reply) => {
259 let resp = reply.into_response();
260 let status = resp.status();
261 (Poll::Ready(Ok((Logged(resp),))), status)
262 }
263 Err(reject) => {
264 let status = reject.status();
265 (Poll::Ready(Err(reject)), status)
266 }
267 };
268
269 route::with(|route| {
270 (self.log.func)(Info {
271 route,
272 start: self.started,
273 status,
274 });
275 });
276
277 result
278 }
279 }
280}