Creare log in stile Apache in Axum
Vi condivido una piccola funzione che uso per creare un log in stile Apache in Axum.
In pratica ad ogni richiesta viene creata una riga.
Ogni giorno viene creato un file apposito.
L'unico vero problema è indicarvi le dipendenze esatte da usare, perchè ne ho tante; quindi scusatemi ma ve le metto tutte:
[dependencies]
axum = { version = "0.8.6", features = ["multipart"] }
serde = { version = "1.0.228", features = ["derive"] }
tokio = { version = "1.47.1", features = ["full"] }
tower-http = { version = "0.6.6", features = ["cors", "trace"] }
sqlx = { version = "0.8.6", features = ["mysql", "runtime-tokio-rustls", "macros", "chrono", "rust_decimal"] }
anyhow = "1.0.99"
dotenvy = "0.15.7"
serde_json = "1.0.145"
chrono = { version = "0.4.42", features = ["serde"] }
jsonwebtoken = { version = "10", features = ["rust_crypto"] }
tracing = "0.1.41"
tracing-subscriber = "0.3.22"
tracing-appender = "0.2.3"
rust_decimal = "1.38.0"
bytes = "1.10.1"
image = "0.25.8"
Comunque in questo caso specifico non sto usando tracing per diversi motivi.
Questa la funzione:
use axum::{body::Body, http::Request, middleware::Next, response::Response};
use crate::state::AppState;
use axum::extract::State;
pub async fn log_requests(
State(_state): State<AppState>,
req: Request<Body>,
next: Next,
) -> Response {
use chrono::Local;
use std::fs::OpenOptions;
use std::io::Write;
use std::time::Instant;
let start = Instant::now();
let method = req.method().clone();
let uri = req.uri().clone();
let path = uri.path().to_string();
let query = uri.query().unwrap_or("").to_string();
let version = format!("{:?}", req.version());
let user_agent = req
.headers()
.get("user-agent")
.and_then(|v| v.to_str().ok())
.unwrap_or("-")
.to_string();
let referer = req
.headers()
.get("referer")
.and_then(|v| v.to_str().ok())
.unwrap_or("-")
.to_string();
let client_ip = req
.headers()
.get("x-forwarded-for")
.and_then(|v| v.to_str().ok())
.unwrap_or("localhost")
.to_string();
let response = next.run(req).await;
let status = response.status().as_u16();
let latency_ms = start.elapsed().as_millis();
let now = Local::now();
let ts = now.format("%Y-%b-%d:%H:%M:%S %z");
let date = now.format("%Y-%m-%d");
let filename = format!("logs/access_{}.log", date);
let full_path = if query.is_empty() {
path
} else {
format!("{}?{}", path, query)
};
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open(&filename)
.unwrap();
writeln!(
file,
r#"{} - [{}] "{} {} {}" {} - "{}" "{}" {}ms"#,
client_ip, ts, method, full_path, version, status, referer, user_agent, latency_ms
)
.unwrap();
file.flush().unwrap();
response
}
La aggiungiamo così:
let pool = MySqlPoolOptions::new()
.max_connections(5)
.connect(&config.database_url)
.await?;
let state = AppState { pool };
let app = Router::new()
.route("/", get(root))
.merge(utenti_routes())
.merge(auth_routes())
.merge(anagrafiche_routes())
.merge(causali_routes())
.merge(conti_routes())
.merge(bilanci_routes())
.merge(movimenti_routes())
.merge(note_routes())
.merge(fatture_passive_routes())
.layer(from_fn_with_state(state.clone(), log_requests))
.layer(CorsLayer::permissive())
.with_state(state);
Anche qui, ci possono essere cose che magari non vi servono.
Insomma dovete fare un pò di lavoro per renderla compatibile con il codice che avete voi.
Enjoy!
rust cargo log apache axum
Commentami!