---
title: "Tracing Rust: Observabilidade, Logs e OpenTelemetry em 2026"
url: "https://rustlang.com.br/ecossistema/tracing/"
markdown_url: "https://rustlang.com.br/ecossistema/tracing.MD"
description: "Guia completo de Tracing em Rust: spans, events, logs estruturados, tracing-subscriber, Axum, Tower, OpenTelemetry, produção e carreira backend."
date: ""
author: ""
---

# Tracing Rust: Observabilidade, Logs e OpenTelemetry em 2026

Guia completo de Tracing em Rust: spans, events, logs estruturados, tracing-subscriber, Axum, Tower, OpenTelemetry, produção e carreira backend.


## Introdução

O **Tracing** é o framework de observabilidade padrão do ecossistema Rust. Diferente de sistemas de logging tradicionais que registram mensagens de texto isoladas, o Tracing trabalha com **spans** (intervalos de tempo) e **events** (pontos no tempo), permitindo rastrear o fluxo de execução de forma estruturada e hierárquica.

Isso é especialmente poderoso em aplicações assíncronas, onde uma requisição pode saltar entre tasks e threads. O Tracing mantém o contexto da execução mesmo nesses cenários, permitindo que você correlacione logs, métricas e traces de forma coerente.

O ecossistema Tracing é composto por várias crates complementares:
- **tracing**: a API de instrumentação (spans, events, macros)
- **tracing-subscriber**: implementações de subscribers (formatadores, filtros)
- **tracing-appender**: escrita em arquivos com rotação
- **tracing-opentelemetry**: exportação para OpenTelemetry

### Por que usar o Tracing?

- **Estruturado**: dados são campos tipados, não apenas texto
- **Hierárquico**: spans aninhados mostram o fluxo de execução
- **Async-aware**: funciona corretamente com async/await
- **Performático**: overhead mínimo quando desabilitado
- **Extensível**: subscribers customizáveis para qualquer destino
- **Ecossistema rico**: JSON, OpenTelemetry, Jaeger, Datadog, etc.
- **Padrão da indústria**: usado por Tokio, Axum, Hyper, Tonic e praticamente todo o ecossistema

## Tracing Rust em 2026: onde ele encaixa

Quem pesquisa **Tracing Rust** normalmente já passou do "hello world" e quer responder uma pergunta operacional: como descobrir o que um serviço Rust está fazendo quando uma requisição fica lenta, um worker falha ou uma dependência externa começa a oscilar? A resposta moderna começa com spans e events bem modelados, não com `println!` espalhado pelo código.

Em aplicações backend, Tracing fica no centro da stack. [Tokio](/ecossistema/tokio/) preserva contexto em tarefas assíncronas, [Axum](/ecossistema/axum/) e [Tower](/ecossistema/tower/) usam camadas como `TraceLayer` para instrumentar HTTP, [Tonic](/ecossistema/tonic/) leva a mesma mentalidade para gRPC, e [SQLx](/ecossistema/sqlx/) ou workers de mensageria precisam registrar latência, erro e cardinalidade de campos com cuidado. O artigo [Rust com OpenTelemetry em produção](/blog/rust-opentelemetry-producao-2026/) aprofunda a etapa seguinte: exportar esses sinais para coletores, traces distribuídos e dashboards.

Para carreira, Tracing é um diferencial porque aproxima Rust do trabalho real de produção. Muitas [vagas Rust](/vagas/) não escrevem "tracing" no título, mas pedem backend, plataforma, infraestrutura, cloud, observabilidade, SRE, fintech ou sistemas distribuídos. Saber explicar request IDs, campos estáveis, logs JSON, sampling, OpenTelemetry Collector e investigação de incidentes mostra maturidade para [empresas que usam Rust](/empresas/) além da linguagem em si.

Use esta página como referência técnica e conecte com guias práticos de produção: [serviços resilientes com Tower, Axum e Tokio](/blog/rust-servicos-resilientes-tower-axum-2026/), [Rust para mensageria com Kafka, RabbitMQ e NATS](/blog/rust-mensageria-kafka-rabbitmq-nats-2026/), [deploy Rust em VPS](/blog/deploy-rust-vps-docker-systemd-2026/) e [Rust e eBPF para observabilidade](/blog/rust-ebpf-observabilidade-seguranca-2026/). O objetivo não é ter telemetria bonita em demo; é conseguir responder rapidamente o que quebrou, desde quando, quem foi afetado e qual ação reduz o impacto.

## Instalação

Adicione o Tracing ao seu `Cargo.toml`:

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

Para uma configuração mais completa:

```toml
[dependencies]
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = [
    "env-filter",    # Filtragem via RUST_LOG
    "json",          # Output em JSON
    "fmt",           # Formatação de texto
    "time",          # Timestamps
] }
tracing-appender = "0.2"     # Escrita em arquivos
tracing-opentelemetry = "0.27" # OpenTelemetry
opentelemetry = "0.27"
opentelemetry-otlp = "0.27"
```

## Uso Básico

### Configuração mínima

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

fn main() {
    // Inicializar o subscriber padrão
    tracing_subscriber::fmt::init();

    info!("Aplicação iniciada");
    debug!("Modo debug ativo");
    warn!("Este é um aviso");
    error!("Este é um erro");

    // Eventos com campos estruturados
    info!(
        usuario = "maria",
        acao = "login",
        "Usuário fez login"
    );

    // Campos numéricos
    info!(
        latencia_ms = 42,
        status = 200,
        metodo = "GET",
        caminho = "/api/usuarios",
        "Requisição processada"
    );
}

// Output:
// 2026-02-27T10:30:00.000Z  INFO app: Aplicação iniciada
// 2026-02-27T10:30:00.001Z  INFO app: Usuário fez login usuario="maria" acao="login"
```

### Spans: rastreando intervalos de tempo

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

fn main() {
    tracing_subscriber::fmt::init();

    // Criar um span manualmente
    let span = info_span!("processamento", tarefa_id = 42);
    let _guard = span.enter();

    info!("Iniciando processamento");
    processar_dados();
    info!("Processamento concluído");
}

fn processar_dados() {
    // Span aninhado
    let span = info_span!("validacao", etapa = "dados");
    let _guard = span.enter();

    info!("Validando dados de entrada");

    // Outro nível de aninhamento
    let span = info_span!("transformacao");
    let _guard = span.enter();

    info!("Transformando dados");
    warn!("Campo opcional ausente, usando valor padrão");
}

// Output mostra a hierarquia:
// INFO processamento{tarefa_id=42}: Iniciando processamento
// INFO processamento{tarefa_id=42}:validacao{etapa="dados"}: Validando dados de entrada
// INFO processamento{tarefa_id=42}:validacao{etapa="dados"}:transformacao: Transformando dados
// WARN processamento{tarefa_id=42}:validacao{etapa="dados"}:transformacao: Campo opcional ausente
```

### A macro #[instrument]

A forma mais ergonômica de instrumentar funções:

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

#[instrument]
fn processar_pedido(pedido_id: u64, cliente: &str) {
    info!("Processando pedido");
    validar_estoque(pedido_id);
    calcular_frete(pedido_id, "SP");
    info!("Pedido processado com sucesso");
}

#[instrument(skip(pedido_id))] // Não incluir pedido_id nos campos do span
fn validar_estoque(pedido_id: u64) {
    info!("Verificando estoque");
}

#[instrument(fields(regiao = %destino))] // Campo customizado
fn calcular_frete(pedido_id: u64, destino: &str) {
    info!(valor = 15.90, "Frete calculado");
}

fn main() {
    tracing_subscriber::fmt::init();

    processar_pedido(12345, "Maria");
}

// Output:
// INFO processar_pedido{pedido_id=12345 cliente="Maria"}: Processando pedido
// INFO processar_pedido{pedido_id=12345 cliente="Maria"}:validar_estoque: Verificando estoque
// INFO processar_pedido{pedido_id=12345 cliente="Maria"}:calcular_frete{regiao="SP"}: Frete calculado valor=15.9
```

### Instrument com funções async

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

#[instrument]
async fn buscar_usuario(id: u64) -> Result<String, String> {
    info!("Buscando no banco de dados");
    tokio::time::sleep(std::time::Duration::from_millis(50)).await;
    Ok(format!("Usuário {}", id))
}

#[instrument]
async fn processar_requisicao(usuario_id: u64) {
    info!("Iniciando processamento");

    match buscar_usuario(usuario_id).await {
        Ok(usuario) => info!(usuario = %usuario, "Usuário encontrado"),
        Err(e) => tracing::error!(erro = %e, "Falha ao buscar usuário"),
    }
}

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

    processar_requisicao(42).await;
}
```

## Recursos Avançados

### Configuração detalhada do subscriber

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

fn configurar_tracing_desenvolvimento() {
    tracing_subscriber::fmt()
        // Formato legível para desenvolvimento
        .pretty()
        // Mostrar nível do evento
        .with_level(true)
        // Mostrar nome do target (módulo)
        .with_target(true)
        // Mostrar timestamps
        .with_timer(fmt::time::ChronoLocal::new("%H:%M:%S%.3f".to_string()))
        // Mostrar número da linha
        .with_line_number(true)
        // Mostrar nome do arquivo
        .with_file(true)
        // Filtro padrão
        .with_env_filter(EnvFilter::new("debug"))
        .init();
}

fn configurar_tracing_producao() {
    tracing_subscriber::fmt()
        // JSON para produção (fácil de parsear por ferramentas)
        .json()
        // Timestamps ISO 8601
        .with_timer(fmt::time::ChronoUtc::rfc_3339())
        // Incluir spans atuais
        .with_current_span(true)
        // Incluir hierarquia de spans
        .with_span_list(true)
        // Filtro via variável RUST_LOG
        .with_env_filter(
            EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| EnvFilter::new("info")),
        )
        .init();
}

fn main() {
    let ambiente = std::env::var("APP_ENV").unwrap_or_else(|_| "dev".to_string());

    if ambiente == "producao" {
        configurar_tracing_producao();
    } else {
        configurar_tracing_desenvolvimento();
    }

    tracing::info!("Aplicação iniciada no ambiente: {}", ambiente);
}
```

### Filtragem com EnvFilter

```rust
use tracing_subscriber::EnvFilter;

fn exemplos_filtros() {
    // Filtro simples por nível
    let _ = EnvFilter::new("info");

    // Filtro por módulo
    let _ = EnvFilter::new("minha_app=debug,hyper=warn,tower=info");

    // Filtro complexo
    let _ = EnvFilter::new(
        "info,minha_app=trace,minha_app::db=debug,sqlx=warn,hyper=error"
    );

    // Filtro com spans específicos
    let _ = EnvFilter::new("info,[requisicao]=debug");

    // Via variável de ambiente RUST_LOG
    let _ = EnvFilter::try_from_default_env()
        .unwrap_or_else(|_| EnvFilter::new("info"));
}

// Uso na linha de comando:
// RUST_LOG=debug cargo run
// RUST_LOG=minha_app=trace,sqlx=warn cargo run
// RUST_LOG="info,[requisicao{id=42}]=trace" cargo run
```

### JSON logging para produção

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

fn configurar_json_logging() {
    tracing_subscriber::fmt()
        .json()
        // Flatten campos de eventos e spans
        .flatten_event(true)
        // Incluir span hierárquico
        .with_current_span(true)
        .with_span_list(true)
        // Timestamp
        .with_timer(fmt::time::ChronoUtc::rfc_3339())
        .init();
}

#[instrument(fields(request_id = %uuid::Uuid::new_v4()))]
async fn handler_exemplo(metodo: &str, caminho: &str) {
    info!(
        metodo = metodo,
        caminho = caminho,
        "Requisição recebida"
    );

    // Simular processamento
    tokio::time::sleep(std::time::Duration::from_millis(10)).await;

    info!(
        status = 200,
        latencia_ms = 10,
        "Resposta enviada"
    );
}

#[tokio::main]
async fn main() {
    configurar_json_logging();

    handler_exemplo("GET", "/api/usuarios").await;
}

// Output JSON (uma linha por evento):
// {"timestamp":"2026-02-27T10:30:00.000Z","level":"INFO","message":"Requisição recebida","metodo":"GET","caminho":"/api/usuarios","request_id":"a1b2c3d4-...","target":"app"}
// {"timestamp":"2026-02-27T10:30:00.010Z","level":"INFO","message":"Resposta enviada","status":200,"latencia_ms":10,"request_id":"a1b2c3d4-...","target":"app"}
```

### Logging em arquivo com rotação

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

fn configurar_log_arquivo() {
    // Rotação diária de arquivos de log
    let file_appender = rolling::daily("logs", "app.log");
    let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);

    // Combinar stdout e arquivo
    let stdout_layer = fmt::layer()
        .pretty()
        .with_filter(EnvFilter::new("info"));

    let file_layer = fmt::layer()
        .json()
        .with_writer(non_blocking)
        .with_filter(EnvFilter::new("debug"));

    tracing_subscriber::registry()
        .with(stdout_layer)
        .with(file_layer)
        .init();

    // IMPORTANTE: _guard deve ser mantido vivo durante toda a execução
    // Se _guard for dropado, os logs pendentes podem ser perdidos
}

fn configurar_log_arquivo_com_guard() -> tracing_appender::non_blocking::WorkerGuard {
    let file_appender = rolling::daily("logs", "app.log");
    let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);

    tracing_subscriber::fmt()
        .json()
        .with_writer(non_blocking)
        .with_env_filter(EnvFilter::new("info"))
        .init();

    guard // Retornar o guard para mantê-lo vivo
}

fn main() {
    let _guard = configurar_log_arquivo_com_guard();

    tracing::info!("Aplicação iniciada");
    // ... resto da aplicação

    // O guard é dropado aqui, garantindo que todos os logs são escritos
}
```

### Múltiplos subscribers (layers)

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

fn configurar_multiplos_layers() {
    // Layer para console (texto formatado)
    let console_layer = fmt::layer()
        .pretty()
        .with_filter(EnvFilter::new("info"));

    // Layer para arquivo (JSON)
    let file_appender = tracing_appender::rolling::daily("logs", "app.log");
    let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);

    let file_layer = fmt::layer()
        .json()
        .with_writer(non_blocking)
        .with_filter(EnvFilter::new("debug"));

    // Layer para erros (arquivo separado)
    let error_appender = tracing_appender::rolling::daily("logs", "errors.log");
    let (error_non_blocking, _error_guard) = tracing_appender::non_blocking(error_appender);

    let error_layer = fmt::layer()
        .json()
        .with_writer(error_non_blocking)
        .with_filter(EnvFilter::new("error"));

    // Combinar todos os layers
    tracing_subscriber::registry()
        .with(console_layer)
        .with(file_layer)
        .with(error_layer)
        .init();
}
```

### Integração com OpenTelemetry

```rust
use opentelemetry::trace::TracerProvider;
use opentelemetry_otlp::WithExportConfig;
use tracing_subscriber::{prelude::*, EnvFilter, fmt};

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

    let provider = opentelemetry_sdk::trace::TracerProvider::builder()
        .with_batch_exporter(exporter, opentelemetry_sdk::runtime::Tokio)
        .with_resource(opentelemetry_sdk::Resource::new(vec![
            opentelemetry::KeyValue::new("service.name", "minha-app"),
            opentelemetry::KeyValue::new("service.version", "1.0.0"),
        ]))
        .build();

    let tracer = provider.tracer("minha-app");

    // Layer do OpenTelemetry
    let otel_layer = tracing_opentelemetry::layer().with_tracer(tracer);

    // Layer do console
    let fmt_layer = fmt::layer()
        .pretty()
        .with_filter(EnvFilter::new("info"));

    // Combinar
    tracing_subscriber::registry()
        .with(otel_layer)
        .with(fmt_layer)
        .init();

    Ok(())
}

use tracing::{info, instrument};

#[instrument]
async fn processar_pedido(pedido_id: u64) {
    info!("Processando pedido");

    validar_pagamento(pedido_id).await;
    enviar_notificacao(pedido_id).await;

    info!("Pedido concluído");
}

#[instrument]
async fn validar_pagamento(pedido_id: u64) {
    info!("Validando pagamento");
    tokio::time::sleep(std::time::Duration::from_millis(100)).await;
    info!("Pagamento validado");
}

#[instrument]
async fn enviar_notificacao(pedido_id: u64) {
    info!("Enviando notificação");
    tokio::time::sleep(std::time::Duration::from_millis(50)).await;
    info!("Notificação enviada");
}

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

    processar_pedido(12345).await;

    // Dar tempo para o exporter enviar os dados
    opentelemetry::global::shutdown_tracer_provider();

    Ok(())
}
```

### Campos dinâmicos em spans

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

fn exemplo_campos_dinamicos() {
    let span = info_span!("requisicao", request_id = tracing::field::Empty, user_id = tracing::field::Empty);
    let _guard = span.enter();

    // Preencher campos depois de criar o span
    Span::current().record("request_id", "req-abc-123");

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

    // Após autenticação, adicionar user_id
    Span::current().record("user_id", 42);

    info!("Usuário autenticado");
}
```

## Boas Práticas

### 1. Use campos estruturados, não interpolação de strings

```rust
use tracing::info;

fn exemplo_boas_praticas() {
    // RUIM: dados interpolados na string
    let usuario_id = 42;
    let acao = "login";
    info!("Usuário {} realizou {}", usuario_id, acao);

    // BOM: campos estruturados (parseáveis por ferramentas)
    info!(
        usuario_id = usuario_id,
        acao = acao,
        "Ação do usuário"
    );
}
```

### 2. Use #[instrument] em todas as funções importantes

```rust
use tracing::instrument;

#[instrument(skip(senha))] // Nunca logar senhas!
async fn autenticar(email: &str, senha: &str) -> Result<u64, String> {
    // ...
    Ok(42)
}

#[instrument(err)] // Logar automaticamente se retornar Err
async fn buscar_dados(id: u64) -> Result<String, std::io::Error> {
    // ...
    Ok("dados".to_string())
}

#[instrument(ret)] // Logar o valor de retorno
fn calcular(a: i32, b: i32) -> i32 {
    a + b
}

#[instrument(
    name = "processar_pagamento", // Nome customizado do span
    skip(cartao),                 // Pular dados sensíveis
    fields(
        valor_brl = %valor,       // Campo customizado
        moeda = "BRL",            // Campo estático
    )
)]
async fn processar(pedido_id: u64, valor: f64, cartao: &str) -> Result<(), String> {
    // ...
    Ok(())
}
```

### 3. Configure filtros por ambiente

```rust
use tracing_subscriber::EnvFilter;

fn filtro_para_ambiente(ambiente: &str) -> EnvFilter {
    match ambiente {
        "dev" => EnvFilter::new("debug,hyper=info,h2=info"),
        "staging" => EnvFilter::new("info,minha_app=debug"),
        "producao" => EnvFilter::new("warn,minha_app=info"),
        _ => EnvFilter::new("info"),
    }
}
```

### 4. Nunca logue dados sensíveis

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

// BOM: skip de campos sensíveis
#[instrument(skip(token, senha))]
async fn criar_conta(email: &str, senha: &str, token: &str) -> Result<u64, String> {
    info!(email = email, "Criando conta");
    Ok(1)
}

// BOM: mascarar dados parcialmente
fn mascarar_email(email: &str) -> String {
    let partes: Vec<&str> = email.split('@').collect();
    if partes.len() == 2 {
        let usuario = partes[0];
        let dominio = partes[1];
        let visivel = if usuario.len() > 2 { 2 } else { 1 };
        format!("{}***@{}", &usuario[..visivel], dominio)
    } else {
        "***".to_string()
    }
}
```

### 5. Adicione contexto com spans

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

async fn processar_requisicao(request_id: &str, usuario_id: u64) {
    // Span de nível de requisição
    let span = info_span!(
        "requisicao",
        request_id = request_id,
        usuario_id = usuario_id,
    );

    async {
        info!("Processando requisição");

        // Sub-operações herdam o contexto
        buscar_dados().await;
        salvar_resultado().await;

        info!("Requisição concluída");
    }
    .instrument(span) // Associar o span à future
    .await;
}

async fn buscar_dados() {
    let span = info_span!("buscar_dados");
    async {
        info!("Consultando banco");
    }
    .instrument(span)
    .await;
}

async fn salvar_resultado() {
    let span = info_span!("salvar_resultado");
    async {
        info!("Salvando no banco");
    }
    .instrument(span)
    .await;
}
```

## Exemplos Práticos

### Serviço web observável com Axum

```rust
use axum::{
    extract::{Path, State},
    http::{Request, StatusCode},
    middleware::{self, Next},
    response::{IntoResponse, Response},
    routing::get,
    Json, Router,
};
use serde::Serialize;
use std::time::{Duration, Instant};
use tracing::{error, info, info_span, instrument, warn, Instrument};
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
use uuid::Uuid;

// === Configuração de tracing ===

fn configurar_tracing() {
    let ambiente = std::env::var("APP_ENV").unwrap_or_else(|_| "dev".to_string());

    if ambiente == "producao" {
        // JSON para produção
        tracing_subscriber::fmt()
            .json()
            .flatten_event(true)
            .with_current_span(true)
            .with_span_list(true)
            .with_env_filter(
                EnvFilter::try_from_default_env()
                    .unwrap_or_else(|_| EnvFilter::new("info,tower_http=debug")),
            )
            .init();
    } else {
        // Texto formatado para desenvolvimento
        tracing_subscriber::fmt()
            .pretty()
            .with_env_filter(EnvFilter::new(
                "debug,hyper=info,h2=info,tower=info",
            ))
            .init();
    }
}

// === Middleware de tracing ===

async fn middleware_tracing(request: Request<axum::body::Body>, next: Next) -> Response {
    let request_id = Uuid::new_v4().to_string();
    let metodo = request.method().clone();
    let uri = request.uri().clone();
    let versao = request.version();

    let span = info_span!(
        "requisicao",
        request_id = %request_id,
        metodo = %metodo,
        uri = %uri,
        versao = ?versao,
        status = tracing::field::Empty,
        latencia_ms = tracing::field::Empty,
    );

    let inicio = Instant::now();

    let response = next.run(request).instrument(span.clone()).await;

    let latencia = inicio.elapsed();
    let status = response.status().as_u16();

    span.record("status", status);
    span.record("latencia_ms", latencia.as_millis() as u64);

    let _guard = span.enter();

    if status >= 500 {
        error!(
            status = status,
            latencia_ms = latencia.as_millis() as u64,
            "Erro interno do servidor"
        );
    } else if status >= 400 {
        warn!(
            status = status,
            latencia_ms = latencia.as_millis() as u64,
            "Erro do cliente"
        );
    } else {
        info!(
            status = status,
            latencia_ms = latencia.as_millis() as u64,
            "Requisição concluída"
        );
    }

    response
}

// === Handlers ===

#[derive(Serialize)]
struct Usuario {
    id: u64,
    nome: String,
    email: String,
}

#[instrument(skip(state))]
async fn listar_usuarios(State(state): State<AppState>) -> Json<Vec<Usuario>> {
    info!("Listando usuários");

    let usuarios = buscar_usuarios_do_banco(&state).await;

    info!(total = usuarios.len(), "Usuários carregados");
    Json(usuarios)
}

#[instrument(skip(state))]
async fn obter_usuario(
    State(state): State<AppState>,
    Path(id): Path<u64>,
) -> Result<Json<Usuario>, StatusCode> {
    info!("Buscando usuário");

    match buscar_usuario_por_id(&state, id).await {
        Some(usuario) => {
            info!(nome = %usuario.nome, "Usuário encontrado");
            Ok(Json(usuario))
        }
        None => {
            warn!("Usuário não encontrado");
            Err(StatusCode::NOT_FOUND)
        }
    }
}

#[instrument]
async fn saude() -> impl IntoResponse {
    info!("Health check");
    Json(serde_json::json!({"status": "ok"}))
}

// === Funções de negócio instrumentadas ===

#[instrument(skip(state))]
async fn buscar_usuarios_do_banco(state: &AppState) -> Vec<Usuario> {
    info!("Consultando banco de dados");

    // Simular latência do banco
    tokio::time::sleep(Duration::from_millis(20)).await;

    vec![
        Usuario {
            id: 1,
            nome: "Maria Silva".to_string(),
            email: "maria@email.com".to_string(),
        },
        Usuario {
            id: 2,
            nome: "João Santos".to_string(),
            email: "joao@email.com".to_string(),
        },
    ]
}

#[instrument(skip(state))]
async fn buscar_usuario_por_id(state: &AppState, id: u64) -> Option<Usuario> {
    info!("Consultando banco de dados por ID");

    tokio::time::sleep(Duration::from_millis(10)).await;

    if id <= 2 {
        Some(Usuario {
            id,
            nome: format!("Usuário {}", id),
            email: format!("usuario{}@email.com", id),
        })
    } else {
        None
    }
}

// === Estado e App ===

#[derive(Clone)]
struct AppState {
    db_url: String,
}

fn criar_app() -> Router {
    let state = AppState {
        db_url: "postgres://localhost/app".to_string(),
    };

    Router::new()
        .route("/saude", get(saude))
        .route("/usuarios", get(listar_usuarios))
        .route("/usuarios/{id}", get(obter_usuario))
        .layer(middleware::from_fn(middleware_tracing))
        .with_state(state)
}

#[tokio::main]
async fn main() {
    configurar_tracing();

    info!(
        versao = env!("CARGO_PKG_VERSION"),
        "Iniciando servidor"
    );

    let app = criar_app();

    let addr = "0.0.0.0:3000";
    info!(endereco = addr, "Servidor pronto");

    let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
    axum::serve(listener, app).await.unwrap();
}
```

## Comparação com Alternativas

| Característica | Tracing | log + env_logger | slog | log4rs |
|---|---|---|---|---|
| **Abordagem** | Spans + events | Events apenas | Structured logging | Logging framework |
| **Estruturado** | Sim (nativo) | Não | Sim | Parcial |
| **Spans** | Sim | Não | Não | Não |
| **Async-aware** | Sim | Não | Não | Não |
| **Hierárquico** | Sim (spans) | Não | Parcial | Não |
| **OpenTelemetry** | Nativo | Via bridge | Via bridge | Não |
| **JSON output** | Sim | Não | Sim | Sim |
| **Performance** | Excelente | Boa | Boa | Boa |
| **Ecossistema** | Tokio, Axum, etc. | Amplo | Moderado | Limitado |

- **Tracing** vs **log**: log é mais simples e adequado para libs que não precisam de spans. Tracing é superior para aplicações, especialmente async. O Tracing implementa o facade do log, então libs usando log funcionam com subscribers do Tracing.
- **Tracing** vs **slog**: slog foi pioneiro em structured logging no Rust, mas Tracing se tornou o padrão com melhor integração com o ecossistema async.
- **Quando usar log**: em bibliotecas que querem dependência mínima e não precisam de spans.
- **Quando usar Tracing**: em aplicações, especialmente as que usam async/await.

## Conclusão

O Tracing transformou a observabilidade em Rust de simples "imprimir mensagens" para um sistema sofisticado de rastreamento estruturado. A combinação de spans hierárquicos, eventos estruturados e integração nativa com o ecossistema async torna-o indispensável para qualquer aplicação de produção.

O investimento em instrumentar seu código com Tracing paga dividendos enormes quando você precisa diagnosticar problemas em produção. A capacidade de correlacionar eventos dentro de spans, filtrar por contexto e exportar para sistemas como Jaeger, Datadog ou Grafana Tempo faz toda a diferença na operação de serviços em escala.

## Tracing para produção e portfólio

Um projeto de portfólio convincente não precisa ser enorme. Uma API [Axum](/ecossistema/axum/) com [SQLx](/ecossistema/sqlx/), PostgreSQL, um worker [Tokio](/ecossistema/tokio/) e Tracing bem configurado já demonstra muito: cada requisição recebe um `request_id`, cada operação de banco registra duração e classe de erro, cada worker cria um span com `event_id` e resultado, e logs JSON podem ser enviados para o ambiente de produção sem mudar o código de negócio.

Se o projeto usa middleware, conecte Tracing com [Tower](/ecossistema/tower/) e `tower-http::trace::TraceLayer`. Se usa gRPC, aplique a mesma disciplina em [Tonic](/ecossistema/tonic/). Se usa filas, leia também [Rust para mensageria](/blog/rust-mensageria-kafka-rabbitmq-nats-2026/) para modelar idempotência e dead letters com campos rastreáveis. Em todos os casos, evite registrar dados sensíveis; prefira IDs sintéticos, rota normalizada, classe de erro, versão do serviço e dependência lógica.

O próximo passo natural é [OpenTelemetry em Rust](/blog/rust-opentelemetry-producao-2026/). Tracing continua sendo a base dentro da aplicação; OpenTelemetry entra como camada de exportação e correlação entre serviços. Para quem mira [backend Rust](/carreira/nicho-web-backend/), [DevOps e infraestrutura com Rust](/carreira/nicho-devops-infra/), [vagas Rust](/vagas/) ou [empresas que usam Rust](/empresas/), essa combinação comunica que você sabe construir e operar software, não apenas compilar exemplos.

### Próximos passos

- Configure **tracing-opentelemetry** para exportar traces para Jaeger, Grafana Tempo ou OpenTelemetry Collector
- Use **tower-http** TraceLayer para instrumentar automaticamente requisições [Axum](/ecossistema/axum/)
- Explore **tracing-appender** para rotação de arquivos de log
- Combine com [Config](/ecossistema/config/) para configurar níveis de log por ambiente
- Aprenda sobre **métricas** com OpenTelemetry Metrics para complementar os traces
