docs: Integrationsanleitungen auf Stand 2.0 bringen, Status "stopped" ergänzen
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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
65e60899f9
commit
5f9b0c5596
@@ -54,6 +54,34 @@ dem nächsten Agenten das Parsen des Stacktrace.
|
|||||||
Schweregrade: `idea` (Gedanke für später), `wishlist` (Backlog),
|
Schweregrade: `idea` (Gedanke für später), `wishlist` (Backlog),
|
||||||
`low`, `medium`, `high`, `critical`.
|
`low`, `medium`, `high`, `critical`.
|
||||||
|
|
||||||
|
### Laufzeitfehler automatisch melden
|
||||||
|
Für den globalen Exception-Handler einer Anwendung gibt es einen schlankeren
|
||||||
|
Eingang — Titel und Dringlichkeit leitet der Server ab:
|
||||||
|
|
||||||
|
`POST /api/errors/v1/report`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"project_slug": "myapp",
|
||||||
|
"exception": "PDOException",
|
||||||
|
"message": "Exakte Fehlermeldung",
|
||||||
|
"stack_trace": "...",
|
||||||
|
"level": "error",
|
||||||
|
"build": "v1.4.2",
|
||||||
|
"environment": "production",
|
||||||
|
"file": "src/Core/Service.php",
|
||||||
|
"line": 42
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`level`: `fatal` (Prozess beendet), `error` (Vorgang fehlgeschlagen, Programm
|
||||||
|
läuft weiter), `warning`.
|
||||||
|
|
||||||
|
Gleiche Fehler werden zu einer Gruppe zusammengefasst und hochgezählt;
|
||||||
|
veränderliche Anteile wie Schlüsselwerte, Adressen und Zeitstempel werden dabei
|
||||||
|
ausgeblendet. Kommt `"ignored": true` zurück, ist der Fehler als bekannt und
|
||||||
|
harmlos eingestuft — dann nicht weiter untersuchen, sondern nur zählen lassen.
|
||||||
|
|
||||||
### Arbeit übernehmen
|
### Arbeit übernehmen
|
||||||
Bevor du an einem Item arbeitest, übernimm es — sonst arbeiten zwei Agenten
|
Bevor du an einem Item arbeitest, übernimm es — sonst arbeiten zwei Agenten
|
||||||
parallel am selben Problem:
|
parallel am selben Problem:
|
||||||
|
|||||||
@@ -1,16 +1,58 @@
|
|||||||
# Deploymentcenter — Lizenzsystem Integration für KI-Agenten
|
# Deploymentcenter — Lizenzsystem Integration für KI-Agenten
|
||||||
|
|
||||||
> **⚠️ Geändert in Version 2.0** — `/api/license/v1/validate` bleibt unverändert
|
|
||||||
> und ohne Token erreichbar. `/api/license/v1/deactivate` verlangt weiterhin
|
|
||||||
> Authentifizierung, allerdings mit dem **neu erzeugten** `shared_key`: der alte
|
|
||||||
> Wert lag im Repository und wurde ersetzt. Die Signatur der Offline-Lizenzdateien
|
|
||||||
> ist jetzt ein ehrlich benanntes HMAC-SHA256 statt der irreführenden Bezeichnung
|
|
||||||
> `ED25519_SIG_`. Umstellungsschritte: **[UPGRADE.md](./UPGRADE.md)**.
|
|
||||||
|
|
||||||
|
|
||||||
> **Zielgruppe**: KI-Agenten & Softwareentwickler
|
> **Zielgruppe**: KI-Agenten & Softwareentwickler
|
||||||
> **Gültig ab**: Hardware-ID v2 Specification (August 2026)
|
> **Gültig ab**: Hardware-ID v2, Deploymentcenter 2.0
|
||||||
> **Plattformen**: Windows, Linux (inkl. systemd Services & Docker-Container), macOS
|
> **Plattformen**: Windows, Linux (inkl. systemd und Docker), macOS
|
||||||
|
|
||||||
|
> **⚠️ Änderungen in Version 2.0**
|
||||||
|
> - `/api/license/v1/validate` bleibt **unverändert** und ohne Token erreichbar.
|
||||||
|
> Ausgelieferte Clients laufen ohne Anpassung weiter.
|
||||||
|
> - `/api/license/v1/deactivate` verlangt weiterhin Authentifizierung — aber mit
|
||||||
|
> dem **neu erzeugten** `shared_key`. Der alte Wert lag im Repository und wurde
|
||||||
|
> ersetzt; wer ihn irgendwo eingetragen hat, muss nachziehen.
|
||||||
|
> - Die Signatur der Offline-Lizenzdateien heißt jetzt ehrlich HMAC-SHA256 statt
|
||||||
|
> irreführend `ED25519_SIG_`. Der frühere „Server Public Key" im WebUI war frei
|
||||||
|
> erfunden und wurde entfernt.
|
||||||
|
>
|
||||||
|
> Umstellungsschritte: **[UPGRADE.md](./UPGRADE.md)**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. Antwortformat — bitte beachten
|
||||||
|
|
||||||
|
Die Lizenz-Endpunkte antworten **ohne** den `status`/`error`-Umschlag der
|
||||||
|
übrigen Deploymentcenter-API. Das Feld `status` auf oberster Ebene trägt den
|
||||||
|
**Lizenzzustand**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "validation_result",
|
||||||
|
"status": "valid",
|
||||||
|
"issued_at": 1786435199,
|
||||||
|
"expires_at": 1817971199,
|
||||||
|
"cache_ttl_hours": 168,
|
||||||
|
"hardware_id": "2:win:a765bd47...",
|
||||||
|
"license_key": "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX",
|
||||||
|
"nonce": "der übergebene Wert",
|
||||||
|
"endpoints": { "validate": "/api/license/v1/validate",
|
||||||
|
"deactivate": "/api/license/v1/deactivate" },
|
||||||
|
"message": "License is valid"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| `status` | Bedeutung |
|
||||||
|
|---|---|
|
||||||
|
| `valid` | Lizenz gültig, Aktivierung eingetragen |
|
||||||
|
| `not_found` | Projekt oder Schlüssel unbekannt |
|
||||||
|
| `revoked` | Lizenz widerrufen **oder diese Hardware gesperrt** |
|
||||||
|
| `suspended` | Lizenz vorübergehend ausgesetzt |
|
||||||
|
| `expired` | Ablaufdatum überschritten |
|
||||||
|
| `activation_limit` | Maximale Anzahl Aktivierungen erreicht |
|
||||||
|
|
||||||
|
> Diese Sonderstellung ist bewusst: Ein Umschlag mit `status: "success"` würde
|
||||||
|
> von jedem bestehenden Client als „nicht valid" gelesen — sämtliche Lizenzen
|
||||||
|
> gälten schlagartig als ungültig. Wer eine eigene Anbindung schreibt, muss
|
||||||
|
> `status` also **von der obersten Ebene** lesen, nicht aus einem `result`-Objekt.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -123,6 +165,41 @@ my-service --license-set-key LLAB1-98A72-B3C4D-5E6F7-89012
|
|||||||
my-service --license-deactivate
|
my-service --license-deactivate
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Deaktivierung braucht Authentifizierung
|
||||||
|
|
||||||
|
`/api/license/v1/deactivate` gibt einen Aktivierungsplatz frei und ist deshalb
|
||||||
|
geschützt — sonst könnte jeder fremde Installationen abmelden. Der Aufruf
|
||||||
|
verlangt den `shared_key` aus `config/config.php`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Der Schlüssel gehört auf den Administrationsrechner, nicht in die
|
||||||
|
// ausgelieferte Anwendung.
|
||||||
|
var client = new LicenseClient();
|
||||||
|
bool released = await client.DeactivateAsync(
|
||||||
|
productSlug: "myapp",
|
||||||
|
licenseKey: "LLAB1-98A72-B3C4D-5E6F7-89012",
|
||||||
|
serverBaseUrl: "https://dc.mhdf.de",
|
||||||
|
authToken: Environment.GetEnvironmentVariable("DC_SHARED_KEY"));
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST https://dc.mhdf.de/api/license/v1/deactivate \
|
||||||
|
-H "Authorization: Bearer $DC_SHARED_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"product":"myapp","license_key":"XXXXX-...","hardware_id":"2:win:a765..."}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternativ genügt für Einzelfälle der Knopf **Freigeben** in der Hardware-Liste
|
||||||
|
des WebUI — das ist der übliche Weg und braucht keinen Schlüssel im Feld.
|
||||||
|
|
||||||
|
### Was passiert bei Ratenbegrenzung
|
||||||
|
|
||||||
|
`/validate` ist auf 120 Anfragen pro Minute und IP begrenzt. Darüber kommt
|
||||||
|
`429` mit `{"status":"error","error":{"code":"rate_limited"}}` — hier greift
|
||||||
|
ausnahmsweise das Umschlagformat, weil die Drosselung vor der Lizenzlogik
|
||||||
|
zuschlägt. Ein Client sollte in dem Fall den lokalen Cache verwenden und es
|
||||||
|
später erneut versuchen, statt die Anwendung zu blockieren.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Migration v1 → v2 ohne Platzverlust
|
## 5. Migration v1 → v2 ohne Platzverlust
|
||||||
|
|||||||
@@ -159,6 +159,75 @@ update-agent --project myapp --channel prod --action list
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 4a. Prüf-Endpunkte (für eigene Anbindungen)
|
||||||
|
|
||||||
|
Die Lese-Endpunkte sind bewusst **ohne Token** erreichbar, damit ausgelieferte
|
||||||
|
Anwendungen ohne Anpassung weiter nach Updates suchen können. Sie liefern nur
|
||||||
|
Release-Metadaten, die über die Download-URL ohnehin öffentlich sind.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GET /api/updateservice/v1/check?product=myapp&version=1.4.2&channel=prod
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"update_available": true,
|
||||||
|
"current_version": "1.4.2",
|
||||||
|
"latest_version": "1.4.3",
|
||||||
|
"is_critical": false,
|
||||||
|
"latest_release": {
|
||||||
|
"version": "1.4.3",
|
||||||
|
"download_url": "https://dc.mhdf.de/releases/myapp/prod/1.4.3/package.tar.gz",
|
||||||
|
"sha256_hash": "e3b0c442...",
|
||||||
|
"git_commit": "a21536f",
|
||||||
|
"size_bytes": 8412160,
|
||||||
|
"release_notes": "Behebt den Login-Fehler.",
|
||||||
|
"is_critical": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Weitere Endpunkte:
|
||||||
|
|
||||||
|
| Aufruf | Zweck |
|
||||||
|
|---|---|
|
||||||
|
| `GET .../latest?product=myapp&channel=prod` | Höchstes Release, unabhängig von der Client-Version |
|
||||||
|
| `GET .../releases?product=myapp` | Alle Releases, nach Versionsordnung sortiert |
|
||||||
|
|
||||||
|
### Versionsvergleich
|
||||||
|
|
||||||
|
Der Vergleich folgt der semantischen Versionsordnung. Konkret bedeutet das:
|
||||||
|
|
||||||
|
| Installiert | Verfügbar | Update? |
|
||||||
|
|---|---|---|
|
||||||
|
| `1.9.0` | `1.10.0` | ja — zweistellige Minor ist höher |
|
||||||
|
| `1.10.0` | `1.9.0` | nein |
|
||||||
|
| `1.0.0-rc.1` | `1.0.0` | ja — Release schlägt Vorabversion |
|
||||||
|
| `1.0.0` | `1.0.0-rc.1` | nein |
|
||||||
|
| `v1.4.2` | `v1.4.3` | ja — führendes `v` wird ignoriert |
|
||||||
|
|
||||||
|
> Zuvor verglich der Server lexikografisch. `1.9.0` galt dadurch als neuer als
|
||||||
|
> `1.10.0`, und Clients bekamen ein Downgrade als Update angeboten. Derselbe
|
||||||
|
> Fehler steckte im .NET-Client bei `v`-präfigierten Versionen und ist dort
|
||||||
|
> ebenfalls behoben.
|
||||||
|
|
||||||
|
### Verknüpfung mit dem Bugtracker
|
||||||
|
|
||||||
|
Beim Veröffentlichen schließen sich alle Bugtracker-Items, deren
|
||||||
|
`resolved_in_build` der veröffentlichten Version entspricht, automatisch. Die
|
||||||
|
Antwort nennt die Anzahl:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "status": "success", "release_id": 12, "created": true, "auto_resolved": 3,
|
||||||
|
"message": "Release 1.4.3 (prod) für \"myapp\" veröffentlicht. 3 Bugtracker-Item(s) automatisch geschlossen." }
|
||||||
|
```
|
||||||
|
|
||||||
|
Damit schließt sich der Kreis: Ein Agent markiert einen Bug als „gelöst in
|
||||||
|
v1.4.3", der Packager veröffentlicht v1.4.3, und das Item schließt sich selbst.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 5. LEMP Verzeichnisstruktur auf dem Server
|
## 5. LEMP Verzeichnisstruktur auf dem Server
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
|||||||
@@ -232,6 +232,18 @@ Das Deploymentcenter interpretiert die Namen nicht — es liest nur `ok` und
|
|||||||
`warning` herabgestuft. **Es müssen keine Ports geöffnet werden**, der Weg ist
|
`warning` herabgestuft. **Es müssen keine Ports geöffnet werden**, der Weg ist
|
||||||
ausgehend.
|
ausgehend.
|
||||||
|
|
||||||
|
Neu sind außerdem die Statuswerte `stopped` und `maintenance`. Ohne sie erzeugte
|
||||||
|
jedes geplante Herunterfahren wenige Minuten später einen Fehlalarm — es gab
|
||||||
|
schlicht keinen Weg, ein beabsichtigtes Ende mitzuteilen:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "source": "polytrader-worker", "status": "stopped",
|
||||||
|
"message": "Dienst planmäßig beendet" }
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Evaluator lässt solche Monitore in Ruhe, bis wieder ein normaler Heartbeat
|
||||||
|
eintrifft.
|
||||||
|
|
||||||
## 12. Abhängigkeitsbewusste Alarmierung
|
## 12. Abhängigkeitsbewusste Alarmierung
|
||||||
|
|
||||||
Fällt ein Monitor aus, für den `parent_source` gesetzt ist, und ist der
|
Fällt ein Monitor aus, für den `parent_source` gesetzt ist, und ist der
|
||||||
|
|||||||
@@ -1,162 +1,391 @@
|
|||||||
# Deploymentcenter — Watchdog Integration für KI-Agenten
|
# Watchdog-Integration
|
||||||
|
|
||||||
> **⚠️ Geändert in Version 2.0** — Neu ist der Evaluator unter
|
Überwachung von Anwendungen, Diensten und Servern über Heartbeats.
|
||||||
> `GET /api/watchdog/v1/evaluate`, der per Cron minütlich laufen muss. Ohne ihn
|
|
||||||
> ändert sich der Zustand eines Monitors nur beim Eintreffen eines Heartbeats,
|
|
||||||
> ein ausgefallenes System bliebe dauerhaft `up`. `expected_interval_sec`,
|
|
||||||
> `is_muted` und `suppress_until_utc` werden jetzt ausgewertet. Neben den
|
|
||||||
> bisherigen `wd_live_`-Tokens werden auch zentrale Tokens mit dem Scope
|
|
||||||
> `watchdog:ping` akzeptiert. Umstellungsschritte: **[UPGRADE.md](./UPGRADE.md)**.
|
|
||||||
|
|
||||||
|
> **Stand:** Version 2.0 — vollständig überarbeitet. Wer eine ältere Integration
|
||||||
> **Zielgruppe**: KI-Agenten & Softwareentwickler
|
> betreibt, findet die Änderungen in Abschnitt 8.
|
||||||
> **Zweck**: Einbindung von Heartbeat-Monitoring, Statusmeldungen und Telemetrie in Anwendungen & Serverdienste.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Übersicht
|
## 1. Wie der Zustand ermittelt wird
|
||||||
|
|
||||||
Der **Watchdog** in Deploymentcenter überwacht kontinuierlich den Zustand von Hosts, Diensten, Cronjobs und Proxmox-Hypervisoren.
|
Eine Anwendung meldet sich in festen Abständen. Bleibt die Meldung aus, stuft
|
||||||
|
der **Evaluator** den Monitor herab:
|
||||||
|
|
||||||
Anwendungen senden in regelmäßigen Abständen (standardmäßig alle 60 Sekunden) einen HTTP POST Ping an die Watchdog API. Ausbleibende Pings oder gemeldete Fehler erzeugen automatisch Warnungen im Admin-Dashboard.
|
| 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.
|
||||||
|
>
|
||||||
|
> ```bash
|
||||||
|
> * * * * * curl -fsS -H "Authorization: Bearer <SHARED_KEY>" https://dc.mhdf.de/api/watchdog/v1/evaluate > /dev/null
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> Fehlt der Job, zeigt das WebUI oben einen Warnhinweis.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. API Endpunkt & Authentifizierung
|
## 2. Token besorgen
|
||||||
|
|
||||||
- **URL**: `POST https://dc.mhdf.de/api/watchdog/v1/ping`
|
**Nicht** das Beispiel-Token aus älteren Fassungen dieser Anleitung verwenden —
|
||||||
- **Content-Type**: `application/json`
|
es ist ein Demo-Wert und an eine einzelne Source gebunden.
|
||||||
- **Header**: `X-Agent-Token: <dein_watchdog_agent_token>`
|
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
### Request Body Schema (JSON)
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"source": "srv-db-01",
|
"source": "polytrader-worker",
|
||||||
"instance": "default",
|
"instance": "default",
|
||||||
"type": "heartbeat",
|
"type": "heartbeat",
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"message": "Service running smoothly",
|
|
||||||
"interval": 60,
|
"interval": 60,
|
||||||
"group": "Infrastructure",
|
"message": "Verarbeite Warteschlange",
|
||||||
"os": "Ubuntu 24.04 LTS"
|
"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 }
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Felder:
|
| Feld | Pflicht | Bedeutung |
|
||||||
- `source` *(string, erforderlich)*: Eindeutiger Name des Dienstes oder Hostnames (z.B. `srv-db-01` oder `PolyTrader Worker`).
|
|---|---|---|
|
||||||
- `instance` *(string, optional)*: Instanzbezeichner (Standard: `default`).
|
| `source` | ja | Eindeutiger Name des Dienstes oder Hosts |
|
||||||
- `type` *(string)*: `heartbeat`, `host`, `hypervisor_node` oder `guest`.
|
| `instance` | nein | Mehrere Instanzen desselben Dienstes (Vorgabe `default`) |
|
||||||
- `status` *(string)*: `ok`, `warning` oder `error`.
|
| `type` | nein | `heartbeat`, `host`, `hypervisor_node`, `guest` |
|
||||||
- `message` *(string, optional)*: Status- oder Fehlermeldung.
|
| `status` | nein | siehe unten (Vorgabe `ok`) |
|
||||||
- `interval` *(int)*: Erwarteter Abstand in Sekunden zwischen zwei Pings (Standard: `60`).
|
| `interval` | nein | Erwarteter Abstand in Sekunden (Vorgabe 60) |
|
||||||
- `group` *(string, optional)*: Gruppierung im Dashboard (z.B. `Applications`, `Infrastructure`).
|
| `message` | nein | Kurztext, erscheint im Dashboard |
|
||||||
- `os` *(string, optional)*: Betriebssystem-Name (z.B. `.NET 8 Service`, `Debian 12`).
|
| `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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Implementierungsbeispiele
|
## 4. Gesundheitszustand mitsenden
|
||||||
|
|
||||||
### 3.1 C# (.NET Core / .NET 8+)
|
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:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"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:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "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`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 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** über `group`. Die Gruppe
|
||||||
|
> dient nur der Anzeige.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Beispiele
|
||||||
|
|
||||||
|
Alle Beispiele lesen das Token aus der Umgebung.
|
||||||
|
|
||||||
|
### C# — Hintergrunddienst
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
using System;
|
using System;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
public class WatchdogHeartbeatService
|
public sealed class WatchdogReporter
|
||||||
{
|
{
|
||||||
private static readonly HttpClient Client = new HttpClient();
|
private static readonly HttpClient Http = new() { Timeout = TimeSpan.FromSeconds(10) };
|
||||||
private static readonly string PingUrl = "https://dc.mhdf.de/api/watchdog/v1/ping";
|
|
||||||
private static readonly string Token = "wd_live_token_infra_01_secure";
|
|
||||||
|
|
||||||
public static async Task SendPingAsync(string sourceName, string status = "ok", string message = "Service active")
|
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
|
var payload = new
|
||||||
{
|
{
|
||||||
source = sourceName,
|
source = _source,
|
||||||
instance = "default",
|
status, // ok | warning | error | stopped | maintenance
|
||||||
type = "heartbeat",
|
|
||||||
status = status,
|
|
||||||
message = message,
|
|
||||||
interval = 60,
|
interval = 60,
|
||||||
group = "Services",
|
message,
|
||||||
os = Environment.OSVersion.ToString()
|
os = Environment.OSVersion.ToString(),
|
||||||
|
checks,
|
||||||
|
metrics
|
||||||
};
|
};
|
||||||
|
|
||||||
string json = JsonSerializer.Serialize(payload);
|
var request = new HttpRequestMessage(HttpMethod.Post, _url)
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, PingUrl)
|
|
||||||
{
|
{
|
||||||
Content = new StringContent(json, Encoding.UTF8, "application/json")
|
Content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")
|
||||||
};
|
};
|
||||||
request.Headers.Add("X-Agent-Token", Token);
|
request.Headers.Add("Authorization", $"Bearer {_token}");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
HttpResponseMessage response = await Client.SendAsync(request);
|
await Http.SendAsync(request, ct);
|
||||||
if (response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
Console.WriteLine("[✔] Watchdog Heartbeat erfolgreich gesendet.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"[✖] Watchdog Ping Fehlgeschlagen: {ex.Message}");
|
// Ein nicht erreichbarer Monitoring-Server darf die Anwendung
|
||||||
|
// niemals stoppen.
|
||||||
|
Console.Error.WriteLine($"[Watchdog] Heartbeat fehlgeschlagen: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3.2 Python 3
|
Einbindung als `BackgroundService`:
|
||||||
|
|
||||||
```python
|
```csharp
|
||||||
import requests
|
public class HeartbeatService : BackgroundService
|
||||||
|
{
|
||||||
|
private readonly WatchdogReporter _reporter = new();
|
||||||
|
private readonly IMarketFeed _feed;
|
||||||
|
private readonly IJobQueue _queue;
|
||||||
|
|
||||||
WATCHDOG_URL = "https://dc.mhdf.de/api/watchdog/v1/ping"
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
AGENT_TOKEN = "wd_live_token_infra_01_secure"
|
{
|
||||||
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
bool feedOk = _feed.LastTickAge < TimeSpan.FromMinutes(5);
|
||||||
|
|
||||||
def send_heartbeat(source_name, status="ok", message="Python Background Task running"):
|
await _reporter.SendAsync(
|
||||||
headers = {
|
status: feedOk ? "ok" : "warning",
|
||||||
"Content-Type": "application/json",
|
message: feedOk ? "Betrieb normal" : "Datenfeed veraltet",
|
||||||
"X-Agent-Token": AGENT_TOKEN
|
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);
|
||||||
}
|
}
|
||||||
payload = {
|
}
|
||||||
"source": source_name,
|
|
||||||
"instance": "default",
|
|
||||||
"type": "heartbeat",
|
|
||||||
"status": status,
|
|
||||||
"message": message,
|
|
||||||
"interval": 60,
|
|
||||||
"group": "Python Services"
|
|
||||||
}
|
|
||||||
try:
|
|
||||||
response = requests.post(WATCHDOG_URL, json=payload, headers=headers, timeout=10)
|
|
||||||
if response.status_code == 200:
|
|
||||||
print("[✔] Watchdog Ping OK")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[✖] Watchdog Ping Error: {e}")
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3.3 Bash / Cronjob (Linux)
|
### Python
|
||||||
|
|
||||||
|
```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
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
WATCHDOG_URL="https://dc.mhdf.de/api/watchdog/v1/ping"
|
: "${DC_TOKEN:?DC_TOKEN ist nicht gesetzt}"
|
||||||
TOKEN="wd_live_token_infra_01_secure"
|
URL="${DC_URL:-https://dc.mhdf.de}/api/watchdog/v1/ping"
|
||||||
SOURCE="$(hostname)"
|
SOURCE="${DC_SOURCE:-$(hostname)}"
|
||||||
|
|
||||||
curl -s -X POST "$WATCHDOG_URL" \
|
DISK=$(df --output=pcent / | tail -1 | tr -dc '0-9')
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-H "X-Agent-Token: $TOKEN" \
|
curl -fsS -X POST "$URL" \
|
||||||
-d '{"source": "'"$SOURCE"'", "status": "ok", "message": "Hourly Backup Task Completed", "interval": 3600}'
|
-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
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Best Practices für KI-Agenten
|
## 8. Ereignisse protokollieren
|
||||||
|
|
||||||
1. **Heartbeat-Schleife**: Lasse in eigenständigen Hoster-Diensten einen periodischen Timer (z.B. `System.Threading.Timer` oder `BackgroundService`) alle 60s `SendPingAsync` aufrufen.
|
Für einmalige Vorkommnisse statt zyklischer Meldungen:
|
||||||
2. **Graceful Shutdown**: Sende beim Beenden des Dienstes einen Ping mit Status `stopped` oder `maintenance`.
|
|
||||||
3. **Fehlerbehandlung**: Fange Netzwerkfehler bei Watchdog-Pings stets stumm/abgefangen ab, damit der Ausfall des Monitoring-Servers niemals den Hauptanwendungsfluss unterbricht.
|
```bash
|
||||||
|
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
|
||||||
|
|
||||||
|
1. **Intervall realistisch wählen.** Ein Backup-Job, der stündlich läuft, meldet
|
||||||
|
`interval: 3600` — nicht 60.
|
||||||
|
2. **Netzwerkfehler immer abfangen.** Der Ausfall des Monitoring-Servers darf
|
||||||
|
die überwachte Anwendung nicht beeinträchtigen.
|
||||||
|
3. **Beim Beenden `stopped` senden.** Sonst folgt wenige Minuten später ein
|
||||||
|
Fehlalarm.
|
||||||
|
4. **`checks` nutzen.** Ein Heartbeat sagt nur, dass ein Thread läuft.
|
||||||
|
5. **Hierarchie pflegen**, wenn Dienste auf gemeinsamen Hosts laufen — sonst
|
||||||
|
bringt ein Hostausfall eine Alarmlawine.
|
||||||
|
|||||||
+11
-3
@@ -311,10 +311,18 @@ $spec = [
|
|||||||
'properties' => [
|
'properties' => [
|
||||||
'source' => ['type' => 'string'],
|
'source' => ['type' => 'string'],
|
||||||
'instance' => ['type' => 'string', 'default' => 'default'],
|
'instance' => ['type' => 'string', 'default' => 'default'],
|
||||||
'status' => ['type' => 'string', 'enum' => ['ok', 'warning', 'error']],
|
'status' => [
|
||||||
'interval' => ['type' => 'integer', 'description' => 'Erwarteter Abstand in Sekunden; danach gilt der Monitor als auffaellig'],
|
'type' => 'string',
|
||||||
|
'enum' => ['ok', 'warning', 'error', 'stopped', 'maintenance'],
|
||||||
|
'description' => 'stopped und maintenance sind angekuendigte Zustaende - der Evaluator meldet dafuer keinen Ausfall.',
|
||||||
|
],
|
||||||
|
'interval' => ['type' => 'integer', 'description' => 'Erwarteter Abstand in Sekunden. Nach dem Doppelten gilt der Monitor als auffaellig, nach dem Vierfachen als ausgefallen.'],
|
||||||
'message' => ['type' => 'string'],
|
'message' => ['type' => 'string'],
|
||||||
'metrics' => ['type' => 'object'],
|
'checks' => [
|
||||||
|
'type' => 'object',
|
||||||
|
'description' => 'Selbst ermittelter Gesundheitszustand, z. B. {"db":{"ok":true},"feed":{"ok":false,"message":"..."}}. Die Namen werden nicht interpretiert; eine fehlgeschlagene Pruefung stuft einen als ok gemeldeten Heartbeat auf warning herab.',
|
||||||
|
],
|
||||||
|
'metrics' => ['type' => 'object', 'description' => 'Numerische Werte; landen im Verlauf und sind ueber /metrics abrufbar.'],
|
||||||
'os' => ['type' => 'string'],
|
'os' => ['type' => 'string'],
|
||||||
],
|
],
|
||||||
]]],
|
]]],
|
||||||
|
|||||||
@@ -102,10 +102,17 @@ final class MonitorRepo
|
|||||||
? json_encode($checks, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
|
? json_encode($checks, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
// "stopped" und "maintenance" sind angekuendigte Zustaende: der Dienst
|
||||||
|
// wurde bewusst beendet bzw. gewartet. Der Evaluator laesst solche
|
||||||
|
// Monitore in Ruhe, statt sie als Ausfall zu melden. Ohne diese beiden
|
||||||
|
// Werte gab es keinen Weg, ein geplantes Herunterfahren mitzuteilen -
|
||||||
|
// jeder saubere Shutdown erzeugte kurz darauf einen Fehlalarm.
|
||||||
$state = match ($status) {
|
$state = match ($status) {
|
||||||
'ok' => 'up',
|
'ok' => 'up',
|
||||||
'warning' => 'warning',
|
'warning' => 'warning',
|
||||||
default => 'down',
|
'stopped' => 'stopped',
|
||||||
|
'maintenance' => 'maintenance',
|
||||||
|
default => 'down',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Eine fehlgeschlagene Pruefung stuft einen als "ok" gemeldeten
|
// Eine fehlgeschlagene Pruefung stuft einen als "ok" gemeldeten
|
||||||
@@ -114,6 +121,14 @@ final class MonitorRepo
|
|||||||
$state = 'warning';
|
$state = 'warning';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// last_status kennt nur ok/warning/error; angekuendigte Zustaende
|
||||||
|
// gelten dort als unauffaellig.
|
||||||
|
$lastStatus = match ($status) {
|
||||||
|
'ok', 'stopped', 'maintenance' => 'ok',
|
||||||
|
'warning' => 'warning',
|
||||||
|
default => 'error',
|
||||||
|
};
|
||||||
|
|
||||||
$previous = $this->getMonitor($source, $instance);
|
$previous = $this->getMonitor($source, $instance);
|
||||||
$previousState = $previous !== null ? (string)$previous['state'] : null;
|
$previousState = $previous !== null ? (string)$previous['state'] : null;
|
||||||
|
|
||||||
@@ -155,7 +170,7 @@ final class MonitorRepo
|
|||||||
':type' => $type,
|
':type' => $type,
|
||||||
':state' => $state,
|
':state' => $state,
|
||||||
':interval' => $intervalSec,
|
':interval' => $intervalSec,
|
||||||
':last_status' => in_array($status, ['ok', 'warning', 'error'], true) ? $status : 'error',
|
':last_status' => $lastStatus,
|
||||||
':message' => $message,
|
':message' => $message,
|
||||||
':metrics' => $metricsJson,
|
':metrics' => $metricsJson,
|
||||||
':health' => $healthJson,
|
':health' => $healthJson,
|
||||||
|
|||||||
Reference in New Issue
Block a user