Monitoreo de aplicaciones Rust: métricas y alertas de la A a la Z
\n\nCuando tu aplicación Rust llega a producción, el código deja de ser solo código. Se convierte en un servicio del que dependen usuarios y negocios. Sin monitoreo, vuelas a ciegas: las fallas se detectan por quejas y el rendimiento se evalúa "a ojo". En este artículo, analizaremos cómo construir un sistema de monitoreo completo para Rust, desde la recopilación de métricas básicas hasta la configuración de alertas en Prometheus y la visualización en Grafana.
\n\n1. Por qué el monitoreo es importante para proyectos Rust
\nRust es conocido por su rendimiento y seguridad de memoria, pero eso no elimina la necesidad de supervisar la aplicación. Problemas típicos en producción:
\n- \n
- Fugas de memoria (por ejemplo, a través de
Arco referencias cíclicas). \n - Bloqueos en código async (deadlocks o starvation en tokio). \n
- Pánicos inesperados (incluso con
catch_unwind). \n - Degradación del rendimiento debido a algoritmos no óptimos. \n
El monitoreo responde a las preguntas: "¿Qué está pasando ahora?", "¿Por qué sucedió?" y "¿Cómo prevenirlo?".
\n\n2. Recopilación de métricas: biblioteca metrics y exportación a Prometheus
\nEl stack estándar para Rust es el crate metrics (o prometheus). Usamos metrics porque abstrae la exportación y facilita cambiar de backend.
2.1. Instalación y configuración básica
\nAgrega las dependencias en Cargo.toml:
[dependencies]\nmetrics = "0.21"\nmetrics-exporter-prometheus = "0.12"\ntokio = { version = "1", features = ["full"] }\n\nCreamos un servidor HTTP simple para el endpoint /metrics:
use metrics_exporter_prometheus::PrometheusBuilder;\nuse std::net::SocketAddr;\nuse tokio::net::TcpListener;\n\n#[tokio::main]\nasync fn main() {\n // Inicializamos el exportador Prometheus\n let (recorder, exporter) = PrometheusBuilder::new()\n .build()\n .expect("No se pudo crear el exportador Prometheus");\n\n metrics::set_boxed_recorder(Box::new(recorder))\n .expect("No se pudo establecer el grabador global");\n\n // Iniciamos el 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 disponibles en 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
\nAgregamos un contador de solicitudes y un histograma de tiempo de respuesta:
\nuse metrics::{counter, histogram, increment_counter};\nuse std::time::Instant;\n\nasync fn handle_request() {\n let start = Instant::now();\n\n // Tu lógica de negocio\n tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;\n\n // Registramos las métricas\n increment_counter!("http_requests_total", "method" => "GET");\n histogram!("http_request_duration_seconds", start.elapsed().as_secs_f64(), "method" => "GET");\n}\nAhora en /metrics verás:
# 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