---
title: "Log e env_logger: Logging Idiomático em Rust"
url: "https://rustlang.com.br/ecossistema/log-env-logger/"
markdown_url: "https://rustlang.com.br/ecossistema/log-env-logger.MD"
description: "Guia completo da crate log e env_logger em Rust. Aprenda a usar a facade de logging padrão com info!, warn!, error!, debug!, trace!, configuração com RUST_LOG, formatação personalizada e filtros por módulo."
date: ""
author: ""
---

# Log e env_logger: Logging Idiomático em Rust

Guia completo da crate log e env_logger em Rust. Aprenda a usar a facade de logging padrão com info!, warn!, error!, debug!, trace!, configuração com RUST_LOG, formatação personalizada e filtros por módulo.


O logging é uma das práticas mais fundamentais no desenvolvimento de software. Em Rust, a crate `log` estabelece a **facade de logging padrão** do ecossistema — uma interface unificada que separa a emissão de logs (quem produz) da implementação de logging (quem consome e exibe). A crate `env_logger` é a implementação mais popular dessa facade, permitindo configurar o nível e o filtro de logs através de variáveis de ambiente.

Juntas, `log` e `env_logger` formam a solução de logging mais simples e amplamente compatível do ecossistema Rust. Praticamente toda crate que emite logs usa a facade `log`, o que significa que ao configurar `env_logger` na sua aplicação, você automaticamente captura logs de todas as suas dependências.

## Instalação

Adicione ambas as crates ao seu `Cargo.toml`:

```toml
[dependencies]
log = "0.4"
env_logger = "0.11"
```

A crate `log` fornece as macros de logging (`info!`, `warn!`, etc.), enquanto `env_logger` fornece a implementação que efetivamente exibe os logs no terminal.

Se você quiser apenas emitir logs em uma biblioteca (sem decidir como exibi-los), basta adicionar `log`:

```toml
[dependencies]
log = "0.4"
```

## Uso Básico

### Inicializando o Logger

O primeiro passo é inicializar o `env_logger` no início da sua aplicação:

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

fn main() {
    // Inicializa o env_logger. Deve ser chamado uma única vez,
    // preferencialmente no início de main().
    env_logger::init();

    info!("Aplicação iniciada");
    debug!("Modo debug ativado");
    warn!("Atenção: configuração padrão em uso");
    error!("Falha ao conectar ao banco de dados");
    trace!("Detalhes internos de execução");
}
```

### Níveis de Log

A crate `log` define cinco níveis de severidade, do mais severo ao mais detalhado:

| Nível   | Macro      | Uso típico                                    |
|---------|------------|-----------------------------------------------|
| `Error` | `error!()` | Erros que impedem operação normal             |
| `Warn`  | `warn!()`  | Situações inesperadas, mas recuperáveis       |
| `Info`  | `info!()`  | Informações operacionais relevantes           |
| `Debug` | `debug!()` | Informações úteis para debugging              |
| `Trace` | `trace!()` | Detalhes granulares de execução interna       |

### Controlando com RUST_LOG

A variável de ambiente `RUST_LOG` controla quais logs são exibidos:

```bash
# Mostra todos os logs de nível info ou superior
RUST_LOG=info cargo run

# Mostra tudo (incluindo trace)
RUST_LOG=trace cargo run

# Apenas erros
RUST_LOG=error cargo run

# Debug para o seu crate, info para o resto
RUST_LOG=meu_app=debug,info cargo run
```

Se `RUST_LOG` não estiver definida, nenhum log será exibido por padrão.

### Logging com Valores Formatados

As macros de log suportam formatação idêntica ao `println!`:

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

fn processar_pedido(id: u64, valor: f64) {
    info!("Processando pedido #{} no valor de R${:.2}", id, valor);

    if valor > 10_000.0 {
        warn!("Pedido #{} excede o limite padrão: R${:.2}", id, valor);
    }
}

fn conectar_banco(url: &str) -> Result<(), String> {
    info!("Conectando ao banco: {}", url);
    // Simulando erro
    let resultado: Result<(), String> = Err("Conexão recusada".to_string());

    match &resultado {
        Ok(()) => info!("Conexão estabelecida com sucesso"),
        Err(e) => error!("Falha na conexão com {}: {}", url, e),
    }

    resultado
}
```

### Logging com key=value (Estruturado)

A partir de versões recentes, `log` suporta pares chave-valor:

```rust
use log::info;

fn registrar_requisicao(metodo: &str, caminho: &str, status: u16, duracao_ms: u64) {
    info!(
        metodo = metodo,
        caminho = caminho,
        status = status,
        duracao_ms = duracao_ms;
        "Requisição HTTP processada"
    );
}
```

## Recursos Avançados

### Filtragem por Módulo

Uma das funcionalidades mais poderosas do `RUST_LOG` é a filtragem por caminho de módulo:

```bash
# Apenas logs do módulo 'servidor::http'
RUST_LOG=servidor::http=debug cargo run

# Múltiplos filtros
RUST_LOG=servidor::http=debug,servidor::db=info,warn cargo run

# Filtro por regex (requer feature 'regex' no env_logger)
RUST_LOG="servidor::http::handler.*=trace" cargo run
```

Isso é extremamente útil para depurar um módulo específico sem ser inundado por logs de outras partes do sistema:

```rust
// src/db/conexao.rs
mod conexao {
    use log::{debug, info, trace};

    pub fn conectar(url: &str) {
        // Visível com RUST_LOG=meu_app::db::conexao=debug
        debug!("Tentando conexão com: {}", url);
        trace!("Parâmetros de conexão: timeout=30s, pool=5");
        info!("Conexão estabelecida");
    }
}

// src/http/servidor.rs
mod servidor {
    use log::{info, debug};

    pub fn iniciar(porta: u16) {
        // Visível com RUST_LOG=meu_app::http::servidor=debug
        debug!("Configurando servidor na porta {}", porta);
        info!("Servidor HTTP escutando na porta {}", porta);
    }
}
```

### Formatação Personalizada

O `env_logger` permite personalizar completamente o formato dos logs:

```rust
use env_logger::Builder;
use log::{info, warn, LevelFilter};
use std::io::Write;

fn main() {
    Builder::new()
        .format(|buf, record| {
            writeln!(
                buf,
                "[{} {} {}:{}] {}",
                chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
                record.level(),
                record.file().unwrap_or("desconhecido"),
                record.line().unwrap_or(0),
                record.args()
            )
        })
        .filter(None, LevelFilter::Info)
        .init();

    info!("Servidor iniciado");
    warn!("Memória acima de 80%");
}
// Saída: [2026-02-27 14:30:00 INFO src/main.rs:18] Servidor iniciado
// Saída: [2026-02-27 14:30:00 WARN src/main.rs:19] Memória acima de 80%
```

### Formatação com Cores

O `env_logger` suporta cores no terminal por padrão:

```rust
use env_logger::Builder;
use log::{info, warn, error, debug, LevelFilter};
use std::io::Write;

fn main() {
    Builder::new()
        .format(|buf, record| {
            let level_style = buf.default_level_style(record.level());
            writeln!(
                buf,
                "{level_style}[{:<5}]{level_style:#} {} - {}",
                record.level(),
                record.target(),
                record.args()
            )
        })
        .filter(None, LevelFilter::Debug)
        .init();

    info!("Tudo certo");
    warn!("Cuidado");
    error!("Problema!");
    debug!("Detalhes");
}
```

### Configuração Programática com Fallback

Você pode definir um nível padrão e ainda permitir override via `RUST_LOG`:

```rust
use env_logger::Builder;
use log::LevelFilter;
use std::env;

fn main() {
    let mut builder = Builder::new();

    // Define um padrão
    builder.filter(None, LevelFilter::Info);

    // Permite override via RUST_LOG
    if let Ok(rust_log) = env::var("RUST_LOG") {
        builder.parse_filters(&rust_log);
    }

    builder.init();
}
```

### Direcionando Logs para Arquivo

Embora `env_logger` escreva para stderr por padrão, você pode redirecionar:

```rust
use env_logger::Builder;
use log::{info, LevelFilter};
use std::fs::File;
use std::io::Write;

fn main() {
    let arquivo = File::create("app.log").expect("Falha ao criar arquivo de log");
    let arquivo = std::sync::Mutex::new(arquivo);

    Builder::new()
        .format(move |_buf, record| {
            let mut arquivo = arquivo.lock().unwrap();
            writeln!(
                arquivo,
                "[{}] {} - {}",
                record.level(),
                record.target(),
                record.args()
            )
        })
        .filter(None, LevelFilter::Info)
        .init();

    info!("Este log vai para o arquivo");
}
```

### Usando log em Bibliotecas

Bibliotecas devem usar apenas a crate `log`, sem inicializar nenhum logger:

```rust
// Arquivo: src/lib.rs de uma biblioteca
use log::{debug, info, warn};

pub struct Cache {
    dados: std::collections::HashMap<String, String>,
    capacidade: usize,
}

impl Cache {
    pub fn novo(capacidade: usize) -> Self {
        info!("Cache criado com capacidade {}", capacidade);
        Cache {
            dados: std::collections::HashMap::new(),
            capacidade,
        }
    }

    pub fn inserir(&mut self, chave: String, valor: String) {
        if self.dados.len() >= self.capacidade {
            warn!("Cache cheio ({}/{}), removendo entrada mais antiga",
                  self.dados.len(), self.capacidade);
            if let Some(primeira_chave) = self.dados.keys().next().cloned() {
                self.dados.remove(&primeira_chave);
            }
        }
        debug!("Inserindo chave '{}' no cache", chave);
        self.dados.insert(chave, valor);
    }

    pub fn obter(&self, chave: &str) -> Option<&String> {
        let resultado = self.dados.get(chave);
        match resultado {
            Some(_) => debug!("Cache hit para '{}'", chave),
            None => debug!("Cache miss para '{}'", chave),
        }
        resultado
    }
}
```

### Compilação Condicional de Logs

A crate `log` suporta features para eliminar logs em tempo de compilação:

```toml
[dependencies]
log = { version = "0.4", features = ["max_level_info", "release_max_level_warn"] }
```

Isso remove completamente as chamadas `debug!` e `trace!` do binário em modo release, resultando em zero overhead.

## Boas Práticas

### 1. Inicialize o Logger Apenas Uma Vez

```rust
fn main() {
    // Correto: inicializar no início de main
    env_logger::init();

    // Todo o resto da aplicação...
    executar_app();
}

// ERRADO: não inicialize em bibliotecas ou funções chamadas múltiplas vezes
// fn processar() {
//     env_logger::init(); // Panic! se chamado mais de uma vez
// }
```

### 2. Use try_init para Testes

```rust
#[cfg(test)]
mod tests {
    use log::info;

    fn inicializar_logger() {
        // try_init não causa panic se já inicializado
        let _ = env_logger::builder()
            .is_test(true)
            .try_init();
    }

    #[test]
    fn teste_com_logs() {
        inicializar_logger();
        info!("Executando teste");
        assert_eq!(2 + 2, 4);
    }

    #[test]
    fn outro_teste_com_logs() {
        inicializar_logger();
        info!("Outro teste");
        assert!(true);
    }
}
```

### 3. Escolha o Nível Correto

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

fn processar_transacao(id: u64, valor: f64) -> Result<(), String> {
    // TRACE: detalhes internos, fluxo de execução
    trace!("Entrando em processar_transacao(id={}, valor={})", id, valor);

    // DEBUG: informações úteis para desenvolvimento
    debug!("Validando transação #{}", id);

    // INFO: eventos operacionais importantes
    info!("Transação #{} processada: R${:.2}", id, valor);

    // WARN: algo inesperado, mas o sistema continua funcionando
    if valor > 50_000.0 {
        warn!("Transação #{} com valor alto: R${:.2} — requer auditoria", id, valor);
    }

    // ERROR: algo deu errado
    if valor < 0.0 {
        error!("Transação #{} com valor negativo: R${:.2}", id, valor);
        return Err("Valor inválido".to_string());
    }

    trace!("Saindo de processar_transacao");
    Ok(())
}
```

### 4. Inclua Contexto Suficiente

```rust
use log::{error, info};

// RUIM: sem contexto
fn processar_ruim(dados: &[u8]) {
    info!("Processando...");
    if dados.is_empty() {
        error!("Erro!");
    }
}

// BOM: com contexto útil
fn processar_bom(id: &str, dados: &[u8]) {
    info!("Processando requisição id={} ({} bytes)", id, dados.len());
    if dados.is_empty() {
        error!("Requisição id={} contém payload vazio", id);
    }
}
```

### 5. Evite Logging em Loops Quentes

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

fn processar_lote(itens: &[u32]) {
    info!("Processando lote com {} itens", itens.len());

    // RUIM: log dentro de loop com milhares de iterações
    // for item in itens {
    //     debug!("Processando item {}", item);
    // }

    // BOM: log apenas em marcos importantes
    for (i, item) in itens.iter().enumerate() {
        // Log a cada 1000 itens
        if i % 1000 == 0 && i > 0 {
            debug!("Progresso: {}/{} itens processados", i, itens.len());
        }
        let _ = item; // processar item
    }

    info!("Lote processado: {} itens", itens.len());
}
```

## Exemplos Práticos

### Exemplo Completo: Servidor de Aplicação com Logging

```rust
use log::{debug, error, info, trace, warn, LevelFilter};
use env_logger::Builder;
use std::io::Write;
use std::time::Instant;

// --- Módulo de Configuração ---

mod config {
    use log::{debug, info, warn};

    pub struct AppConfig {
        pub porta: u16,
        pub max_conexoes: usize,
        pub timeout_ms: u64,
    }

    impl AppConfig {
        pub fn carregar() -> Self {
            info!("Carregando configuração da aplicação");

            let porta = std::env::var("APP_PORTA")
                .ok()
                .and_then(|v| v.parse().ok())
                .unwrap_or_else(|| {
                    warn!("APP_PORTA não definida, usando padrão 8080");
                    8080
                });

            let max_conexoes = std::env::var("APP_MAX_CONN")
                .ok()
                .and_then(|v| v.parse().ok())
                .unwrap_or_else(|| {
                    debug!("APP_MAX_CONN não definida, usando padrão 100");
                    100
                });

            let timeout_ms = std::env::var("APP_TIMEOUT")
                .ok()
                .and_then(|v| v.parse().ok())
                .unwrap_or(5000);

            info!(
                "Configuração carregada: porta={}, max_conexoes={}, timeout={}ms",
                porta, max_conexoes, timeout_ms
            );

            AppConfig {
                porta,
                max_conexoes,
                timeout_ms,
            }
        }
    }
}

// --- Módulo de Banco de Dados ---

mod db {
    use log::{debug, error, info, trace};
    use std::collections::HashMap;

    pub struct Database {
        dados: HashMap<String, String>,
    }

    impl Database {
        pub fn conectar(url: &str) -> Result<Self, String> {
            info!("Conectando ao banco de dados: {}", url);
            debug!("Parâmetros: pool_size=5, timeout=30s");

            // Simulação de conexão
            if url.is_empty() {
                error!("URL do banco de dados vazia");
                return Err("URL inválida".to_string());
            }

            info!("Conexão com banco de dados estabelecida");
            Ok(Database {
                dados: HashMap::new(),
            })
        }

        pub fn inserir(&mut self, chave: &str, valor: &str) -> Result<(), String> {
            trace!("DB INSERT: chave='{}', valor='{}'", chave, valor);
            self.dados.insert(chave.to_string(), valor.to_string());
            debug!("Registro inserido: chave='{}'", chave);
            Ok(())
        }

        pub fn buscar(&self, chave: &str) -> Option<String> {
            trace!("DB SELECT: chave='{}'", chave);
            let resultado = self.dados.get(chave).cloned();
            match &resultado {
                Some(v) => debug!("Registro encontrado: chave='{}', tamanho={}", chave, v.len()),
                None => debug!("Registro não encontrado: chave='{}'", chave),
            }
            resultado
        }
    }
}

// --- Módulo de Serviço ---

mod servico {
    use log::{debug, error, info, warn};
    use std::time::Instant;

    pub struct Requisicao {
        pub metodo: String,
        pub caminho: String,
        pub corpo: Option<String>,
    }

    pub struct Resposta {
        pub status: u16,
        pub corpo: String,
    }

    pub fn processar_requisicao(req: &Requisicao) -> Resposta {
        let inicio = Instant::now();
        info!("{} {}", req.metodo, req.caminho);

        let resposta = match req.caminho.as_str() {
            "/saude" => {
                debug!("Health check solicitado");
                Resposta {
                    status: 200,
                    corpo: r#"{"status": "ok"}"#.to_string(),
                }
            }
            "/api/usuarios" => {
                debug!("Listando usuários");
                Resposta {
                    status: 200,
                    corpo: r#"[{"id": 1, "nome": "Maria"}]"#.to_string(),
                }
            }
            _ => {
                warn!("Rota não encontrada: {}", req.caminho);
                Resposta {
                    status: 404,
                    corpo: r#"{"erro": "Não encontrado"}"#.to_string(),
                }
            }
        };

        let duracao = inicio.elapsed();
        if duracao.as_millis() > 100 {
            warn!(
                "{} {} - {} (LENTO: {:?})",
                req.metodo, req.caminho, resposta.status, duracao
            );
        } else {
            info!(
                "{} {} - {} ({:?})",
                req.metodo, req.caminho, resposta.status, duracao
            );
        }

        if resposta.status >= 500 {
            error!(
                "Erro interno: {} {} retornou {}",
                req.metodo, req.caminho, resposta.status
            );
        }

        resposta
    }
}

fn inicializar_logger() {
    Builder::new()
        .format(|buf, record| {
            let nivel = record.level();
            let estilo = buf.default_level_style(nivel);
            writeln!(
                buf,
                "{estilo}[{:<5}]{estilo:#} [{}] {} ({}:{})",
                nivel,
                buf.timestamp_millis(),
                record.args(),
                record.file().unwrap_or("?"),
                record.line().unwrap_or(0),
            )
        })
        .filter(None, LevelFilter::Info)
        .parse_default_env()
        .init();
}

fn main() {
    inicializar_logger();

    info!("=== Iniciando aplicação ===");
    let inicio = Instant::now();

    // Carregar configuração
    let config = config::AppConfig::carregar();

    // Conectar ao banco
    let mut db = match db::Database::conectar("postgres://localhost/meu_app") {
        Ok(db) => db,
        Err(e) => {
            error!("Falha fatal ao conectar ao banco: {}", e);
            std::process::exit(1);
        }
    };

    // Inserir dados de exemplo
    let _ = db.inserir("usuario:1", r#"{"nome": "Maria", "email": "maria@ex.com"}"#);
    let _ = db.inserir("usuario:2", r#"{"nome": "João", "email": "joao@ex.com"}"#);

    // Simular requisições
    let requisicoes = vec![
        servico::Requisicao {
            metodo: "GET".to_string(),
            caminho: "/saude".to_string(),
            corpo: None,
        },
        servico::Requisicao {
            metodo: "GET".to_string(),
            caminho: "/api/usuarios".to_string(),
            corpo: None,
        },
        servico::Requisicao {
            metodo: "GET".to_string(),
            caminho: "/api/inexistente".to_string(),
            corpo: None,
        },
    ];

    for req in &requisicoes {
        let resp = servico::processar_requisicao(req);
        debug!("Corpo da resposta: {}", resp.corpo);
    }

    // Buscar dados
    match db.buscar("usuario:1") {
        Some(dados) => info!("Usuário encontrado: {}", dados),
        None => warn!("Usuário não encontrado no banco"),
    }

    let duracao_total = inicio.elapsed();
    info!(
        "=== Aplicação finalizada em {:?} (porta configurada: {}) ===",
        duracao_total, config.porta
    );
}
```

Execute com diferentes níveis de log:

```bash
# Apenas informações operacionais
RUST_LOG=info cargo run

# Debug completo
RUST_LOG=debug cargo run

# Debug apenas do módulo de banco
RUST_LOG=meu_app::db=debug,info cargo run

# Trace no serviço, info no resto
RUST_LOG=meu_app::servico=trace,info cargo run
```

## Comparação com Alternativas

### log + env_logger vs tracing

| Característica          | log + env_logger       | tracing                      |
|------------------------|------------------------|------------------------------|
| Complexidade           | Simples                | Mais complexo                |
| Dados estruturados     | Básico                 | Nativo (spans e fields)      |
| Contexto entre funções | Manual                 | Automático via spans         |
| Async                  | Funcional              | Projetado para async         |
| Performance            | Muito bom              | Excelente                    |
| Ecossistema            | Amplo (facade padrão)  | Crescendo rapidamente        |
| Caso de uso ideal      | Aplicações simples     | Sistemas distribuídos/async  |

**Quando usar `log` + `env_logger`:**
- Aplicações de linha de comando (CLIs)
- Bibliotecas que querem compatibilidade máxima
- Projetos simples sem necessidade de spans
- Quando simplicidade é prioridade

**Quando usar `tracing`:**
- Sistemas assíncronos com Tokio
- Microsserviços e sistemas distribuídos
- Quando você precisa de contexto automático entre chamadas async
- Integração com OpenTelemetry, Jaeger, etc.

Note que `tracing` é compatível com `log` — se você usar `tracing-log`, logs emitidos via `log` são capturados pelo `tracing`.

### Outras Implementações da Facade log

Além de `env_logger`, existem outras implementações populares:

| Implementação    | Características                              |
|-----------------|----------------------------------------------|
| `env_logger`    | Simples, configuração via variável ambiente  |
| `pretty_env_logger` | Como env_logger, mas com saída formatada |
| `simplelog`     | Múltiplos destinos (arquivo, terminal)       |
| `fern`          | Altamente configurável, múltiplos outputs    |
| `log4rs`        | Inspirado no Log4j, configuração via arquivo |

## Conclusão

A dupla `log` + `env_logger` é a porta de entrada para logging em Rust. A separação entre facade (`log`) e implementação (`env_logger`) é um padrão elegante que permite que bibliotecas emitam logs sem impor decisões sobre como esses logs serão tratados.

Para a maioria das aplicações, `env_logger` oferece tudo que você precisa: filtragem por nível e módulo, formatação personalizável e configuração simples via `RUST_LOG`. Para cenários mais avançados — como logging estruturado em sistemas distribuídos — considere migrar para `tracing`, que mantém compatibilidade com o ecossistema `log`.

**Próximos passos:**
- Explore a crate [tracing](/ecossistema/tracing/) para observabilidade estruturada
- Veja [anyhow e thiserror](/ecossistema/anyhow-thiserror/) para combinar logging com tratamento de erros robusto
- Aprenda a configurar logging em aplicações web com [Axum](/ecossistema/axum/) ou [Actix Web](/ecossistema/actix-web/)
