presseportale/dev/glitch-tip/README.md
2026-10-06 13:17:19 +00:00

191 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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` | `<meta name="pk-error-reporting">` 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/<ID>/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/<ID>/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 erhöhen). Ablauf seit `v1.1.1`: Git-Deploy per Tag, [`../betrieb/git-deploy.md`](../betrieb/git-deploy.md) Abschnitte C und D (Assets bauen und committen, Tag, Push, auf dem Server `deploy.sh`).
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.
Seit dem Git-Deploy schreibt `dev/betrieb/deploy.sh` sie beim Deploy (`echo "$tag" > RELEASE`),
GlitchTip zeigt also genau den ausgelieferten Tag. Bei den SFTP-Deploys bis
`v1.1.0` wurde sie von Hand erzeugt; daher stammen Releases wie
`v1.1.0-3-gb1cec16` in älteren Einträgen.
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 `<input>` → 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 |