205 lines
12 KiB
Markdown
205 lines
12 KiB
Markdown
# Betriebsüberwachung, Bedienbarkeit, Nutzungsstatistik — Plan
|
||
|
||
**Stand:** 05.10.2026 · **Phase 1 live seit 05.10.2026 (v1.1.0)**
|
||
|
||
Ziel: Fehler fallen auf, bevor Kunden sie melden — Serverfehler aus der
|
||
Programmierung ebenso wie Bedienungs- und Usability-Probleme. Dazu eine
|
||
Nutzungsstatistik, aus der sich ablesen lässt, wer was tut und wo Arbeit
|
||
aufhört.
|
||
|
||
## Entscheidungen (Betreiber, 02.10.2026)
|
||
|
||
| Nr. | Frage | Entscheidung |
|
||
|---|---|---|
|
||
| M1 | Fehlerwerkzeug | **GlitchTip, selbst gehostet** (wie Umami auf sys.adametz.media), angebunden über die offiziellen Sentry-SDKs (`sentry/sentry-laravel`, `@sentry/browser`) |
|
||
| M2 | Umami im Portal | **ja, mit Zuordnung zum Nutzer** (`umami.identify` mit der internen User-ID, keine E-Mail, keine Namen) |
|
||
| M3 | Alarmweg | **Mail bleibt**, zusätzlich **ntfy** (HTTP-POST, JSON) |
|
||
|
||
Bei der Umsetzung getroffen (Bestätigung offen):
|
||
|
||
| Nr. | Frage | Entscheidung |
|
||
|---|---|---|
|
||
| M4 | Personenbezug in GlitchTip | Nur die interne User-ID, `send_default_pii` aus, Request-Bodys nie (`max_request_body_size = never`) |
|
||
| M5 | Browserfehler-SDK | Wird erst beim ersten Fehler nachgeladen (dynamischer Import) — kein Ladegewicht auf fehlerfreien Seiten, dafür ohne Breadcrumbs |
|
||
| M6 | Umami-Schalter | Getrennt: `UMAMI_ENABLED` (Webseiten), `UMAMI_PORTAL_ENABLED` (Kundenbereich), `UMAMI_IDENTIFY_USERS` (Zuordnung). Portal und Zuordnung erst nach DSE-Anpassung einschalten |
|
||
| M7 | Umami im Admin, unter Impersonation, auf Auth-Seiten | Nicht getrackt: Admin-Nutzung verfälscht die Kundenstatistik, Impersonation würde Admin-Klicks dem Kunden zuschreiben, Auth-Seiten tragen Tokens im Pfad (`/reset-password/{token}`) |
|
||
| M8 | Query-Parameter an Umami | Nur `utm_*`, `src`, `ref`, `q`, `page` gehen raus; alles andere (Signaturen, Tokens) filtert `data-before-send` — gilt auch für die Webseiten |
|
||
| M9 | Fehlgeschlagene Logins | Im Verlauf mit User-Bezug (falls Konto existiert) und gekürztem HMAC der IP statt Klartext-IP |
|
||
|
||
## Phase 1 — Fehler fallen sofort auf
|
||
|
||
| Schritt | Inhalt | Stand |
|
||
|---|---|---|
|
||
| 1.1 | GlitchTip-Anbindung Server: `sentry/sentry-laravel`, `Integration::handles()` in `bootstrap/app.php`, Release aus `SENTRY_RELEASE`, User-ID als einziger Personenbezug | erledigt |
|
||
| 1.2 | Browserfehler (JS-Fehler, unbehandelte Promises) auf Webseiten, Auth-Seiten und Portal; eigener Vite-Eintrag `resources/js/error-reporting.js`, SDK erst beim ersten Fehler | erledigt |
|
||
| 1.3 | Fehler ohne Exception als GlitchTip-Warnung: 404 mit Referrer der eigenen Domain (kaputte interne Links), 403 und 429 für angemeldete Nutzer, 419 (Seite abgelaufen, Arbeit verloren). Je Signal gedrosselt | erledigt |
|
||
| 1.4 | Scheduler-Herzschlag (`monitoring:heartbeat`, minütlich) und Check im Systemstatus | erledigt |
|
||
| 1.5 | Tiefer Health-Endpunkt `/health` mit Token: DB, Cache, Queue-Rückstau, Scheduler-Herzschlag, Plattenplatz → 200/503 JSON, für Uptime Kuma bzw. Ploi | erledigt |
|
||
| 1.6 | ntfy als zweiter Alarmweg in `OperationalAlert` | erledigt |
|
||
| 1.7 | Fehlgeschlagene Logins, Login-Sperren und 2FA-Fehler im Aktivitäts-Verlauf | erledigt |
|
||
| 1.8 | `queue:prune-failed` (30 Tage) im Scheduler | erledigt |
|
||
| 1.9 | Umami: Portal (Kundenbereich) mit `identify`, Query-Filter, Web Vitals; Schalter getrennt (M6) | erledigt |
|
||
|
||
Signale, die **nicht** im Browser erfasst werden, weil der Server sie schon
|
||
sieht: fehlgeschlagene Livewire-Requests mit 5xx (Exception in GlitchTip) und
|
||
419 (Schritt 1.3).
|
||
|
||
## Phase 2 — Bedienprobleme sichtbar machen
|
||
|
||
- Validierungsfehler zentral zählen: Formular/Komponente × Feld × Regel, nie der Wert.
|
||
- Schritt-Tracking für die Kernabläufe (Registrierung, Newsroom anlegen,
|
||
PM erstellen/einreichen, Checkout, Bildbestätigung): `step_viewed` /
|
||
`step_completed`, pseudonymer Ablauf-Schlüssel, eigene kleine Tabelle,
|
||
Schreiben per `defer()`.
|
||
- Benannte Umami-Events (`data-umami-event`) an den Kern-Buttons.
|
||
- „Problem melden“ im Portal: URL, Browser, letzte GlitchTip-Event-ID, Freitext.
|
||
- Geschäftstrichter aus vorhandenen Daten (Registrierung → erste PM →
|
||
eingereicht → bezahlt → veröffentlicht → zweite PM), ohne neues Tracking.
|
||
|
||
## Phase 3 — Rückgänge und Abwanderung erkennen
|
||
|
||
- Tägliche Kennzahlen-Tabelle (Datum × Kennzahl × Dimension → Anzahl), nächtlich verdichtet.
|
||
- Alarme bei ausbleibender Aktivität: Einlieferer ohne Einlieferung, Tage
|
||
ohne Registrierung/Login/Zahlung, Funktionen deutlich unter dem 4-Wochen-Mittel.
|
||
- Überfällig-Liste je Kunde (übliches PM-Intervall × 2).
|
||
- Admin-Seite „Betrieb & Nutzung“; `conversion:funnel` und `api:usage-report` mit Oberfläche.
|
||
- Morgendliche Sammelmail.
|
||
- Löschfristen für `activity_events` und `api_usage_logs` (Vorschlag 395 Tage).
|
||
|
||
## Datenschutz (Betreiber/Anwalt, vor dem Einschalten)
|
||
|
||
- DSE §5 (Umami): Portal-Nutzung mit Zuordnung zur Nutzer-ID ist personenbezogen
|
||
(pseudonym) — Abschnitt ergänzen, bevor `UMAMI_PORTAL_ENABLED`/`UMAMI_IDENTIFY_USERS` an.
|
||
- Neuer Abschnitt Fehlerprotokollierung (GlitchTip, selbst gehostet; User-ID,
|
||
URL, Browser, Fehlermeldung; Frist = GlitchTip-Aufbewahrung, Vorschlag 90 Tage).
|
||
- Aktivitäts-Verlauf mit fehlgeschlagenen Anmeldungen (IP nur als gekürzter HMAC).
|
||
- Unabhängig davon offen: `activity_events`/`api_usage_logs` ohne Löschfrist und ohne DSE-Erwähnung.
|
||
|
||
## Live-Schritte Phase 1
|
||
|
||
Ausgeführt mit Deploy v1.1.0 — vollständiger Ablauf in [`deploy-v1.1.0.md`](deploy-v1.1.0.md).
|
||
|
||
**Infrastruktur (einmalig)**
|
||
|
||
1. GlitchTip auf sys.adametz.media (Docker Compose: web, worker, Postgres,
|
||
Redis; `GLITCHTIP_MAX_EVENT_LIFE_DAYS=90`). Zwei Projekte anlegen:
|
||
`pressekonto-server` (PHP/Laravel) und `pressekonto-browser` (JavaScript).
|
||
Alert-Regel je Projekt: Mail an das Ops-Postfach bei neuem Fehlertyp.
|
||
2. ntfy: Server (ntfy.sh oder selbst gehostet), Topic mit Zugriffsschutz,
|
||
Token anlegen. Optional GlitchTip-Webhook an ntfy über dessen
|
||
Template-Funktion.
|
||
3. Uptime Kuma (oder Ploi-Monitor) auf `https://pressekonto.com/health` mit
|
||
Header `Authorization: Bearer <MONITORING_HEALTH_TOKEN>`, Intervall 1 min,
|
||
Benachrichtigung über ntfy und Mail.
|
||
|
||
**`.env` auf Live**
|
||
|
||
```
|
||
SENTRY_LARAVEL_DSN=… # Projekt pressekonto-server
|
||
SENTRY_ENVIRONMENT=production
|
||
SENTRY_RELEASE= # leer lassen, siehe RELEASE-Datei unten
|
||
SENTRY_BROWSER_DSN=… # Projekt pressekonto-browser
|
||
MONITORING_HEALTH_TOKEN=<zufällig, ≥ 32 Zeichen>
|
||
MONITORING_NTFY_URL=https://push.adametz.media
|
||
MONITORING_NTFY_TOPIC=pressekonto-ops
|
||
MONITORING_NTFY_TOKEN=…
|
||
UMAMI_ENABLED=true # Webseiten
|
||
UMAMI_PORTAL_ENABLED=false # erst nach DSE-Anpassung
|
||
UMAMI_IDENTIFY_USERS=false # erst nach DSE-Anpassung
|
||
```
|
||
|
||
Danach `config:cache`, `npm run build`, `queue:restart`.
|
||
|
||
**Bei jedem Deploy (SFTP/SSH, solange es kein Git-Deploy gibt):** lokal vor
|
||
dem Upload `git describe --tags --always --dirty > RELEASE` und die Datei `RELEASE`
|
||
mit hochladen, danach auf dem Server `php artisan config:cache`. GlitchTip
|
||
ordnet Fehler so dem Stand zu (`…-dirty` = mit nicht committeten Änderungen
|
||
deployt). Beim späteren Git-Deploy schreibt das Deploy-Skript dieselbe Datei.
|
||
|
||
GlitchTip-Instanz: `errors.adametz.media`, Projekt 1 = Server, Projekt 2 = Browser.
|
||
Umgebungen: `production` (Live), `local` (Entwicklung).
|
||
|
||
## Umsetzungsprotokoll
|
||
|
||
### 02.10.2026 — Phase 1 umgesetzt (Branch `technik/monitoring-phase-1`)
|
||
|
||
Neue Abhängigkeiten: `sentry/sentry-laravel` ^4.28 (Composer, nur neue
|
||
Pakete), `@sentry/browser` ^11.3 (npm).
|
||
|
||
| Schritt | Dateien |
|
||
|---|---|
|
||
| 1.1 | `config/sentry.php` (veröffentlicht, gehärtet), `app/Support/Monitoring/ErrorReportScrubber.php`, `bootstrap/app.php` (`Integration::handles`) |
|
||
| 1.2 | `resources/js/error-reporting.js` + `error-reporting-sdk.js` (Chunk 32 kB gzip, nur bei Fehler), `resources/views/components/monitoring/browser-errors.blade.php`, eingebunden in `partials/head` (Portal), `web/layouts/web-master`, `components/layouts/auth/pressekonto`; Vite-Einträge in beiden Configs |
|
||
| 1.3 | `app/Support/Monitoring/ErrorSignal.php`, `app/Http/Middleware/ReportErrorSignals.php` (global) |
|
||
| 1.4 | `app/Console/Commands/MonitoringHeartbeat.php`, Scheduler minütlich, Check `scheduler` im Systemstatus |
|
||
| 1.5 | `app/Services/Monitoring/HealthProbe.php`, `app/Http/Controllers/HealthController.php`, Route `/health` (ohne Gruppe, throttle 30/min), Basic-Auth-Ausnahme; Check `disk` im Systemstatus |
|
||
| 1.6 | `app/Support/OperationalAlert.php` (Mail und ntfy unabhängig, beide Best-Effort) |
|
||
| 1.7 | `ActivityType::LoginFailed/LoginLockout/TwoFactorFailed`, `RecordAuthActivity` (Grund `unknown_account`/`no_password`/`wrong_password`, `ip_hash`), Login-Komponente feuert `Failed` und `Lockout` mit Adresse |
|
||
| 1.8 | `queue:prune-failed --hours=720` täglich 03:55 |
|
||
| 1.9 | `components/web/umami.blade.php` (Web + Portal, `data-tag`, `data-before-send`, `data-performance`, `identify('user-<id>')`), `config/services.php` (`portal_enabled`, `identify_users`) |
|
||
|
||
Nebenbefund: Das Umami-Snippet der Webseiten schickte bisher die volle Query
|
||
mit — signierte Claim-Links (`?signature=…`) landeten so in Umami. Der Filter
|
||
(M8) gilt jetzt auch dort.
|
||
|
||
Tests: `tests/Feature/Monitoring/*` (neu), `tests/Feature/Auth/FailedLoginActivityTest.php`
|
||
(neu), `OperationalAlertTest` und `UmamiSnippetTest` erweitert. Gesamtsuite
|
||
2312 grün (1 übersprungen), Larastan ohne Meldung, Pint sauber.
|
||
|
||
Nicht im Browser geprüft (kein GlitchTip/ntfy angebunden): das tatsächliche
|
||
Einliefern an GlitchTip und die Darstellung dort. Erster Test nach dem
|
||
Aufsetzen: im Browser `setTimeout(() => { throw new Error('GlitchTip-Test') })`
|
||
auf einer Portalseite, serverseitig `php artisan tinker --execute 'report(new RuntimeException("GlitchTip-Test"));'`.
|
||
|
||
### 05.10.2026 — Composer-Update
|
||
|
||
`composer update` innerhalb der bestehenden Grenzen (48 Updates, u. a.
|
||
Laravel 12.69.3, Livewire 4.4.7, Flux 2.20.1, Fortify 1.40.0); damit sind die
|
||
beiden `league/commonmark`-Advisories behoben (2.10.3), `composer audit` sauber.
|
||
|
||
- Livewire 4.4.7 rendert `ModelNotFoundException` im Testhelfer als 404,
|
||
statt sie zu werfen — zwei Tests auf `assertNotFound()` umgestellt
|
||
(`CustomerCompanyContextTest`, `ContactAccessTest`). Laufzeitverhalten unverändert.
|
||
- **Deploy:** Liegt auf Live `public/vendor/livewire/` (veröffentlichte Assets),
|
||
nach `composer install` `php artisan vendor:publish --tag=livewire:assets --force`
|
||
ausführen — sonst läuft ein altes `livewire.js` gegen das neue Backend.
|
||
Lokal war die Kopie schon vor dem Update veraltet (Hash 3dabe98a statt 8ea5922c).
|
||
|
||
### 05.10.2026 — GlitchTip angebunden (lokal)
|
||
|
||
DSNs eingetragen (errors.adametz.media, Projekte 1 und 2), lokal
|
||
`SENTRY_ENVIRONMENT=local`. Release-Kennung ohne Git auf dem Server:
|
||
`App\Support\Monitoring\Release` liest `SENTRY_RELEASE`, sonst die beim
|
||
Deploy hochgeladene Datei `RELEASE` (in `.gitignore`). Test-Events lokal
|
||
abgeschickt: Server-Exception (`report()`), toter interner Link per HTTP.
|
||
|
||
### 05.10.2026 — ntfy angebunden (lokal)
|
||
|
||
ntfy auf `push.adametz.media` (gleicher Server wie GlitchTip),
|
||
`auth-default-access: deny-all` (anonymes Lesen → 403 geprüft). Eigener
|
||
Benutzer `pressekonto-alerts` mit `write-only` auf `pressekonto-ops`, Token in
|
||
`MONITORING_NTFY_TOKEN`; Lesen über den persönlichen Benutzer in der App.
|
||
Test-Alert aus `OperationalAlert` ohne Fehler angenommen.
|
||
|
||
### 05.10.2026 — Versionsnummer
|
||
|
||
Versionen sind Git-Tags nach SemVer (`v1.0.0` Go-Live 20.07., nächster Deploy
|
||
`v1.1.0`). `config('app.version')` = `APP_VERSION` oder Datei `RELEASE`
|
||
(`git describe --tags --always --dirty`); dieselbe Kennung als GlitchTip-Release,
|
||
in `/health` (`version`), im Admin-Systemstatus und in `php artisan about`.
|
||
Grundlage für die spätere Changelog-Seite.
|
||
|
||
### 05.10.2026 — Phase 1 live (v1.1.0)
|
||
|
||
Mit Deploy v1.1.0 ausgeliefert ([`deploy-v1.1.0.md`](deploy-v1.1.0.md)).
|
||
GlitchTip (`production`), ntfy und Scheduler-Herzschlag auf Live aktiv.
|
||
Offen aus Phase 1: externer Monitor auf `/health`; DSE-Absätze, danach
|
||
`UMAMI_PORTAL_ENABLED`/`UMAMI_IDENTIFY_USERS`.
|
||
|
||
### 06.10.2026 — Erste Auswertung von GlitchTip
|
||
|
||
Lesezugriff per API eingerichtet; Arbeitsanleitung, Regeln und Protokoll der
|
||
Einträge in [`../glitch-tip/README.md`](../glitch-tip/README.md). Befunde:
|
||
Testsuite meldete an die echte DSN (behoben, `2f02acc`), Search-Console-Lauf
|
||
robuster (`a90f24a`), Registrierung mit Alpine-Fehler, Promise-Ablehnungen
|
||
lesbar, Tippfehler bei artisan nicht mehr gemeldet (`16af374`).
|