Files
OllaHubBackend/src/middlewares/logging.rs
T

146 lines
4.3 KiB
Rust

use actix_web::{
dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
Error,
};
use std::{
future::{ready, Ready},
pin::Pin,
rc::Rc,
time::Instant,
};
use crate::utils::log;
pub struct LoggingMiddleware;
impl<S, B> Transform<S, ServiceRequest> for LoggingMiddleware
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = LoggingMiddlewareService<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(LoggingMiddlewareService {
service: Rc::new(service),
}))
}
}
pub struct LoggingMiddlewareService<S> {
service: Rc<S>,
}
impl<S, B> Service<ServiceRequest> for LoggingMiddlewareService<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = Pin<Box<dyn std::future::Future<Output = Result<Self::Response, Self::Error>>>>;
forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
let start = Instant::now();
let method = req.method().clone();
let path = req.path().to_string();
let query_string = req.query_string();
let full_path = if query_string.is_empty() {
path.clone()
} else {
format!("{}?{}", path, query_string)
};
// Obter IP do cliente
let peer_addr = req.peer_addr()
.map(|addr| addr.ip().to_string())
.unwrap_or_else(|| "unknown".to_string());
// Obter User-Agent
let user_agent = req.headers()
.get("user-agent")
.and_then(|h| h.to_str().ok())
.unwrap_or("unknown")
.to_string();
// Obter Content-Type
let content_type = req.headers()
.get("content-type")
.and_then(|h| h.to_str().ok())
.unwrap_or("unknown")
.to_string();
// Obter Content-Length
let content_length = req.headers()
.get("content-length")
.and_then(|h| h.to_str().ok())
.unwrap_or("0")
.to_string();
// Obter Authorization header (mascarado)
let auth_header = req.headers()
.get("authorization")
.and_then(|h| h.to_str().ok())
.map(|h| {
if h.starts_with("Bearer ") {
let token = &h[7..];
if token.len() > 10 {
format!("Bearer {}...", &token[..10])
} else {
"Bearer [token]".to_string()
}
} else {
"[present]".to_string()
}
})
.unwrap_or_else(|| "none".to_string());
let service = self.service.clone();
Box::pin(async move {
let res = service.call(req).await?;
let elapsed_ms = start.elapsed().as_millis();
let status = res.status();
let status_code = status.as_u16();
// Obter tamanho da resposta (se disponível)
let response_size = res.response().headers()
.get("content-length")
.and_then(|h| h.to_str().ok())
.unwrap_or("unknown")
.to_string();
// Log formatado completo
let log_message = format!(
"API ACCESS | {} {} | Status: {} | IP: {} | Time: {}ms | UA: {} | CT: {} | CL: {} | Auth: {} | RespSize: {}",
method,
full_path,
status_code,
peer_addr,
elapsed_ms,
user_agent,
content_type,
content_length,
auth_header,
response_size
);
// Log de erro para status >= 400
if status_code >= 400 {
log::error(&log_message);
} else {
log::info(&log_message);
}
Ok(res)
})
}
}