From 9e35c73d2383be15afec91af1b80d515eedec673 Mon Sep 17 00:00:00 2001 From: Kevin Adametz Date: Tue, 6 Oct 2026 13:12:41 +0000 Subject: [PATCH] =?UTF-8?q?dev:=20GlitchTip-Dokumentation=20(dev/glitch-ti?= =?UTF-8?q?p)=20=E2=80=94=20=C3=9Cberblick,=20Meldewege,=20API-Abruf,=20Ab?= =?UTF-8?q?lauf=20zur=20Fehlerbehebung,=20Regeln,=20Protokoll=20der=20Eint?= =?UTF-8?q?r=C3=A4ge;=20Verweis=20in=20CLAUDE.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 10 ++ dev/betrieb/monitoring-plan.md | 8 ++ dev/glitch-tip/README.md | 190 +++++++++++++++++++++++++++++++++ 3 files changed, 208 insertions(+) create mode 100644 dev/glitch-tip/README.md diff --git a/CLAUDE.md b/CLAUDE.md index 8ff6419c..6082b40d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -162,6 +162,16 @@ Also relevant: `percona-toolkit` on the server, which is deliberately **not** part of the standard setup. +### Fehlerprotokoll (GlitchTip) and operations + +Errors from production are collected in a self-hosted GlitchTip +(`errors.adametz.media`, project 1 = server, 2 = browser). When asked to +look at GlitchTip or at production errors, follow +`dev/glitch-tip/README.md` — it contains the read-only API commands +(token `GLITCHTIP_API_TOKEN` in `.env`, never print it), the triage +workflow and the privacy rules for error reporting. Monitoring decisions: +`dev/betrieb/monitoring-plan.md`; deploys: `dev/betrieb/deploy-runbook.md`. + ### Testing Framework - **Pest PHP** (v3.8+) - Modern testing framework built on PHPUnit diff --git a/dev/betrieb/monitoring-plan.md b/dev/betrieb/monitoring-plan.md index 397c2caf..153e9d13 100644 --- a/dev/betrieb/monitoring-plan.md +++ b/dev/betrieb/monitoring-plan.md @@ -195,3 +195,11 @@ 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`). diff --git a/dev/glitch-tip/README.md b/dev/glitch-tip/README.md new file mode 100644 index 00000000..2db83d01 --- /dev/null +++ b/dev/glitch-tip/README.md @@ -0,0 +1,190 @@ +# GlitchTip — Fehlerprotokoll von Pressekonto + +**Stand:** 06.10.2026 · **Gilt ab:** v1.1.0 (live seit 05.10.2026) +**Für:** KI-Chats und Entwickler, die Fehler aus GlitchTip auswerten und beheben. + +Dieses Dokument beschreibt, **was** GlitchTip hier ist, **wie** Fehler dorthin +gelangen, **was zu tun ist**, wenn jemand sagt „schau mal in GlitchTip“, und +welche Regeln dabei nicht verletzt werden dürfen. Hintergrund und +Entscheidungen: [`../betrieb/monitoring-plan.md`](../betrieb/monitoring-plan.md) +(Phase 1, Entscheidungen M1–M9). + +--- + +## 1. Überblick + +| Was | Wert | +|---|---| +| Instanz | `https://errors.adametz.media` (selbst gehostet, Sentry-kompatibel) | +| Organisation (Slug) | `adametz-media` | +| Projekt 1 | `server-pressekonto` — PHP/Laravel: Exceptions und Fehlersignale | +| Projekt 2 | `browser-pressekonto` — JavaScript-Fehler aus dem Browser | +| Umgebungen | `production` (Live), `local` (Entwicklung). Nur `production` ist Betrieb | +| Release | Git-Tag aus der Datei `RELEASE` (z. B. `v1.1.0`), siehe Abschnitt 6 | +| Aufbewahrung | 90 Tage (`GLITCHTIP_MAX_EVENT_LIFE_DAYS` auf dem GlitchTip-Server) | +| Alarmierung | GlitchTip-Alert-Regel: Mail bei neuem Fehlertyp. Betriebs-Alerts der App (fehlgeschlagene Jobs/Tasks) laufen getrennt über Mail + ntfy (`push.adametz.media`, Topic `pressekonto-ops`) | + +GlitchTip ist **nicht** das einzige Signal. Daneben: `/health` (externer +Monitor), Admin ▸ Reports ▸ Betriebsüberwachung, `storage/logs/*.log`. + +--- + +## 2. Wie Fehler nach GlitchTip gelangen + +### Server (Projekt 1) + +| Weg | Datei | Was | +|---|---|---| +| Gemeldete Exceptions | `bootstrap/app.php` → `Integration::handles($exceptions)` | Alles, was Laravel über `report()` meldet — unbehandelte Exceptions in Requests, Jobs, Commands | +| Fehlersignale ohne Exception | `app/Http/Middleware/ReportErrorSignals.php`, `app/Support/Monitoring/ErrorSignal.php` | Warnungen: 404 mit eigenem Referrer („Toter interner Link“), 403/429 für Angemeldete, 419 („Seite abgelaufen“). Je Signal und Ort höchstens 5 Meldungen in 10 Minuten | +| Konfiguration | `config/sentry.php` | DSN, Release, Datenschutz-Optionen | +| Datenschutzfilter | `app/Support/Monitoring/ErrorReportScrubber.php` (`before_send`) | Nur User-ID, Tokens/Signaturen aus URL/Query/Referer, E-Mails aus Meldungen | + +**Bewusst nicht gemeldet:** +- Laravels Standard-`dontReport`: 404, Validierung, Authentifizierung, 419 als Exception (419 kommt als Fehlersignal). +- `CommandNotFoundException` — vertippte `artisan`-Befehle (`bootstrap/app.php`). +- `App\Services\SearchConsole\SearchConsoleUnavailable` — Google hängt vorübergehend; nur `Log::warning`, der Lauf alarmiert erst bei Abbruch oder ≥ 50 % Fehlern. + +### Browser (Projekt 2) + +| Datei | Was | +|---|---| +| `resources/js/error-reporting.js` | Hört auf `error` und `unhandledrejection`; lädt das SDK erst beim ersten Fehler; filtert URLs/E-Mails; Ablehnungen ohne `Error` werden mit Typ und Inhalt beschrieben | +| `resources/js/error-reporting-sdk.js` | Re-Export der benötigten SDK-Funktionen (Tree-Shaking) | +| `resources/views/components/monitoring/browser-errors.blade.php` | `` mit DSN, Release, User-ID, Bereich; nur mit `SENTRY_BROWSER_DSN` | +| Eingebunden in | `partials/head` (Portal, `area: portal`), `web/layouts/web-master` (`area: web`), `components/layouts/auth/pressekonto` (`area: auth`) | + +Fehlgeschlagene Livewire-Requests (5xx, 419) meldet der **Server**, nicht der Browser. + +### Tests melden nie an GlitchTip + +`phpunit.xml` leert `SENTRY_LARAVEL_DSN`, `SENTRY_DSN`, `SENTRY_BROWSER_DSN`. +Wächtertest: `tests/Feature/Monitoring/ErrorReportingTest.php` („the test suite +never reports to the real glitchtip“). Tests, die Sentry-Ereignisse prüfen, +benutzen `Tests\Support\FakeGlitchTip::install()`. + +**Erkennungszeichen für Test-Rauschen** in GlitchTip: Umgebung `local` und +Texte wie „Queue weg“, „Datenbank nicht erreichbar“, SQLite-Fehler +(„General error: 1 no such table“ — Live ist MySQL), „no role named +`customer`“. Bis 06.10.2026 kamen solche Einträge aus Testläufen mit lokaler DSN. + +--- + +## 3. Zugriff per API (lesend) + +Token: `GLITCHTIP_API_TOKEN` in der lokalen `.env` — **nur lesen** +(`project:read`, `event:read`). Kein `org:read` (Organisationsliste → 403), +kein Schreiben (Auflösen → 403). Den Token nie ausgeben, nie committen. + +Alle Befehle aus dem Projektverzeichnis: + +```bash +T=$(grep -E "^GLITCHTIP_API_TOKEN=" .env | cut -d= -f2- | tr -d "'\"") +B=https://errors.adametz.media/api/0 +``` + +**Offene Einträge eines Projekts** (Projekt 1 = Server, 2 = Browser; `environment=production` für Live, weglassen für alle): + +```bash +curl -s -H "Authorization: Bearer $T" \ + "$B/organizations/adametz-media/issues/?project=1&query=is:unresolved&environment=production&limit=100" \ +| python3 -c " +import json,sys +for i in json.load(sys.stdin): + print(i['id'], i['level'], i['count'], i['firstSeen'][:16], i['lastSeen'][:16], '|', (i['title'] or '')[:100])" +``` + +**Letztes Ereignis eines Eintrags** mit Umgebung, Release, URL und Stacktrace: + +```bash +curl -s -H "Authorization: Bearer $T" "$B/issues//events/latest/" | python3 -c " +import json,sys +e=json.load(sys.stdin); tags={t['key']:t['value'] for t in e.get('tags',[])} +print('env:',tags.get('environment'),'| release:',tags.get('release'),'| area:',tags.get('area'),'| browser:',tags.get('browser')) +for en in e.get('entries',[]): + if en['type']=='exception': + for v in en['data'].get('values',[]): + print('EXC',v.get('type'),':',(v.get('value') or '')[:300]) + for f in ((v.get('stacktrace') or {}).get('frames') or [])[-8:]: + print(' ',f.get('filename') or f.get('absPath'),f.get('function'),f.get('lineNo'),f.get('colNo')) + if en['type']=='request': print('REQ',en['data'].get('url'))" +``` + +**Mehrere Ereignisse eines Eintrags:** `$B/issues//events/?limit=10`. +**Projekte:** `$B/projects/`. + +Weboberfläche: `https://errors.adametz.media/adametz-media/issues?project=1`. + +--- + +## 4. Aufgabe: GlitchTip auswerten und Fehler beheben + +Wenn der Auftrag lautet „schau in GlitchTip“, „was sagt GlitchTip“ o. Ä.: + +1. **Abrufen.** Offene Einträge beider Projekte, zuerst `environment=production`, dann ohne Filter. +2. **Einordnen** — jeder Eintrag in genau eine Klasse: + + | Klasse | Erkennen | Vorgehen | + |---|---|---| + | Test-/Entwicklungsrauschen | Umgebung `local`, Texte aus Tests (Abschnitt 2) | Nichts am Code; Betreiber markiert „Resolved“. Taucht es mit `production` auf → echter Fehler | + | Bedienfehler am Server | z. B. `CommandNotFoundException` | In `dontReport` aufnehmen, falls wiederkehrend | + | Externer Dienst vorübergehend | Timeouts/5xx von Google, OpenAI, Stripe … | Retry + Warnung statt `report()`, Abbruch- und Zeitbudget; Muster: `SearchConsoleClient` + `SearchConsoleUnavailable` | + | Echter Bug | Stacktrace in `app/`, `resources/`, Browserfehler mit eigener Datei/URL | Schritte 3–6 | + | Unklar | z. B. `Error: [object Object]` | Erfassung verbessern (mehr Kontext, keine personenbezogenen Daten), auf das nächste Auftreten warten | + +3. **Code finden.** Stacktrace (`filename`, `function`, `lineNo`), `culprit`, URL (`REQ`), Bereich (`area`). Bei Browserfehlern zeigen die Frames oft nur `livewire.min.js` — der Hinweis steckt dann im Funktionsnamen (z. B. `[Alpine] __('…')` = Alpine-Ausdruck) → in `resources/views` nach dem Ausdruck suchen. +4. **Reproduzieren mit rotem Test**, dann Fix (Projektregel: jede Änderung mit Test). Ist der Fehler ein Muster, das wieder auftreten kann, einen **Wächtertest** über alle Dateien schreiben (Beispiele: `BladePhpBlockGuardTest`, `BladeAlpineTranslationGuardTest`). +5. **Qualitätsgates:** betroffene Tests, dann `php artisan test --compact --parallel`, `vendor/bin/phpstan analyse --memory-limit=2G`, `vendor/bin/pint --dirty --format agent`; bei JS `npm run build`. +6. **Dokumentieren:** Eintrag in Abschnitt 7 (ID, Klasse, Ursache, Fix, Commit). Commit-Nachricht nennt die GlitchTip-ID. +7. **Ausliefern** über den nächsten Deploy (Hotfix: letzte Versionsstelle, z. B. `v1.1.1`; Ablauf wie [`../betrieb/deploy-v1.1.0.md`](../betrieb/deploy-v1.1.0.md)). +8. **Auflösen** in GlitchTip macht der Betreiber (Token ist nur lesend) — erst nach dem Deploy. Kommt der Fehler mit neuem Release wieder, öffnet GlitchTip den Eintrag erneut. + +--- + +## 5. Regeln, die nicht verletzt werden dürfen + +- **Datenschutz (M4):** `send_default_pii` bleibt `false`, `max_request_body_size` bleibt `never`, Log- und Cache-Breadcrumbs bleiben aus, `ErrorReportScrubber` bleibt in `before_send`. Kein Name, keine E-Mail, keine IP an GlitchTip — nur die interne User-ID. Neue Query-Parameter, die mitgehen sollen, nur über `ErrorReportScrubber::AllowedQueryParameters` (und dieselbe Liste in `error-reporting.js` / Umami-Filter). +- **Keine variablen Zahlen in Fehlermeldungen**, wenn sie gruppiert werden sollen: GlitchTip gruppiert nach Text. `…after 20002 ms` und `…after 20001 ms` wurden drei getrennte Einträge (Issues 11, 13, 14). Variable Werte in den Kontext, nicht in die Meldung — oder einen Fingerprint setzen wie `ErrorSignal`. +- **Vorübergehende Fremdfehler nicht einzeln melden.** Sonst flutet ein Google-Ausfall GlitchTip und verdeckt echte Fehler. +- **Tests melden nie an GlitchTip** (Abschnitt 2). Neue Sentry-Tests nur mit `FakeGlitchTip`. +- **Token nur lesend.** Für Schreibrechte (Auflösen per API) erst mit dem Betreiber sprechen. + +--- + +## 6. Konfiguration + +| Variable | Wo | Wert | +|---|---|---| +| `SENTRY_LARAVEL_DSN` | `.env` | DSN Projekt 1 | +| `SENTRY_BROWSER_DSN` | `.env` | DSN Projekt 2 (öffentlich, steht im HTML) | +| `SENTRY_ENVIRONMENT` | `.env` | `production` (Live) bzw. `local` | +| `SENTRY_RELEASE` / `APP_VERSION` | `.env` | leer lassen — kommt aus der Datei `RELEASE` | +| `GLITCHTIP_API_TOKEN` | nur lokale `.env` | Lese-Token für Abschnitt 3 | + +**Release:** `App\Support\Monitoring\Release::current()` liest `APP_VERSION` +bzw. `SENTRY_RELEASE`, sonst die Datei `RELEASE` im Projektwurzelverzeichnis. +Die Datei entsteht beim Paketbau **auf dem Tag** (`git describe --tags --always > RELEASE`) +und wird mit ausgeliefert. Nicht später auf einem anderen Stand neu erzeugen: +dann steht z. B. `v1.1.0-3-gb1cec16` statt `v1.1.0` (so am 05./06.10. auf Live). +Nach Änderung: `php artisan config:cache`. + +--- + +## 7. Protokoll der Einträge + +Stand 06.10.2026 (Abruf per API). „Fix“ = im Code behoben; ausgeliefert mit dem nächsten Deploy. + +| ID | Projekt | Umgebung | Eintrag | Klasse | Ursache / Maßnahme | Stand | +|---|---|---|---|---|---|---| +| 1 | Server | local | GlitchTip-Test Server | Test | Anbindungstest 05.10. | auflösen | +| 8 | Server | production | Deploy-Test v1.1.0 | Test | Deploy-Prüfung 05.10. | auflösen | +| 3, 16 | Server | local | Queue weg | Test-Rauschen | `ExpireGrandfatheringTest` meldete an echte DSN | Fix `2f02acc`, auflösen | +| 5, 17 | Server | local | Datenbank nicht erreichbar | Test-Rauschen | `PressReleaseSchedulingTest` | Fix `2f02acc`, auflösen | +| 4 | Server | local | no such table: conversion_events | Test-Rauschen | SQLite im Test | Fix `2f02acc`, auflösen | +| 6 | Server | local | Wasserstandsmarke steht auf 1 statt 0 | Test-Rauschen | Konkurrenztest der Aufruf-Aggregation | Fix `2f02acc`, auflösen | +| 7 | Server | local | no role named `customer` | Test-Rauschen | Tests ohne Rollen-Seeder | Fix `2f02acc`, auflösen | +| 9 | Server | production | Command "config:cachels" is not defined | Bedienfehler | Tippfehler auf dem Server | `dontReport` (16af374); auflösen | +| 11, 13, 14 | Server | production | cURL error 28 … searchconsole.googleapis.com | Extern | Google URL Inspection hing; jeder Timeout einzeln gemeldet, Lauf 1¾ h | Fix a90f24a: Retry, Warnung statt Meldung, Abbruch nach 5 in Folge, 40-min-Budget; nach Deploy auflösen | +| 12 | Server | production | URL Inspection API antwortet mit 500 | Extern | Google-interner Fehler | wie 11 | +| 15 | Browser | production | ReferenceError: __ is not defined | **Bug** | `/register`: `:placeholder="__('…')"` an `` → Alpine wertet PHP-Helfer im Browser aus | Fix 16af374: `placeholder="{{ __('…') }}"`, Wächtertest `BladeAlpineTranslationGuardTest`; nach Deploy auflösen | +| 10 | Browser | production | Error: [object Object] | Unklar | Promise-Ablehnung mit Objekt auf `businessportal24.com/de` | Erfassung verbessert (16af374, Typ + Inhalt); nach Deploy neues Auftreten abwarten |