use axum::{
extract::{MatchedPath, Request},
middleware::Next,
response::Response,
};
use tracing::trace;
pub async fn http_logging_middleware(request: Request, next: Next) -> Response {
let method = request.method().clone();
let uri = request.uri().clone();
let version = request.version();
let route = request
.extensions()
.get::<MatchedPath>()
.map(|path| path.as_str().to_owned())
.unwrap_or_else(|| "<unknown>".to_owned());
let headers = request.headers();
let content_type = headers
.get("content-type")
.and_then(|v| v.to_str().ok())
.unwrap_or("<none>")
.to_owned();
let content_length = headers
.get("content-length")
.and_then(|v| v.to_str().ok())
.unwrap_or("<none>")
.to_owned();
let user_agent = headers
.get("user-agent")
.and_then(|v| v.to_str().ok())
.unwrap_or("<none>")
.to_owned();
trace!(
method = %method,
uri = %uri,
route = %route,
version = ?version,
content_type = %content_type,
content_length = %content_length,
user_agent = %user_agent,
"HTTP request received"
);
let start = std::time::Instant::now();
let response = next.run(request).await;
let duration = start.elapsed();
let status = response.status();
let response_content_length = response
.headers()
.get("content-length")
.and_then(|v| v.to_str().ok())
.unwrap_or("<none>");
trace!(
method = %method,
uri = %uri,
route = %route,
status = %status,
duration_ms = %duration.as_millis(),
response_content_length = %response_content_length,
"HTTP response sent"
);
response
}
#[cfg(test)]
mod tests {
use super::*;
use axum::{Router, body::Body, http::StatusCode, routing::get};
use tower::ServiceExt;
#[tokio::test]
async fn test_http_logging_middleware() {
async fn test_handler() -> &'static str {
"OK"
}
let app = Router::new()
.route("/test", get(test_handler))
.layer(axum::middleware::from_fn(http_logging_middleware));
let response = app
.oneshot(Request::builder().uri("/test").body(Body::empty()).unwrap())
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
}
#[tokio::test]
async fn test_http_logging_with_headers() {
async fn test_handler() -> &'static str {
"OK"
}
let app = Router::new()
.route("/test", get(test_handler))
.layer(axum::middleware::from_fn(http_logging_middleware));
let response = app
.oneshot(
Request::builder()
.uri("/test")
.header("content-type", "application/json")
.header("user-agent", "test-agent")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
}
}