O Modelo de Logging do Node.js
Um log é um registro estruturado e com timestamp de um único fato que seu processo observou - uma requisição concluída, um pagamento falhou, um job em background iniciado.
Busque em todas as páginas da documentação
Um log é um registro estruturado e com timestamp de um único fato que seu processo observou - uma requisição concluída, um pagamento falhou, um job em background iniciado.
Parece console.log com etapas extras, mas a diferença importa: uma linha de log escrita para uma pessoa observando um terminal e uma linha de log escrita para uma máquina indexar, filtrar e alertar resolvem problemas diferentes, mesmo que pareçam semelhantes na tela.
Esta página é o modelo mental por trás do restante da seção de logging: Noções Básicas de Logging mostra código funcional para logging estruturado com Pino, e as páginas sobre IDs de correlação, redação e AsyncLocalStorage aprofundam-se em mecânicas específicas.
Aqui, o objetivo é entender o que um log realmente é, qual pipeline ele percorre e por que esse pipeline molda quase todas as decisões de logging que você tomará.
Antes do logging estruturado, a maioria do código Node apenas escrevia texto para stdout: console.log("usuário logado: " + userId).
Isso é aceitável para um humano olhando para um terminal durante o desenvolvimento local, mas falha no momento em que mais de uma pessoa, mais de um serviço, ou mais do que algumas requisições por minuto estão envolvidas - um humano não consegue extrair significado de milhares de frases em formato livre rápido o suficiente para importar durante um incidente.
O logging estruturado resolve isso tratando cada linha de log como dado primeiro, mensagem segundo: um objeto JSON com campos nomeados (userId, action, statusCode) e uma mensagem curta legível por humanos anexada, em vez de uma frase com valores embutidos.
Uma analogia útil é um diário de bordo de navio versus um diário pessoal.
Um diário de bordo registra fatos discretos e estruturados em um formato fixo - hora, posição, evento - especificamente para que um leitor diferente, dias ou anos depois, possa reconstruir exatamente o que aconteceu e quando, sem precisar do contexto do escritor original.
Um diário pessoal é prosa, escrito para a própria recordação do escritor; é expressivo, mas ninguém pode escrever uma consulta contra ele.
O logging de produção visa o diário de bordo, não o diário pessoal - mesmo quando ele ainda é legível de relance.
O nível de log é a outra ideia fundamental: fatal, error, warn, info, debug (na convenção do Pino) não são uma escala de humor para o quão chateado o desenvolvedor estava ao escrever a linha - cada nível é uma promessa sobre o que um operador deve fazer ao vê-lo. error significa que algo precisa de atenção; warn significa que algo degradou, mas se recuperou; info significa atividade normal e esperada que vale a pena registrar; debug significa detalhes úteis no desenvolvimento ou na solução de problemas direcionada, não ruído de produção rotineiro.
Cada linha de log, desde o momento em que seu código chama logger.info(...) até o momento em que alguém a procura em um dashboard, percorre um pipeline:
local da chamada -> filtro de nível -> serializar -> transporte -> agregador -> índice -> consulta
(logger.info) (abaixo (objeto para (stdout, (Datadog, (armazenamento (dashboards,
do limiar? string JSON) arquivo, socket) Loki, CW) pesquisável) alertas)
descartar cedo)
O filtro de nível é executado primeiro e importa mais do que parece: um logger bem construído verifica o nível configurado antes de fazer o trabalho caro de serializar o objeto de log, então uma chamada debug em um processo de produção rodando em info custa quase nada - o objeto nunca é transformado em string, nunca é escrito em lugar nenhum.
É por isso que o conselho genérico "apenas registre tudo" é ingênuo; não é realmente "tudo" que é caro, é serializar e transportar tudo que é caro, e a etapa de filtro existe precisamente para evitar pagar esse custo por linhas que ninguém lerá.
Transporte é onde o logging intersecta diretamente com o event loop: escrever para stdout de forma síncrona é barato para pequenos volumes, mas pode se tornar uma fonte de backpressure sob carga pesada, já que um consumidor lento (um processo piped, um buffer de disco cheio) pode fazer com que as escritas bloqueiem.
A maioria das configurações de produção mantém o caminho de escrita da aplicação simples - escrever JSON estruturado para stdout - e deixa um agente externo (um sidecar, um shipper de logs) lidar com o trabalho mais lento de transporte e agregação, para que o próprio processo da aplicação nunca bloqueie em I/O de rede apenas para emitir uma linha de log.
// por que a ordem de serialização importa: o filtro é executado antes do trabalho (caro)
function log(level: "info" | "debug", threshold: "info" | "debug", fields: object): void {
const levels = { debug: 0, info: 1 };
if (levels[level] < levels[threshold]) return; // verificação barata, sem serialização ainda
process.stdout.write(JSON.stringify({ level, ...fields }) + "\n");
}Correlação é o mecanismo que une as linhas de log dispersas de uma requisição de volta: um requestId (ou trace_id quando correlacionado com tracing) anexado uma vez, no início do ciclo de vida da requisição, e transportado através de todas as linhas de log subsequentes para essa requisição - seja passando um logger filho pela pilha de chamadas ou lendo-o de AsyncLocalStorage em cada local de chamada.
Sem correlação, os logs de um serviço ocupado são um fluxo intercalado de requisições não relacionadas, e reconstruir "tudo o que aconteceu para esta requisição falha" se torna uma busca manual e propensa a erros.
A cardinalidade é geralmente o principal impulsionador de custo no logging em escala, mais do que a contagem bruta de linhas.
Um campo como statusCode tem um punhado de valores possíveis e comprime lindamente na maioria dos índices de log; um campo como userId ou uma mensagem de erro bruta com um ID embutido tem valores distintos efetivamente ilimitados, e a indexação em campos de alta cardinalidade é o que realmente infla o custo de armazenamento e consulta nos modelos de precificação da maioria das plataformas de agregação de logs.
É por isso que as equipes frequentemente reservam campos como userId para valores nos quais você pesquisará seletivamente, em vez de indexar todos os campos possíveis por padrão.
A amostragem de logs - descartar deliberadamente uma fração de eventos rotineiros e de alto volume (como pings de health-check bem-sucedidos) enquanto mantém todos os erros - é a resposta usual quando "registrar tudo" se torna financeiramente ou operacionalmente insustentável; troca uma pequena quantidade de completude no caminho rotineiro por uma grande redução de ruído e custo, enquanto intencionalmente nunca amostra os eventos que realmente importam para depuração.
O logging também está em tensão com segurança e conformidade: os mesmos campos estruturados que tornam os logs úteis para depuração (endereços de e-mail, IPs, corpos de requisição) são frequentemente dados regulamentados sob GDPR, PCI-DSS ou frameworks semelhantes, razão pela qual a redação no momento da serialização - não "lembre-se de não registrar senhas" como um hábito de desenvolvedor - é o controle confiável. Redação de PII e Conformidade cobre isso em profundidade.
A direção da indústria é convergência, não divergência: o sinal de Logs do OpenTelemetry está padronizando logs estruturados ao lado de traces e métricas sob um único modelo de correlação, de modo que uma linha de log, um span e um ponto de dados métrico sobre a mesma requisição compartilham cada vez mais o mesmo trace_id e nomes de campo semânticos em vez de viverem em três ferramentas não relacionadas.
| Abordagem | Força | Fraqueza | Melhor Ajuste |
|---|---|---|---|
| Logs de texto não estruturados | Configuração zero; legível em um terminal bruto | Não consultável em escala; sem extração confiável de campos; incentiva mensagens inconsistentes | Apenas desenvolvimento local |
| Logs JSON estruturados (Pino) | Analisável por máquina; rápido para filtrar/alertar; baixo overhead por chamada | Requer disciplina na nomeação de campos entre serviços; menos agradável de inspecionar bruto | Serviços de produção, escolha padrão |
| Sinal de Logs OpenTelemetry | Correlação nativa com traces/métricas via trace_id compartilhado; pipeline neutro em relação ao fornecedor | Mais novo, menos universalmente suportado por todos os backends; adiciona superfície de SDK | Serviços já instrumentados com tracing OTel |
console.log está bom, desde que funcione." Ele ignora completamente a filtragem de nível, a serialização estruturada e a redação, e geralmente é a primeira coisa que quebra um pipeline de logs apenas JSON downstream.error para eventos rotineiros e auto-recuperáveis (como uma única tentativa) treina os operadores a ignorar erros reais.Um log estruturado é um objeto de dados (tipicamente serializado para JSON) com campos nomeados mais uma mensagem curta, em vez de uma única string de formato livre com valores interpolados nela. A distinção importa porque as máquinas podem filtrar, agregar e alertar sobre campos nomeados; elas não podem analisar frases arbitrárias de forma confiável.
Porque o filtro de nível é executado primeiro, antes que qualquer serialização ou I/O aconteça - uma chamada debug em um serviço rodando em nível info custa quase nada, pois o objeto nunca é transformado em string ou escrito em lugar algum. Entender essa ordem explica por que o uso liberal de chamadas debug é barato em produção, desde que o nível esteja configurado corretamente.
O nível de log é um sinal técnico sobre a resposta do operador (investigar agora, observar uma tendência, informativo), enquanto o impacto no negócio é um eixo separado - uma tentativa de retentativa de nível warn pode ter zero impacto no negócio se for bem-sucedida, enquanto uma linha de info "reembolso processado" pode ser muito importante para uma equipe específica, mesmo que nada esteja quebrado.
Um identificador único (um requestId, ou um trace_id compartilhado com tracing) é gerado ou recebido uma vez, no início do ciclo de vida da requisição, e então anexado a todas as linhas de log subsequentes para essa requisição - seja através de um logger "filho" vinculado passado pela pilha de chamadas, ou lido do armazenamento de contexto (AsyncLocalStorage) em cada local de chamada.
A maioria das plataformas de agregação de logs indexa campos para torná-los pesquisáveis, e o custo de indexação escala com quantos valores distintos um campo tem, não apenas com quantas linhas existem. Um campo como statusCode (um punhado de valores) indexa de forma barata; um campo com valores efetivamente ilimitados e únicos (como uma mensagem de erro bruta com um ID embutido) pode ser muito mais caro para indexar do que a contagem bruta de linhas sugere.
Quando o caminho rotineiro e de alto volume (health checks bem-sucedidos, polling repetitivo) contribui com custo e ruído sem adicionar valor de depuração - amostre esses, enquanto nunca amostra os eventos que realmente indicam um problema (erros, eventos de segurança, transações críticas de negócios).
Não - ele ignora a filtragem de nível, a serialização estruturada e todas as regras de redação, e escreve de forma síncrona de uma maneira que não compõe com pipelines de logs estruturados downstream. Um logger real é um pequeno sistema com comportamento configurável; console.log é um único comportamento fixo.
Eles respondem a perguntas diferentes: um trace mostra timing e estrutura causal entre serviços para uma requisição, enquanto um log carrega detalhes específicos e muitas vezes arbitrários (uma mensagem de erro exata, um campo de payload) que os spans estruturados de um trace tipicamente não capturam. Correlacioná-los através de um ID compartilhado permite que você pule de "esta requisição foi lenta" (trace) para "aqui está exatamente o porquê" (log) em uma única etapa.
Logs são frequentemente retidos por mais tempo, replicados para mais sistemas (agregadores, backups, SaaS de terceiros) e acessados por mais pessoas do que o banco de dados primário - o que significa que PII em logs pode violar políticas de tratamento de dados do GDPR, PCI-DSS ou internas, mesmo que o armazenamento de dados primário esteja totalmente em conformidade. A redação no momento da serialização é a correção confiável, não apenas a disciplina do desenvolvedor.
Não na prática - ferramentas como pino-pretty reformulam o JSON estruturado em uma linha legível e colorida para desenvolvimento local, enquanto a produção ainda recebe e armazena o objeto estruturado bruto. Você obtém ambos: armazenamento consultável por máquina e saída local legível por humanos, a partir dos mesmos dados subjacentes.
Pino continua sendo a biblioteca de emissão; o sinal de Logs do OpenTelemetry é um esforço de padronização sobre como logs estruturados são representados e correlacionados com traces e métricas, usando identificadores compartilhados como trace_id. Os dois não são concorrentes - muitas configurações usam Pino para emitir e um pipeline compatível com OTel para correlacionar e exportar.
Tratá-lo como gratuito - registrar tudo em info sem pensar em nível, cardinalidade ou redação - e só descobrir o custo (financeiro, em ruído ou em risco de conformidade) quando o volume cresce além do que um único desenvolvedor olhando a saída pode absorver.
Versões de Stack: Esta página é conceitual e não está vinculada a uma versão específica de stack, embora os trechos ilustrativos assumam Node.js 24 LTS e TypeScript 5.6+.
Revisado por Chris St. John·Última atualização: 19 de jul. de 2026