use http::{Request, Response};
use std::sync::Arc;
pub use self::impl_log::{log, LogMiddleware};
pub use self::impl_stdlog::stdlog;
pub trait Logger {
type Instance: Logging;
fn start<T>(&self, request: &Request<T>) -> Self::Instance;
}
impl<L: Logger> Logger for Arc<L> {
type Instance = L::Instance;
fn start<T>(&self, request: &Request<T>) -> Self::Instance {
(**self).start(request)
}
}
pub trait Logging {
fn finish<T>(self, response: &Response<T>);
}
impl<L: Logging> Logging for Option<L> {
fn finish<T>(self, response: &Response<T>) {
if let Some(instance) = self {
instance.finish(response);
}
}
}
mod impl_log {
use super::super::{Middleware, Service};
use super::{Logger, Logging};
use futures::{Async, Future, Poll};
use http::{Request, Response};
pub fn log<L>(logger: L) -> LogMiddleware<L>
where
L: Logger + Clone,
{
LogMiddleware { logger }
}
#[allow(missing_docs)]
#[derive(Debug, Clone)]
pub struct LogMiddleware<L> {
logger: L,
}
impl<S, L, ReqBody, ResBody> Middleware<S> for LogMiddleware<L>
where
S: Service<Request = Request<ReqBody>, Response = Response<ResBody>>,
L: Logger + Clone,
{
type Request = Request<ReqBody>;
type Response = Response<ResBody>;
type Error = S::Error;
type Service = LogService<S, L>;
fn wrap(&self, inner: S) -> Self::Service {
LogService {
inner,
logger: self.logger.clone(),
}
}
}
#[derive(Debug)]
pub struct LogService<S, L> {
inner: S,
logger: L,
}
impl<S, L, ReqBody, ResBody> Service for LogService<S, L>
where
S: Service<Request = Request<ReqBody>, Response = Response<ResBody>>,
L: Logger,
{
type Request = Request<ReqBody>;
type Response = Response<ResBody>;
type Error = S::Error;
type Future = LogServiceFuture<S::Future, L::Instance>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.inner.poll_ready()
}
fn call(&mut self, request: Self::Request) -> Self::Future {
let log_session = self.logger.start(&request);
LogServiceFuture {
future: self.inner.call(request),
log_session: Some(log_session),
}
}
}
#[derive(Debug)]
pub struct LogServiceFuture<Fut, L> {
future: Fut,
log_session: Option<L>,
}
impl<Fut, L, Bd> Future for LogServiceFuture<Fut, L>
where
Fut: Future<Item = Response<Bd>>,
L: Logging,
{
type Item = Response<Bd>;
type Error = Fut::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let response = try_ready!(self.future.poll());
let instance = self
.log_session
.take()
.expect("The future has already polled");
instance.finish(&response);
Ok(Async::Ready(response))
}
}
}
mod impl_stdlog {
use super::{log, LogMiddleware, Logger, Logging};
use http::{Method, Request, Response, Uri, Version};
use log::{logger, Level, Record};
use std::time::Instant;
pub fn stdlog(level: Level, target: &'static str) -> LogMiddleware<StdLog> {
log(StdLog { level, target })
}
#[derive(Debug, Copy, Clone)]
pub struct StdLog {
level: Level,
target: &'static str,
}
impl Logger for StdLog {
type Instance = Option<StdLogInstance>;
fn start<T>(&self, request: &Request<T>) -> Self::Instance {
if log_enabled!(target: self.target, self.level) {
let start = Instant::now();
Some(StdLogInstance {
target: self.target,
level: self.level,
method: request.method().clone(),
uri: request.uri().clone(),
version: request.version(),
start,
})
} else {
None
}
}
}
#[derive(Debug)]
pub struct StdLogInstance {
target: &'static str,
level: Level,
method: Method,
uri: Uri,
version: Version,
start: Instant,
}
impl Logging for StdLogInstance {
fn finish<T>(self, response: &Response<T>) {
logger().log(
&Record::builder()
.args(format_args!(
"{} {} -> {} ({:?})",
self.method,
self.uri,
response.status(),
self.start.elapsed()
)).level(self.level)
.target(self.target)
.build(),
);
}
}
}
#[cfg(test)]
mod tests {
use endpoint;
use log::Level;
use server;
#[test]
#[ignore]
fn compiletest_stdlog() {
server::start(endpoint::cloned("foo"))
.with_middleware(super::stdlog(Level::Debug, "target"))
.serve("127.0.0.1:4000")
.unwrap();
}
}