1use std::fmt;
4use std::time::{Duration, Instant};
5
6use http::{header, StatusCode};
7
8use crate::filter::{Filter, WrapSealed};
9use crate::reject::IsReject;
10use crate::reply::Reply;
11use crate::route::Route;
12
13use self::internal::WithLog;
14
15pub fn log(name: &'static str) -> Log<impl Fn(Info<'_>) + Copy> {
33 let func = move |info: Info<'_>| {
34 log::info!(
37 target: name,
38 "\"{} {} {:?}\" {} \"{}\" \"{}\" {:?}",
39 info.method(),
40 info.path(),
41 info.route.version(),
42 info.status().as_u16(),
43 OptFmt(info.referer()),
44 OptFmt(info.user_agent()),
45 info.elapsed(),
46 );
47 };
48 Log { func }
49}
50
51pub fn custom<F>(func: F) -> Log<F>
72where
73 F: Fn(Info<'_>),
74{
75 Log { func }
76}
77
78#[derive(Clone, Copy, Debug)]
80pub struct Log<F> {
81 func: F,
82}
83
84#[allow(missing_debug_implementations)]
86pub struct Info<'a> {
87 route: &'a Route,
88 start: Instant,
89 status: StatusCode,
90}
91
92impl<FN, F> WrapSealed<F> for Log<FN>
93where
94 FN: Fn(Info<'_>) + Clone + Send,
95 F: Filter + Clone + Send,
96 F::Extract: Reply,
97 F::Error: IsReject,
98{
99 type Wrapped = WithLog<FN, F>;
100
101 fn wrap(&self, filter: F) -> Self::Wrapped {
102 WithLog {
103 filter,
104 log: self.clone(),
105 }
106 }
107}
108
109impl<'a> Info<'a> {
110 pub fn method(&self) -> &http::Method {
112 self.route.method()
113 }
114
115 pub fn path(&self) -> &str {
117 self.route.full_path()
118 }
119
120 pub fn version(&self) -> http::Version {
122 self.route.version()
123 }
124
125 pub fn status(&self) -> http::StatusCode {
127 self.status
128 }
129
130 pub fn referer(&self) -> Option<&str> {
132 self.route
133 .headers()
134 .get(header::REFERER)
135 .and_then(|v| v.to_str().ok())
136 }
137
138 pub fn user_agent(&self) -> Option<&str> {
140 self.route
141 .headers()
142 .get(header::USER_AGENT)
143 .and_then(|v| v.to_str().ok())
144 }
145
146 pub fn elapsed(&self) -> Duration {
148 tokio::time::Instant::now().into_std() - self.start
149 }
150
151 pub fn host(&self) -> Option<&str> {
153 self.route
154 .headers()
155 .get(header::HOST)
156 .and_then(|v| v.to_str().ok())
157 }
158
159 pub fn request_headers(&self) -> &http::HeaderMap {
161 self.route.headers()
162 }
163}
164
165struct OptFmt<T>(Option<T>);
166
167impl<T: fmt::Display> fmt::Display for OptFmt<T> {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 if let Some(ref t) = self.0 {
170 fmt::Display::fmt(t, f)
171 } else {
172 f.write_str("-")
173 }
174 }
175}
176
177mod internal {
178 use std::future::Future;
179 use std::pin::Pin;
180 use std::task::{Context, Poll};
181 use std::time::Instant;
182
183 use futures_util::{ready, TryFuture};
184 use pin_project::pin_project;
185
186 use super::{Info, Log};
187 use crate::filter::{Filter, FilterBase, Internal};
188 use crate::reject::IsReject;
189 use crate::reply::{Reply, Response};
190 use crate::route;
191
192 #[allow(missing_debug_implementations)]
193 pub struct Logged(pub(super) Response);
194
195 impl Reply for Logged {
196 #[inline]
197 fn into_response(self) -> Response {
198 self.0
199 }
200 }
201
202 #[allow(missing_debug_implementations)]
203 #[derive(Clone, Copy)]
204 pub struct WithLog<FN, F> {
205 pub(super) filter: F,
206 pub(super) log: Log<FN>,
207 }
208
209 impl<FN, F> FilterBase for WithLog<FN, F>
210 where
211 FN: Fn(Info<'_>) + Clone + Send,
212 F: Filter + Clone + Send,
213 F::Extract: Reply,
214 F::Error: IsReject,
215 {
216 type Extract = (Logged,);
217 type Error = F::Error;
218 type Future = WithLogFuture<FN, F::Future>;
219
220 fn filter(&self, _: Internal) -> Self::Future {
221 let started = tokio::time::Instant::now().into_std();
222 WithLogFuture {
223 log: self.log.clone(),
224 future: self.filter.filter(Internal),
225 started,
226 }
227 }
228 }
229
230 #[allow(missing_debug_implementations)]
231 #[pin_project]
232 pub struct WithLogFuture<FN, F> {
233 log: Log<FN>,
234 #[pin]
235 future: F,
236 started: Instant,
237 }
238
239 impl<FN, F> Future for WithLogFuture<FN, F>
240 where
241 FN: Fn(Info<'_>),
242 F: TryFuture,
243 F::Ok: Reply,
244 F::Error: IsReject,
245 {
246 type Output = Result<(Logged,), F::Error>;
247
248 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
249 let pin = self.as_mut().project();
250 let (result, status) = match ready!(pin.future.try_poll(cx)) {
251 Ok(reply) => {
252 let resp = reply.into_response();
253 let status = resp.status();
254 (Poll::Ready(Ok((Logged(resp),))), status)
255 }
256 Err(reject) => {
257 let status = reject.status();
258 (Poll::Ready(Err(reject)), status)
259 }
260 };
261
262 route::with(|route| {
263 (self.log.func)(Info {
264 route,
265 start: self.started,
266 status,
267 });
268 });
269
270 result
271 }
272 }
273}