Monitoramento de aplicações Rust: métricas e alertas do A ao Z
\n\nQuando sua aplicação Rust entra em produção, o código deixa de ser apenas código. Ele se torna um serviço do qual dependem usuários e negócios. Sem monitoramento, você está voando às cegas: falhas são descobertas por reclamações e o desempenho é avaliado "no olho". Neste artigo, vamos abordar como construir um sistema completo de monitoramento para Rust — desde a coleta de métricas básicas até a configuração de alertas no Prometheus e visualização no Grafana.
\n\n1. Por que o monitoramento é importante para projetos Rust
\nRust é conhecido por seu desempenho e segurança de memória, mas isso não elimina a necessidade de monitorar a aplicação. Problemas típicos em produção:
\n- \n
- Vazamentos de memória (por exemplo, através de
Arcou referências cíclicas). \n - Bloqueios em código async (deadlocks ou starvation no tokio). \n
- Pânicos inesperados (mesmo com
catch_unwind). \n - Degradação de desempenho devido a algoritmos não otimizados. \n
O monitoramento responde às perguntas: "O que está acontecendo agora?", "Por que isso aconteceu?" e "Como prevenir isso?".
\n\n2. Coleta de métricas: biblioteca metrics e exportação para Prometheus
\nA stack padrão para Rust é a crate metrics (ou prometheus). Usamos metrics pois ele abstrai a exportação e facilita a troca de backend.
2.1. Instalação e configuração básica
\nAdicione as dependências no Cargo.toml:
[dependencies]\nmetrics = "0.21"\nmetrics-exporter-prometheus = "0.12"\ntokio = { version = "1", features = ["full"] }\n\nCriamos um servidor HTTP simples para o endpoint /metrics:
use metrics_exporter_prometheus::PrometheusBuilder;\nuse std::net::SocketAddr;\nuse tokio::net::TcpListener;\n\n#[tokio::main]\nasync fn main() {\n // Inicializa o exportador Prometheus\n let (recorder, exporter) = PrometheusBuilder::new()\n .build()\n .expect("Falha ao criar exportador Prometheus");\n\n metrics::set_boxed_recorder(Box::new(recorder))\n .expect("Falha ao definir o gravador global");\n\n // Inicia o servidor de métricas\n let addr: SocketAddr = "0.0.0.0:9090".parse().unwrap();\n let listener = TcpListener::bind(addr).await.unwrap();\n println!("Métricas disponíveis em http://{}", addr);\n\n loop {\n let (mut stream, _) = listener.accept().await.unwrap();\n let body = exporter.encode().unwrap();\n let response = format!(\n "HTTP/1.1 200 OK\\r\\nContent-Type: text/plain\\r\\nContent-Length: {}\\r\\n\\r\\n{}",\n body.len(),\n body\n );\n tokio::io::AsyncWriteExt::write_all(&mut stream, response.as_bytes())\n .await\n .unwrap();\n }\n}\n\n2.2. Registro de métricas personalizadas
\nAdicionamos um contador de requisições e um histograma de tempo de resposta:
\nuse metrics::{counter, histogram, increment_counter};\nuse std::time::Instant;\n\nasync fn handle_request() {\n let start = Instant::now();\n\n // Sua lógica de negócio\n tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;\n\n // Registra as métricas\n increment_counter!("http_requests_total", "method" => "GET");\n histogram!("http_request_duration_seconds", start.elapsed().as_secs_f64(), "method" => "GET");\n}\nAgora em /metrics você verá:
# HELP http_requests_total Total HTTP requests\n# TYPE http_requests_total counter\nhttp_requests_total{method="GET"} 1\n# HELP http_request_duration_seconds HTTP request duration\n# TYPE http_request_duration_seconds histogram\nhttp_request_duration_seconds_bucket{method="GET",le="0.005"} 0\nhttp_request_duration_seconds_bucket{method="GET",le="0.01"} 0\nhttp_request_duration_seconds_bucket{method="GET",le="0.025"} 0\n