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 Transform for LoggingMiddleware where S: Service, Error = Error> + 'static, S::Future: 'static, B: 'static, { type Response = ServiceResponse; type Error = Error; type InitError = (); type Transform = LoggingMiddlewareService; type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { ready(Ok(LoggingMiddlewareService { service: Rc::new(service), })) } } pub struct LoggingMiddlewareService { service: Rc, } impl Service for LoggingMiddlewareService where S: Service, Error = Error> + 'static, S::Future: 'static, B: 'static, { type Response = ServiceResponse; type Error = Error; type Future = Pin>>>; 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) }) } }