Registro en aplicaciones Rust: mejores prácticas en la práctica
El registro es uno de los elementos clave de cualquier aplicación de producción. Ayuda a diagnosticar errores, rastrear el comportamiento del sistema y recopilar métricas. En el ecosistema de Rust, existen varias soluciones maduras para esto. En este artículo, analizaremos las mejores prácticas de registro con ejemplos de código reales.
1. Fundamentos: crate log y arquitectura de fachada
En Rust, el estándar de facto es el crate log. Proporciona una fachada abstracta, no vinculada a una implementación específica. Esto significa que escribes registros a través de las macros de log, y qué backend los procesará (mostrar en consola, en un archivo, enviar a ELK) se decide al conectar otro crate.
Conexión del registro básico
// Cargo.toml[dependencies]log = "0.4"env_logger = "0.11"// main.rsuse log::{info, warn, error, debug};
fn main() { // Inicializamos env_logger (lee la variable RUST_LOG) env_logger::init();
info!("Aplicación iniciada"); warn!("Usando configuración por defecto"); error!("Error crítico: {}", "archivo no encontrado");}Ejecute la aplicación con la variable de entorno:
RUST_LOG=info cargo runEn la pantalla verá:
[2025-01-15T10:30:00Z INFO my_app] Aplicación iniciada[2025-01-15T10:30:00Z WARN my_app] Usando configuración por defecto[2025-01-15T10:30:00Z ERROR my_app] Error crítico: archivo no encontrado2. Configuración flexible de niveles de registro
La variable RUST_LOG permite filtrar mensajes de forma flexible por módulos y niveles. Esto es crítico para proyectos grandes, donde el ruido de los registros de depuración puede ocultar eventos importantes.
Ejemplos de filtrado
# Solo errores y advertencias de toda la aplicaciónRUST_LOG=warn
# Depuración solo en el módulo database, el resto infoRUST_LOG=info,my_crate::database=debug
# Todos los niveles, incluido traceRUST_LOG=traceEn la práctica, use este enfoque:
use std::env;
fn setup_logging() { if env::var("RUST_LOG").is_err() { // Valor por defecto para desarrollo env::set_var("RUST_LOG", "info,my_crate=debug"); } env_logger::init();}3. Registro estructurado con tracing
Para sistemas complejos (microservicios, aplicaciones asíncronas) es mejor usar el crate tracing. Soporta spans y campos estructurados, lo que permite vincular registros en cadenas de solicitudes.
Comparación de log y tracing
- log — simple, ligero, adecuado para proyectos pequeños.
- tracing — potente, soporta spans, eventos, campos estructurados; ideal para código async y sistemas con instrumentación de tracing.
Ejemplo con tracing y tracing-subscriber
// Cargo.toml[dependencies]tracing = "0.1"tracing-subscriber = "0.3"use tracing::{info, error, span, Level};use tracing_subscriber::FmtSubscriber;
#[tokio::main]async fn main() { // Configuración del subscriber para salida en stdout let subscriber = FmtSubscriber::builder() .with_max_level(Level::TRACE) .finish(); tracing::subscriber::set_global_default(subscriber) .expect("No se pudo establecer el subscriber");
// Creamos un span para la solicitud HTTP let request_span = span!(Level::INFO, "http_request", method = "GET", path = "/api/users"); let _guard = request_span.enter();
info!("Procesamiento de solicitud iniciado");
// Span anidado para trabajar con la BD let db_span = span!(Level::DEBUG, "db_query", query = "SELECT * FROM users"); let _db_gu