Observabilidade em Rust: tracing, métricas e logs para sistemas confiáveis

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Por que observabilidade é um superpoder para o desenvolvedor Rust



Imagine: seu serviço em Rust está rodando em produção, mas de repente a latência dispara para as alturas. Os logs silenciam, as métricas mostram 200 OK, e os usuários reclamam. Familiar? Este é o clássico "caixa preta". Sem observabilidade em Rust, você está cego.



Observabilidade não é apenas "logs + métricas". É a capacidade de fazer qualquer pergunta ao sistema sem precisar escrever novo código. Em Rust, com seu foco em desempenho e confiabilidade, a observabilidade se torna um ingrediente obrigatório para aplicações de nível de produção.



Neste artigo, vamos abordar os três pilares da observabilidade em Rust: tracing, métricas e logs. Você terá exemplos de código prontos com tracing, OpenTelemetry e Prometheus que podem ser implementados imediatamente no seu projeto.



Tracing em Rust: a jornada da requisição pelo sistema



Tracing é a história de vida de uma requisição. Em vez de logs isolados, você obtém um caminho conectado: desde a entrada na API até a consulta ao banco de dados e a resposta ao cliente. Para Rust, a melhor ferramenta é a crate tracing da comunidade Tokio.



Configuração do tracing em uma aplicação Rust



Vamos começar com a configuração básica. Adicione ao Cargo.toml:



[dependencies]tracing = "0.1"tracing-subscriber = "0.3"tracing-opentelemetry = "0.23"opentelemetry = "0.22"opentelemetry-otlp = "0.15"


Agora inicializamos o tracer no 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()?;

// Vinculando tracing ao 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(())}


Criação de spans e traces



Agora vamos adicionar tracing à lógica de negócio:



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>> { // Simulação de consulta ao BD 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)}


O atributo #[instrument] cria automaticamente um span para a função e registra todos os argumentos. No Jaeger

Blogs

Book Recommendations