---
title: "Tracing vs Log Rust: Qual Usar em 2026? | Rust Brasil"
url: "https://rustlang.com.br/artigos/tracing-vs-log/"
markdown_url: "https://rustlang.com.br/artigos/tracing-vs-log.MD"
description: "Tracing vs log em Rust: structured logging, spans, subscribers e instrumentação. Qual escolher para observabilidade?"
date: "2026-02-23"
author: "Equipe Rust Brasil"
---

# Tracing vs Log Rust: Qual Usar em 2026? | Rust Brasil

Tracing vs log em Rust: structured logging, spans, subscribers e instrumentação. Qual escolher para observabilidade?


## Introdução

Logging e observabilidade são fundamentais para qualquer aplicação em produção. No ecossistema Rust, duas abordagens dominam: a facade **`log`** (simples e tradicional) e o framework **`tracing`** (moderno e estruturado).

A crate **`log`** existe desde os primórdios do Rust e oferece uma facade de logging simples com níveis (error, warn, info, debug, trace). Ela é leve, fácil de usar e tem amplo suporte.

O **`tracing`** foi criado pela equipe do Tokio como evolução do `log`, adicionando conceitos fundamentais para observabilidade moderna: **spans** (contextos de execução), **structured logging** (campos tipados), e integração nativa com sistemas de rastreamento distribuído como OpenTelemetry e Jaeger.

Neste artigo, vamos comparar as duas abordagens e mostrar por que o `tracing` é a recomendação para projetos modernos.

## Tabela Comparativa

| Característica | `log` | `tracing` |
|---|---|---|
| **Tipo** | Facade simples | Framework de observabilidade |
| **Structured logging** | Não nativo | Sim, campos tipados |
| **Spans** | Não | Sim |
| **Async support** | Básico | Nativo (instrument) |
| **OpenTelemetry** | Via adaptador | Integração nativa |
| **Performance** | Muito leve | Leve (zero-cost quando desabilitado) |
| **Ecossistema** | Amplo (env_logger, fern, etc.) | Crescente (tracing-subscriber, etc.) |
| **Compatibilidade** | Padrão Rust | Compatível com log via bridge |
| **Curva de aprendizado** | Baixa | Moderada |

## Dependências no Cargo.toml

Para `log`:

```toml
[dependencies]
log = "0.4"
env_logger = "0.11"    # Backend simples
```

Para `tracing`:

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

# Opcional: bridge para crates que usam log
tracing-log = "0.2"

# Opcional: OpenTelemetry
tracing-opentelemetry = "0.27"
opentelemetry = "0.27"
opentelemetry_sdk = "0.27"
```

## Comparação Básica

### Logging com `log`

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

fn processar_pedido(pedido_id: u64, valor: f64) {
    info!("Processando pedido {pedido_id}");
    debug!("Valor do pedido: R${valor:.2}");

    if valor > 10000.0 {
        warn!("Pedido {pedido_id} com valor alto: R${valor:.2}");
    }

    match validar_pagamento(pedido_id) {
        Ok(_) => info!("Pedido {pedido_id} processado com sucesso"),
        Err(e) => error!("Erro no pedido {pedido_id}: {e}"),
    }
}

fn validar_pagamento(_id: u64) -> Result<(), String> {
    Ok(())
}

fn main() {
    env_logger::init();
    processar_pedido(42, 15000.0);
}
```

Saída:

```
[2026-02-23T10:30:00Z INFO  meu_app] Processando pedido 42
[2026-02-23T10:30:00Z DEBUG meu_app] Valor do pedido: R$15000.00
[2026-02-23T10:30:00Z WARN  meu_app] Pedido 42 com valor alto: R$15000.00
[2026-02-23T10:30:00Z INFO  meu_app] Pedido 42 processado com sucesso
```

### Logging com `tracing`

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

#[instrument(fields(valor_formatado = format!("R${valor:.2}")))]
fn processar_pedido(pedido_id: u64, valor: f64) {
    info!("Processando pedido");
    debug!(valor, "Detalhes do pedido");

    if valor > 10000.0 {
        warn!(limite = 10000.0, "Pedido com valor alto");
    }

    match validar_pagamento(pedido_id) {
        Ok(_) => info!("Pedido processado com sucesso"),
        Err(e) => error!(erro = %e, "Erro ao processar pedido"),
    }
}

fn validar_pagamento(_id: u64) -> Result<(), String> {
    Ok(())
}

fn main() {
    tracing_subscriber::fmt()
        .with_target(true)
        .with_thread_ids(true)
        .init();

    processar_pedido(42, 15000.0);
}
```

Saída:

```
2026-02-23T10:30:00.123Z  INFO ThreadId(1) processar_pedido{pedido_id=42 valor=15000.0 valor_formatado="R$15000.00"}: meu_app: Processando pedido
2026-02-23T10:30:00.123Z DEBUG ThreadId(1) processar_pedido{pedido_id=42 valor=15000.0}: meu_app: Detalhes do pedido valor=15000.0
2026-02-23T10:30:00.123Z  WARN ThreadId(1) processar_pedido{pedido_id=42 valor=15000.0}: meu_app: Pedido com valor alto limite=10000.0
2026-02-23T10:30:00.123Z  INFO ThreadId(1) processar_pedido{pedido_id=42 valor=15000.0}: meu_app: Pedido processado com sucesso
```

Note como o `tracing` automaticamente inclui o contexto do span (`pedido_id`, `valor`) em cada linha de log, sem precisar repetir essas informações manualmente.

## Spans: O Grande Diferencial

Spans são contextos de execução que rastreiam operações do início ao fim. São o conceito mais poderoso do `tracing`.

```rust
use tracing::{info, info_span, instrument, Instrument};
use tokio::time::{sleep, Duration};

// Usando o atributo #[instrument]
#[instrument]
async fn buscar_usuario(user_id: u64) -> String {
    info!("Consultando banco de dados");
    sleep(Duration::from_millis(50)).await;
    format!("Usuario-{user_id}")
}

// Usando span manual
async fn processar_requisicao(req_id: &str) {
    let span = info_span!("processar_requisicao", req_id);
    let _enter = span.enter();

    info!("Requisição recebida");

    // Span aninhado para sub-operação
    let resultado = {
        let _span = info_span!("validacao").entered();
        info!("Validando dados");
        true
    };

    if resultado {
        info!("Requisição válida, processando...");
    }
}

// Spans com async usando .instrument()
async fn pipeline_completo() {
    let span = info_span!("pipeline", etapa = "completo");

    async {
        info!("Iniciando pipeline");

        let usuario = buscar_usuario(42).await;
        info!(usuario = %usuario, "Usuário encontrado");

        sleep(Duration::from_millis(100)).await;
        info!("Pipeline concluído");
    }
    .instrument(span)
    .await;
}

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt()
        .with_target(false)
        .init();

    pipeline_completo().await;
}
```

## Structured Logging: Campos Tipados

O `tracing` permite adicionar campos estruturados (key-value) a eventos e spans:

```rust
use tracing::{info, warn, error};
use std::time::Instant;

struct Pedido {
    id: u64,
    cliente: String,
    valor: f64,
    itens: usize,
}

fn processar(pedido: &Pedido) {
    let inicio = Instant::now();

    // Campos estruturados com diferentes formatações
    info!(
        pedido_id = pedido.id,
        cliente = %pedido.cliente,     // % = Display
        valor = pedido.valor,
        itens = pedido.itens,
        "Processando pedido"
    );

    // Campos podem ser expressões
    let duracao = inicio.elapsed();
    info!(
        pedido_id = pedido.id,
        duracao_ms = duracao.as_millis() as u64,
        "Pedido processado"
    );
}

fn main() {
    // Saída em JSON para sistemas de log
    tracing_subscriber::fmt()
        .json()
        .init();

    let pedido = Pedido {
        id: 123,
        cliente: "Maria Silva".to_string(),
        valor: 299.90,
        itens: 3,
    };

    processar(&pedido);
}
```

Saída em JSON:

```json
{"timestamp":"2026-02-23T10:30:00.123Z","level":"INFO","fields":{"message":"Processando pedido","pedido_id":123,"cliente":"Maria Silva","valor":299.9,"itens":3},"target":"meu_app"}
```

Esse formato é ideal para ingestão em sistemas como Elasticsearch, Datadog, Grafana Loki e CloudWatch.

## Subscribers: Configuração de Saída

O `tracing-subscriber` oferece diversas formas de configurar a saída:

### Formatação Customizada

```rust
use tracing_subscriber::{fmt, EnvFilter, prelude::*};

fn setup_tracing() {
    tracing_subscriber::registry()
        .with(
            fmt::layer()
                .with_target(true)
                .with_thread_ids(true)
                .with_file(true)
                .with_line_number(true)
                .with_level(true)
                .compact()
        )
        .with(
            EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| EnvFilter::new("info"))
        )
        .init();
}

fn main() {
    setup_tracing();
    tracing::info!("Aplicação iniciada");
}
```

### Múltiplos Outputs

```rust
use tracing_subscriber::{fmt, EnvFilter, prelude::*};
use std::fs::File;

fn setup_tracing() {
    let arquivo = File::create("app.log").unwrap();

    tracing_subscriber::registry()
        // Layer para stdout (formato legível)
        .with(
            fmt::layer()
                .pretty()
                .with_filter(EnvFilter::new("info"))
        )
        // Layer para arquivo (formato JSON)
        .with(
            fmt::layer()
                .json()
                .with_writer(arquivo)
                .with_filter(EnvFilter::new("debug"))
        )
        .init();
}
```

## Filtragem Avançada

O `EnvFilter` permite filtragem granular por módulo e span:

```rust
use tracing_subscriber::EnvFilter;

fn main() {
    // Via variável de ambiente
    // RUST_LOG=info,meu_app::db=debug,tower_http=trace

    // Via código
    let filtro = EnvFilter::new("info")
        .add_directive("meu_app::db=debug".parse().unwrap())
        .add_directive("meu_app::auth=trace".parse().unwrap())
        .add_directive("tower_http=debug".parse().unwrap())
        .add_directive("sqlx=warn".parse().unwrap());

    tracing_subscriber::fmt()
        .with_env_filter(filtro)
        .init();
}
```

Exemplos de diretivas de filtro:

```bash
# Tudo em info, módulo db em debug
RUST_LOG="info,meu_app::db=debug"

# Apenas warnings e erros
RUST_LOG="warn"

# Tudo em trace para o app, info para dependências
RUST_LOG="meu_app=trace,info"

# Filtrar por campo de span
RUST_LOG="meu_app[pedido_id]=debug"
```

## Integração com OpenTelemetry

O `tracing` se integra nativamente com OpenTelemetry para rastreamento distribuído:

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

```rust
use tracing::{info, instrument};
use tracing_subscriber::{prelude::*, EnvFilter};
use opentelemetry::trace::TracerProvider;
use opentelemetry_sdk::trace::SdkTracerProvider;
use opentelemetry_otlp::SpanExporter;

fn init_tracing() -> SdkTracerProvider {
    let exporter = SpanExporter::builder()
        .with_tonic()
        .build()
        .unwrap();

    let provider = SdkTracerProvider::builder()
        .with_batch_exporter(exporter)
        .build();

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

    tracing_subscriber::registry()
        .with(tracing_opentelemetry::layer().with_tracer(tracer))
        .with(tracing_subscriber::fmt::layer())
        .with(EnvFilter::new("info"))
        .init();

    provider
}

#[instrument]
async fn processar_pedido(pedido_id: u64) {
    info!("Processando pedido");
    validar_pedido(pedido_id).await;
    cobrar_pagamento(pedido_id).await;
    info!("Pedido concluído");
}

#[instrument]
async fn validar_pedido(pedido_id: u64) {
    info!("Validando pedido");
    tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
}

#[instrument]
async fn cobrar_pagamento(pedido_id: u64) {
    info!("Cobrando pagamento");
    tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}

#[tokio::main]
async fn main() {
    let provider = init_tracing();

    processar_pedido(42).await;

    // Garantir que os traces são enviados antes de sair
    provider.shutdown().unwrap();
}
```

Os spans do `tracing` são automaticamente convertidos em traces do OpenTelemetry, que podem ser visualizados no Jaeger, Zipkin ou qualquer backend compatível.

## Compatibilidade entre log e tracing

Se você tem crates que usam `log` e quer capturá-las no `tracing`:

```rust
use tracing_subscriber::prelude::*;
use tracing_log::LogTracer;

fn main() {
    // Redirecionar eventos do `log` para o `tracing`
    LogTracer::init().unwrap();

    tracing_subscriber::fmt()
        .with_env_filter("info")
        .init();

    // Eventos de crates que usam log::info! etc.
    // agora aparecem no tracing
    log::info!("Isso vem do crate log");
    tracing::info!("Isso vem do tracing");
}
```

## Performance

Ambas as bibliotecas são projetadas para alto desempenho:

| Cenário | `log` (com env_logger) | `tracing` (com fmt) |
|---|---|---|
| Log desabilitado | ~1 ns | ~1 ns |
| Log em nível filtrado | ~5 ns | ~10 ns |
| Log ativo (stdout) | ~1 us | ~2 us |
| Log ativo (JSON) | N/A nativo | ~3 us |

O `tracing` é ligeiramente mais lento devido à complexidade adicional de spans e campos estruturados, mas a diferença é negligível para qualquer aplicação real.

## Quando Usar Cada Um

### Use `log` quando:

- Está escrevendo uma **library crate** que precisa ser leve
- O projeto é **muito simples** e não precisa de spans ou structured logging
- Precisa da **menor footprint** possível

### Use `tracing` quando:

- Está escrevendo uma **aplicação** (binário)
- Precisa de **structured logging** para sistemas de observabilidade
- Trabalha com código **async** (spans rastreiam contexto entre awaits)
- Precisa de **rastreamento distribuído** (OpenTelemetry)
- Usa [Axum](/artigos/axum-vs-actix/) ou [Tokio](/artigos/tokio-guia-completo/) (integração nativa)
- Quer **filtragem granular** por módulo, span ou campo

### Recomendação prática

**Use `tracing` para aplicações e `log` para libraries.** Libraries que usam `log` são automaticamente compatíveis com `tracing` via bridge. Para aplicações, o `tracing` oferece tudo que o `log` oferece e muito mais.

## Boas Práticas

1. **Sempre use `#[instrument]`** em funções async — propaga contexto automaticamente
2. **Prefira campos estruturados** sobre interpolação de strings
3. **Configure `EnvFilter`** para controle dinâmico via `RUST_LOG`
4. **Use JSON** em produção para ingestão em sistemas de log
5. **Use formato legível** em desenvolvimento local
6. **Adicione request_id** nos spans de nível superior para correlação

```rust
use tracing::{info, instrument};
use uuid::Uuid;

#[instrument(skip(body), fields(request_id = %Uuid::new_v4()))]
async fn handle_request(method: &str, path: &str, body: &[u8]) {
    info!(method, path, body_size = body.len(), "Requisição recebida");
    // Todos os logs dentro desta função incluem request_id
}
```

## Conclusão

O `tracing` representa a evolução natural do logging em Rust. Enquanto o `log` continua sendo útil para libraries que precisam ser leves, o `tracing` é a escolha definitiva para aplicações que precisam de observabilidade moderna — especialmente no contexto de microserviços e sistemas distribuídos.

## Veja Também

- [Receita: Variáveis de Ambiente](/receitas/variaveis-ambiente/)
- [Tokio: O Runtime Async Mais Popular de Rust](/artigos/tokio-guia-completo/)
- [Axum vs Actix Web: Qual Framework Escolher](/artigos/axum-vs-actix/)
- [Error Handling: anyhow vs thiserror vs miette](/artigos/error-handling-libs/)
- [Cargo: Ferramentas Essenciais para Produção](/artigos/cargo-ferramentas-essenciais/)

---

Se você trabalha com logging e observabilidade em outras linguagens, confira também:

- <a href="https://golang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Structured Logging em Go: slog e logging estruturado na standard library</a>
- <a href="https://python.dev.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'python.dev.br' })">Python logging: o módulo de logging padrão e boas práticas</a>
