Registro de Requisições com Middleware em Node.js
Quando algo quebra na sua API às 2 da manhã, a primeira coisa que você busca são os seus logs. Se eles estiverem ausentes, incompletos ou enterrados em ruído, a depuração se torna adivinhação. O registro de requisições HTTP em Node.js é um daqueles fundamentos que é fácil errar—e caro ignorar.
Este artigo aborda como o middleware de registro de requisições funciona no Express, quando usar bibliotecas estabelecidas como Morgan ou Pino, e como é o registro pronto para produção na prática.
Principais Conclusões
- O middleware de registro no Express intercepta requisições no início da cadeia e escuta o evento
finishdorespara capturar dados de resposta como códigos de status e duração. - Morgan fornece logs de acesso legíveis e rápidos, adequados para desenvolvimento, enquanto Pino oferece saída JSON estruturada e rápida, construída para ambientes de produção.
- Use
AsyncLocalStorageno Node.js moderno para propagar IDs de correlação através de operações assíncronas sem passá-los manualmente por cada chamada de função. - Nunca registre dados sensíveis como headers
Authorization, cookies ou corpos de requisição por padrão—use redação integrada ou sanitize manualmente.
Como o Middleware de Registro se Encaixa no Ciclo de Vida de Requisições do Express
No Express, middleware é uma função com a assinatura (req, res, next). Ela intercepta cada requisição antes de chegar ao seu manipulador de rota. O middleware de registro fica no topo dessa cadeia, registrando o que entrou e o que saiu.
[Cliente] → [Middleware de Registro] → [Middleware de Autenticação] → [Manipulador de Rota] → [Resposta]
O insight chave: você não pode registrar a resposta completa—código de status, duração—até que a resposta esteja finalizada. É por isso que o middleware de registro escuta o evento finish do res em vez de registrar imediatamente.
import crypto from 'node:crypto'
// Middleware Express para registro de requisições
const logRequests = (req, res, next) => {
const start = Date.now()
res.on('finish', () => {
logger.info({
method: req.method,
url: req.url,
status: res.statusCode,
duration: Date.now() - start,
requestId: req.headers['x-request-id'] ?? crypto.randomUUID(),
})
})
next()
}
app.use(logRequests)
Note que crypto.randomUUID() requer a importação do módulo node:crypto (ou usar o crypto global disponível no Node.js 19+). Além disso, o objeto logger aqui é um placeholder—você o substituiria pela instância real da sua biblioteca de registro (como Pino ou console).
Este padrão funciona em qualquer servidor HTTP Node.js, não apenas no Express.
Registro de Acesso Tradicional com Morgan
Morgan é o middleware clássico de registro de requisições do Express. Duas linhas e você tem logs de acesso no estilo Apache:
import morgan from 'morgan'
app.use(morgan('combined'))
// Exemplo de saída:
// ::1 - - [01/Jan/2025:00:00:00 +0000] "GET /api/users HTTP/1.1" 200 1234
Morgan é adequado para desenvolvimento e implantações simples. Sua saída é legível para humanos, mas não facilmente analisável por máquinas—o que se torna um problema quando você está enviando logs para Datadog, Loki ou qualquer sistema de logs estruturado.
Registro Estruturado em Node.js com Pino
Para produção, registro estruturado significa emitir JSON. Cada linha de log se torna um registro consultável. Pino é a escolha padrão aqui—é significativamente mais rápido que Winston ou Bunyan, com sobrecarga mínima.
O pacote pino-http se integra como middleware do Express:
import express from 'express'
import pinoHttp from 'pino-http'
const app = express()
app.use(pinoHttp({
level: process.env.LOG_LEVEL ?? 'info',
redact: ['req.headers.authorization', 'req.headers.cookie'],
}))
app.get('/', (req, res) => {
req.log.info('handling root request')
res.send('ok')
})
Pino escreve para stdout. Sua infraestrutura (Docker, systemd, um expedidor de logs) cuida do roteamento dessas linhas para onde quer que precisem ir.
Discover how at OpenReplay.com.
IDs de Correlação e Contexto de Requisição
Quando você está rastreando uma requisição através de múltiplas operações assíncronas, você precisa de um ID de requisição consistente anexado a cada linha de log. No Node.js moderno, use AsyncLocalStorage para isso—não o módulo domain descontinuado ou async hooks de baixo nível.
import { AsyncLocalStorage } from 'node:async_hooks'
import crypto from 'node:crypto'
export const requestContext = new AsyncLocalStorage()
app.use((req, res, next) => {
const requestId = req.headers['x-request-id'] ?? crypto.randomUUID()
requestContext.run({ requestId }, next)
})
Qualquer chamada de logger dentro do contexto assíncrono dessa requisição agora pode obter o requestId sem passá-lo manualmente por cada função. Veja como você o recuperaria:
// Em qualquer módulo downstream
import { requestContext } from './context.js'
function doWork() {
const { requestId } = requestContext.getStore()
logger.info({ requestId, msg: 'doing work' })
}
Registrando de Forma Responsável
Algumas regras que importam em produção:
- Nunca registre headers
Authorization, cookies ou tokens de API. Use a opçãoredactdo Pino ou sanitize manualmente antes de escrever. - Tenha cuidado com IPs de clientes atrás de proxies.
req.socket.remoteAddressfornecerá o IP do proxy. Se sua aplicação está atrás de um proxy reverso, configure corretamente a configuraçãotrust proxydo Express e trate headersX-Forwarded-Forcom cuidado. - Não registre corpos de requisição por padrão. Eles podem ser grandes, binários ou conter PII. Registre-os seletivamente, com limites de tamanho.
Escolhendo Entre Morgan e Pino
| Morgan | Pino (pino-http) | |
|---|---|---|
| Formato de saída | Texto (estilo Apache) | JSON (estruturado) |
| Performance | Boa | Excelente |
| Redação de logs | Manual | Integrada |
| Pronto para produção | Limitado | Sim |
| Tempo de configuração | ~2 min | ~5 min |
Use Morgan para desenvolvimento local se você preferir saída legível. Use Pino para qualquer coisa que vá para produção.
Conclusão
Um bom middleware de registro é invisível até você precisar dele—e então é tudo. Comece com pino-http, emita JSON estruturado para stdout e deixe sua infraestrutura cuidar do roteamento e armazenamento. Combine-o com IDs de correlação via AsyncLocalStorage para que você possa rastrear qualquer requisição de ponta a ponta. Mantenha dados sensíveis fora dos seus logs desde o primeiro dia, e você terá uma base de observabilidade que escala com sua aplicação.
Perguntas Frequentes
Sim, você pode executar ambos lado a lado. Um padrão comum é usar Morgan em desenvolvimento para saída de console legível e Pino em produção para logs JSON estruturados. Use uma variável de ambiente para aplicar condicionalmente um ou outro, evitando assim entradas de log duplicadas em qualquer ambiente único.
Pino escreve JSON para stdout por design. Use um expedidor de logs como Fluent Bit, Filebeat ou o Datadog Agent para coletar a saída stdout do seu container ou processo e encaminhá-la para sua plataforma de registro. Isso mantém o código da sua aplicação desacoplado de qualquer destino de log específico.
AsyncLocalStorage propaga o contexto automaticamente através de toda a cadeia de chamadas assíncronas sem modificar assinaturas de função. Passar manualmente um ID de requisição por cada função é propenso a erros e polui seu código. AsyncLocalStorage é estável no Node.js 16 e superior e é a abordagem recomendada para contexto com escopo de requisição.
Com Pino, a sobrecarga é tipicamente negligenciável porque ele usa serialização rápida e escreve logs eficientemente para stdout. Morgan também é leve para a maioria das cargas de trabalho. O padrão de listener do evento `finish` significa que o registro é executado após a resposta ter sido entregue ao sistema operacional, então geralmente não afeta a latência do cliente. Evite escritas síncronas em arquivo ou serialização pesada no caminho de registro.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.