Introduction : Pourquoi la surveillance des applications Rust est un défi et une opportunité
Rust est réputé pour ses performances et sa sécurité mémoire. Cependant, lorsque votre application fonctionne aux limites du matériel (par exemple, un serveur WebSocket à forte charge ou un analyseur de données), les métriques standard de CPU et de RAM s'avèrent souvent insuffisantes. Au niveau avancé, la surveillance des applications Rust nécessite non seulement la collecte de données, mais aussi la création d'un système d'alertes précis capable de prédire une panne avant qu'elle ne se produise.
Dans cet article, nous verrons comment construire un système de surveillance prêt pour la production pour Rust : des métriques personnalisées utilisant la crate prometheus à la configuration des alertes via Alertmanager et Grafana.
1. Métriques avancées : pas seulement les requêtes HTTP
La surveillance de base (nombre de requêtes, latences) est le minimum. Au niveau avancé, nous collectons des métriques qui reflètent l'état interne de l'application : taille des files d'attente, nombre de tâches actives dans Tokio, état du pool de connexions.
1.1. Métriques personnalisées avec les types Histogram et Gauge
Utilisons la crate prometheus. Voici un exemple de collecte de métriques pour un pool de connexions à une base de données :
use prometheus::{HistogramOpts, HistogramVec, Gauge, register_histogram_vec, register_gauge};use lazy_static::lazy_static;
lazy_static! { static ref DB_CONNECTION_POOL: Gauge = register_gauge!( "db_connection_pool_size", "Nombre actuel de connexions actives dans le pool" ).unwrap();
static ref DB_QUERY_DURATION: HistogramVec = register_histogram_vec!( HistogramOpts::new("db_query_duration_seconds", "Temps d'exécution de la requête à la BD"), &["query_type", "status"] ).unwrap();}
// Utilisation dans le codefn execute_query(query_type: &str) -> Result<(), String> { DB_CONNECTION_POOL.inc(); let start = std::time::Instant::now(); // ... exécution de la requête ... let duration = start.elapsed().as_secs_f64(); DB_QUERY_DURATION.with_label_values(&[query_type, "success"]).observe(duration); DB_CONNECTION_POOL.dec(); Ok(())}Important : Utilisez HistogramVec pour catégoriser les requêtes par type (SELECT, INSERT) et statut (success, error). Cela permettra de construire des cartes thermiques dans Grafana.
1.2. Métriques internes du runtime Tokio
Pour les applications asynchrones, il est crucial de connaître la charge du planificateur. Utilisons tokio-metrics :
use tokio_metrics::RuntimeMetrics;use std::sync::Arc;
async fn monitor_runtime(metrics: Arc<RuntimeMetrics>) { loop { tokio::time::sleep(std::time::Duration::from_secs(5)).await; let num_alive_tasks = metrics.num_alive_tasks(); let blocking_threads = metrics.num_blocking_threads(); // Envoyer ces valeurs à Prometheus via Gauge println!("Tâches actives : {}, Threads bloquants : {}", num_alive_tasks, blocking_threads); }}Ces métriques aident à identifier les fuites de tâches (lorsque num_alive_tasks augmente constamment) ou une utilisation excessive de block_in_place.
2. Architecture de collecte des métriques : Push vs Pull
Dans l'écosystème Rust, le modèle Pull (scraper Prometheus) est le plus souvent utilisé, mais pour les processus de courte durée (utilitaires CLI, analyseurs), l'approche Push via Pushgateway est pratique.
2.1. Modèle Pull : point de terminaison HTTP
La méthode standard consiste à lancer un serveur HTTP séparé sur le port 9090 (ou un autre) au sein de votre application :
use warp::Filter;use prometheus::{Encoder, TextEncoder};
async fn metrics_handler() -> Result<impl warp::Reply, warp::Rejection> { let metric_families = prometheus::gather(); let mut buffer = Vec::new(); let encoder = TextEncoder::new(); encoder.encode(&metric_families, &mut buffer).unwrap(); Ok(warp::reply::with_header(bu