webdav_server/api/middleware/log.rs
1use std::net::Ipv4Addr;
2
3use crate::logger::Entry;
4use crate::logger::GLOBAL_LOGGER;
5use axum::response::IntoResponse;
6use axum::{
7 extract::{ConnectInfo, Request},
8 middleware::Next,
9};
10
11/// Middleware for post-request error telemetry.
12///
13/// This middleware runs after the route handler has completed and inspects the
14/// response for a [`crate::logger::Entry`] that may have been inserted by
15/// [`crate::Error::into_response`]. If an entry is present and the global logger
16/// is active, it prepends the client IP address, HTTP method, and request path
17/// to the message and dispatches the entry to the logging service.
18///
19/// This function is only attached to the router when [`crate::logger::logging_enabled`]
20/// returns `true`. When the logger is inactive the entire middleware layer is absent
21/// from the router, so no overhead is incurred.
22///
23/// # Design: zero-allocation on the happy path
24///
25/// The HTTP method (`Method`) and URI (`Uri`) are cloned before the handler runs.
26/// Both types are cheap to clone (`Method` is a small enum, `Uri` is backed by an
27/// `Arc`-managed buffer). The format string that prepends the request context to the
28/// log message is only allocated inside the `if let` block, which is only entered
29/// when an error entry is actually present in the response extensions.
30pub async fn log_middleware(
31 ConnectInfo(addr): ConnectInfo<Ipv4Addr>,
32 request: Request,
33 next: Next,
34) -> impl IntoResponse {
35 let method = request.method().clone();
36 let path = request.uri().clone();
37
38 let mut response = next.run(request).await;
39
40 if let Some(mut entry) = response.extensions_mut().remove::<Entry>()
41 && let Some(sender) = GLOBAL_LOGGER.get()
42 {
43 entry.message = format!(
44 "[{}] {} {} FAILED:\n{}",
45 addr, method, path, entry.message
46 );
47
48 let _ = sender.try_send(entry);
49 }
50
51 response
52}