---
title: "Logging e Observabilidade em Rust: Guia | Rust Brasil"
url: "https://rustlang.com.br/artigos/logging-observabilidade/"
markdown_url: "https://rustlang.com.br/artigos/logging-observabilidade.MD"
description: "Guia de logging e observabilidade em Rust: tracing, log, métricas, OpenTelemetry e structured logging."
date: "2026-02-23"
author: "Equipe Rust Brasil"
---

# Logging e Observabilidade em Rust: Guia | Rust Brasil

Guia de logging e observabilidade em Rust: tracing, log, métricas, OpenTelemetry e structured logging.


## Introdução

Ter um programa que compila e passa nos testes é apenas o começo. Quando sua aplicação está em produção atendendo milhares de requisições, a pergunta muda de "funciona?" para "**como está funcionando?**". Logging e observabilidade são os olhos e ouvidos da sua aplicação em produção.

Rust possui um ecossistema maduro de observabilidade, liderado pela crate `tracing`, que vai muito além de simples `println!`. Neste artigo, vamos explorar desde logging básico até distributed tracing com OpenTelemetry, métricas com Prometheus e como integrar tudo em um sistema de observabilidade profissional.

## O Problema: Debugging em Produção às Cegas

### Não Faça Isso: `println!` como Logging

```rust
// ERRADO: println! em produção
fn processar_pedido(id: u64, valor: f64) -> Result<(), String> {
    println!("Processando pedido {} com valor {}", id, valor);

    if valor > 10000.0 {
        println!("AVISO: Pedido de alto valor detectado!");
    }

    // Simula processamento
    println!("Pedido {} processado com sucesso", id);
    Ok(())
}

fn main() {
    processar_pedido(42, 15000.0).unwrap();
    // Problemas:
    // - Sem timestamps
    // - Sem níveis de log (info, warn, error)
    // - Sem contexto estruturado
    // - Sem filtro — tudo vai para stdout
    // - Sem correlação entre eventos relacionados
}
```

### Não Faça Isso: Logging Sem Estrutura

```rust
// ERRADO: Logs como texto livre — impossível de parsear automaticamente
fn processar_pagamento(usuario_id: u64, valor: f64) {
    eprintln!(
        "[2026-02-23 10:30:45] INFO - Usuário {} fez pagamento de R${:.2}",
        usuario_id, valor
    );
    // Cada desenvolvedor formata de um jeito diferente
    // Ferramentas de análise não conseguem extrair campos
}
```

## A Solução: `tracing` para Observabilidade Completa

### Passo 1: Configuração Básica

```toml
# Cargo.toml
[dependencies]
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
```

```rust
use tracing::{info, warn, error, debug, instrument};
use tracing_subscriber::{fmt, EnvFilter};

fn configurar_logging() {
    let filtro = EnvFilter::try_from_default_env()
        .unwrap_or_else(|_| EnvFilter::new("info"));

    tracing_subscriber::fmt()
        .with_env_filter(filtro)
        .with_target(true)        // Mostra o módulo de origem
        .with_thread_ids(true)    // Mostra o ID da thread
        .with_file(true)          // Mostra arquivo e linha
        .with_line_number(true)
        .init();
}

fn main() {
    configurar_logging();

    info!("Aplicação iniciada");
    debug!("Modo debug ativado");

    processar_pedido(42, 1500.0);
}

fn processar_pedido(id: u64, valor: f64) {
    info!(pedido_id = id, valor = valor, "Processando pedido");

    if valor > 10000.0 {
        warn!(pedido_id = id, valor = valor, "Pedido de alto valor detectado");
    }

    info!(pedido_id = id, "Pedido processado com sucesso");
}
```

Controle o nível de log via variável de ambiente:

```bash
# Todos os logs de info para cima
RUST_LOG=info cargo run

# Debug apenas para seu crate
RUST_LOG=meu_app=debug,info cargo run

# Trace para um módulo específico
RUST_LOG=meu_app::pagamento=trace cargo run
```

### Passo 2: Structured Logging com JSON

Para produção, use output JSON que ferramentas como ELK e Datadog conseguem parsear:

```rust
use tracing::info;
use tracing_subscriber::{fmt, EnvFilter};

fn configurar_logging_producao() {
    let filtro = EnvFilter::try_from_default_env()
        .unwrap_or_else(|_| EnvFilter::new("info"));

    tracing_subscriber::fmt()
        .with_env_filter(filtro)
        .json()                    // Output em JSON
        .with_current_span(true)   // Inclui span atual no JSON
        .flatten_event(true)       // Achata campos do evento
        .init();
}

fn main() {
    configurar_logging_producao();

    info!(
        usuario_id = 42,
        acao = "login",
        ip = "192.168.1.1",
        "Usuário autenticado"
    );
    // Output:
    // {"timestamp":"2026-02-23T10:30:45Z","level":"INFO","fields":{"message":"Usuário autenticado","usuario_id":42,"acao":"login","ip":"192.168.1.1"},"target":"meu_app"}
}
```

### Passo 3: Spans para Contexto Hierárquico

Spans agrupam eventos relacionados e medem duração:

```rust
use tracing::{info, warn, error, instrument, Span};

#[derive(Debug)]
struct Pedido {
    id: u64,
    usuario_id: u64,
    itens: Vec<String>,
    valor_total: f64,
}

#[instrument(skip(pedido), fields(pedido_id = pedido.id, usuario = pedido.usuario_id))]
fn processar_pedido(pedido: &Pedido) -> Result<(), String> {
    info!("Iniciando processamento");

    validar_estoque(pedido)?;
    processar_pagamento(pedido)?;

    info!(valor = pedido.valor_total, "Pedido concluído");
    Ok(())
}

#[instrument(skip(pedido))]
fn validar_estoque(pedido: &Pedido) -> Result<(), String> {
    info!(itens = ?pedido.itens, "Verificando estoque");
    // Simula verificação
    if pedido.itens.is_empty() {
        error!("Pedido sem itens");
        return Err("Pedido vazio".into());
    }
    info!("Estoque validado");
    Ok(())
}

#[instrument(skip(pedido), fields(valor = pedido.valor_total))]
fn processar_pagamento(pedido: &Pedido) -> Result<(), String> {
    info!("Processando pagamento");

    if pedido.valor_total > 50000.0 {
        warn!("Pagamento de alto valor — verificação extra necessária");
    }

    info!("Pagamento aprovado");
    Ok(())
}
```

O macro `#[instrument]` automaticamente:
- Cria um span com o nome da função
- Registra argumentos como campos (exceto os listados em `skip`)
- Mede a duração da execução
- Mantém o contexto hierárquico (span pai → span filho)

### Passo 4: Logging em Aplicações Async (Tokio)

```toml
# Cargo.toml
[dependencies]
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tokio = { version = "1", features = ["full"] }
axum = "0.8"
```

```rust
use axum::{extract::Path, routing::get, Router};
use tracing::{info, instrument};

#[instrument]
async fn buscar_usuario(Path(id): Path<u64>) -> String {
    info!("Buscando usuário no banco");

    // Simula busca assíncrona
    tokio::time::sleep(std::time::Duration::from_millis(50)).await;

    info!("Usuário encontrado");
    format!("Usuário {id}")
}

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt()
        .with_env_filter("info")
        .init();

    info!("Servidor iniciando");

    let app = Router::new().route("/usuarios/{id}", get(buscar_usuario));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
        .await
        .unwrap();

    info!("Escutando em 0.0.0.0:3000");
    axum::serve(listener, app).await.unwrap();
}
```

### Passo 5: OpenTelemetry para Distributed Tracing

```toml
# Cargo.toml
[dependencies]
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-opentelemetry = "0.28"
opentelemetry = "0.27"
opentelemetry_sdk = { version = "0.27", features = ["rt-tokio"] }
opentelemetry-otlp = "0.27"
tokio = { version = "1", features = ["full"] }
```

```rust
use opentelemetry::trace::TracerProvider;
use opentelemetry_otlp::WithExportConfig;
use opentelemetry_sdk::runtime;
use tracing::info;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};

fn init_telemetria() -> Result<(), Box<dyn std::error::Error>> {
    let exporter = opentelemetry_otlp::SpanExporter::builder()
        .with_tonic()
        .with_endpoint("http://localhost:4317")
        .build()?;

    let provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
        .with_batch_exporter(exporter, runtime::Tokio)
        .build();

    let tracer = provider.tracer("meu-servico");

    let telemetry_layer = tracing_opentelemetry::layer().with_tracer(tracer);

    tracing_subscriber::registry()
        .with(EnvFilter::new("info"))
        .with(tracing_subscriber::fmt::layer())
        .with(telemetry_layer)
        .init();

    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    init_telemetria()?;

    info!("Serviço iniciado com OpenTelemetry");

    // Seus spans agora são exportados para Jaeger/Zipkin/etc.

    Ok(())
}
```

## Métricas com Prometheus

```toml
# Cargo.toml
[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
metrics = "0.24"
metrics-exporter-prometheus = "0.16"
```

```rust
use axum::{routing::get, Router};
use metrics::{counter, histogram, gauge};
use metrics_exporter_prometheus::PrometheusBuilder;
use std::time::Instant;

fn configurar_metricas() -> metrics_exporter_prometheus::PrometheusHandle {
    PrometheusBuilder::new()
        .install_recorder()
        .expect("Falha ao instalar recorder de métricas")
}

async fn processar_requisicao() -> &'static str {
    let inicio = Instant::now();

    // Incrementa contador de requisições
    counter!("http_requests_total", "endpoint" => "/api", "method" => "GET")
        .increment(1);

    // Simula processamento
    tokio::time::sleep(std::time::Duration::from_millis(50)).await;

    // Registra duração
    let duracao = inicio.elapsed().as_secs_f64();
    histogram!("http_request_duration_seconds", "endpoint" => "/api")
        .record(duracao);

    "OK"
}

async fn metricas_handler(
    handle: axum::extract::State<metrics_exporter_prometheus::PrometheusHandle>,
) -> String {
    handle.render()
}

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt().with_env_filter("info").init();

    let handle = configurar_metricas();

    // Gauge para conexões ativas (exemplo)
    gauge!("conexoes_ativas").set(0.0);

    let app = Router::new()
        .route("/api", get(processar_requisicao))
        .route("/metrics", get(metricas_handler))
        .with_state(handle);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
        .await
        .unwrap();

    tracing::info!("Servidor com métricas em http://localhost:3000/metrics");
    axum::serve(listener, app).await.unwrap();
}
```

Acesse `/metrics` para ver a saída no formato Prometheus:

```
# HELP http_requests_total Total de requisições HTTP
# TYPE http_requests_total counter
http_requests_total{endpoint="/api",method="GET"} 42

# HELP http_request_duration_seconds Duração das requisições HTTP
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{endpoint="/api",le="0.005"} 10
```

## Armadilhas Comuns

### 1. Logging de Dados Sensíveis

```rust
use tracing::info;

// ERRADO: Logando dados sensíveis
fn autenticar(usuario: &str, senha: &str) {
    info!(usuario = usuario, senha = senha, "Tentativa de login");
    // A senha vai parar nos logs!
}

// CORRETO: Nunca logue dados sensíveis
fn autenticar_seguro(usuario: &str, _senha: &str) {
    info!(usuario = usuario, "Tentativa de login");
}
```

### 2. Nível de Log Incorreto

```rust
use tracing::{debug, info, warn, error};

// Use cada nível corretamente:
fn exemplo_niveis() {
    error!("Falha crítica que impede operação");   // Requer ação imediata
    warn!("Situação anormal, mas tratável");        // Atenção necessária
    info!("Eventos importantes do fluxo normal");   // Operações normais
    debug!("Detalhes para debugging");              // Desenvolvimento
    // trace! para detalhes muito granulares
}

// ERRADO: Tudo como info
fn logs_ruins() {
    info!("Entrando na função X");           // Deveria ser debug ou trace
    info!("Conexão ao banco falhou!");       // Deveria ser error
    info!("Valor incomum detectado");        // Deveria ser warn
}
```

### 3. Logging em Hot Path

```rust
use tracing::{debug, info};

// ERRADO: Log em cada iteração de um loop apertado
fn processar_itens(itens: &[u64]) -> u64 {
    let mut total = 0;
    for &item in itens {
        info!(item = item, "Processando item"); // Milhares de logs por segundo!
        total += item;
    }
    total
}

// CORRETO: Log apenas do resultado agregado
fn processar_itens_melhor(itens: &[u64]) -> u64 {
    debug!(total_itens = itens.len(), "Iniciando processamento em lote");
    let total: u64 = itens.iter().sum();
    info!(total_itens = itens.len(), resultado = total, "Lote processado");
    total
}
```

### 4. Spans Sem Fechamento Adequado

```rust
use tracing::{info_span, info, Instrument};

// CORRETO: Span em código assíncrono
async fn operacao_async() {
    let span = info_span!("operacao_longa");
    async {
        info!("Dentro do span");
        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
        info!("Operação concluída");
    }
    .instrument(span)
    .await;
}
```

## Exemplo do Mundo Real: Stack de Observabilidade Completa

```toml
# Cargo.toml
[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tower-http = { version = "0.6", features = ["trace"] }
```

```rust
use axum::{
    extract::Path,
    routing::get,
    Json, Router,
};
use serde::Serialize;
use tower_http::trace::TraceLayer;
use tracing::{info, instrument};

#[derive(Serialize)]
struct Produto {
    id: u64,
    nome: String,
    preco: f64,
}

#[instrument]
async fn listar_produtos() -> Json<Vec<Produto>> {
    info!("Listando todos os produtos");

    let produtos = vec![
        Produto { id: 1, nome: "Teclado".into(), preco: 250.0 },
        Produto { id: 2, nome: "Mouse".into(), preco: 150.0 },
    ];

    info!(total = produtos.len(), "Produtos listados");
    Json(produtos)
}

#[instrument]
async fn buscar_produto(Path(id): Path<u64>) -> Json<Produto> {
    info!("Buscando produto");
    Json(Produto {
        id,
        nome: "Teclado Mecânico".into(),
        preco: 450.0,
    })
}

#[tokio::main]
async fn main() {
    // Configuração: JSON em produção, formatado em desenvolvimento
    let is_prod = std::env::var("RUST_ENV")
        .map(|v| v == "production")
        .unwrap_or(false);

    if is_prod {
        tracing_subscriber::fmt()
            .json()
            .with_env_filter("info")
            .init();
    } else {
        tracing_subscriber::fmt()
            .pretty()
            .with_env_filter("debug")
            .init();
    }

    let app = Router::new()
        .route("/produtos", get(listar_produtos))
        .route("/produtos/{id}", get(buscar_produto))
        .layer(TraceLayer::new_for_http()); // Log automático de requisições HTTP

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
        .await
        .unwrap();

    info!("Servidor pronto em http://localhost:3000");
    axum::serve(listener, app).await.unwrap();
}
```

## Resumo: Os Três Pilares da Observabilidade

| Pilar | Ferramenta Rust | Para Que Serve |
|---|---|---|
| **Logs** | `tracing` + `tracing-subscriber` | Eventos discretos com contexto |
| **Traces** | `tracing` + `tracing-opentelemetry` | Fluxo de execução entre serviços |
| **Métricas** | `metrics` + `metrics-exporter-prometheus` | Agregações numéricas ao longo do tempo |

---

## Veja Também

- [Receita: Variáveis de Ambiente](/receitas/variaveis-ambiente/) — Configure níveis de log via ambiente
- [Boas Práticas de Error Handling](/artigos/boas-praticas-error-handling/) — Logue erros corretamente
- [Segurança em Rust](/artigos/seguranca-rust/) — Evite vazar dados sensíveis nos logs
- [CI/CD para Projetos Rust](/artigos/ci-cd-rust/) — Configure logging no pipeline de deploy
- [Otimização de Performance](/artigos/otimizacao-performance/) — Use métricas para identificar gargalos

---

Logging e observabilidade são essenciais em qualquer stack. Confira outras abordagens:

- <a href="https://golang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Logging em Go: slog, zap e structured logging para aplicações em produção</a>
- <a href="https://python.dev.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'python.dev.br' })">Logging em Python: módulo logging, structlog e observabilidade</a>
