Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Logs com Pino no Node.js

Atualizado em: 23 de agosto de 2026

Estação de trabalho usada no desenvolvimento de aplicações Node.js

Implementar logs com Pino no Node.js permite registrar eventos estruturados em JSON com baixo overhead. Em produção, logs precisam responder o que aconteceu, em qual serviço, durante qual requisição e com quais consequências, sem bloquear o event loop nem expor credenciais.

Pino separa geração de logs da formatação visual. A aplicação escreve JSON rapidamente para stdout, enquanto uma ferramenta externa coleta, transporta e apresenta os eventos. Essa abordagem combina bem com containers, systemd, Kubernetes e plataformas centralizadas.

Neste guia, você aprenderá níveis, child loggers, serialização de erros, redaction, request IDs, transporte, pretty printing, integração HTTP, AsyncLocalStorage, testes e estratégias para controlar custo e volume.

O que é Pino?

Pino é uma biblioteca de logging estruturado para Node.js. A documentação oficial do Pino apresenta configuração e transports. O repositório oficial do Pino contém referências de API e exemplos.

Para saída padrão, consulte Console no Node.js. Para contexto por requisição, veja AsyncLocalStorage no Node.js.

Instalando

npm install pino

Para desenvolvimento legível:

npm install --save-dev pino-pretty

Logger básico

const pino = require('pino');

const logger = pino({
  level: process.env.LOG_LEVEL || 'info'
});

logger.info('Aplicação iniciada');

A saída contém timestamp, nível, PID, hostname e mensagem, conforme a configuração e versão.

Campos estruturados

logger.info({
  orderId: 842,
  status: 'created',
  durationMs: 37
}, 'Pedido criado');

Campos estruturados permitem filtros e métricas. Evite concatenar tudo na mensagem.

Níveis

  • trace: detalhes muito granulares;
  • debug: diagnóstico de desenvolvimento;
  • info: eventos normais importantes;
  • warn: situação inesperada recuperável;
  • error: operação falhou;
  • fatal: processo não pode continuar.

Produção costuma usar info ou warn. Alterar para debug aumenta volume e custo.

Não use error para tudo

Uma resposta 404 esperada não é necessariamente erro operacional. Níveis incorretos geram alertas inúteis e escondem incidentes reais.

Erros JavaScript

try {
  await processPayment();
} catch (error) {
  logger.error({ err: error }, 'Falha no pagamento');
  throw error;
}

Usar a chave err ativa a serialização padrão de Error, incluindo mensagem, tipo e stack.

Erro como mensagem

logger.error(error);

Embora possa funcionar, o formato explícito com { err: error } permite adicionar contexto sem perder a stack.

Serializers

const logger = pino({
  serializers: {
    user(value) {
      return {
        id: value.id,
        role: value.role
      };
    }
  }
});

Serializers controlam campos, mas não devem executar consultas ou trabalho pesado.

Redaction

const logger = pino({
  redact: {
    paths: [
      'password',
      'token',
      'req.headers.authorization',
      'config.databaseUrl'
    ],
    censor: '[REDACTED]'
  }
});

Redaction precisa cobrir caminhos reais. Um objeto aninhado em formato diferente pode escapar.

Não registre corpos completos

Corpos podem conter senha, cartão, token, documento e dados pessoais. Prefira campos selecionados e IDs internos.

Child logger

const requestLogger = logger.child({
  requestId,
  route: '/api/orders',
  method: 'POST'
});

requestLogger.info('Requisição iniciada');

O child adiciona contexto a todas as mensagens sem repetir argumentos.

Logger por módulo

const paymentLogger = logger.child({
  component: 'payment-service'
});

Use nomes estáveis para facilitar filtros.

Request ID

Aceite um ID externo apenas se formato e tamanho forem válidos; caso contrário, gere outro:

const requestId = isValidRequestId(header)
  ? header
  : crypto.randomUUID();

Envie o valor na resposta e para serviços downstream.

AsyncLocalStorage

const { AsyncLocalStorage } = require('node:async_hooks');
const storage = new AsyncLocalStorage();

function getLogger() {
  const context = storage.getStore();
  return context?.logger || logger;
}

Na entrada HTTP:

storage.run({
  logger: logger.child({ requestId })
}, () => next());

Funções internas usam getLogger() sem encaminhar requestId manualmente.

Integração com Fastify

Fastify possui integração com Pino:

const fastify = require('fastify')({
  logger: {
    level: process.env.LOG_LEVEL || 'info',
    redact: ['req.headers.authorization']
  }
});

Consulte Fastify com Node.js.

Integração com Express

O pacote pino-http cria logs de requisição:

const pinoHttp = require('pino-http');

app.use(pinoHttp({
  logger,
  genReqId(req, res) {
    const id = crypto.randomUUID();
    res.setHeader('x-request-id', id);
    return id;
  }
}));

Rotas de saúde

Health checks frequentes podem gerar ruído. Configure autoLogging para ignorar:

autoLogging: {
  ignore: req => req.url.startsWith('/health')
}

Consulte Health Checks no Node.js.

Timestamp

Pino usa timestamp numérico por padrão. Para ISO:

const logger = pino({
  timestamp: pino.stdTimeFunctions.isoTime
});

JSON numérico é mais compacto; a plataforma pode converter na visualização.

Base fields

const logger = pino({
  base: {
    service: 'orders-api',
    version: process.env.APP_VERSION,
    environment: process.env.NODE_ENV
  }
});

Não inclua hostname como label de alta cardinalidade se a plataforma já adiciona metadados do container.

Pretty printing

node app.js | pino-pretty

Pretty printing é para desenvolvimento. Em produção, mantenha JSON e formate na ferramenta de observabilidade.

Transport

const transport = pino.transport({
  target: 'pino-pretty',
  options: {
    colorize: true
  }
});

const logger = pino(transport);

Transports podem executar em worker thread. Valide shutdown para não perder mensagens pendentes.

stdout versus arquivo

Em containers e systemd, escreva para stdout. A plataforma cuida de rotação e envio.

Veja systemd com Node.js e Docker Multi-stage para Node.js.

Destino de arquivo

const destination = pino.destination({
  dest: '/var/log/my-api/app.log',
  sync: false
});

Se usar arquivos, configure permissões, rotação e espaço em disco. Não grave na camada efêmera do container sem plano.

Logging síncrono

Saída síncrona aumenta segurança em eventos finais, mas pode bloquear o processo. Use com critério.

Fatal e shutdown

process.on('uncaughtException', error => {
  logger.fatal({ err: error }, 'Exceção não capturada');
  process.exitCode = 1;
});

Depois de uncaughtException, inicie shutdown e não continue atendendo normalmente.

Unhandled rejection

process.on('unhandledRejection', reason => {
  logger.fatal({ err: reason }, 'Promise rejeitada');
  process.exitCode = 1;
});

Flush

Antes de saída forçada, aguarde o destino quando a API usada permitir. Não chame process.exit() imediatamente após logger.fatal.

Graceful shutdown

Pare requisições, conclua operações e finalize transport. Consulte Graceful Shutdown no Node.js.

Logs e OpenTelemetry

Inclua traceId e spanId quando houver contexto:

logger.info({
  traceId,
  spanId,
  orderId
}, 'Pedido processado');

Veja OpenTelemetry no Node.js.

Cardinalidade

Logs aceitam IDs, mas índices de plataforma podem ficar caros. Configure quais campos são indexados e retenção por nível.

Amostragem

Em endpoints muito movimentados, registre todos os erros e uma amostra de sucessos. Não use amostragem que esconda auditoria obrigatória.

Log de auditoria

Auditoria possui requisitos diferentes de log operacional. Registre ator, ação, recurso e resultado em armazenamento protegido, sem incluir segredos.

Performance

Evite construir objetos caros quando o nível está desabilitado:

if (logger.isLevelEnabled('debug')) {
  logger.debug({ detail: buildExpensiveDetail() });
}

Serialização circular

Não envie objetos enormes ou com referências circulares sem serializer. Selecione propriedades relevantes.

Testes

Crie um destino em memória e analise cada linha JSON. Verifique:

  • nível;
  • mensagem;
  • requestId;
  • erro e stack;
  • redaction;
  • campos base;
  • ausência de senha;
  • health check ignorado;
  • shutdown;
  • volume de logs.

Teste de redaction

test('remove authorization', () => {
  logger.info({
    req: {
      headers: {
        authorization: 'Bearer secret'
      }
    }
  });

  assert.equal(output.includes('secret'), false);
});

Erros comuns

  • Pretty em produção: CPU e parsing aumentam.
  • Corpo completo: dados pessoais vazam.
  • Error sem err: stack pode ser perdida.
  • Nível debug permanente: custo explode.
  • Sem requestId: eventos não são correlacionados.
  • Arquivo sem rotação: disco enche.
  • process.exit imediato: logs finais são perdidos.

Boas práticas

  • Use JSON em produção.
  • Defina níveis corretos.
  • Adicione contexto com child logger.
  • Use request ID.
  • Configure redaction.
  • Registre erros com err.
  • Envie para stdout.
  • Filtre health checks.
  • Controle volume e retenção.
  • Teste ausência de segredos.

Conclusão

Usar logs com Pino no Node.js oferece saída estruturada e eficiente, adequada a aplicações de alto volume. Child loggers, request IDs e serializers transformam eventos isolados em uma narrativa pesquisável.

A qualidade depende da disciplina: níveis corretos, redaction, campos estáveis e nenhum dado sensível. Com JSON em stdout, coleta externa e correlação com traces, Pino fornece observabilidade sem transformar logging em gargalo do event loop.

Os 10 Melhores Cursos de Programação de 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita