Buenas prácticas de logging para sistemas en producción
Logging estructurado, niveles de log y correlation IDs: los patrones que convierten una salida ruidosa en observabilidad buscable y accionable.

La mayoría del logging en producción cae en dos categorías: demasiado ruido para ser útil, o muy poca información para depurar nada. Un buen logging es una decisión de diseño, no algo que se piensa al final. Requiere elegir el formato correcto, el nivel correcto y el contexto correcto para cada declaración de log.
Logs estructurados en vez de texto plano
Los logs de texto plano son legibles para humanos en una terminal e inútiles en cualquier otro lugar. Las herramientas de agregación de logs como Datadog, Elastic y CloudWatch necesitan datos estructurados para filtrar, buscar y generar alertas.
// ❌ Plain text — impossible to parse or filter at scale
console.log(`User ${userId} placed order ${orderId} for $${total}`);
// "User cust_123 placed order ord_456 for $99.50"
// ✅ Structured JSON — every field is searchable
logger.info("Order placed", {
userId: "cust_123",
orderId: "ord_456",
total: 99.5,
currency: "USD",
itemCount: 3,
});
// {"level":"info","msg":"Order placed","userId":"cust_123","orderId":"ord_456","total":99.5,"currency":"USD","itemCount":3,"timestamp":"2020-02-19T14:30:00Z"}Con logs estructurados, puedes consultar "muéstrame todas las órdenes de más de $100 del usuario cust_123" sin acrobacias con regex.
Niveles de log que significan algo
La mayoría de los equipos usan los niveles de log de forma inconsistente. Define semánticas claras y hazlas cumplir en el code review.
| Nivel | Cuándo usarlo | Ejemplo |
|---|---|---|
error | Algo falló y necesita atención humana | Falló el cobro de un pago, se perdió la conexión a la base de datos |
warn | Algo inesperado pero manejado | Se acerca al rate limit, se llamó a una API obsoleta |
info | Eventos de negocio significativos | Orden realizada, usuario registrado, despliegue iniciado |
debug | Detalles internos para troubleshooting | Cache hit/miss, tiempos de query, estado intermedio |
// ❌ Wrong levels — error used for non-errors, info used for noise
logger.error("User not found"); // This is expected behavior, not an error
logger.info(`Cache key: ${key}`); // Debug-level detail flooding production
// ✅ Correct levels — each level has clear semantics
logger.warn("User not found", { userId, endpoint: "/api/profile" });
logger.debug("Cache lookup", { key, hit: false, latencyMs: 2 });En producción, configura el nivel mínimo en info. Habilita debug temporalmente cuando investigues un problema específico. Nunca dejes debug corriendo permanentemente — genera demasiado volumen y cuesta dinero real en almacenamiento de logs.
Correlation IDs para el trazado de solicitudes
Cuando una sola solicitud de usuario toca múltiples servicios, los correlation IDs conectan toda la cadena.
import { randomUUID } from "crypto";
function requestIdMiddleware(req: Request, res: Response, next: NextFunction) {
const requestId = req.headers["x-request-id"] ?? randomUUID();
// Attach to response for client debugging
res.setHeader("x-request-id", requestId);
// Attach to request context for downstream logging
req.requestId = requestId;
next();
}// Every log in the request lifecycle includes the same correlationId
function createRequestLogger(requestId: string) {
return {
info: (msg: string, data?: Record<string, unknown>) =>
logger.info(msg, { ...data, requestId }),
warn: (msg: string, data?: Record<string, unknown>) =>
logger.warn(msg, { ...data, requestId }),
error: (msg: string, data?: Record<string, unknown>) =>
logger.error(msg, { ...data, requestId }),
};
}
// In a route handler:
app.post("/api/orders", async (req, res) => {
const log = createRequestLogger(req.requestId);
log.info("Order creation started", { userId: req.userId });
const order = await createOrder(req.body, log);
log.info("Order created", { orderId: order.id });
await chargePayment(order, log);
log.info("Payment charged", { orderId: order.id });
res.json(order);
});Cuando algo falla, busca por requestId para ver la línea de tiempo completa de la solicitud a través de todos los servicios.
Qué loguear (y qué no)
Siempre loguear
- Inicio y fin de la solicitud (con duración)
- Eventos de negocio (orden realizada, pago procesado, acción de usuario)
- Errores y excepciones con stack traces completos
- Llamadas a APIs externas (con tiempo de respuesta y status)
Nunca loguear
- Contraseñas, tokens, API keys o secretos
- Números completos de tarjetas de crédito o SSNs
- Cuerpos de solicitud que contengan datos sensibles de usuario
- Solicitudes de health check (ahogan el tráfico real)
// ❌ Logging sensitive data
logger.info("User login", { email, password: req.body.password });
// ✅ Redact sensitive fields
logger.info("User login", { email, passwordProvided: !!req.body.password });Logging de errores con contexto
Un log de error sin contexto es casi inútil. Incluye todo lo necesario para reproducir el problema.
// ❌ Bare error message — who was affected? What were the inputs?
logger.error("Payment failed");
// ✅ Full context — actionable without opening a debugger
try {
await paymentGateway.charge(amount, paymentMethodId);
} catch (error) {
logger.error("Payment charge failed", {
userId,
orderId,
amount,
paymentMethodId,
gateway: "stripe",
errorCode: error.code,
errorMessage: error.message,
stack: error.stack,
});
throw error;
}El objetivo es que alguien leyendo el log a las 3 AM pueda entender qué pasó, quién se vio afectado y qué investigar — sin tener que desplegar nuevo logging.
Logging de rendimiento
Registra las operaciones lentas de forma proactiva. No esperes a que los usuarios reporten latencia.
function withTiming<T>(
name: string,
fn: () => Promise<T>,
log: Logger,
thresholdMs = 1000,
): Promise<T> {
const start = performance.now();
return fn().finally(() => {
const durationMs = Math.round(performance.now() - start);
if (durationMs > thresholdMs) {
log.warn("Slow operation detected", { operation: name, durationMs });
} else {
log.debug("Operation completed", { operation: name, durationMs });
}
});
}
// Usage
const users = await withTiming("getActiveUsers", () => getActiveUsers(), log);Este patrón expone las regresiones de rendimiento antes de que se conviertan en incidentes.
Puntos clave
- Usa logs JSON estructurados — el texto plano no es consultable a escala
- Define los niveles de log de forma consistente — error significa "necesita atención humana", no "pasó algo inesperado"
- Los correlation IDs conectan todo el ciclo de vida de una solicitud a través de los servicios
- Nunca loguees datos sensibles — contraseñas, tokens y PII son riesgos de seguridad en los almacenes de logs
- Incluye contexto completo en los logs de error — haz posible depurar a las 3 AM sin cambios de código
- Registra las operaciones lentas de forma proactiva — los umbrales de rendimiento detectan regresiones a tiempo


