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
- No bloquear el request path: logging asíncrono.
- Usar un logger de alto rendimiento (p. ej. Pino).
- Bufferizar y enviar en lotes.
- Offload a workers o colas para tráfico intenso.
- Enmascarar datos sensibles antes de persistir.
- Rotación y archivado de logs.
- 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.

Deja un comentario