Registrar cada llamada API sin ralentizar el servidor

Resumen: Cómo registrar cada llamada a tu API sin degradar el rendimiento. Enfoque práctico: logging asíncrono, buffering, offload a workers, enmascarado de datos y rotación.

Introducción

Registrar cada petición API es crítico para depuración, auditoría y análisis, pero hacerlo mal puede convertir el logging en el principal cuello de botella.

La regla de oro: nunca hagas que el manejador principal espere a que los logs se escriban. El registro debe ser asíncrono y, preferiblemente, salir del camino de ejecución principal.

Prerrequisitos

Los ejemplos usan Node.js y bibliotecas comunes; adapta las ideas a otros runtimes si es necesario.

  • Dependencia de un logger de alto rendimiento (p. ej. Pino).
  • Mecanismo para buffering o una cola (memoria, Redis, Kafka).
  • Worker o servicio externo para persistencia y envío a sistemas centralizados.

Desarrollo

Procedimiento

Paso 1 — Decide qué registrar: evita sobre-logging. Campos típicos: timestamp, método, ruta, status, latencia, IP, userId, requestId y payload con datos sensibles enmascarados.

{
  "time": "2025-08-11T14:23:45.123Z",
  "method": "POST",
  "url": "/api/orders",
  "status": 201,
  "responseTimeMs": 123,
  "userId": "usr_1a2b3c",
  "ip": "203.0.113.42",
  "userAgent": "Mozilla/5.0 (Macintosh...)",
  "requestId": "req_abcd1234"
}
Lenguaje del código: JSON / JSON con comentarios (json)

Paso 2 — Usa un logger no bloqueante. Olvida console.log en producción; emplea un logger diseñado para escritura asíncrona y serialización rápida.

const pino = require('pino');
const logger = pino({
  level: 'info',
  transport: {
    target: 'pino-pretty'
  }
});

app.use((req, res, next) => {
  const start = Date.now();
  
  res.on('finish', () => {
    logger.info({
      method: req.method,
      url: req.originalUrl,
      status: res.statusCode,
      responseTime: Date.now() - start
    });
  });
  
  next();
});
Lenguaje del código: JavaScript (javascript)

Paso 3 — Bufferiza en memoria y envía en lotes. Convierte miles de escrituras pequeñas en pocas escrituras grandes.

let logBuffer = [];
const BUFFER_SIZE = 50;
const FLUSH_INTERVAL = 5000; // ms

function flushLogs() {
  if (logBuffer.length > 0) {
    logger.info({ batch: logBuffer });
    logBuffer = [];
  }
}

setInterval(flushLogs, FLUSH_INTERVAL);

app.use((req, res, next) => {
  const start = Date.now();

  res.on('finish', () => {
    logBuffer.push({
      method: req.method,
      url: req.originalUrl,
      status: res.statusCode,
      time: Date.now(),
      latency: Date.now() - start
    });

    if (logBuffer.length >= BUFFER_SIZE) {
      flushLogs();
    }
  });

  next();
});
Lenguaje del código: JavaScript (javascript)

Paso 4 — Desacopla mediante cola y workers: para tráfico intenso, envía logs a una cola y procesa desde procesos independientes.

const Redis = require('ioredis');
const pub = new Redis();

app.use((req, res, next) => {
  const start = Date.now();

  res.on('finish', () => {
    pub.publish('api_logs', JSON.stringify({
      method: req.method,
      url: req.originalUrl,
      status: res.statusCode,
      latency: Date.now() - start
    }));
  });

  next();
});
Lenguaje del código: JavaScript (javascript)
const sub = new Redis();
sub.subscribe('api_logs');
sub.on('message', (channel, message) => {
  const logData = JSON.parse(message);
  // Persistir logData de forma asíncrona (BD, ELK, S3...)
});
Lenguaje del código: JavaScript (javascript)

Paso 5 — Envío remoto asíncrono: si mandas logs a sistemas como ELK o CloudWatch, hazlo en lotes o por workers para evitar latencia en el request path.

const winston = require('winston');
require('winston-cloudwatch');

winston.add(new winston.transports.CloudWatch({
  logGroupName: 'my-api-logs',
  logStreamName: 'production',
  awsRegion: 'us-east-1',
  jsonMessage: true
}));

app.use((req, res, next) => {
  const start = Date.now();
  res.on('finish', () => {
    winston.info({
      method: req.method,
      url: req.originalUrl,
      status: res.statusCode,
      latency: Date.now() - start
    });
  });
  next();
});
Lenguaje del código: JavaScript (javascript)

Paso 6 — Enmascara datos sensibles antes de loguear: nunca escribas contraseñas, números de tarjeta o tokens sin protección.

function maskSensitive(obj) {
  const clone = { ...obj };
  if (clone.password) clone.password = '******';
  if (clone.cardNumber) clone.cardNumber = '**** **** **** ' + clone.cardNumber.slice(-4);
  return clone;
}

app.use(express.json());
app.use((req, res, next) => {
  req.body = maskSensitive(req.body);
  next();
});
Lenguaje del código: JavaScript (javascript)

Paso 7 — Mide y ajusta: compara latencias y uso de CPU/memoria antes y después del cambio. Usa herramientas de carga y APM para validar impacto.

autocannon -c 100 -d 30 http://localhost:3000
Lenguaje del código: Bash (bash)

Paso 8 — Rotación y archivo: configura rotación por tamaño o día y mueve logs antiguos a almacenamiento económico.

pino server.js | tee >(pino-pretty) | rotatelogs ./logs/api-%Y-%m-%d.log 86400
Lenguaje del código: Bash (bash)

Paso 9 — Distribuye a gran escala: evita escribir localmente, usa shippers (Filebeat/Fluent Bit), Kafka para ingesta masiva y sampling para reducir volumen de success logs.

Paso 10 — Arquitectura de producción (resumen): gateway → collectors/colas → workers → almacenamiento indexable → dashboards y alertas.

Ejemplos

Los ejemplos anteriores cubren patrones comunes: logger rápido (Pino), buffering en proceso, publicación a Redis y consumo por workers, y envío asíncrono a servicios remotos.

Checklist

  1. No bloquear el request path: logging asíncrono.
  2. Usar un logger de alto rendimiento (p. ej. Pino).
  3. Bufferizar y enviar en lotes.
  4. Offload a workers o colas para tráfico intenso.
  5. Enmascarar datos sensibles antes de persistir.
  6. Rotación y archivado de logs.
  7. Medir impacto y ajustar frecuencias/ tamaños de lote.

Conclusión

Registrar cada llamada API es viable si se diseña para no bloquear el flujo principal: emplea loggers rápidos, buffering, colas y workers; enmascara datos; y monitoriza el costo del logging.

Aplica estas prácticas progresivamente y verifica con pruebas de carga y APM antes de desplegar en producción.

Comments

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *