//! Logs each and every request out in a format similar to that of Apache's logs. use axum::{ extract, http::{Request, Response}, RequestExt, }; use futures::future::BoxFuture; use std::{ fmt::Debug, task::{Context, Poll}, }; use tower::Service; use tracing::{error, info, Instrument}; pub trait GenericError: std::error::Error + Debug + Send + Sync {} #[derive(Clone)] pub struct LoggingMiddleware(pub S); impl Service> for LoggingMiddleware where S: Service, Response = Response, Error = std::convert::Infallible> + Clone + Send + 'static, S::Future: Send + 'static, S::Response: Default + Debug, ReqBody: Send + Debug + 'static, ResBody: Default + Send + 'static, { type Response = S::Response; type Error = S::Error; type Future = BoxFuture<'static, Result>; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { self.0.poll_ready(cx) } fn call(&mut self, mut req: Request) -> Self::Future { // best practice is to clone the inner service like this // see https://github.com/tower-rs/tower/issues/547 for details let clone = self.0.clone(); let mut inner = std::mem::replace(&mut self.0, clone); let request_id = uuid::Uuid::new_v4(); let span = tracing::info_span!("web", "request_id" = request_id.to_string().as_str()); Box::pin(async move { let start = std::time::Instant::now(); let user_agent = req.headers().get(axum::http::header::USER_AGENT).cloned(); let method = req.method().clone(); let uri = req.uri().path().to_string(); let socket_addr = req .extract_parts::>() .await .map_or_else(|_| "0.0.0.0:0".parse().unwrap(), |v| v.0); // this is infallible because of the type of S::Error let response = inner.call(req).await?; if response.status().is_server_error() { error!( "{ip} - \"{method} {uri}\" {status} {duration:?} \"{user_agent}\" \"{error:?}\"", ip = socket_addr, method = method, uri = uri, status = response.status().as_u16(), duration = start.elapsed(), user_agent = user_agent .as_ref() .and_then(|v| v.to_str().ok()) .unwrap_or("unknown"), error = match response.extensions().get::>() { Some(e) => Err(e), None => Ok(()), } ); } else { info!( "{ip} - \"{method} {uri}\" {status} {duration:?} \"{user_agent}\" \"{error:?}\"", ip = socket_addr, method = method, uri = uri, status = response.status().as_u16(), duration = start.elapsed(), user_agent = user_agent .as_ref() .and_then(|v| v.to_str().ok()) .unwrap_or("unknown"), error = match response.extensions().get::>() { Some(e) => Err(e), None => Ok(()), } ); } Ok(response) }.instrument(span)) } }