Skip to content

Alertas

El subsistema de alertas de Ongrid es un único loop de tick que recorre cada fila de regla habilitada, le pregunta al backend apropiado (Prom para métricas + spanmetrics de trazas, Loki para logs) si el predicado matchea, y registra los firings en la tabla incidents.

No hay Alertmanager separado, no hay archivo separado de rules. Las reglas viven en MySQL, el evaluator las consulta en un refresh de caché de 30s, y las notificaciones se fan-outean a través del registry de canales.

Los 14 rule kinds

Las reglas se almacenan con una columna kind. El compilador hace dispatch sobre ella.

El compilador está en rules.go y los evaluators en evaluators_phaseA.go + evaluators_phaseB.go.

La división 8+6 es Phase-A (métricas) / Phase-B (logs + trazas) de HLD-004, aterrizado el 2026-05-08.

Kinds de métrica (Phase A)

KindQué haceCampos de spec
metric_rawLa expresión PromQL ES el predicado. Dispara por cada entrada de vector devuelta.expr
metric_anomalyZ-score o MAD sobre una ventana de baseline rolling.metric, method, baseline_window, baseline_step, deviation, for_seconds
metric_forecastpredict_linear(metric[fit_window], predict_seconds) <op> threshold.metric, fit_window, predict_seconds, operator, threshold
metric_burn_rateMulti-ventana multi-burn estilo SRE de Google sobre un SLO. TODAS las ventanas deben disparar.sli, slo, burns[].window, burns[].multiplier

El kind legacy prom_query se renombró a metric_raw. La forma legacy metric_threshold ahora es una entrada solo-UI que compila a metric_raw al guardar — no hay evaluator separado para ella.

go
// internal/manager/biz/alert/rules.go:36
type MetricRawRule struct {
    ID         uint64
    RuleKey    string
    Name       string
    Severity   string
    ScopeType  string // host / global / monitoring_pipeline
    RunbookURL string
    Labels     map[string]string
    Expr       string // canonical predicate, e.g. `up == 0`
}

Kinds de log + traza (Phase B)

KindQué haceBackend
log_matchcount_over_time(<stream> |~ <filter> [window]) <op> threshold contra Loki. Firing por label-set.Loki
log_volumeMisma forma que log_match, conteo de ventana actual vs threshold absoluto.Loki
trace_latencyhistogram_quantile(q, sum by(le)(rate(traces_spanmetrics_latency_bucket[w]))) > threshold_ms.Prom (spanmetrics)
trace_error_rate100 * (sum rate(traces_spanmetrics_calls_total{status_code="STATUS_CODE_ERROR"}) / sum rate(...)) > pct.Prom (spanmetrics)

Los kinds de traza consultan Prometheus, no Tempo. El generador de spanmetrics scrapea Tempo y escribe series traces_spanmetrics_* de vuelta a Prom — consultar Prom mantiene al evaluator de alertas en un motor de query y reutiliza toda la lógica de filtrado de operador / threshold.

Tipos de scope

Cada regla tiene scope_type ∈ {host, global, monitoring_pipeline}. Default por-kind definido en defaultScopeForKind en rules.go.

  • host — el incidente debe llevar un device_id. El evaluator parsea la label device_id de las labels de resultado de Prom; validateFiring rechaza firings host-scoped sin uno.
  • global — alertas a nivel de servicio (trace_, log_) que no se fijan a un solo host.
  • monitoring_pipeline — meta-alertas sobre el propio Ongrid (scrape_down, prom_ingest_fail, ...).

El tick del evaluator

PipelineEvaluator.evaluate corre cada Interval (default 5 min, configurable vía PipelineEvaluatorOpts.Interval).

go
func (e *PipelineEvaluator) evaluate(ctx context.Context) {
    now := e.now()
    if e.edges != nil {
        e.refreshDeviceStalenessGauge(ctx, now)
    }
    if e.prom != nil {
        e.evaluatePromQuery(ctx, now)
        e.evaluateMetricAnomaly(ctx, now)
        e.evaluateMetricForecast(ctx, now)
        e.evaluateMetricBurnRate(ctx, now)
        e.evaluateTraceLatency(ctx, now)
        e.evaluateTraceErrorRate(ctx, now)
    }
    if e.logq != nil {
        e.evaluateLogMatch(ctx, now)
        e.evaluateLogVolume(ctx, now)
    }
}

Un backend nil salta silenciosamente los kinds correspondientes — Loki caído no rompe las alertas de métrica.

Dedup + recovery

El evaluator trackea firingSnapshot[ruleKey] = set<dedupeKey> a través de ticks. Una key presente el tick pasado pero ausente este tick → el filtro de comparación de PromQL descartó la serie → predicado limpio → SystemResolveIncident dispara con "prom condition cleared". Así es como las alarmas se recuperan sin un evaluator separado de "resolve".

Forma de dedupe key: pipeline:<rule_key>:<sorted-label-set> — las labels de procedencia (__name__, ongrid_source) se eliminan para que la misma alarma reportada por el collector embebido y el de la nube deduplique en un incidente, no dos (labelSetKey).

Fan-out por canal

Cuando un incidente dispara, la ruta Notifier.MaybeNotify consulta al ChannelResolver:

  1. Pinning por-regla — si rule.notify_channel_ids_json no está vacío, solo matchean esos channel ids (y solo los habilitados).
  2. En caso contrario, cada fila notification_channels habilitada se filtra por match_severity_min y match_scope_types.
  3. Si nada matchea, el resolver cae en una lista sintética de canales sembrada desde DefaultChannels para que las notificaciones nunca desaparezcan.

Ver router.go.

Inhibición

Dos reglas de inhibición integradas (inhibit.go), cubriendo los casos default ruidosos:

  • edge_offline:edge_X inhibe cualquier host:X:* — cuando un edge es inalcanzable, cada alarma host-scoped sobre él se suprime.
  • pipeline:prom_ingest_fail inhibe pipeline:scrape_down:* — cuando el propio Prometheus no puede ingestar, cada alarma "target down" es ruido.

Una tabla futura inhibition_rules extiende esto a grupos definidos por admin.

Cooldown + dampening

NotifyOpts.Cooldown (default 10 minutos) limita la re-notificación sobre la misma dedupe_key. El filtro de dampening está dentro de Usecase.MaybeNotify para que el resolver de canales y el inhibidor sigan corriendo en cada firing — solo se salta el Notifier.Send real.

Ver también

  • RCA — qué pasa cuando un incidente dispara.
  • Logs — Loki + los evaluators log_match / log_volume.
  • Trazas — Tempo + los evaluators trace_latency / trace_error_rate.
  • Overview de canales — cómo se configuran los canales Slack / Telegram / Lark / DingTalk / WeCom + webhook.
  • Esquema de reglas de alerta — el wire format de la fila de regla.