146 lines
4.3 KiB
Rust
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)
|
|
})
|
|
}
|
|
}
|
|
|