Back

Registro de Peticiones con Middleware en Node.js

Registro de Peticiones con Middleware en Node.js

Cuando algo falla en tu API a las 2 de la madrugada, lo primero que buscas son tus logs. Si están incompletos, ausentes o enterrados en ruido, la depuración se convierte en adivinanza. El registro de peticiones HTTP en Node.js es uno de esos fundamentos que es fácil hacer mal—y costoso ignorar.

Este artículo cubre cómo funciona el middleware de registro de peticiones en Express, cuándo usar bibliotecas establecidas como Morgan o Pino, y cómo se ve realmente el registro listo para producción.

Puntos Clave

  • El middleware de registro en Express intercepta las peticiones temprano en la cadena y escucha el evento finish de res para capturar datos de respuesta como códigos de estado y duración.
  • Morgan proporciona logs de acceso legibles y rápidos adecuados para desarrollo, mientras que Pino ofrece salida JSON estructurada y rápida diseñada para entornos de producción.
  • Usa AsyncLocalStorage en Node.js moderno para propagar IDs de correlación a través de operaciones asíncronas sin tener que pasarlos manualmente por cada llamada de función.
  • Nunca registres datos sensibles como encabezados Authorization, cookies o cuerpos de petición por defecto—usa redacción incorporada o sanitiza manualmente.

Cómo Encaja el Middleware de Registro en el Ciclo de Vida de Peticiones de Express

En Express, un middleware es una función con la firma (req, res, next). Intercepta cada petición antes de que llegue a tu manejador de ruta. El middleware de registro se sitúa en la parte superior de esa cadena, registrando lo que entró y lo que salió.

[Cliente] → [Middleware de Registro] → [Middleware de Auth] → [Manejador de Ruta] → [Respuesta]

La clave: no puedes registrar la respuesta completa—código de estado, duración—hasta que la respuesta esté finalizada. Por eso el middleware de registro escucha el evento finish de res en lugar de registrar inmediatamente.

import crypto from 'node:crypto'

// Middleware de Express para registro de peticiones
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)

Nota que crypto.randomUUID() requiere importar el módulo node:crypto (o usar el crypto global disponible en Node.js 19+). Además, el objeto logger aquí es un marcador de posición—lo reemplazarías con tu instancia real de biblioteca de registro (como Pino o console).

Este patrón funciona en cualquier servidor HTTP de Node.js, no solo en Express.

Registro de Acceso Tradicional con Morgan

Morgan es el middleware clásico de registro de peticiones para Express. Con dos líneas tienes logs de acceso estilo Apache:

import morgan from 'morgan'

app.use(morgan('combined'))
// Ejemplo de salida:
// ::1 - - [01/Jan/2025:00:00:00 +0000] "GET /api/users HTTP/1.1" 200 1234

Morgan está bien para desarrollo y despliegues simples. Su salida es legible para humanos pero no fácilmente procesable por máquinas—lo cual se convierte en un problema cuando estás enviando logs a Datadog, Loki o cualquier sistema de logs estructurados.

Registro Estructurado en Node.js con Pino

Para producción, registro estructurado significa emitir JSON. Cada línea de log se convierte en un registro consultable. Pino es la opción estándar aquí—es significativamente más rápido que Winston o Bunyan, con sobrecarga mínima.

El paquete pino-http se integra como middleware de 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 escribe a stdout. Tu infraestructura (Docker, systemd, un recolector de logs) se encarga de enrutar esas líneas a donde sea necesario.

IDs de Correlación y Contexto de Petición

Cuando estás rastreando una petición a través de múltiples operaciones asíncronas, necesitas un ID de petición consistente adjunto a cada línea de log. En Node.js moderno, usa AsyncLocalStorage para esto—no el módulo domain obsoleto ni hooks asíncronos de bajo nivel.

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)
})

Cualquier llamada de logger dentro del contexto asíncrono de esa petición ahora puede obtener el requestId sin tener que pasarlo manualmente a través de cada función. Así es como lo recuperarías:

// En cualquier módulo descendiente
import { requestContext } from './context.js'

function doWork() {
  const { requestId } = requestContext.getStore()
  logger.info({ requestId, msg: 'doing work' })
}

Registro Responsable

Algunas reglas que importan en producción:

  • Nunca registres encabezados Authorization, cookies o tokens de API. Usa la opción redact de Pino o sanitiza manualmente antes de escribir.
  • Ten cuidado con las IPs de cliente detrás de proxies. req.socket.remoteAddress te dará la IP del proxy. Si tu aplicación está detrás de un proxy inverso, configura correctamente la configuración trust proxy de Express y trata los encabezados X-Forwarded-For con cuidado.
  • No registres cuerpos de petición por defecto. Pueden ser grandes, binarios o contener información personal identificable (PII). Regístralos selectivamente, con límites de tamaño.

Elegir Entre Morgan y Pino

MorganPino (pino-http)
Formato de salidaTexto (estilo Apache)JSON (estructurado)
RendimientoBuenoExcelente
Redacción de logsManualIncorporada
Listo para producciónLimitado
Tiempo de configuración~2 min~5 min

Usa Morgan para desarrollo local si prefieres salida legible. Usa Pino para cualquier cosa que se despliegue a producción.

Conclusión

Un buen middleware de registro es invisible hasta que lo necesitas—y entonces lo es todo. Comienza con pino-http, emite JSON estructurado a stdout, y deja que tu infraestructura maneje el enrutamiento y almacenamiento. Combínalo con IDs de correlación mediante AsyncLocalStorage para que puedas rastrear cualquier petición de principio a fin. Mantén los datos sensibles fuera de tus logs desde el primer día, y tendrás una base de observabilidad que escala con tu aplicación.

Preguntas Frecuentes

Sí, puedes ejecutar ambos lado a lado. Un patrón común es usar Morgan en desarrollo para salida de consola legible y Pino en producción para logs JSON estructurados. Usa una variable de entorno para aplicar condicionalmente uno u otro para evitar entradas de log duplicadas en cualquier entorno individual.

Pino escribe JSON a stdout por diseño. Usa un recolector de logs como Fluent Bit, Filebeat o el Datadog Agent para recopilar la salida stdout de tu contenedor o proceso y reenviarla a tu plataforma de registro. Esto mantiene el código de tu aplicación desacoplado de cualquier destino de log específico.

AsyncLocalStorage propaga el contexto automáticamente a través de toda la cadena de llamadas asíncronas sin modificar las firmas de función. Pasar manualmente un ID de petición a través de cada función es propenso a errores y desordena tu código. AsyncLocalStorage es estable en Node.js 16 y superior y es el enfoque recomendado para contexto con alcance de petición.

Con Pino, la sobrecarga es típicamente insignificante porque usa serialización rápida y escribe logs eficientemente a stdout. Morgan también es ligero para la mayoría de las cargas de trabajo. El patrón de listener del evento `finish` significa que el registro se ejecuta después de que la respuesta ha sido entregada al sistema operativo, por lo que generalmente no afecta la latencia del cliente. Evita escrituras de archivo síncronas o serialización pesada en tu ruta 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.

OpenReplay