mivita/dev/git-deploy-monitoring/02-fehlerprotokoll-glitchtip.md
Kevin e1c6178bc7 Git-Deploy per Tag: deploy.sh, Version aus RELEASE, Worker-Pruefung
- dev/betrieb/deploy.sh: Tag auschecken, composer, Caches leeren (bewusst
  kein route:/config:cache wegen hostabhaengiger Routen und env()-Aufrufen),
  horizon:terminate, FPM-Reload, Worker-Pruefung am Ende
- dev/betrieb/README.md: Einrichtung Server, Umstellung des Live-Verzeichnisses,
  Release, Deploy, Rollback
- config('app.version') aus RELEASE, php artisan about --only=mivita
- monitoring:queue-probe prueft nach dem Deploy, ob Horizon Jobs abholt
- Vorlage dev/git-deploy-monitoring aus Pressekonto

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 10:11:34 +00:00

10 KiB

02 — Fehlerprotokoll mit GlitchTip

GlitchTip ist ein selbst gehostetes, Sentry-kompatibles Fehlerprotokoll. Die App spricht es über die offiziellen Sentry-SDKs an (sentry/sentry-laravel, @sentry/browser); ohne DSN sind beide ein No-op. Gleiche Fehler werden zu einem Eintrag gruppiert (Häufigkeit, betroffene URLs, Release), gemeldet wird nur ein neuer Fehlertyp.


1. GlitchTip-Server (einmalig, für alle Projekte)

Vorlage: vorlagen/server/glitchtip-compose.yml (GlitchTip 6, Postgres 18, Valkey; gegen die offizielle compose.sample.yml abgleichen).

  1. SECRET_KEY (openssl rand -hex 32), GLITCHTIP_DOMAIN, EMAIL_URL, DEFAULT_FROM_EMAIL setzen. Aufbewahrung: GLITCHTIP_RETENTION_DAYS (Standard 90 — ältere Versionen hießen GLITCHTIP_MAX_EVENT_LIFE_DAYS).
  2. docker compose -f glitchtip-compose.yml up -d, Reverse Proxy mit TLS (vorlagen/server/Caddyfile).
  3. Erstes Konto anlegen, danach ENABLE_USER_REGISTRATION: "False", neu starten.
  4. Organisation anlegen (Slug merken, z. B. meine-org).

2. Je Projekt: zwei GlitchTip-Projekte

Projekt Plattform .env
<app>-server PHP / Laravel SENTRY_LARAVEL_DSN
<app>-browser JavaScript SENTRY_BROWSER_DSN

Getrennt, weil Browserfehler (Erweiterungen, alte Browser) eine andere Qualität haben als Serverfehler. Je Projekt eine Alert-Regel: Mail bei neuem Fehlertyp, wenn möglich nur für Umgebung production.

Umgebungen: SENTRY_ENVIRONMENT=production auf Live, local lokal. Lokal die DSN nur zum Testen setzen.


3. Laravel-Anbindung (Server)

composer require sentry/sentry-laravel

Dateien aus vorlagen/laravel/ kopieren:

Datei Zweck
config/sentry.php gehärtete Konfiguration (ersetzt die veröffentlichte)
app/Support/Monitoring/ErrorReportScrubber.php before_send: nur User-ID, Tokens/Signaturen aus URL/Query/Referer, E-Mails aus Meldungen
app/Support/Monitoring/Release.php Version aus APP_VERSION oder Datei RELEASE
app/Support/Monitoring/ErrorSignal.php Warnung ohne Exception, gedrosselt (5 je 10 min und Ort)
app/Http/Middleware/ReportErrorSignals.php 404 mit eigenem Referrer, 403/429 für Angemeldete, 419

bootstrap/app.php:

use App\Http\Middleware\ReportErrorSignals;
use Sentry\Laravel\Integration;
use Symfony\Component\Console\Exception\CommandNotFoundException;

    ->withMiddleware(function (Middleware $middleware): void {
        // Fehler ohne Exception (toter interner Link, 403/429 für Angemeldete, 419) —
        // global, weil 404 vor dem Routing entsteht
        $middleware->append(ReportErrorSignals::class);
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        // Gemeldete Exceptions an GlitchTip (ohne DSN ein No-op);
        // Laravels dontReport (404, Validierung, Auth) gilt weiter
        Integration::handles($exceptions);

        // Vertippte artisan-Befehle sind kein Anwendungsfehler
        $exceptions->dontReport(CommandNotFoundException::class);
    })

config/app.php:

'version' => App\Support\Monitoring\Release::current(env('APP_VERSION')),

config/sentry.php liest release aus derselben Quelle.

Was die gehärtete config/sentry.php anders macht

Option Wert Warum
send_default_pii false (fest) keine IP, keine Cookies, keine Header mit Personenbezug
max_request_body_size never Formular- und JSON-Inhalte verlassen den Server nie
before_send ErrorReportScrubber::beforeSend User-ID statt Person, Tokens raus, E-Mails → [email]
breadcrumbs.logs, breadcrumbs.cache false Lognachrichten tragen oft E-Mail-Adressen
enable_metrics false versteht GlitchTip nicht
ignore_transactions /up, /health Monitor-Aufrufe sind kein Signal

Je Projekt anpassen: ErrorReportScrubber::AllowedQueryParameters — welche Query-Parameter unverändert mitgehen dürfen (utm_* immer). Dieselbe Liste steht in error-reporting.js.


4. Browserfehler

npm install @sentry/browser
Datei Zweck
resources/js/error-reporting.js hört auf error und unhandledrejection; lädt das SDK erst beim ersten Fehler (1,5 kB statt 32 kB auf fehlerfreien Seiten); filtert URLs und E-Mails
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

Vite-Eingang ergänzen (vite.config.js, input):

input: ['resources/css/app.css', 'resources/js/app.js', 'resources/js/error-reporting.js'],

Im <head> jedes Layouts (Build-Ordner und Bereich anpassen):

<x-monitoring.browser-errors build="build" area="web" />

area erscheint in GlitchTip als Tag (z. B. web, portal, auth).

config/monitoring.php aus der Vorlage liefert monitoring.browser.dsn.


5. Tests — nie an das echte GlitchTip melden

phpunit.xml:

<!-- Tests provozieren Fehler absichtlich — nie an das echte GlitchTip -->
<env name="SENTRY_LARAVEL_DSN" value="" force="true"/>
<env name="SENTRY_DSN" value="" force="true"/>
<env name="SENTRY_BROWSER_DSN" value="" force="true"/>

Tests aus der Vorlage: tests/Feature/Monitoring/ErrorReportingTest.php (Datenschutzfilter, Wächtertest „the test suite never reports to the real glitchtip“), ErrorSignalsTest.php, BrowserErrorReportingTest.php, tests/Unit/Monitoring/ReleaseTest.php. Ereignisse fängt tests/Support/FakeGlitchTip.php ab (FakeGlitchTip::install()).

Die Tests setzen RefreshDatabase voraus (in tests/Pest.php aktivieren) und eine User-Factory.


6. Regeln für den Code

  • Keine variablen Zahlen in Fehlermeldungen, wenn sie gruppiert werden sollen — GlitchTip gruppiert nach Text. Variable Werte in den Kontext oder einen Fingerprint setzen (Muster: ErrorSignal).

  • Vorübergehende Fremdfehler nicht einzeln melden (Timeouts, 5xx, 429 von externen APIs): einmal wiederholen, dann Log::warning; Alarm erst, wenn ein Lauf abbricht oder die Mehrheit scheitert. Muster:

    try {
        $response = Http::timeout(20)
            ->retry(2, 3000, fn (Throwable $e) => $e instanceof ConnectionException
                || ($e instanceof RequestException && ($e->response->serverError() || $e->response->status() === 429)),
                throw: false)
            ->post($url, $payload);
    } catch (ConnectionException $e) {
        throw new ServiceTemporarilyUnavailable('… nicht erreichbar', previous: $e);   // eigene Klasse
    }
    // Aufrufer: catch (ServiceTemporarilyUnavailable) → Log::warning, sonst report()
    
  • Keine Personenbezüge in Meldungen bauen; der Filter ist das Netz, nicht der Plan.


7. Auswertung per API (für Entwickler und KI-Chats)

Lese-Token in GlitchTip (Profil ▸ Auth Tokens): Scopes project:read, event:read — kein Schreibrecht. Lokal als GLITCHTIP_API_TOKEN in die .env, nie ausgeben, nie committen. Ohne org:read liefert die Organisationsliste 403; Projekte und Issues funktionieren.

T=$(grep -E "^GLITCHTIP_API_TOKEN=" .env | cut -d= -f2- | tr -d "'\"")
B=https://errors.example.com/api/0
ORG=meine-org

# Projekte (IDs)
curl -s -H "Authorization: Bearer $T" "$B/projects/" | python3 -c "
import json,sys; [print(p['id'], p['slug']) for p in json.load(sys.stdin)]"

# Offene Einträge eines Projekts, nur Live
curl -s -H "Authorization: Bearer $T" \
  "$B/organizations/$ORG/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['count'], i['firstSeen'][:16], i['lastSeen'][:16], '|', (i['title'] or '')[:100])"

# Letztes Ereignis: Umgebung, Release, URL, Stacktrace
curl -s -H "Authorization: Bearer $T" "$B/issues/<ID>/events/latest/" | python3 -c "
import json,sys
e=json.load(sys.stdin); t={x['key']:x['value'] for x in e.get('tags',[])}
print('env:',t.get('environment'),'| release:',t.get('release'),'| area:',t.get('area'))
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'),f.get('function'),f.get('lineNo'))
    if en['type']=='request': print('REQ',en['data'].get('url'))
    if en['type']=='breadcrumbs':
        for b in en['data'].get('values',[])[-6:]: print('BC',b.get('category'),(b.get('message') or '')[:120])"

Ablauf „schau in GlitchTip“

  1. Offene Einträge beider Projekte abrufen, zuerst environment=production.

  2. Einordnen:

    Klasse Erkennen Vorgehen
    Test-/Entwicklungsrauschen Umgebung local, Texte aus Tests nichts am Code; auflösen
    Bedienfehler am Server z. B. CommandNotFoundException dontReport
    Externer Dienst vorübergehend Timeouts/5xx fremder APIs Muster aus Abschnitt 6
    Echter Bug Stacktrace in app/, resources/, Browserfehler mit eigener URL roter Test → Fix → Deploy
    Unklar z. B. Error: [object Object] Erfassung verbessern, nächstes Auftreten abwarten
  3. Bei Browserfehlern zeigen die Frames oft nur livewire.min.js — der Hinweis steckt im Funktionsnamen ([Alpine] __('…') = Alpine-Ausdruck) → in resources/views danach suchen. Kann ein Muster wiederkommen: Wächtertest über alle Dateien.

  4. Fix ausliefern (Hotfix-Tag), danach in GlitchTip auflösen (von Hand — Token ist nur lesend). Kommt der Fehler mit neuem Release wieder, öffnet GlitchTip den Eintrag neu.

  5. Breadcrumbs lesen: Sie zeigen die SQL-Abfragen vor dem Fehler — oft erkennt man daran Job, Modell und Datensatz (z. B. failed_jobs-Insert → Fehler in einem Queue-Job; payload.createdAt verrät alte Jobs).

Im Projekt eine Kurzfassung mit den echten Werten (Instanz, Org-Slug, Projekt-IDs) unter dev/glitch-tip/README.md ablegen und in CLAUDE.md verweisen — dann finden KI-Chats sie automatisch.


8. Datenschutz (vor dem Einschalten)

  • Datenschutzerklärung: Fehlerprotokollierung (Dienst selbst gehostet, Kategorien: User-ID, URL, Browser, Fehlermeldung; Frist = Aufbewahrung in GlitchTip, z. B. 90 Tage; Rechtsgrundlage berechtigtes Interesse am sicheren Betrieb).
  • Kein Auftragsverarbeiter, wenn GlitchTip auf eigenem Server läuft (Hoster ggf. AVV).