Reglas de Registro y Observabilidad
Los registros estructurados y los IDs de correlación hacen que los servicios de Node sean depurables en producción sin SSH y sin buscar cadenas no estructuradas.
Receta
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
import pino from "pino";
import { randomUUID } from "node:crypto";
export const logger = pino({ level: process.env.LOG_LEVEL ?? "info" });
app.use((req, res, next) => {
const requestId = req.headers["x-request-id"]?.toString() ?? randomUUID();
res.setHeader("X-Request-Id", requestId);
req.log = logger.child({ requestId });
next();
});Cuándo usarlo:
- Cada servicio HTTP de producción y trabajador en segundo plano.
- Las llamadas entre servicios necesitan cadenas de solicitud rastreables.
- El cumplimiento requiere pistas de auditoría sin fugas de PII.
Ejemplo de trabajo
// src/logging/logger.ts
import pino from "pino";
export const rootLogger = pino({
level: process.env.LOG_LEVEL ?? "info",
redact: ["req.headers.authorization", "password", "creditCard"],
});
export type AppLogger = pino.Logger;// src/middleware/request-context.ts
import { randomUUID } from "node:crypto";
import type { Request, Response, NextFunction } from "express";
import { rootLogger } from "../logging/logger.js";
declare global {
namespace Express {
interface Request {
log: import("pino").Logger;
requestId: string;
}
}
}
export function requestContext(req: Request, res: Response, next: NextFunction) {
const requestId = (req.headers["x-request-id"] as string) ?? randomUUID();
req.requestId = requestId;
res.setHeader("X-Request-Id", requestId);
req.log = rootLogger.child({ requestId, method: req.method, path: req.path });
req.log.info("request started");
res.on("finish", () => req.log.info({ status: res.statusCode }, "request finished"));
next();
}// src/services/billing.ts
export async function charge(log: AppLogger, customerId: string, cents: number) {
log.info({ customerId, cents }, "charge started");
// ...
log.info({ customerId }, "charge completed");
}Lo que esto demuestra:
- Registros JSON a través de Pino con redacción de campos.
- Se respeta el
X-Request-Identrante; se genera si está ausente. - El logger hijo lleva el
requestIda través de la capa de servicio.
Análisis profundo
Cómo funciona
- Los registros estructurados son líneas JSON que pueden ser ingeridas por Loki, CloudWatch, Datadog.
- El ID de correlación se propaga a HTTP saliente a través del encabezado en los clientes internos.
- Niveles de registro:
errorpara fallos que necesitan acción,warnpara degradados,infopara el ciclo de vida de la solicitud,debugsolo para desarrollo. - Las métricas (histograma de duración de la solicitud, tasa de error) complementan los registros para los SLO.
Campos requeridos
| Campo | Origen |
|---|---|
requestId | Encabezado o UUID |
level | Pino predeterminado |
msg | Evento legible por humanos |
time | Marca de tiempo ISO automática |
service | name en la configuración del logger |
Notas de TypeScript
- Aumenta el tipo
req.logpara Express; Fastify usareq.logincorporado con integraciónpino. - Nunca registres el
req.bodycompleto; registra solo los IDs y los nombres de las acciones.
Errores comunes
- console.log en producción - No estructurado, sin niveles, sin redacción. Solución: ESLint
no-consoleensrc/. - Correlación faltante en trabajadores - Trabajos en cola no rastreables. Solución: pasa
requestIden la carga útil del trabajo desde el manejador HTTP. - Registro de PII - Violaciones de GDPR. Solución: redacta rutas; hashea los IDs de usuario en los registros si es necesario.
- Registros de depuración siempre activados - Costo y ruido en producción. Solución:
LOG_LEVEL=infopor defecto; depura mediante un interruptor dinámico con caducidad. - Registro dentro de bucles cerrados - Picos de volumen de registro. Solución: agrega o muestrea; registra recuentos de resumen.
Alternativas
| Alternativa | Usar cuándo | No usar cuándo |
|---|---|---|
| Rastros de OpenTelemetry | Trazado distribuido completo | MVP de servicio único pequeño |
| Winston | Estándar heredado del equipo | Nuevo proyecto (Pino es más rápido en Node) |
| Contexto de AsyncLocalStorage | requestId implícito sin req.log | Aplicaciones Express simples (el middleware es suficiente) |
Preguntas frecuentes
¿Es Pino obligatorio?
No, pero el registro estructurado JSON es obligatorio. Pino es la opción común de Node 24; Fastify se integra de forma nativa.
¿Cómo propagar el requestId saliente?
await fetch(url, { headers: { "X-Request-Id": requestId } });¿Qué pasa con NestJS?
Usa nestjs-pino o OTel; se aplican las mismas reglas de encabezado de correlación.
¿Deben los errores incluir rastros de pila?
Sí, en los registros de error del lado del servidor; nunca devuelvas rastros de pila a los clientes de la API en producción.
¿Muestreo de registros?
Muestra la depuración/información en comprobaciones de estado de alto RPS; nunca muestrees los registros de errores.
¿Formato de registro del trabajador?
El mismo esquema JSON con los campos jobId y requestId para la unión de rastros.
¿Métricas vs registros?
Métricas para paneles de SLO; registros para depuración de incidentes. Ambos son necesarios para operaciones maduras.
¿Cuánto tiempo retener los registros?
Política de la organización; típicamente 30-90 días en caliente, archivo frío más largo para dominios de auditoría.
¿Solo stdout?
Sí, en Kubernetes, los recolectores extraen stdout; no hay rotación de archivos en el contenedor.
¿Registro de comprobación de estado?
No registres cada /health en info en producción; usa depuración o excluye la ruta en el middleware.
Relacionado
- Lista de verificación de reglas del proyecto Node - reglas 14-15
- Reglas de seguridad - superposición de redacción de PII
- Reglas de API - respuesta de error vs detalle del registro
Versiones de la pila: Esta página fue escrita para Node.js 24.18.0 (LTS activa), npm 10+, TypeScript 5.6+, Express 5, Fastify 5 y NestJS 11.