finchers/server/middleware/
log.rs

1//! An implementation of logging middleware.
2
3use http::{Request, Response};
4use std::sync::Arc;
5
6pub use self::impl_log::{log, LogMiddleware};
7pub use self::impl_stdlog::stdlog;
8
9/// A trait representing a logger.
10pub trait Logger {
11    type Instance: Logging;
12
13    fn start<T>(&self, request: &Request<T>) -> Self::Instance;
14}
15
16impl<L: Logger> Logger for Arc<L> {
17    type Instance = L::Instance;
18
19    fn start<T>(&self, request: &Request<T>) -> Self::Instance {
20        (**self).start(request)
21    }
22}
23
24/// A trait representing a log session.
25pub trait Logging {
26    fn finish<T>(self, response: &Response<T>);
27}
28
29impl<L: Logging> Logging for Option<L> {
30    fn finish<T>(self, response: &Response<T>) {
31        if let Some(instance) = self {
32            instance.finish(response);
33        }
34    }
35}
36
37// ==== LogMiddleware ====
38
39mod impl_log {
40    use super::super::{Middleware, Service};
41    use super::{Logger, Logging};
42
43    use futures::{Async, Future, Poll};
44    use http::{Request, Response};
45
46    /// Create a logging middleware from the specified logger.
47    pub fn log<L>(logger: L) -> LogMiddleware<L>
48    where
49        L: Logger + Clone,
50    {
51        LogMiddleware { logger }
52    }
53
54    #[allow(missing_docs)]
55    #[derive(Debug, Clone)]
56    pub struct LogMiddleware<L> {
57        logger: L,
58    }
59
60    impl<S, L, ReqBody, ResBody> Middleware<S> for LogMiddleware<L>
61    where
62        S: Service<Request = Request<ReqBody>, Response = Response<ResBody>>,
63        L: Logger + Clone,
64    {
65        type Request = Request<ReqBody>;
66        type Response = Response<ResBody>;
67        type Error = S::Error;
68        type Service = LogService<S, L>;
69
70        fn wrap(&self, inner: S) -> Self::Service {
71            LogService {
72                inner,
73                logger: self.logger.clone(),
74            }
75        }
76    }
77
78    #[derive(Debug)]
79    pub struct LogService<S, L> {
80        inner: S,
81        logger: L,
82    }
83
84    impl<S, L, ReqBody, ResBody> Service for LogService<S, L>
85    where
86        S: Service<Request = Request<ReqBody>, Response = Response<ResBody>>,
87        L: Logger,
88    {
89        type Request = Request<ReqBody>;
90        type Response = Response<ResBody>;
91        type Error = S::Error;
92        type Future = LogServiceFuture<S::Future, L::Instance>;
93
94        fn poll_ready(&mut self) -> Poll<(), Self::Error> {
95            self.inner.poll_ready()
96        }
97
98        fn call(&mut self, request: Self::Request) -> Self::Future {
99            let log_session = self.logger.start(&request);
100            LogServiceFuture {
101                future: self.inner.call(request),
102                log_session: Some(log_session),
103            }
104        }
105    }
106
107    #[derive(Debug)]
108    pub struct LogServiceFuture<Fut, L> {
109        future: Fut,
110        log_session: Option<L>,
111    }
112
113    impl<Fut, L, Bd> Future for LogServiceFuture<Fut, L>
114    where
115        Fut: Future<Item = Response<Bd>>,
116        L: Logging,
117    {
118        type Item = Response<Bd>;
119        type Error = Fut::Error;
120
121        fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
122            let response = try_ready!(self.future.poll());
123            let instance = self
124                .log_session
125                .take()
126                .expect("The future has already polled");
127            instance.finish(&response);
128            Ok(Async::Ready(response))
129        }
130    }
131}
132
133// ==== StdLogger ====
134
135mod impl_stdlog {
136    use super::{log, LogMiddleware, Logger, Logging};
137
138    use http::{Method, Request, Response, Uri, Version};
139    use log::{logger, Level, Record};
140    use std::time::Instant;
141
142    /// Create a logging middleware which use the standard `log` crate.
143    pub fn stdlog(level: Level, target: &'static str) -> LogMiddleware<StdLog> {
144        log(StdLog { level, target })
145    }
146
147    #[derive(Debug, Copy, Clone)]
148    pub struct StdLog {
149        level: Level,
150        target: &'static str,
151    }
152
153    impl Logger for StdLog {
154        type Instance = Option<StdLogInstance>;
155
156        fn start<T>(&self, request: &Request<T>) -> Self::Instance {
157            if log_enabled!(target: self.target, self.level) {
158                let start = Instant::now();
159                Some(StdLogInstance {
160                    target: self.target,
161                    level: self.level,
162                    method: request.method().clone(),
163                    uri: request.uri().clone(),
164                    version: request.version(),
165                    start,
166                })
167            } else {
168                None
169            }
170        }
171    }
172
173    #[derive(Debug)]
174    pub struct StdLogInstance {
175        target: &'static str,
176        level: Level,
177        method: Method,
178        uri: Uri,
179        version: Version,
180        start: Instant,
181    }
182
183    impl Logging for StdLogInstance {
184        fn finish<T>(self, response: &Response<T>) {
185            logger().log(
186                &Record::builder()
187                    .args(format_args!(
188                        "{} {} -> {} ({:?})",
189                        self.method,
190                        self.uri,
191                        response.status(),
192                        self.start.elapsed()
193                    )).level(self.level)
194                    .target(self.target)
195                    .build(),
196            );
197        }
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use endpoint;
204    use log::Level;
205    use server;
206
207    #[test]
208    #[ignore]
209    fn compiletest_stdlog() {
210        server::start(endpoint::cloned("foo"))
211            .with_middleware(super::stdlog(Level::Debug, "target"))
212            .serve("127.0.0.1:4000")
213            .unwrap();
214    }
215}