Depuração em Produção
Bugs em produção diferem de falhas locais: a forma do tráfego, o volume de dados e a configuração do ambiente escondem problemas que seu laptop nunca vê. Logs estruturados, traces distribuídos, gdb/lldb e tokio-console fecham essa lacuna sem reinícios arriscados.
Receita
Cartão de receita de referência rápida - pronto para copiar e colar.
use tracing::{info, instrument};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
pub fn init_tracing() {
tracing_subscriber::registry()
.with(EnvFilter::from_default_env())
.with(tracing_subscriber::fmt::layer().json())
.init();
}
#[instrument(skip(pool), fields(trace_id, tenant_id))]
async fn handle_order(
trace_id: String,
tenant_id: String,
pool: &sqlx::PgPool,
) -> Result<(), sqlx::Error> {
tracing::Span::current().record("trace_id", &trace_id);
tracing::Span::current().record("tenant_id", &tenant_id);
info!("order_created", order_id = "ord_123");
sqlx::query("SELECT 1").execute(pool).await?;
Ok(())
}# Inspeção de tarefas async em tempo real (habilite tokio_unstable + console subscriber em staging)
RUSTFLAGS="--cfg tokio_unstable" cargo run --features console
# Anexe o depurador a um binário em execução (precisa de símbolos de depuração na imagem)
lldb -p $(pgrep orders-api)
(gdb) thread apply all btQuando recorrer a isso:
- Taxa de erro aumenta sem correlação óbvia com deploy
- Latência cresce, mas CPU parece ociosa (tarefas bloqueadas ou espera de I/O)
- O problema aparece apenas para um tenant ou região
- Reprodução local falha após combinar versão do Rust e variáveis de ambiente
Exemplo de Trabalho
//! prod_debug_helpers.rs - toolkit mínimo para correlacionar evidências de produção.
use std::time::Instant;
use serde::Serialize;
use tracing::{info, instrument};
#[derive(Debug, Serialize)]
pub struct DeployContext {
pub git_sha: String,
pub rust_version: String,
pub service: String,
}
pub fn deploy_context() -> DeployContext {
DeployContext {
git_sha: std::env::var("GIT_SHA").unwrap_or_else(|_| "unknown".into()),
rust_version: env!("RUSTC_VERSION").to_string(),
service: std::env::var("SERVICE_NAME").unwrap_or_else(|_| "api".into()),
}
}
#[instrument(skip(f), fields(request_id, trace_id))]
pub async fn with_request_scope<F, T>(
request_id: &str,
trace_id: &str,
f: F,
) -> T
where
F: std::future::Future<Output = T>,
{
let span = tracing::Span::current();
span.record("request_id", request_id);
span.record("trace_id", trace_id);
let ctx = deploy_context();
info!(?ctx, "request_started");
let start = Instant::now();
let result = f.await;
info!(duration_ms = start.elapsed().as_millis() as u64, "request_finished");
result
}# Filtre logs JSON por trace_id em produção
kubectl logs deploy/orders-api | jq 'select(.fields.trace_id=="abc-123")'
# Amostre stacks sem reiniciar (Linux, precisa de permissões de perf)
perf record -p $(pgrep orders-api) -g -- sleep 30
perf scriptO que isso demonstra:
- Cada requisição carrega
trace_id,tenant_ide SHA do deploy em logs estruturados - Spans envolvem handlers async para que as camadas
sqlxe Axum se aninhem corretamente - Depurador e profiler se anexam ao mesmo binário que você envia (com símbolos)
- A coleta de evidências precede as mudanças de código
Mergulho Profundo
Como Funciona
- Correlacione primeiro - Combine
trace_identre logs de API, worker e slow-query do banco de dados antes de hipotetizar. - Repro em Sandbox - Copie payload anonimizado de produção, feature flags e nível
RUST_LOGpara um pod de staging. - Ferramentas específicas para Async -
tokio-consolemostra tarefas de polling longas;tracingmostra qual span consumiu tempo. - Depuração Nativa -
gdb/lldbpara segfaults, SIGABRT deabort(), ou threads travadas em código misto sync/async. - Estabilize - Revert ou limite a taxa antes de RCA profundo durante incidentes ativos.
Sinais Específicos do Rust
| Sintoma | Causa Provável | Primeira Verificação |
|---|---|---|
| Pânicos 500 súbitos, sem deploy | Pânico em unwrap() | RUST_BACKTRACE=1 em replay de staging |
| CPU estável, latência alta | Thread do runtime bloqueada | tokio-console, durações de span de tracing |
| Pod OOMKilled | Pico de alocador ou vazamento | dhat/heaptrack em teste de carga |
| Dados incorretos intermitentes | Data race em unsafe | miri em CI, audite blocos unsafe |
Stack de Observabilidade
// Dependências do Cargo.toml (serviço Axum típico)
// tracing, tracing-subscriber, tracing-opentelemetry
// tower-http com TraceLayer para IDs de requisição HTTPConecte TraceLayer no Axum para que cada requisição HTTP receba um trace_id propagado para spans downstream e linhas de log.
Armadilhas
- Símbolos de depuração removidos na imagem de produção -
lldbmostra???. Correção: Envie um pacote de símbolos de depuração separado ou uma build com-gdividida para a equipe de plantão. - Logging em
debugem produção - Explosão de disco e custo. Correção:RUST_LOGdinâmico via env, padrãoinfo. - Bloqueio em handler async -
std::fs::readem uma thread worker do Tokio trava todas as tarefas nessa thread. Correção:tokio::task::spawn_blocking. - Span ausente no pool
sqlx- Tempo do DB invisível. Correção: Habilite o recurso de tracing dosqlxe a instrumentação do pool. - Hotfix sem tag de SHA do deploy - Não é possível correlacionar regressão. Correção: Registre
GIT_SHAna inicialização e em cada relatório de erro.
Alternativas
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
rr record-and-replay | Heisenbugs, races raras | Host de produção (apenas dev) |
flamegraph + perf | Caminhos quentes da CPU | Latência limitada por I/O (enganoso) |
| Análise de core dump | Segfault após hook de pânico | Deadlocks lógicos do Tokio |
| Replay de tráfego Shadow | Repro sem impacto no usuário | Escritas ou efeitos colaterais não idempotentes |
FAQs
Posso anexar lldb a uma build de release?
Sim, se os símbolos de depuração estiverem disponíveis. Muitas equipes enviam -C force-frame-pointers=yes e armazenam os símbolos separadamente.
Quando usar tokio-console vs tracing?
tracing responde o que aconteceu por requisição; tokio-console responde quais tarefas estão ativas e quanto tempo os polls levam.
Como reproduzir pânico exclusivo de produção?
Combine RUSTFLAGS, feature flags, contagem de CPU (TOKIO_WORKER_THREADS) e tamanho da entrada. Use fixture de produção sanitizada.
RUST_BACKTRACE=full é seguro em produção?
Brevemente em staging ou em um único pod canary. Backtraces completos são verbosos e podem vazar caminhos; use =1 para incidentes.
Como depurar exaustão do pool de conexões sqlx?
Habilite métricas do pool, registre a duração do span acquire, compare com max_connections do DB e lag de réplica.
E para alvos WASM ou embarcados?
Use defmt/probe-rs para embarcados; WASM no navegador depende de console_error_panic_hook e source maps.
A equipe de plantão deve alterar o código ao vivo?
Estabilize com rollback ou flags primeiro. Edições ao vivo contornam CI e perdem o rastro de auditoria.
Quanto tempo coletar logs antes do conserto?
15-30 minutos de evidências correlacionadas geralmente superam adivinhações. Estenda se intermitente (>1h de ciclo).
Traces distribuídos entre serviços?
Exporte spans de tracing via OpenTelemetry OTLP; propague o header traceparent em middleware Axum.
Quando escalar para suporte do fornecedor/nuvem?
Quando as evidências apontarem para hipervisor, failover de DB gerenciado ou má configuração de LB fora do seu binário.
Relacionados
- Tracing & Instrumentação - configuração de span
- Modos Comuns de Falha em Produção - catálogo de sintomas
- Playbooks de Resposta a Incidentes - processo
- Ferramentas de Depuração - ferramentas locais
- Desligamento Gracioso - depuração segura de deploy
Versões da Stack: Esta página foi escrita para Rust 1.97.0 (edição 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, e Polars 0.46+.