Pourquoi l'observabilité est un super-pouvoir pour le développeur Rust
Imaginez : votre service en Rust fonctionne en production, mais soudainement la latence monte en flèche. Les logs se taisent, les métriques affichent 200 OK, et les utilisateurs se plaignent. Cela vous dit quelque chose ? C'est le classique « boîte noire ». Sans observabilité en Rust, vous êtes aveugle.
L'observabilité n'est pas simplement « logs + métriques ». C'est la capacité de poser n'importe quelle question à votre système sans avoir à écrire de nouveau code. En Rust, avec son accent sur la performance et la fiabilité, l'observabilité devient un ingrédient obligatoire pour les applications de qualité production.
Dans cet article, nous allons décomposer les trois piliers de l'observabilité en Rust : le tracing, les métriques et les logs. Vous obtiendrez des exemples de code prêts à l'emploi avec tracing, OpenTelemetry et Prometheus, que vous pouvez immédiatement intégrer dans votre projet.
Tracing en Rust : le voyage d'une requête à travers le système
Le tracing est l'histoire de la vie d'une requête. Au lieu de logs éparpillés, vous obtenez un chemin lié : de l'entrée dans l'API à la requête dans la base de données et à la réponse au client. Pour Rust, le meilleur outil est la crate tracing de la communauté Tokio.
Configuration du tracing dans une application Rust
Commençons par la configuration de base. Ajoutez dans Cargo.toml :
[dependencies]tracing = "0.1"tracing-subscriber = "0.3"tracing-opentelemetry = "0.23"opentelemetry = "0.22"opentelemetry-otlp = "0.15"Maintenant, initialisons le traceur dans main.rs :
use tracing_subscriber::{fmt, prelude::*, EnvFilter};use opentelemetry::{global, KeyValue};use opentelemetry_otlp::WithExportConfig;
fn init_tracer() -> Result<(), Box<dyn std::error::Error>> { global::set_text_map_propagator(opentelemetry_jaeger::Propagator::new()); let tracer = opentelemetry_otlp::new_pipeline() .tracing() .with_exporter( opentelemetry_otlp::new_exporter() .tonic() .with_endpoint("http://localhost:4317") ) .with_trace_config( opentelemetry::sdk::trace::config() .with_resource(opentelemetry::sdk::Resource::new(vec![ KeyValue::new("service.name", "rust-observability-demo") ])) ) .install_simple()?;
// Lier tracing à OpenTelemetry let telemetry = tracing_opentelemetry::layer().with_tracer(tracer); let subscriber = fmt::Subscriber::builder() .with_env_filter(EnvFilter::from_default_env()) .finish() .with(telemetry);
tracing::subscriber::set_global_default(subscriber)?; Ok(())}Création de spans et de traces
Ajoutons maintenant le tracing à la logique métier :
use tracing::{span, Span, Level, instrument};
#[instrument]async fn handle_request(user_id: u64) -> Result<String, Box<dyn std::error::Error>> { let user_data = get_user_data(user_id).await?; let processed = process_data(&user_data).await?; Ok(processed)}
#[instrument(skip(db_pool))]async fn get_user_data(user_id: u64) -> Result<User, Box<dyn std::error::Error>> { // Simulation d'une requête à la base de données let span = Span::current(); span.record("db.query", "SELECT * FROM users WHERE id = $1"); tokio::time::sleep(std::time::Duration::from_millis(50)).await; Ok(User { id: user_id, name: "Alice".into() })}
#[instrument]async fn process_data(user: &User) -> Result<String, Box<dyn std::error::Error>> { let result = format!("Processed user: {}", user.name); tracing::info!(user_id = user.id, "Data processed successfully"); Ok(result)}L'attribut #[instrument] crée automatiquement un span pour la fonction et enregistre tous les arguments. Dans Jaege