Observabilidad · 2026-09-04 · 6 min de lectura
La regla de drop que se comía los logs de error
Un cliente corre FlowFuse — una plataforma para gestionar instancias de Node-RED — sobre Kubernetes, en clusters de AWS y on-premise. Los flujos que corren dentro de esas instancias son lógica de producción: mueven datos, hablan con bases, alimentan otros sistemas. El pedido era razonable y chico: avisennos cuando los flujos tiren errores.
Entre esa oración y una alerta funcionando aparecieron tres capas de problemas, cada una escondiendo la siguiente. La alerta en sí fue la parte menos interesante.
Capa 1: los logs no existían
No se puede alertar sobre logs que nunca salen del contenedor. Primero tenían que entrar dos habilitadores:
logPassthroughen los values del Helm de FlowFuse, para que los logs de flujo de cada instancia Node-RED se escriban al stdout del contenedor en lugar de quedar internos a la plataforma.- Una regla de relabel en Alloy que deriva la label
nr_instancedel labelnamedel pod (custodiada por el labelnodered=truede la plataforma), para que Loki distinga instancias. Sin eso, los logs de todas las instancias caen en un stream indiferenciado y una alerta por flujo no puede decir cuál.
Pipeline completo: instancia → stdout → Alloy → Loki, consultable por instancia. O eso parecía.
Capa 2: los logs se estaban comiendo
La config de Alloy traía un stage de drop cuya intención era descartar los access logs ruidosos de la plataforma:
stage.drop {
expression = "INFO"
}
El stage de drop toma una expresión regular RE2 — y, como verificamos por las malas, matchea case-sensitive y sin anclar: un match parcial en cualquier parte de la línea descarta la línea entera. Así que esta regla no significa "descartar logs de nivel INFO". Significa "descartar cualquier línea que contenga el substring INFO en cualquier parte". Dos consecuencias verificadas:
- En los clusters de AWS descartaba la salida completa de la plataforma (toda línea lleva
"level":"INFO"), así que el namespace de la plataforma directamente no existía en Loki. Nadie lo había notado — ¿quién alerta sobre la ausencia de logs? - Peor: cualquier log de flujo cuyo mensaje contuviera "INFO" también se descartaba en silencio — incluidas líneas de nivel error que mencionaran, por ejemplo,
INFORMATION_SCHEMA. Un error de query contra el information schema de una base es exactamente la línea para la que estás construyendo alertas, y el pipeline la tiraba antes de que Loki la viera.
El fix es una línea — anclar la expresión a la key JSON que siempre quiso matchear:
stage.drop {
expression = "\"level\":\"INFO\""
}
La lección generaliza: un stage de drop es filtrado destructivo. Lo que descarta se pierde sin rastro, sin métrica, sin error. Cualquier match de substring sin anclar en una regla de drop es un bug de pérdida silenciosa de datos esperando la línea de log correcta — auditá qué matchea de verdad tu expresión, no qué quisiste que matchee.
Capa 3: el reinicio que GitOps no puede ver
Con la config mergeada y sincronizada, las instancias seguían sin emitir nada. El flag de passthrough queda horneado en el Deployment de cada instancia Node-RED como variable de entorno — y ese Deployment no lo gestiona ArgoCD. Lo crea dinámicamente el driver de Kubernetes de la plataforma. Un kubectl rollout restart no ayuda (misma env horneada), y GitOps no tiene ninguna palanca sobre el recurso: el único camino real es Suspend → Start desde el propio panel de la plataforma, que regenera el Deployment con el flag nuevo.
Esa frontera vale conocerla en cualquier setup de plataforma-sobre-Kubernetes: los recursos que crea el orquestador propio de la plataforma viven afuera de tu red de GitOps. Tu repo puede estar en verde mientras los workloads reales corren config vieja.
Nota de método sobre el cambio de config: todo se validó primero en vivo sobre el cluster de test, editando los recursos corriendo a mano — y después los values de Helm se escribieron para reproducir el ConfigMap validado byte a byte, de modo que el sync de ArgoCD fuera un no-op verificado y no un acto de fe. Rollback de todo el cambio: un solo git revert.
Después: medir antes de poner umbrales
Con los logs por fin fluyendo y completos, la tentación es escribir la regla de alerta. Primero medimos una línea de base de 24 horas — y reencuadró el pedido entero:
- Producción: 313 errores de flujo en 24 horas, y todos y cada uno eran el mismo error —
ECONNRESET, una conexión cortada desde el nodo cliente de MySQL de los flujos. No eran bugs de queries: era infraestructura. - El patrón: una base crónica de 1–14 errores por hora (promedio ~7), más un pico de 176 errores en una sola hora, a las 5 AM — un incidente real que había pasado completamente inadvertido.
Esa forma dicta el diseño de la alerta. Alertar por evento significa ~7 notificaciones por hora de puro ruido de fondo — la alerta se mutea en una semana, y el mute es donde las alertas van a morir. Un umbral por tasa (del orden de 30 errores en 15 minutos) separa limpio el incidente estilo 5 AM del zumbido crónico.
Y hace emerger la conversación honesta con el cliente: hay un problema crónico de conectividad a la base que nadie está atendiendo. Una alerta encima de una condición crónica sin arreglar es solo un recordatorio programado del problema. La recomendación salió como dos vías en paralelo — alerta por tasa ya, e investigación de causa raíz de los cortes de conexión al lado — en lugar de fingir que la alerta sola era el entregable. (Con una nota más de honestidad: los números de la línea de base son un piso, medidos mientras la regla de drop todavía se comía líneas. Re-medir después del fix.)
Letra chica para las queries
Dos trampas de parseo encontradas en el camino, que valen su peso en debugging evitado:
- No confiar en el campo
ts. El launcher de la plataforma lo construye comologEntry.ts + ('' + count).padStart(4,'0')— un número más un string, que en JavaScript es concatenación, no suma. El timestamp publicado lleva cuatro dígitos de más y llega como string JSON entre comillas. Usar el timestamp de ingesta de Loki. msgno siempre es string. Cuando un nodo de flujo pasa un objeto de error,msgllega como objeto JSON. Castear antes de operar como texto, o la query se rompe justo en las líneas que importan — los errores.
Lecciones
- 1. "Agregá una alerta" es una auditoría de observabilidad disfrazada. Verificá que los logs existen, llegan completos y son atribuibles antes de escribir reglas encima.
- 2. Las reglas de drop sin anclar son pérdida silenciosa de datos. Un filtro destructivo merece el mismo rigor de revisión que un
DELETEsinWHERE. - 3. Sabé dónde termina tu red de GitOps. Los recursos creados por la plataforma pueden correr config vieja mientras el repo muestra verde.
- 4. Línea de base antes que umbral. Veinticuatro horas de medición convirtieron "alertá sobre errores" en "alerta por tasa + un problema crónico que tu cliente no sabía que tenía".
- 5. Una alerta sobre una condición crónica conocida es un botón de snooze. Emparejala con la vía de causa raíz, o la fatiga de mute gana.