Files
crewli/dev-docs/RFC-WS-7-OBSERVABILITY.md
bert.hausmans 932788c643 docs: glitchtip runbook + setup + RFC §3.1 dev amendment
Operational docs for the GlitchTip stack landed in the previous two
commits.

- dev-docs/GLITCHTIP.md: new runbook covering local dev, project
  provisioning + DSN-to-vault flow, production deploy on
  monitoring.hausdesign.nl (DNS, DirectAdmin Let's Encrypt, Apache
  reverse proxy with WS upgrade), backup install + restore drill,
  smoke tests, troubleshooting.
- dev-docs/SETUP.md: services table now includes GlitchTip; new
  docker/glitchtip/.env subsection points at the runbook.
- dev-docs/RFC-WS-7-OBSERVABILITY.md §3.1: amended to record that the
  same compose file drives local dev (Mailpit at bm_mailpit:1025), so
  prod and dev cannot drift.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 08:15:27 +02:00

12 KiB

RFC-WS-7 — Observability foundation (GlitchTip)

Status: Approved — implementation-ready Workstream: WS-7 (consolidatie-sprint mei 2026) Voorgangers: ARCH-CONSOLIDATION-2026-04 §3 besluit 8, §6.7 — ARCH-CONSOLIDATION-ADDENDUM-2026-04-24 D-06 Opvolgers: ARCH-API-VALIDATION (geblokkeerd door WS-7), WS-8b (ARCH-OBSERVABILITY.md) Doorlooptijd: 4-5 dagen (charter zei 2-3; revisie wegens PII-scrubbing scope)


1. Doel

Crewli draait in productie zonder geautomatiseerde error-detectie. Stack traces alleen via SSH+grep; frontend errors onzichtbaar tenzij gerapporteerd; geen release-correlatie of alerting. WS-7 levert die foundation.


2. Charter-amendementen

Twee afwijkingen van charter §3 besluit 8, beide bewust:

A. Sentry → GlitchTip. Self-hosted = data binnen perimeter (Crewli verwerkt special category data: dietary, medical, accreditation passport — geen externe processor toevoegen aan het processing register). GlitchTip implementeert het Sentry-event-protocol; sentry-laravel, @sentry/vue, @sentry/cli werken zonder modificatie. Switchen naar Sentry SaaS later is mogelijk zonder app-wijzigingen.

B. Performance monitoring uit scope. Charter zei "Sentry Performance in prod". Deze RFC scope = errors-only. Performance-tracing pakken we later op wanneer er een concrete vraag ontstaat.


3. Architectuur

3.1 Hosting

Self-hosted GlitchTip op productie VPS via Docker Compose (glitchtip-web, glitchtip-worker, glitchtip-postgres, glitchtip-redis). Reverse proxy via DirectAdmin Apache; SSL via DirectAdmin Let's Encrypt op monitoring.hausdesign.nl (consistent met bestaande subdomain-pattern).

Lokale ontwikkeling: dezelfde docker-compose.glitchtip.yml draait lokaal als make services (gecombineerd met de bestaande docker-compose.yml via -f). Web-UI op http://localhost:8200, e-mail naar Mailpit op bm_mailpit:1025. Dev-stack en prod-stack delen één compose-file zodat configuratie-drift uitgesloten is.

3.2 Twee projecten / DSNs

  • crewli-api — Laravel
  • crewli-app — apps/app SPA (single-SPA na WS-3)

Twee aparte projecten omdat issue-ownership (frontend vs backend) helder moet zijn én omdat scrubbing-rules per project verschillen.

Binnen crewli-app werken we met een runtime context-split via beforeSend-hook: routes onder /p/* (token-based, artist/supplier/press) krijgen strictere scrubbing en geen user-context. Organizer-routes en /platform/* krijgen volledige auth-context. Eén SDK, twee zones.

3.3 Environment gating

Lege DSN = SDK no-op (bevestigd voor zowel sentry-laravel als @sentry/vue). Geen runtime-check in app-code nodig.

# .env.development → DSNs leeg
# .env.staging     → DSNs gevuld, GLITCHTIP_ENVIRONMENT=staging
# .env.production  → DSNs gevuld, GLITCHTIP_ENVIRONMENT=production

3.4 Release identifier

Format <app>@<short-sha> (crewli-api@f41951a, crewli-app@f41951a). Bron: git rev-parse --short HEAD als build-time env var, geïnjecteerd in deploy.sh per app-build.

3.5 Source maps

vite build produceert sourcemaps → @sentry/cli sourcemaps upload push naar GlitchTip → .map bestanden verwijderd uit dist/ vóór nginx ze serveert. Geen public-mapped sources op productie. Per-project upload-only auth-token in deploy environment.

3.6 Context tagging

Tag API apps/app
release altijd altijd
environment altijd altijd
app api app
route_name Route::currentRouteName() route.name
http.method altijd n.v.t.
organisation_id (ULID) wanneer auth+scope gebound uit auth store
event_id (ULID) wanneer event-scoped wanneer applicabel
user_id (ULID) auth()?->id() uit auth store, alleen session-mode
actor_type organizer_admin / super_admin / portal_token / volunteer / etc. mirror
impersonation.active bool n.v.t.
impersonation.impersonator_user_id wanneer actief n.v.t.
queue.attempt binnen job-context n.v.t.

Nooit als tag: email, telefoon, naam, IP-adres, raw form_value content, raw cookie content.

Multi-tenant invariant: élke captured event uit een geauthenticeerde controller MOET organisation_id hebben. Een unit-test verifieert dit — als organisation_id ontbreekt op een geauthenticeerd path, faalt de test.

3.7 PII scrubbing

Backend (before_send in sentry-laravel):

  1. Request body keys (recursief, key-name match): password, password_confirmation, current_password, token, api_key, secret, webhook_secret, dsn, signature, authorization, cookie, bearer, iban, bic, passport_number, bsn.
  2. Headers: Authorization, Cookie, Set-Cookie, X-API-Key, X-Impersonation-Token.
  3. Form submissions: élke payload met form_values → recursive value-replace met [scrubbed]. Form values zijn definitionally PII.
  4. URL query string: scrub ?token=..., ?api_key=....
  5. send_default_pii = false globaal (strips locals uit stack traces).

Frontend (beforeSend in @sentry/vue):

  1. Cookies via document.cookie exposure: scrub.
  2. localStorage / sessionStorage: nooit in event context (portal-state in sessionStorage MAG NIET lekken).
  3. Mask-all op input breadcrumbs. Sentry default maskt alleen passwords; wij masken alle <input> values via breadcrumb-integratie config. Crewli heeft email/phone/IBAN/dietary/medical als plain <input> — selectief masken is niet veilig genoeg.
  4. URL query string: zelfde patronen.
  5. Console messages: Sentry.consoleLoggingIntegration uitgeschakeld in productie.

Verificatie — verplicht in CI:

  • tests/Feature/Observability/PiiScrubbingTest.php — mock transport, assert dat sensitive keys gescrubd zijn.
  • apps/app/src/__tests__/sentry-scrub.test.ts — assert form-input breadcrumb-scrubbing en /p/* route context-strip.

Tests blokkeren merge als scrubbing regresseert. Geen "manuele inspectie" als verificatie.

3.8 User context

Sentry::setUser([...]): id = ULID, username = ULID (duplicate, nooit email), ip_address = null. send_default_pii = false.

3.9 Sampling

Errors 100% (self-hosted, geen kostenreden). Performance/Profile niet van toepassing (uit scope per §2 amendement B). Wel een rate-limit op event-volume per project (GlitchTip default 10k/min/project) tegen runaway-errors.

3.10 Boundary met bestaande systemen

GlitchTip captureert programmer errors en infrastructure failures. Het captureert niet verwachte business-uitkomsten (failed payment, failed identity match, dead-letter webhook delivery — die hebben eigen audit-tabellen). Bestaande systemen blijven onaangeroerd: Telescope (dev), activity_log (audit trail), impersonation_audit_logs (security audit), form_webhook_deliveries (delivery audit), Laravel default log (operationeel).

Concreet voor webhooks: form_webhook_deliveries.dead_letter is de audit-record. GlitchTip vuurt alleen bij programmeerfouten (TypeError, missing config), niet wanneer receiver een 500 retourneert.

3.11 Queue-worker integratie

Laravel SDK auto-captureert vanuit queue jobs. Job-attempt-number als tag (queue.attempt) voor "issues die uiteindelijk slagen" vs "altijd falend". Stack-trace-grouping (default) is correct gedrag voor idempotente retries — niet per attempt fingerprinten.

3.12 Alerting

Initieel: email-naar-Bert op nieuwe issue / regression / spike (>10 events in 5min, tune na eerste week). Slack-integratie naar BACKLOG.

3.13 Structured logging conventie (charter §6.7)

BindRequestLogContext middleware op de api route group:

Log::withContext([
    'request_id' => $request->header('X-Request-Id') ?? Str::ulid(),
    'organisation_id' => $request->user()?->organisation_id,
    'user_id' => $request->user()?->id,
    'route' => $request->route()?->getName(),
]);

request_id retour als response header X-Request-Id. apps/app axios interceptor genereert client-side een ULID per request en stuurt mee als header; server respecteert headerwaarde. Dit geeft front-to-back correlation in één klik vanuit GlitchTip-UI naar log-segment op de host.

3.14 Activity_log indexes (addendum D-06)

(subject_type, subject_id) en (causer_type, causer_id) composite indexes op activity_log. Infrastructure-housekeeping; geen functionele wijziging.


4. Privacy / GDPR

Data na scrubbing: alleen ULIDs, stack traces, route-namen, gecureerde tags, request_id. Scrubbing per §3.7 is de primaire control; tests in §3.7 zijn merge-blockers.

Controller / processor: Self-hosted op Crewli-infra → Crewli is controller, niet processor. Geen DPA-uitbreiding.

Right to erasure (Art. 17): Zoek events op user.id = <ULID>, verwijder via GlitchTip-API of directe SQL op glitchtip-postgres. Initieel handmatig; geautomatiseerd erasure-script naar BACKLOG.

Retention: 90 dagen, daarna purged.

Processing register-entry: Crewli Error Tracking (GlitchTip) — defectdetectie en service-availability monitoring — pseudonieme identifiers + technische metadata — recipient: Bert — retention 90 dagen — TLS in transit, full-disk encryption at rest, SSH-key + 2FA op web-UI.

SECURITY_AUDIT.md update in PR-4: GlitchTip als processing entry, scrubbing-config als security control, erasure-procedure als GDPR-runbook.


5. Vastgelegde defaults

Geen open vragen meer; deze zijn vastgesteld:

  • DirectAdmin Let's Encrypt voor monitoring.hausdesign.nl (consistent met bestaande subdomains).
  • Retention: 90 dagen.
  • Email-alerting initieel; Slack naar BACKLOG.
  • Mask-all op frontend input breadcrumbs.
  • 2FA verplicht op GlitchTip web-UI.
  • Daily postgres-backup naar zelfde target als Crewli main-DB.
  • Capture failures-to-write-activity-log (silent failures zijn slecht).
  • Impersonation: zowel impersonator als target user_id getagd (per §3.6 tabel).
  • Big-bang structured logging: BindRequestLogContext middleware in PR-2, geen call-site-wijzigingen.
  • Mock transport voor scrubbing-tests (geen externe afhankelijkheid in CI).
  • Source-map upload-token in deploy .env op productie-host (later naar 1Password wanneer CI komt).

6. Acceptance criteria

WS-7 is compleet wanneer:

  1. GlitchTip draait op monitoring.hausdesign.nl met TLS, alleen toegankelijk voor Bert (2FA aan).
  2. Twee projecten aangemaakt; DSNs in vault.
  3. Laravel SDK geïntegreerd; errors uit prod-API verschijnen <60s.
  4. apps/app SDK geïntegreerd; errors verschijnen met org/user/release context. /p/* routes hebben strictere scrubbing en geen user-context.
  5. Source-maps upload werkt; leesbare stack traces in UI; .map bestanden afwezig in publieke bundle.
  6. PII scrubbing-tests groen (backend + frontend).
  7. Smoke test: induced 500 in staging, verifieer dat hij verschijnt met alle verwachte tags én geen PII lekt.
  8. ARCH-OBSERVABILITY.md geschreven (WS-8b).
  9. Email-alerting geconfigureerd; getest met sample issue.
  10. Retention-policy (90 dagen) toegepast.
  11. Daily postgres-backup-script in place.
  12. Activity_log indexes (addendum D-06) gemigreerd.
  13. Structured logging conventie geïmplementeerd; X-Request-Id round-trip getest.
  14. SECURITY_AUDIT.md bijgewerkt.

7. Deliverables (4 PRs, --no-ff per CLAUDE.md)

PR Inhoud
PR-1: Infra docker-compose.glitchtip.yml, monitoring.hausdesign.nl DNS + TLS, twee projecten aangemaakt, DSNs in vault, daily-backup script
PR-2: Backend SDK + structured logging sentry-laravel install + config + scrubbing + context tagging, BindRequestLogContext middleware, X-Request-Id round-trip, PII scrubbing test, activity_log indexes (D-06)
PR-3: Frontend SDK @sentry/vue install + config + context tagging + scrubbing test + /p/* runtime-split + sourcemap upload-step in deploy.sh
PR-4: Docs + WS-8b ARCH-OBSERVABILITY.md, runbook (triage + erasure + scrubbing-tuning), SECURITY_AUDIT.md update

WS-7 closure = alle 4 PRs gemerged + acceptance criteria 1-14 afgevinkt.


8. Verwijzingen

  • ARCH-CONSOLIDATION-2026-04 §3 besluit 8 — originele Sentry-keuze (deze RFC wijzigt naar GlitchTip).
  • ARCH-CONSOLIDATION-2026-04 §6.7 — originele scope WS-7.
  • ARCH-CONSOLIDATION-ADDENDUM-2026-04-24 D-06 — activity_log indexes.
  • AUTH_ARCHITECTURE.md — per-app cookie naming, impersonation flow.
  • SECURITY_AUDIT.md — bestaande audit-posture (te updaten in PR-4).
  • BACKLOG.md — entries voor automated-erasure script, Slack alerting (post-WS-7).
  • GlitchTip docs: https://glitchtip.com/documentation
  • GlitchTip self-hosting: https://glitchtip.com/documentation/install