Qué es “logging útil y mínimo” en una API
El objetivo del logging en una API no es “guardar todo”, sino dejar un rastro suficiente para responder rápido a preguntas típicas: ¿qué request falló?, ¿cuánto tardó?, ¿qué endpoint fue?, ¿con qué código respondió?, ¿qué error ocurrió?, ¿se repite el patrón?
Un logging útil y mínimo suele incluir:
- Contexto del request: método, ruta, status, duración, request id.
- Eventos relevantes: inicio/fin de request (opcional), llamadas a servicios externos, validaciones fallidas, decisiones importantes.
- Errores: stack trace, tipo de error, request id, status final.
console.log vs logger estructurado
console.log es rápido para depurar, pero en producción suele quedarse corto: no tiene niveles consistentes, no estructura los datos, y es difícil de filtrar/consultar (por ejemplo, en un agregador de logs).
Un logger estructurado (como pino o winston) escribe logs como objetos (normalmente JSON), con campos estables. Ventajas:
- Niveles (info, warn, error) para filtrar.
- Campos (requestId, route, statusCode, durationMs) para buscar y agrupar.
- Formato consistente para herramientas de observabilidad.
Implementar un logger básico con Pino (recomendado)
Pino es ligero y rápido, ideal para APIs. La idea será:
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
- Crear un logger base.
- Generar un request id por request.
- Adjuntar un logger “hijo” al request con ese request id.
- Loggear el ciclo del request y los errores centralizados.
Paso 1: crear el logger
Crea un archivo src/logger.js:
const pino = require('pino'); const logger = pino({ level: process.env.LOG_LEVEL || 'info', base: null, timestamp: () => `,"time":"${new Date().toISOString()}"` }); module.exports = logger;Notas:
levelcontrola qué se imprime (por ejemplo, en producción podrías usarinfoowarn).base: nullevita campos automáticos que a veces no necesitas (puedes quitarlo si prefieres).
Paso 2: middleware de request id y logger por request
Crea src/middlewares/requestLogger.js:
const crypto = require('crypto'); const logger = require('../logger'); function requestLogger(req, res, next) { const requestId = crypto.randomUUID ? crypto.randomUUID() : crypto.randomBytes(16).toString('hex'); req.requestId = requestId; res.setHeader('x-request-id', requestId); const log = logger.child({ requestId }); req.log = log; const start = process.hrtime.bigint(); res.on('finish', () => { const end = process.hrtime.bigint(); const durationMs = Number(end - start) / 1e6; const level = res.statusCode >= 500 ? 'error' : res.statusCode >= 400 ? 'warn' : 'info'; log[level]({ method: req.method, path: req.originalUrl, statusCode: res.statusCode, durationMs: Math.round(durationMs) }, 'request completed'); }); next(); } module.exports = requestLogger;Qué hace este middleware:
- Genera un requestId y lo expone en la respuesta (
x-request-id) para que el cliente lo reporte si hay problemas. - Crea
req.logcomo logger hijo con{ requestId }. - Registra al finalizar el request un log con método, ruta, status y duración.
- El nivel se ajusta automáticamente: 2xx/3xx => info, 4xx => warn, 5xx => error.
Paso 3: integrar el middleware en Express
En tu archivo principal (por ejemplo src/app.js o donde configures Express), registra el middleware lo más arriba posible (antes de rutas):
const express = require('express'); const requestLogger = require('./middlewares/requestLogger'); const app = express(); app.use(express.json()); app.use(requestLogger); // ... aquí van tus rutas module.exports = app;Loggear eventos dentro de controladores (sin ruido)
Una vez que existe req.log, úsalo para eventos puntuales. Evita loggear “cada paso” si no aporta valor.
Ejemplo en un controlador:
async function createOrder(req, res, next) { try { req.log.info({ userId: req.user?.id }, 'creating order'); // ... lógica de creación res.status(201).json({ ok: true }); } catch (err) { next(err); } }Recomendación: loggea identificadores (userId, orderId) y no objetos completos si pueden incluir datos sensibles.
Registrar errores en el manejador centralizado
En el manejador de errores, registra el error usando el logger del request si existe (req.log), para que el log quede correlacionado con el requestId.
Ejemplo de error handler:
function errorHandler(err, req, res, next) { const statusCode = err.statusCode || 500; const log = req.log || require('./logger'); log.error({ err: { name: err.name, message: err.message, stack: err.stack }, method: req.method, path: req.originalUrl, statusCode }, 'request failed'); res.status(statusCode).json({ error: { message: statusCode === 500 ? 'Internal Server Error' : err.message, requestId: req.requestId } }); } module.exports = errorHandler;Detalles importantes:
- Se loggea el stack trace en servidor, pero al cliente se le devuelve un mensaje controlado.
- Se incluye
requestIden la respuesta para soporte.
Alternativa: Winston (cuando necesitas múltiples transports)
Si necesitas enviar logs a archivo, rotación, o múltiples destinos, Winston es común. Un setup mínimo con niveles:
const winston = require('winston'); const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: winston.format.json(), transports: [new winston.transports.Console()] }); module.exports = logger;La correlación por request se puede hacer de forma similar creando un “child logger” (con logger.child en versiones/formatos compatibles) o adjuntando requestId manualmente en cada llamada. En APIs, Pino suele ser más directo para este patrón.
Qué NO loggear: datos sensibles y cómo prevenirlo
Un error común es loggear el body completo o headers completos. Eso puede filtrar credenciales o información personal. Reglas prácticas:
- No loggear contraseñas, tokens, cookies, claves API, secretos, números de tarjeta, documentos de identidad.
- No loggear el header
AuthorizationniCookie. - Evitar loggear
req.bodycompleto; si necesitas contexto, loggea solo campos no sensibles o un resumen. - Redactar (masking) campos sensibles si por necesidad deben aparecer.
Ejemplo de “redacción” simple antes de loggear
Si vas a registrar parte del body, crea una función que elimine/oculte campos:
function redactBody(body) { if (!body || typeof body !== 'object') return body; const copy = { ...body }; const sensitiveKeys = ['password', 'token', 'accessToken', 'refreshToken']; for (const key of sensitiveKeys) { if (key in copy) copy[key] = '[REDACTED]'; } return copy; } // Uso: req.log.info({ body: redactBody(req.body) }, 'payload received');Mejor aún: define una lista blanca de campos permitidos para logging (allowlist) en lugar de intentar adivinar todos los sensibles.
Checklist rápido para un logging sano en APIs
| Objetivo | Qué registrar | Qué evitar |
|---|---|---|
| Correlación | requestId en todos los logs | Logs sin contexto |
| Rendimiento | durationMs, statusCode | Loggear cada detalle interno |
| Errores | name, message, stack, status | Enviar stack al cliente |
| Privacidad | IDs y metadatos | Authorization, cookies, passwords |