Pino é uma biblioteca de logs estruturados para Node.js. Em vez de montar linhas de texto manualmente, a aplicação grava objetos JSON com nível, horário, mensagem e contexto. Esse formato facilita busca, alertas, correlação e processamento por plataformas de observabilidade.
Logs não substituem métricas e traces. Eles registram eventos discretos que ajudam a explicar falhas, decisões e mudanças de estado. Uma estratégia boa produz contexto suficiente sem expor segredos ou gerar volume impossível de armazenar.
Instalação
npm install pinoPrimeiro logger
import pino from 'pino';
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
});
logger.info({ port: 3000 }, 'Servidor iniciado');A saída contém campos estruturados. Em produção, envie JSON para stdout e deixe o coletor transportar e indexar.
Níveis
trace: detalhe muito fino;debug: diagnóstico de desenvolvimento;info: eventos operacionais normais;warn: condição inesperada recuperável;error: operação falhou;fatal: processo não consegue continuar.
Não registre toda requisição como error. Isso gera alertas e custos desnecessários.
Child logger
Um child adiciona contexto comum:
const requestLogger = logger.child({
requestId,
method: req.method,
route: req.route?.path,
});
requestLogger.info('Requisição recebida');Use rota normalizada, não URL completa com IDs e query sensível.
AsyncLocalStorage
Contexto assíncrono evita passar logger manualmente por todas as funções:
import { AsyncLocalStorage } from 'node:async_hooks';
const context = new AsyncLocalStorage();
app.use((req, res, next) => {
const requestId = req.get('x-request-id') || crypto.randomUUID();
const child = logger.child({ requestId });
context.run({ logger: child }, next);
});
function currentLogger() {
return context.getStore()?.logger || logger;
}Erros
try {
await processOrder(order);
} catch (error) {
logger.error({ err: error, orderId: order.id }, 'Falha ao processar pedido');
throw error;
}Pino possui serialização apropriada para Error quando o campo segue a convenção esperada. Não use apenas JSON.stringify(error), pois propriedades importantes não são enumeráveis.
Causes
Erros modernos podem possuir cause. Garanta que o serializador e a plataforma preservem a cadeia sem repetir dados sensíveis.
Redaction
Remova campos sensíveis antes da saída:
const logger = pino({
redact: {
paths: [
'req.headers.authorization',
'req.headers.cookie',
'password',
'*.token',
],
censor: '[REDACTED]',
},
});Redaction é uma defesa adicional. O ideal é não passar secrets ao logger.
Não registre corpos completos
Request e response body podem conter senha, cartão, documento, saúde ou conteúdo grande. Registre somente campos permitidos e agregados.
Serializers
const logger = pino({
serializers: {
req(req) {
return {
method: req.method,
url: req.url?.split('?')[0],
remoteAddress: req.socket?.remoteAddress,
};
},
},
});Revise IPs conforme a política de privacidade e proxy confiável.
Pretty print
Em desenvolvimento, uma ferramenta como pino-pretty torna a saída legível. Não use formatação pesada no caminho principal de produção.
node server.js | pino-prettyTransport
Transports processam a saída fora do fluxo principal conforme a arquitetura da biblioteca. Mesmo assim, stdout com agente externo costuma ser simples e robusto em containers.
stdout
Escreva logs na saída padrão. Kubernetes, Docker e systemd coletam o stream. Evite gerenciar arquivos e rotação dentro de cada processo, salvo necessidade específica.
Backpressure
Logs podem se tornar gargalo quando o destino está lento. Não gere megabytes por requisição. Meça throughput, use níveis e amostragem.
Extreme mode
Modos de buffering podem melhorar desempenho, mas aumentam risco de perda em crash. Avalie a durabilidade necessária e o contrato da versão utilizada.
Flush
Antes de encerramento normal, permita flush do destino. Em crash, operações assíncronas podem não concluir. Para eventos fatais, mantenha o handler mínimo e encerre.
Logs de requisição
Campos úteis:
- requestId;
- traceId;
- método;
- rota normalizada;
- status;
- duração;
- bytes;
- tenant técnico;
- resultado.
Não use query inteira, Authorization ou payload.
Middleware de duração
app.use((req, res, next) => {
const start = performance.now();
res.once('finish', () => {
currentLogger().info({
statusCode: res.statusCode,
durationMs: performance.now() - start,
}, 'Requisição concluída');
});
next();
});Trate também close para conexões interrompidas sem duplicar o evento.
Trace ID
Quando OpenTelemetry está ativo, inclua trace ID e span ID:
const span = trace.getActiveSpan();
const spanContext = span?.spanContext();
logger.info({
traceId: spanContext?.traceId,
spanId: spanContext?.spanId,
}, 'Operação');Request ID externo
Valide tamanho e caracteres de X-Request-ID. Ou gere um ID interno e registre o externo separadamente.
Mensagens estáveis
Use mensagem humana curta e campos para detalhes:
logger.info({ userId, plan }, 'Plano alterado');Evite interpolar tudo na mensagem, pois dificulta agregação.
Eventos de auditoria
Auditoria possui requisitos diferentes de logs operacionais. Pode exigir imutabilidade, retenção, acesso restrito e campos obrigatórios. Não misture sem política.
PII
Classifique dados pessoais. Use IDs internos, mascaramento e retenção curta. Email e IP podem ser dados pessoais conforme contexto.
Cardinalidade
Plataformas de logs aceitam mais cardinalidade que métricas, mas valores únicos ainda aumentam custo. Indexe apenas campos usados em busca.
Amostragem
Para eventos muito frequentes:
if (Math.random() < 0.01) {
logger.debug({ operation }, 'Amostra de operação');
}Erros raros e auditoria podem exigir retenção completa. Use amostragem determinística por trace para manter coerência.
Rate limit de logs
Uma dependência falhando pode gerar milhões de mensagens iguais. Agregue contagens ou limite por chave e janela.
Erro e retry
Não registre cada tentativa como error e depois o erro final novamente. Use debug ou warn nas tentativas e error na falha final, conforme impacto.
Health checks
Probes frequentes podem poluir logs. Exclua ou amostre acessos bem-sucedidos, mantendo falhas.
Workers e cluster
Inclua PID, threadId ou worker ID. A ordem entre processos não é garantida; use timestamp e requestId.
Timestamp
Use horário UTC e relógio sincronizado. A plataforma pode adicionar timestamp de ingestão, mas preserve o de emissão.
Configuração dinâmica
Alterar LOG_LEVEL pode ajudar em incidentes. Implemente de forma autenticada, auditada e temporária. Debug permanente aumenta custo e exposição.
Testes
Capture a saída e confirme campos, redaction, nível e serialização de erro. Não faça testes dependentes da ordem de propriedades JSON.
Desempenho
Benchmark com volume real. O custo não está só na biblioteca: serializar objetos grandes e enviar à rede domina. Compare logs ativados e desativados.
Observabilidade do logging
Meça:
- bytes por segundo;
- eventos por nível;
- falhas de transporte;
- fila ou buffer;
- logs descartados;
- latência de ingestão;
- custo de armazenamento.
Graceful shutdown
Pare de aceitar tráfego, finalize tarefas e permita flush. Não aguarde indefinidamente por um coletor indisponível.
Erros comuns
- registrar senha ou token;
- logar body completo;
- usar pretty em produção;
- interpolar todos os campos;
- não correlacionar request e trace;
- registrar retry repetido como error;
- indexar tudo;
- não limitar volume;
- não testar redaction;
- confiar no log como auditoria completa.
Fluxo recomendado
Produza JSON em stdout, use child loggers, AsyncLocalStorage, redaction e mensagens estáveis. Correlacione com traces e controle volume. Combine com AsyncLocalStorage, OpenTelemetry, Graceful Shutdown e Diagnostics Channel.
Consulte a documentação oficial do Pino e o repositório oficial.


