Alert rules

Alerts are plain JSON in ALERTS_FILE (default /data/alerts.json). The file is hot-reloaded when its mtime changes — save it and the new rules take effect without restarting. Evaluation runs every ALERTS_INTERVAL, default 1m.

A complete rules file

alerts.json
{
  "rules": [
    { "name": "api-silent",    "kind": "deadman",      "apps": ["api"], "for": "5m" },
    { "name": "api-looping",   "kind": "restart_loop", "apps": ["api"], "count": 3, "window": "10m" },
    { "name": "api-mem",       "kind": "metric",       "apps": ["api"], "metric": "mem_pct",
      "op": "gt", "value": 85, "for": "5m" },
    { "name": "api-5xx",      "kind": "log_pattern",  "apps": ["api"], "pattern": "status=5\\d\\d",
      "hits": 5, "window": "5m", "repeat": "30m" }
  ]
}
Mount the rules file read-only into /data so it lives with the database: - ./alerts.json:/data/alerts.json:ro. A malformed file keeps the previous rules running and logs the error — alerting never silently stops.

Rule kinds

DEADMAN silence detector "the app stopped talking"

Fires when an app has produced no log lines for longer than for. This is your watchdog: cron jobs, health reporters and webhooks all go quiet when they break.

FieldRequiredMeaning
foryesSilence duration, e.g. 5m.
RESTART_LOOP crash-loop detector "it keeps dying"

Fires when an app accumulates count deaths inside window. Deaths are counted from stored snapshots, so rollout noise stays out of the count by default.

FieldRequiredMeaning
countyesDeaths required to fire.
windowyesSliding window, e.g. 10m.
METRIC threshold with duration "CPU or memory is pinned"

Fires when every sample in the for window satisfies the comparison and the newest sample is fresh. One cool sample inside the window stops the alert, and stale data (newest sample older than twice the window) is ignored so a stopped app cannot page forever.

FieldValuesMeaning
metriccpu, mem, mem_pctWhich series to compare. cpu is percent, mem bytes, mem_pct percent of limit.
opgt, lt, gte, lteComparison operator.
valuenumberThreshold.
fordurationHow long the condition must hold.
LOG_PATTERN regex over new log lines "5xx spike / auth failures"

Fires when at least hits lines matching the regular expression arrive inside window. The pattern is matched against stored rows for the app, so redaction has already run — secrets are not part of the match.

FieldRequiredMeaning
patternyesRegular expression (escape backslashes, e.g. status=5\\d\\d in JSON).
hitsyesMatching lines required inside the window.
windowyesCounting window, e.g. 5m.

Fields shared by every rule

FieldDefaultMeaning
name—Unique identifier, shown in the firing history.
kind—One of deadman, restart_loop, metric, log_pattern.
appsall known appsApp names the rule applies to. Omit to watch everything.
repeat30mSilence between notifications for the same rule and app. The alerts table is the silence window, so restarts cannot reset it.

Channels

Configure one or more with environment variables — see configuration → alerting. All configured channels are notified for every firing; each has its own 10-second timeout and one failure never blocks the rest.

Webhook

ALERT_WEBHOOK_URL — generic JSON POST.

Telegram

ALERT_TELEGRAM_TOKEN + CHAT_ID.

Slack

ALERT_SLACK_URL — incoming webhook.

Discord

ALERT_DISCORD_URL — webhook URL.

Test before the incident, not during it. Use the Send test alert button in the UI, or POST /api/alerts/test, which pushes a test message through every configured channel and returns {results: {webhook: "sent", …}}.

Inspecting armed rules

bash
# armed configuration, secrets stripped
curl -s -u admin:$AUTH_PASS http://localhost:8080/api/alerts/rules

# firing history, newest first
curl -s -u admin:$AUTH_PASS 'http://localhost:8080/api/alerts?app=api&limit=50'

# push a test message on every channel
curl -s -X POST -u admin:$AUTH_PASS http://localhost:8080/api/alerts/test

Full response shapes on the API reference → alerts.