- 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>
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).
SECRET_KEY(openssl rand -hex 32),GLITCHTIP_DOMAIN,EMAIL_URL,DEFAULT_FROM_EMAILsetzen. Aufbewahrung:GLITCHTIP_RETENTION_DAYS(Standard 90 — ältere Versionen hießenGLITCHTIP_MAX_EVENT_LIFE_DAYS).docker compose -f glitchtip-compose.yml up -d, Reverse Proxy mit TLS (vorlagen/server/Caddyfile).- Erstes Konto anlegen, danach
ENABLE_USER_REGISTRATION: "False", neu starten. - 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“
-
Offene Einträge beider Projekte abrufen, zuerst
environment=production. -
Einordnen:
Klasse Erkennen Vorgehen Test-/Entwicklungsrauschen Umgebung local, Texte aus Testsnichts am Code; auflösen Bedienfehler am Server z. B. CommandNotFoundExceptiondontReportExterner Dienst vorübergehend Timeouts/5xx fremder APIs Muster aus Abschnitt 6 Echter Bug Stacktrace in app/,resources/, Browserfehler mit eigener URLroter Test → Fix → Deploy Unklar z. B. Error: [object Object]Erfassung verbessern, nächstes Auftreten abwarten -
Bei Browserfehlern zeigen die Frames oft nur
livewire.min.js— der Hinweis steckt im Funktionsnamen ([Alpine] __('…')= Alpine-Ausdruck) → inresources/viewsdanach suchen. Kann ein Muster wiederkommen: Wächtertest über alle Dateien. -
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.
-
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.createdAtverrä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).