Watchdog-Anleitung vollständig überarbeitet - Alle drei Codebeispiele trugen das geseedete Demo-Token fest im Quelltext. Es ist an die Source "srv-db-01" gebunden — wer es übernommen hätte, wäre für jeden anderen Dienst abgewiesen worden. Jetzt Umgebungsvariable und eine Anleitung, wie man ein eigenes Token erzeugt. - Der Ratschlag "sende beim Beenden einen Ping mit Status stopped" beschrieb etwas, das die API nicht konnte. Statt die Anleitung an die Lücke anzupassen, ist die Lücke geschlossen: status akzeptiert jetzt "stopped" und "maintenance". Der Evaluator lässt solche Monitore in Ruhe, statt wenige Minuten nach jedem sauberen Shutdown einen Fehlalarm zu erzeugen. - Neu dokumentiert: checks (Gesundheitszustand per Push, ohne offene Ports), metrics samt Verlauf und Abweichungsvergleich, Alarmunterdrückung über die Hierarchie, die Schwellen des Evaluators (2x warning, 4x down) und der erforderliche Cron-Job. Lizenz-Anleitung - Neuer Abschnitt zum Antwortformat. Die Lizenz-Endpunkte antworten bewusst ohne den status/error-Umschlag der übrigen API; das Feld status auf oberster Ebene trägt den Lizenzzustand. Genau diese Besonderheit hatte ich beim Umbau übersehen, weshalb sie jetzt ausdrücklich festgehalten ist — samt Tabelle aller Zustände. - Ergänzt: Deaktivierung braucht den shared_key, mit Beispiel für den .NET- Client und curl. Verhalten bei Ratenbegrenzung. UpdateService-Anleitung - Prüf-Endpunkte dokumentiert (check, latest, releases) samt Antwortformat. - Tabelle zum Versionsvergleich mit den Fällen, die vorher falsch liefen. - Auto-Resolve beim Veröffentlichen beschrieben. Agent-Prompt-Vorlage - Fehler-Schnittstelle ergänzt, inklusive Hinweis auf "ignored": true, damit ein Agent bekannte Fehler nicht untersucht. Alle in der Dokumentation genannten API-Pfade und Scopes wurden maschinell gegen Routing und TokenManager abgeglichen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
13 KiB
Watchdog-Integration
Überwachung von Anwendungen, Diensten und Servern über Heartbeats.
Stand: Version 2.0 — vollständig überarbeitet. Wer eine ältere Integration betreibt, findet die Änderungen in Abschnitt 8.
1. Wie der Zustand ermittelt wird
Eine Anwendung meldet sich in festen Abständen. Bleibt die Meldung aus, stuft der Evaluator den Monitor herab:
| Zeit seit dem letzten Heartbeat | Zustand |
|---|---|
| innerhalb des Intervalls | wie gemeldet (up / warning) |
| mehr als das Doppelte | warning |
| mehr als das Vierfache | down |
Bei interval: 60 heißt das: nach 2 Minuten auffällig, nach 4 Minuten
ausgefallen.
Wichtig: Der Evaluator muss per Cron laufen. Ohne ihn ändert sich der Zustand ausschließlich beim Eintreffen eines Heartbeats — ein abgestürzter Dienst bliebe dauerhaft grün.
* * * * * curl -fsS -H "Authorization: Bearer <SHARED_KEY>" https://dc.mhdf.de/api/watchdog/v1/evaluate > /dev/nullFehlt der Job, zeigt das WebUI oben einen Warnhinweis.
2. Token besorgen
Nicht das Beispiel-Token aus älteren Fassungen dieser Anleitung verwenden — es ist ein Demo-Wert und an eine einzelne Source gebunden.
Zwei Wege:
a) Zentrales Token (empfohlen für neue Integrationen) WebUI → Token-Verwaltung → Master-Token erstellen, Recht 🛡️ Watchdog Heartbeat. Nicht an eine Source gebunden, funktioniert für beliebig viele Dienste.
b) Agent-Token je Monitor WebUI → WatchDog → Agent-Tokens. An eine Source gebunden; meldet der Dienst unter einem anderen Namen, wird er abgewiesen. Beim Anlegen eines Monitors entsteht automatisch eines.
Token nie im Quelltext ablegen — Umgebungsvariable oder Konfigurationsdatei.
3. Heartbeat senden
POST https://dc.mhdf.de/api/watchdog/v1/ping
Authorization: Bearer <TOKEN> (oder X-Agent-Token: <TOKEN>)
Content-Type: application/json
{
"source": "polytrader-worker",
"instance": "default",
"type": "heartbeat",
"status": "ok",
"interval": 60,
"message": "Verarbeite Warteschlange",
"group": "Applications",
"os": ".NET 8 Service",
"checks": { "db": { "ok": true },
"market_feed": { "ok": false, "message": "Letzter Tick vor 14 min" } },
"metrics": { "cpu": 18, "ram": 42, "queue_depth": 23 }
}
| Feld | Pflicht | Bedeutung |
|---|---|---|
source |
ja | Eindeutiger Name des Dienstes oder Hosts |
instance |
nein | Mehrere Instanzen desselben Dienstes (Vorgabe default) |
type |
nein | heartbeat, host, hypervisor_node, guest |
status |
nein | siehe unten (Vorgabe ok) |
interval |
nein | Erwarteter Abstand in Sekunden (Vorgabe 60) |
message |
nein | Kurztext, erscheint im Dashboard |
group |
nein | Gruppierung im Dashboard |
os |
nein | Plattform, steuert auch die Icon-Erkennung |
checks |
nein | Selbst ermittelter Gesundheitszustand, siehe 4. |
metrics |
nein | Numerische Werte, siehe 5. |
Zulässige Werte für status
| Wert | Zustand | Wirkung |
|---|---|---|
ok |
up |
Normalbetrieb |
warning |
warning |
Auffällig, aber arbeitsfähig |
error |
down |
Störung |
stopped |
stopped |
Bewusst beendet — der Evaluator meldet keinen Ausfall |
maintenance |
maintenance |
Wartung — von der Bewertung ausgenommen |
stopped und maintenance sind der saubere Weg, ein geplantes Herunterfahren
mitzuteilen. Ohne sie erzeugte jeder ordentliche Shutdown wenige Minuten später
einen Fehlalarm.
4. Gesundheitszustand mitsenden
Ein Heartbeat beweist nur, dass ein Thread läuft — nicht, dass die Anwendung
ihre Arbeit tut. Der klassische Fall: Der Timer meldet brav ok, während der
Datenfeed seit einer Stunde tot ist.
Deshalb kann die Anwendung ihren Zustand selbst beurteilen und mitschicken:
"checks": {
"db": { "ok": true },
"market_feed": { "ok": false, "message": "Letzter Tick vor 14 min" },
"queue": { "ok": true, "value": 23 }
}
Das Deploymentcenter interpretiert die Namen nicht — es liest nur ok und
message. Was „gesund" bedeutet, entscheidet jede Anwendung selbst. Es gibt
also keine zentral gepflegten Kennzahlen, an die sich alle Projekte anpassen
müssten.
Schlägt eine Prüfung fehl, wird ein als ok gemeldeter Heartbeat automatisch
auf warning herabgestuft; die Antwort nennt die betroffenen Prüfungen:
{ "status": "success",
"monitor": { "state": "warning", "failing_checks": ["market_feed"] } }
Kurzform ohne Zusatzangaben ist ebenfalls erlaubt:
"checks": { "db": true, "market_feed": false }
Warum kein Abruf durch den Server? Damit auf euren Maschinen keine Ports geöffnet werden müssen. Der Weg ist ausgehend.
5. Metriken und Verlauf
Numerische Werte aus metrics werden mit Zeitstempel gespeichert
(Aufbewahrung 14 Tage). Verschachtelte Angaben werden flach abgelegt:
{"cpu":{"load":1.2}} wird zu cpu.load.
# Welche Metriken liefert dieser Monitor?
curl "https://dc.mhdf.de/api/watchdog/v1/metrics?source=polytrader-worker" \
-H "Authorization: Bearer $DC_TOKEN"
# Verlauf, auf 15-Minuten-Fenster verdichtet
curl "https://dc.mhdf.de/api/watchdog/v1/metrics?source=polytrader-worker&metric=queue_depth&hours=24" \
-H "Authorization: Bearer $DC_TOKEN"
Die Antwort enthält zusätzlich deviation: den Vergleich des aktuellen Werts
mit dem Sieben-Tage-Durchschnitt desselben Monitors. Damit lassen sich
Auffälligkeiten erkennen, ohne für jedes Projekt Schwellwerte zu pflegen.
6. Hierarchie und Alarmunterdrückung
Ist bei einem Monitor eine übergeordnete Entität gesetzt und fällt diese aus, werden Alarme für die untergeordneten Monitore unterdrückt. Ihr Zustand bleibt im Dashboard sichtbar.
Ohne das erzeugt ein ausgefallener Hypervisor mit zwölf VMs dreizehn Meldungen für ein Problem.
Gepflegt wird das im WebUI unter WatchDog → System-Hierarchie im Feld Übergeordnete Entität. Es funktioniert über mehrere Ebenen (Rack → Host → Anwendung).
Die Zuordnung erfolgt über
parent_source, nicht übergroup. Die Gruppe dient nur der Anzeige.
7. Beispiele
Alle Beispiele lesen das Token aus der Umgebung.
C# — Hintergrunddienst
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
public sealed class WatchdogReporter
{
private static readonly HttpClient Http = new() { Timeout = TimeSpan.FromSeconds(10) };
private readonly string _url = $"{Environment.GetEnvironmentVariable("DC_URL") ?? "https://dc.mhdf.de"}/api/watchdog/v1/ping";
private readonly string _token = Environment.GetEnvironmentVariable("DC_TOKEN") ?? "";
private readonly string _source = Environment.GetEnvironmentVariable("DC_SOURCE") ?? Environment.MachineName;
public async Task SendAsync(string status, string message, object? checks = null,
object? metrics = null, CancellationToken ct = default)
{
var payload = new
{
source = _source,
status, // ok | warning | error | stopped | maintenance
interval = 60,
message,
os = Environment.OSVersion.ToString(),
checks,
metrics
};
var request = new HttpRequestMessage(HttpMethod.Post, _url)
{
Content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")
};
request.Headers.Add("Authorization", $"Bearer {_token}");
try
{
await Http.SendAsync(request, ct);
}
catch (Exception ex)
{
// Ein nicht erreichbarer Monitoring-Server darf die Anwendung
// niemals stoppen.
Console.Error.WriteLine($"[Watchdog] Heartbeat fehlgeschlagen: {ex.Message}");
}
}
}
Einbindung als BackgroundService:
public class HeartbeatService : BackgroundService
{
private readonly WatchdogReporter _reporter = new();
private readonly IMarketFeed _feed;
private readonly IJobQueue _queue;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
bool feedOk = _feed.LastTickAge < TimeSpan.FromMinutes(5);
await _reporter.SendAsync(
status: feedOk ? "ok" : "warning",
message: feedOk ? "Betrieb normal" : "Datenfeed veraltet",
checks: new
{
market_feed = new { ok = feedOk, message = $"Letzter Tick vor {_feed.LastTickAge.TotalMinutes:F0} min" },
queue = new { ok = _queue.Depth < 1000, value = _queue.Depth }
},
metrics: new { queue_depth = _queue.Depth },
ct: stoppingToken);
await Task.Delay(TimeSpan.FromSeconds(60), stoppingToken);
}
// Sauberes Beenden ankündigen, damit kein Fehlalarm entsteht.
await _reporter.SendAsync("stopped", "Dienst planmäßig beendet", ct: CancellationToken.None);
}
}
Python
import os
import requests
URL = os.environ.get("DC_URL", "https://dc.mhdf.de") + "/api/watchdog/v1/ping"
TOKEN = os.environ["DC_TOKEN"]
SOURCE = os.environ.get("DC_SOURCE", os.uname().nodename)
def heartbeat(status="ok", message="", checks=None, metrics=None):
payload = {
"source": SOURCE,
"status": status,
"interval": 60,
"message": message,
}
if checks:
payload["checks"] = checks
if metrics:
payload["metrics"] = metrics
try:
requests.post(URL, json=payload,
headers={"Authorization": f"Bearer {TOKEN}"},
timeout=10)
except Exception as exc:
# Monitoring darf die Anwendung nie zum Stillstand bringen
print(f"[Watchdog] Heartbeat fehlgeschlagen: {exc}")
heartbeat(
status="ok",
message="Import abgeschlossen",
checks={"db": {"ok": True}, "feed": {"ok": True}},
metrics={"rows_imported": 4213},
)
Bash / Cron
#!/usr/bin/env bash
set -euo pipefail
: "${DC_TOKEN:?DC_TOKEN ist nicht gesetzt}"
URL="${DC_URL:-https://dc.mhdf.de}/api/watchdog/v1/ping"
SOURCE="${DC_SOURCE:-$(hostname)}"
DISK=$(df --output=pcent / | tail -1 | tr -dc '0-9')
curl -fsS -X POST "$URL" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $DC_TOKEN" \
-d "{\"source\":\"$SOURCE\",\"status\":\"ok\",\"interval\":3600,
\"message\":\"Nächtliches Backup abgeschlossen\",
\"checks\":{\"disk\":{\"ok\":$([ "$DISK" -lt 90 ] && echo true || echo false),\"message\":\"${DISK}% belegt\"}},
\"metrics\":{\"disk_percent\":$DISK}}" \
> /dev/null
8. Ereignisse protokollieren
Für einmalige Vorkommnisse statt zyklischer Meldungen:
curl -X POST https://dc.mhdf.de/api/watchdog/v1/event \
-H "Authorization: Bearer $DC_TOKEN" \
-H "Content-Type: application/json" \
-d '{"source":"polytrader-worker","kind":"recovered",
"severity":"info","message":"Verbindung zum Feed wiederhergestellt"}'
Zulässige kind-Werte: started, stopped_graceful, crash_suspected,
hard_error, recovered, warning_raised, warning_cleared,
maintenance_start, maintenance_end, watchdog_started.
severity: info, warning, alarm.
9. Was sich gegenüber Version 1 geändert hat
| Thema | Vorher | Jetzt |
|---|---|---|
| Zustandsbewertung | fand nicht statt — ein ausgefallenes System blieb up |
Evaluator per Cron, 2× / 4× Intervall |
expected_interval_sec |
gespeichert, nie ausgewertet | bestimmt die Schwellen |
is_muted, suppress_until_utc |
gespeichert, nie ausgewertet | werden beachtet |
| Token | nur wd_live_-Tokens |
zusätzlich zentrale Tokens mit watchdog:ping |
| Anwendungszustand | nicht übermittelbar | Feld checks |
| Metriken | nur der letzte Wert | Verlauf über 14 Tage, abrufbar |
| Geplantes Beenden | nicht möglich | status: "stopped" bzw. "maintenance" |
| Alarme bei Ausfall eines Hosts | eine Meldung je Kind | Kinder werden unterdrückt |
| Antwortformat | uneinheitlich | {"status":"success",…} bzw. {"status":"error","error":{"code":…}} |
Bestehende Agenten mit wd_live_-Token und einfachem ok-Ping laufen
unverändert weiter — die neuen Felder sind alle optional.
10. Empfehlungen
- Intervall realistisch wählen. Ein Backup-Job, der stündlich läuft, meldet
interval: 3600— nicht 60. - Netzwerkfehler immer abfangen. Der Ausfall des Monitoring-Servers darf die überwachte Anwendung nicht beeinträchtigen.
- Beim Beenden
stoppedsenden. Sonst folgt wenige Minuten später ein Fehlalarm. checksnutzen. Ein Heartbeat sagt nur, dass ein Thread läuft.- Hierarchie pflegen, wenn Dienste auf gemeinsamen Hosts laufen — sonst bringt ein Hostausfall eine Alarmlawine.