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)
| Kind | Qué hace | Campos de spec |
|---|---|---|
metric_raw | La expresión PromQL ES el predicado. Dispara por cada entrada de vector devuelta. | expr |
metric_anomaly | Z-score o MAD sobre una ventana de baseline rolling. | metric, method, baseline_window, baseline_step, deviation, for_seconds |
metric_forecast | predict_linear(metric[fit_window], predict_seconds) <op> threshold. | metric, fit_window, predict_seconds, operator, threshold |
metric_burn_rate | Multi-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.
// 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)
| Kind | Qué hace | Backend |
|---|---|---|
log_match | count_over_time(<stream> |~ <filter> [window]) <op> threshold contra Loki. Firing por label-set. | Loki |
log_volume | Misma forma que log_match, conteo de ventana actual vs threshold absoluto. | Loki |
trace_latency | histogram_quantile(q, sum by(le)(rate(traces_spanmetrics_latency_bucket[w]))) > threshold_ms. | Prom (spanmetrics) |
trace_error_rate | 100 * (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 undevice_id. El evaluator parsea la labeldevice_idde las labels de resultado de Prom;validateFiringrechaza 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).
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:
- Pinning por-regla — si
rule.notify_channel_ids_jsonno está vacío, solo matchean esos channel ids (y solo los habilitados). - En caso contrario, cada fila
notification_channelshabilitada se filtra pormatch_severity_minymatch_scope_types. - Si nada matchea, el resolver cae en una lista sintética de canales sembrada desde
DefaultChannelspara 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_Xinhibe cualquierhost:X:*— cuando un edge es inalcanzable, cada alarma host-scoped sobre él se suprime.pipeline:prom_ingest_failinhibepipeline: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.