Logging em aplicações Rust: melhores práticas na prática
Logging é um dos elementos-chave de qualquer aplicação em produção. Ajuda a diagnosticar erros, monitorar o comportamento do sistema e coletar métricas. No ecossistema Rust, existem várias soluções maduras para isso. Neste artigo, vamos explorar as melhores práticas de logging com exemplos reais de código.
1. Fundamentos: crate log e arquitetura de fachada
Em Rust, o padrão de facto é o crate log. Ele fornece uma fachada abstrata, não vinculada a uma implementação específica. Isso significa que você escreve logs através das macros log, e qual backend irá processá-los (exibir no console, salvar em arquivo, enviar para ELK) é decidido no momento da inclusão de outro crate.
Configuração básica de logging
// Cargo.toml[dependencies]log = "0.4"env_logger = "0.11"// main.rsuse log::{info, warn, error, debug};
fn main() { // Inicializa o env_logger (lê a variável RUST_LOG) env_logger::init();
info!("Aplicação iniciada"); warn!("Usando configuração padrão"); error!("Erro crítico: {}", "arquivo não encontrado");}Execute a aplicação com a variável de ambiente:
RUST_LOG=info cargo runNa tela, você verá:
[2025-01-15T10:30:00Z INFO my_app] Aplicação iniciada[2025-01-15T10:30:00Z WARN my_app] Usando configuração padrão[2025-01-15T10:30:00Z ERROR my_app] Erro crítico: arquivo não encontrado2. Configuração flexível de níveis de logging
A variável RUST_LOG permite filtrar mensagens de forma flexível por módulos e níveis. Isso é crítico para projetos grandes, onde o ruído de logs de depuração pode ocultar eventos importantes.
Exemplos de filtragem
# Apenas erros e avisos de toda a aplicaçãoRUST_LOG=warn
# Depuração apenas no módulo database, o resto infoRUST_LOG=info,my_crate::database=debug
# Todos os níveis, incluindo traceRUST_LOG=traceNa prática, use esta abordagem:
use std::env;
fn setup_logging() { if env::var("RUST_LOG").is_err() { // Valor padrão para desenvolvimento env::set_var("RUST_LOG", "info,my_crate=debug"); } env_logger::init();}3. Logging estruturado com tracing
Para sistemas complexos (microsserviços, aplicações assíncronas), é melhor usar o crate tracing. Ele suporta spans e campos estruturados, permitindo vincular logs em cadeias de requisições.
Comparação entre log e tracing
- log — simples, leve, adequado para projetos pequenos.
- tracing — poderoso, suporta spans, eventos, campos estruturados; ideal para código async e sistemas com instrumentação tracing.
Exemplo com tracing e 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() { // Configura subscriber para saída no stdout let subscriber = FmtSubscriber::builder() .with_max_level(Level::TRACE) .finish(); tracing::subscriber::set_global_default(subscriber) .expect("Falha ao definir subscriber");
// Cria um span para requisição HTTP let request_span = span!(Level::INFO, "http_request", method = "GET", path = "/api/users"); let _guard = request_span.enter();
info!("Processamento da requisição iniciado");
// Span aninhado para operação com BD let db_span = span!(Level::DEBUG, "db_query", query = "SELECT * FROM users"); let _db_gu