docs: observability triage + erasure runbooks
PR-4 commit 2. Both runbooks live under dev-docs/runbooks/ as the first entries in that directory. - observability-triage.md (270 lines): incoming-issue procedure. Tags inspectie (actor_scope, release, actor_type, organisation_id, impersonation), triage classes (P0–P3), reproductie via request_id correlation naar laravel.log, common patterns (validation leakage, runaway errors, multi-tenant invariant violations, CSP black-silence), resolution + audit trail. - observability-erasure.md (293 lines): GDPR Art. 17 procedure. Trigger voorwaarden (upstream eerst), pre-checks, handmatige psql-procedure met counts vóór delete, post-checks, automation BACKLOG verwijzing, edge cases (no-events-in-window, impersonation-target, queued events, mass-erasure batch). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
293
dev-docs/runbooks/observability-erasure.md
Normal file
293
dev-docs/runbooks/observability-erasure.md
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
# Observability erasure runbook (GDPR Art. 17)
|
||||||
|
|
||||||
|
> Procedure voor het verwijderen van een gebruiker's data uit GlitchTip
|
||||||
|
> bij een geldig "right to erasure" verzoek.
|
||||||
|
>
|
||||||
|
> **Audience:** Bert (in zijn rol als controller per Crewli's
|
||||||
|
> processing register), eventueel met legal-input voor het beoordelen
|
||||||
|
> of het verzoek valid is.
|
||||||
|
>
|
||||||
|
> **Trigger:** een Art. 17 verzoek bevestigd door legal én Crewli's
|
||||||
|
> primaire datastores zijn al geërased. GlitchTip is downstream — pas
|
||||||
|
> erase'en als upstream klaar is, anders kunnen nieuwe events de net-
|
||||||
|
> verwijderde user-id opnieuw introduceren.
|
||||||
|
>
|
||||||
|
> **Cross-references:**
|
||||||
|
> [`ARCH-OBSERVABILITY.md §9`](../ARCH-OBSERVABILITY.md) (GDPR sectie),
|
||||||
|
> [`SECURITY_AUDIT.md`](../SECURITY_AUDIT.md) (processing register
|
||||||
|
> entry voor GlitchTip),
|
||||||
|
> [`GLITCHTIP.md`](../GLITCHTIP.md) (operational context — hoe je in
|
||||||
|
> de postgres komt).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §1 Trigger
|
||||||
|
|
||||||
|
Crewli's privacy-policy verplicht de controller (Bert) om bij een
|
||||||
|
geldig Art. 17-verzoek alle persoonsgegevens te verwijderen. Voor
|
||||||
|
GlitchTip betekent dat:
|
||||||
|
|
||||||
|
- Verwijder élk event waarin de `user_id` of (indien van toepassing)
|
||||||
|
`impersonation.impersonator_user_id` van de betreffende gebruiker
|
||||||
|
voorkomt.
|
||||||
|
- Verwijder ook events waarin de gebruiker als `impersonation.impersonator_user_id`
|
||||||
|
staat (super_admin die ooit deze user impersoneerde — events bevatten
|
||||||
|
de admin's ULID, en een verzoek van een admin om eigen data te
|
||||||
|
wissen valt ook onder de procedure).
|
||||||
|
|
||||||
|
**Pas uitvoeren nadat:** de gebruiker is verwijderd uit Crewli's
|
||||||
|
primaire datastores (users tabel, soft-deleted state, gerelateerde
|
||||||
|
records). Anders kan een achterblijvende background job de net-
|
||||||
|
verwijderde user-id opnieuw introduceren in GlitchTip via een
|
||||||
|
exception event.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §2 Pre-checks
|
||||||
|
|
||||||
|
Vóór je SSH'd naar productie:
|
||||||
|
|
||||||
|
1. **Bevestig de user-id (ULID).** Open Crewli's platform-admin UI →
|
||||||
|
activity log → zoek de gebruiker. Noteer:
|
||||||
|
- `user_id` (ULID, 26 chars)
|
||||||
|
- Of de gebruiker ooit super_admin was (zo ja, ook
|
||||||
|
`impersonation.impersonator_user_id` moet gepurged)
|
||||||
|
- Of de gebruiker target is geweest van impersonation (zoek in
|
||||||
|
`impersonation_audit_logs` — als targets/admins gemixt zijn,
|
||||||
|
erase je de admin's ULID via diens eigen request, niet via deze).
|
||||||
|
|
||||||
|
2. **Documenteer ticket-referentie.** Crewli's processing register
|
||||||
|
eist auditable trail. Noteer in een veilige notitie:
|
||||||
|
- Datum verzoek
|
||||||
|
- Ticket-ID / referentie
|
||||||
|
- User-id
|
||||||
|
- Wie heeft het verzoek beoordeeld (Bert + legal indien van
|
||||||
|
toepassing)
|
||||||
|
|
||||||
|
3. **Check retention-window.** GlitchTip purgt na 90 dagen (zie
|
||||||
|
[ARCH §9.3](../ARCH-OBSERVABILITY.md)). Als de gebruiker > 90 dagen
|
||||||
|
geen events heeft gegenereerd, is er waarschijnlijk niets meer te
|
||||||
|
wissen. Doe deze check eerst zodat je geen onnodige delete uitvoert.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §3 Handmatige procedure
|
||||||
|
|
||||||
|
### 3.1 SSH en open psql
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh crewli@<monitoring-host> # monitoring.hausdesign.nl
|
||||||
|
docker exec -it glitchtip-postgres psql -U postgres -d glitchtip
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Counts vóór delete (verifieer scope)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Direct events met user_id tag matchend
|
||||||
|
SELECT COUNT(*) FROM issues_event
|
||||||
|
WHERE tags @> ARRAY[ARRAY['user_id', '<ULID>']]::text[][];
|
||||||
|
|
||||||
|
-- Events waar deze user impersonator was
|
||||||
|
SELECT COUNT(*) FROM issues_event
|
||||||
|
WHERE tags @> ARRAY[ARRAY['impersonation.impersonator_user_id', '<ULID>']]::text[][];
|
||||||
|
|
||||||
|
-- Issues die uitsluitend uit deze user's events bestaan (kandidaten
|
||||||
|
-- voor full issue-delete)
|
||||||
|
SELECT i.id, i.title, COUNT(e.id) AS events_for_user, i.times_seen
|
||||||
|
FROM issues_issue i
|
||||||
|
JOIN issues_event e ON e.issue_id = i.id
|
||||||
|
WHERE e.tags @> ARRAY[ARRAY['user_id', '<ULID>']]::text[][]
|
||||||
|
GROUP BY i.id, i.title, i.times_seen
|
||||||
|
HAVING COUNT(e.id) = i.times_seen;
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Schema-noot:** GlitchTip versies kunnen verschillen in
|
||||||
|
> tabel-namen (`issues_event` vs `events_event`) en tag-storage
|
||||||
|
> (array vs jsonb). Check eerst `\dt` om de actuele tabel-namen te
|
||||||
|
> zien, en `\d issues_event` voor de tag-kolom-definitie.
|
||||||
|
> De queries hier zijn voor v6.x met `tags` als array-of-2-arrays;
|
||||||
|
> pas aan voor andere versies. Overweeg de queries een keer te
|
||||||
|
> verifiëren tegen test-data vóór je een echte delete uitvoert.
|
||||||
|
|
||||||
|
Schrijf de output op (counts) zodat je in §4 kunt verifiëren dat de
|
||||||
|
deletes succesvol waren.
|
||||||
|
|
||||||
|
### 3.3 Delete events
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Direct user_id events
|
||||||
|
DELETE FROM issues_event
|
||||||
|
WHERE tags @> ARRAY[ARRAY['user_id', '<ULID>']]::text[][];
|
||||||
|
|
||||||
|
-- Impersonator-as-admin events (alleen als verzoek dit dekt)
|
||||||
|
DELETE FROM issues_event
|
||||||
|
WHERE tags @> ARRAY[ARRAY['impersonation.impersonator_user_id', '<ULID>']]::text[][];
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 Cleanup van orphan issues
|
||||||
|
|
||||||
|
Issues die nu 0 events hebben moeten ook weg (anders blijft de issue-
|
||||||
|
title — die in theorie geen PII zou bevatten, maar de aggregate-counts
|
||||||
|
worden onnauwkeurig):
|
||||||
|
|
||||||
|
```sql
|
||||||
|
DELETE FROM issues_issue
|
||||||
|
WHERE id NOT IN (SELECT DISTINCT issue_id FROM issues_event);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.5 Bevestig vacuum
|
||||||
|
|
||||||
|
GlitchTip gebruikt postgres standaard auto-vacuum; voor bulk-deletes
|
||||||
|
kan een handmatige vacuum nodig zijn om disk-space terug te winnen:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
VACUUM FULL issues_event;
|
||||||
|
VACUUM FULL issues_issue;
|
||||||
|
```
|
||||||
|
|
||||||
|
`VACUUM FULL` blokkeert reads/writes — doe dit in een laag-traffic
|
||||||
|
window, of skip als de gewiste user weinig events had.
|
||||||
|
|
||||||
|
### 3.6 Exit psql
|
||||||
|
|
||||||
|
```sql
|
||||||
|
\q
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §4 Post-checks
|
||||||
|
|
||||||
|
### 4.1 GlitchTip UI search
|
||||||
|
|
||||||
|
Open `https://monitoring.hausdesign.nl` → projects → search:
|
||||||
|
|
||||||
|
```
|
||||||
|
user_id:<ULID>
|
||||||
|
```
|
||||||
|
|
||||||
|
Verwacht: 0 resultaten.
|
||||||
|
|
||||||
|
```
|
||||||
|
impersonation.impersonator_user_id:<ULID>
|
||||||
|
```
|
||||||
|
|
||||||
|
Verwacht: 0 resultaten.
|
||||||
|
|
||||||
|
### 4.2 Postgres re-count
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec glitchtip-postgres psql -U postgres -d glitchtip -c \
|
||||||
|
"SELECT COUNT(*) FROM issues_event WHERE tags @> ARRAY[ARRAY['user_id', '<ULID>']]::text[][];"
|
||||||
|
```
|
||||||
|
|
||||||
|
Verwacht: 0.
|
||||||
|
|
||||||
|
### 4.3 Audit trail
|
||||||
|
|
||||||
|
Update de notitie uit §2 met:
|
||||||
|
|
||||||
|
- Datum / tijd van uitvoering
|
||||||
|
- Aantal events verwijderd (uit §3.2 vóór-counts)
|
||||||
|
- Eventuele afwijkingen / partial successes
|
||||||
|
- Bevestiging van §4.1 + §4.2 zero-results
|
||||||
|
|
||||||
|
Bewaar in een audit-locatie waar legal + Bert het kunnen terugvinden
|
||||||
|
voor minimaal 6 jaar (per Crewli's algemene compliance-bewaartermijn).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §5 Toekomstige automation
|
||||||
|
|
||||||
|
Geautomatiseerd erasure-script staat op BACKLOG. Wanneer
|
||||||
|
geïmplementeerd:
|
||||||
|
|
||||||
|
- Mogelijk als een Laravel artisan command:
|
||||||
|
`php artisan glitchtip:erase --user-id=<ULID>`
|
||||||
|
- Of als een GlitchTip API-endpoint integratie (GlitchTip heeft een
|
||||||
|
REST API; uit Laravel een HTTPS-call doen die alle matchende events
|
||||||
|
delete).
|
||||||
|
- In beide gevallen: audit-log entry naar `activity_log` met
|
||||||
|
`subject_type='User'`, `event='glitchtip.erasure'`, properties
|
||||||
|
`{requested_at, ticket_ref, count_deleted}`.
|
||||||
|
|
||||||
|
Tot dat erin zit: gebruik deze handmatige procedure.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §6 Edge cases
|
||||||
|
|
||||||
|
### 6.1 User had geen events in 90-day window
|
||||||
|
|
||||||
|
Niets te doen. GlitchTip's eigen retention-loop heeft alles al
|
||||||
|
gepurged. Documenteer in audit trail dat erasure compleet was zonder
|
||||||
|
delete-actie ("user had no events within retention window").
|
||||||
|
|
||||||
|
### 6.2 User_id is impersonation-target
|
||||||
|
|
||||||
|
Een event met `actor_type=org_admin`, `user_id=<TARGET-ULID>`,
|
||||||
|
`impersonation.active=true`, `impersonation.impersonator_user_id=<ADMIN-ULID>`
|
||||||
|
betekent: een super_admin was de organizer aan het impersoneren toen
|
||||||
|
het event vuurde. Voor een verzoek van de TARGET user: deletes lopen
|
||||||
|
via §3.3 op de TARGET ULID — de impersonator-tag blijft (admin-PII die
|
||||||
|
niet onder de target's verzoek valt).
|
||||||
|
|
||||||
|
Voor een verzoek van de ADMIN user (e.g. een uitgetreden super_admin
|
||||||
|
die zijn eigen data wil wissen): deletes lopen via §3.3 op de ADMIN
|
||||||
|
ULID over BEIDE tag-kolommen (`user_id` als de admin zelf gebruiker
|
||||||
|
was, en `impersonation.impersonator_user_id` voor sessies waar hij
|
||||||
|
impersoneerde).
|
||||||
|
|
||||||
|
### 6.3 Events in glitchtip-worker queue maar nog niet in postgres
|
||||||
|
|
||||||
|
Celery-worker batches events. Een event die nog in de queue staat
|
||||||
|
wordt niet door §3.3 delete'd; het komt later alsnog binnen.
|
||||||
|
|
||||||
|
**Mitigation:** wacht 5 minuten na de laatste user-activiteit vóór je
|
||||||
|
de erasure-run start. Crewli's user-soft-delete + de Crewli-API-side
|
||||||
|
deletes geven natuurlijk al een idle-window; gebruik dat.
|
||||||
|
|
||||||
|
Als een laat-binnenkomend event de net-gewiste ULID herintroduceert:
|
||||||
|
draai §3.3 nogmaals na een tweede 5-minuten-window. Documenteer in
|
||||||
|
audit trail.
|
||||||
|
|
||||||
|
### 6.4 Multiple ULIDs (mass-erasure)
|
||||||
|
|
||||||
|
Voor een batch erasure (bv. een deelorganisatie wordt opgeheven):
|
||||||
|
gebruik een tijdelijke tabel:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TEMP TABLE erasure_targets (ulid char(26));
|
||||||
|
\copy erasure_targets FROM '/tmp/erasure-ulids.csv' WITH CSV;
|
||||||
|
|
||||||
|
DELETE FROM issues_event
|
||||||
|
WHERE EXISTS (
|
||||||
|
SELECT 1 FROM erasure_targets t
|
||||||
|
WHERE issues_event.tags @> ARRAY[ARRAY['user_id', t.ulid]]::text[][]
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Document in audit trail: aantal ULIDs, totaal aantal events
|
||||||
|
verwijderd, ticket-batch-referentie.
|
||||||
|
|
||||||
|
### 6.5 Partial failure mid-procedure
|
||||||
|
|
||||||
|
Als psql een error gooit halverwege §3.3 (bv. lock conflict): de
|
||||||
|
DELETE statements draaien standalone (niet in een transactie). Een
|
||||||
|
geslaagde eerste DELETE blijft committed; een gefaalde tweede moet
|
||||||
|
opnieuw. Verifieer met §4.2 counts welke ULIDs / kolommen al gewist
|
||||||
|
zijn voordat je opnieuw runt.
|
||||||
|
|
||||||
|
Voor consistentie kun je beide DELETEs in een transactie wikkelen:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
BEGIN;
|
||||||
|
DELETE FROM issues_event WHERE tags @> ARRAY[ARRAY['user_id', '<ULID>']]::text[][];
|
||||||
|
DELETE FROM issues_event WHERE tags @> ARRAY[ARRAY['impersonation.impersonator_user_id', '<ULID>']]::text[][];
|
||||||
|
COMMIT;
|
||||||
|
```
|
||||||
|
|
||||||
|
— Maar overweeg dat dit langere lock-times veroorzaakt op een live
|
||||||
|
GlitchTip postgres en kan andere event-ingest blokkeren tijdens de
|
||||||
|
transactie. Voor low-volume erasures (één user) is statement-by-
|
||||||
|
statement OK.
|
||||||
270
dev-docs/runbooks/observability-triage.md
Normal file
270
dev-docs/runbooks/observability-triage.md
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
# Observability triage runbook
|
||||||
|
|
||||||
|
> Praktische gids voor wanneer Bert (of een latere op-call) een
|
||||||
|
> GlitchTip alert / email krijgt of zelf de Issues-pagina opent.
|
||||||
|
>
|
||||||
|
> **Audience:** maintainer met repo-toegang en ssh-toegang naar de
|
||||||
|
> productie-host.
|
||||||
|
>
|
||||||
|
> **Cross-references:**
|
||||||
|
> [`ARCH-OBSERVABILITY.md`](../ARCH-OBSERVABILITY.md) (architectuur en
|
||||||
|
> tag-taxonomie), [`GLITCHTIP.md`](../GLITCHTIP.md) (operationele
|
||||||
|
> stack — boot, backup, restore),
|
||||||
|
> [`observability-erasure.md`](./observability-erasure.md) (Art. 17
|
||||||
|
> procedure als triage in een GDPR-deletion-verzoek omdraait).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §1 Eerste 60 seconden — issue inspecteren
|
||||||
|
|
||||||
|
Open de issue in GlitchTip. Vóór je begint na te denken over de
|
||||||
|
exception zelf, check de tags. Het kost 30 seconden en bepaalt vaak
|
||||||
|
direct de prioriteit.
|
||||||
|
|
||||||
|
### 1.1 `actor_scope` — wat is de blast radius?
|
||||||
|
|
||||||
|
| Waarde | Wat het betekent voor triage |
|
||||||
|
|---|---|
|
||||||
|
| `organisation` | Eén klant geraakt. Tag `organisation_id` zegt welke. |
|
||||||
|
| `platform` | super_admin op een platform-route. Bug in admin-tooling, niet in een klant-flow. |
|
||||||
|
| `user` | Authenticated user op een non-org route (`/me/*`, `/portal/profiel`). Vaak account-settings of auth-flow bug. |
|
||||||
|
| `portal` | Token-based portal flow (artist advance, public form fill). Geen `user_id` aanwezig — debug via `request_id` correlation. |
|
||||||
|
| `anonymous` | Public pagina, niet ingelogd. Login-flow / public-form bug. |
|
||||||
|
|
||||||
|
### 1.2 `release` — actuele deploy of oud residual?
|
||||||
|
|
||||||
|
`release=crewli-api@<sha>` of `crewli-app@<sha>`. Vergelijk met
|
||||||
|
`git log --oneline -5` op `main`. Als de SHA matcht het laatste deploy:
|
||||||
|
het probleem zit in de huidige codebase. Als de SHA achterloopt: kan
|
||||||
|
een long-running browser-session zijn die nog op een oude bundle
|
||||||
|
draait, of een achtergrondservice die nog niet herstart is na deploy.
|
||||||
|
|
||||||
|
### 1.3 `actor_type` — wie is geraakt?
|
||||||
|
|
||||||
|
`super_admin` / `organizer_admin` / `org_member` / `portal_token` /
|
||||||
|
`unauthenticated`. Een `super_admin`-only bug is laag-prio voor
|
||||||
|
klanten; een `org_member`-bug raakt vrijwilligers en moet sneller fix.
|
||||||
|
|
||||||
|
### 1.4 `organisation_id` — één klant of platform-breed?
|
||||||
|
|
||||||
|
| Aanwezig op één issue | Eén klant. Reach out direct als blocker. |
|
||||||
|
| Aanwezig met variable values across events binnen 1 issue | Multi-tenant bug; spike in events is een P0-signaal. |
|
||||||
|
| Afwezig | Verwacht voor `actor_scope=platform` / `user` / `anonymous`. Niet aanwezig zijn betekent dus niet automatisch "platform-breed" — check `actor_scope` eerst. |
|
||||||
|
|
||||||
|
### 1.5 `impersonation.active` — admin-context?
|
||||||
|
|
||||||
|
Bijna altijd `'false'`. Wanneer `'true'`, dan was een super_admin
|
||||||
|
aan het impersonating. Bug kan in de target-user's data zitten of
|
||||||
|
in de impersonation-flow zelf. Crosscheck:
|
||||||
|
`impersonation.impersonator_user_id` (admin's ULID) en
|
||||||
|
`impersonation.session_id` voor lookup in `impersonation_audit_logs`.
|
||||||
|
|
||||||
|
### 1.6 `request_id` — correlation naar Laravel logs
|
||||||
|
|
||||||
|
Elke gecaptured event draagt `request_id` als tag of in de extra
|
||||||
|
context (gezet door `BindRequestLogContext` middleware). Pak die ULID,
|
||||||
|
ssh naar productie-host en grep:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep "01HX...." storage/logs/laravel.log | head -50
|
||||||
|
```
|
||||||
|
|
||||||
|
Logregels die door dezelfde request flowden delen die ULID. Geeft
|
||||||
|
context die GlitchTip's eigen breadcrumbs niet hebben (bijv. SQL
|
||||||
|
queries, queue-job dispatches).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §2 Triage-classificatie
|
||||||
|
|
||||||
|
| Klasse | Signaal | Actie |
|
||||||
|
|---|---|---|
|
||||||
|
| **P0 — productie down** | Spike (>10 events / 5 min) op meerdere users; OF auth-flow bug die nieuwe logins blokkeert; OF spike op `actor_type=super_admin` (admin tooling kapot) | Direct fix-deploy. Notify klanten als > 30 min downtime verwacht. |
|
||||||
|
| **P1 — single-user blocker** | Repeating events same `user_id`, geen workaround. User kan niet doorwerken. | Reach out to user, fix in current sprint, mark issue assigned in GlitchTip. |
|
||||||
|
| **P2 — degraded UX** | Cosmetisch (bv. broken UI-state na specifieke actie), geen data-loss, workaround beschikbaar. | BACKLOG entry, plan in volgende sprint. Mark issue ignored in GlitchTip met BACKLOG-link in comment. |
|
||||||
|
| **P3 — known-tolerated** | Bekende edge case, gedocumenteerd in BACKLOG of een ARCH-doc. Niet fix-baar zonder grotere refactor. | Mark issue resolved/ignored in GlitchTip met inline comment naar de tracking-locatie. |
|
||||||
|
|
||||||
|
**Volume-thresholds zijn richtlijnen, niet hard rules.** Eén event op
|
||||||
|
een super_admin-route die data corrupt zou maken is P0 ondanks count=1.
|
||||||
|
Honderd events op een rate-limited public form-spam attempt is geen
|
||||||
|
P0 ondanks volume.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §3 Reproductie-stappen
|
||||||
|
|
||||||
|
### 3.1 Pak het stack-trace + tags
|
||||||
|
|
||||||
|
GlitchTip toont stack trace met sourcemap-resolved frames (zie
|
||||||
|
[`ARCH-OBSERVABILITY.md §8`](../ARCH-OBSERVABILITY.md)). Open de
|
||||||
|
exception, klik op het top frame, lees de regel.
|
||||||
|
|
||||||
|
### 3.2 Cross-correlate met laravel.log
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh crewli@<prod-host>
|
||||||
|
cd /home/crewli/crewli/api
|
||||||
|
grep "<request_id-ULID>" storage/logs/laravel.log
|
||||||
|
```
|
||||||
|
|
||||||
|
Geeft je: alle log-regels die door deze request flowden, in volgorde.
|
||||||
|
Zoek naar: SQL-query timing (langzame query?), queue-dispatch
|
||||||
|
(achtergrond-job triggered?), warning-logs vóór de exception.
|
||||||
|
|
||||||
|
### 3.3 Lokale reproductie
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make services # start mysql + redis + mailpit + glitchtip
|
||||||
|
make api # start Laravel dev server
|
||||||
|
make app # start Vue dev server
|
||||||
|
```
|
||||||
|
|
||||||
|
Voor backend-only state-bugs: `php artisan tinker` om de specifieke
|
||||||
|
state te recreëren.
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Voorbeeld: reproduceer een form-submission bug
|
||||||
|
$schema = \App\Models\FormBuilder\FormSchema::find('01HX...');
|
||||||
|
$submission = \App\Models\FormBuilder\FormSubmission::find('01HY...');
|
||||||
|
event(new \App\Events\FormBuilder\FormSubmissionSubmitted($submission));
|
||||||
|
```
|
||||||
|
|
||||||
|
Voor frontend-state-bugs: gebruik de Vue DevTools om Pinia store-state
|
||||||
|
te dump'en, recreëer de state via `useAuthStore().$patch({...})` in de
|
||||||
|
console.
|
||||||
|
|
||||||
|
### 3.4 Breadcrumbs
|
||||||
|
|
||||||
|
GlitchTip's breadcrumbs tonen de event-historie vóór de exception.
|
||||||
|
Frontend: route-changes, fetch-calls, console-warnings (input-text
|
||||||
|
masked). Backend: query-events (sentry-laravel built-in), job-dispatch.
|
||||||
|
Het patroon "user navigated → fetch failed → exception" is vaak
|
||||||
|
genoeg om de bug te lokaliseren zonder lokale repro.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §4 Common patronen
|
||||||
|
|
||||||
|
### 4.1 "ValidationException doorbreekt naar GlitchTip"
|
||||||
|
|
||||||
|
Mag niet gebeuren per RFC §3.10. `ignore_exceptions` in
|
||||||
|
`config/sentry.php` zou ValidationException moeten filteren. Check:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -A 6 "ignore_exceptions" api/config/sentry.php
|
||||||
|
```
|
||||||
|
|
||||||
|
Als de class ontbreekt: regression — voeg toe en commit. Als de class
|
||||||
|
er staat: misschien een subclass die niet matcht? `ignore_exceptions`
|
||||||
|
matcht via `is_a($exception, $class)` dus subclasses werken normaal,
|
||||||
|
maar verifieer.
|
||||||
|
|
||||||
|
### 4.2 "Same exception fingerprint, multiple events"
|
||||||
|
|
||||||
|
GlitchTip dedupliceert via stack-trace-fingerprint. Honderd events
|
||||||
|
binnen een minuut met dezelfde fingerprint = runaway error. Mogelijke
|
||||||
|
oorzaken:
|
||||||
|
|
||||||
|
- Cron-job die elke minuut faalt → check supervisord / cron-logs op
|
||||||
|
productie-host.
|
||||||
|
- Queue-worker die een job retry'd zonder backoff → check `queue.attempt`
|
||||||
|
tag op de events; als die monoton stijgt, is retry de oorzaak.
|
||||||
|
- Frontend infinite-loop in een Vue component (bijv. setup() throws
|
||||||
|
in een loop) → check `release` tag, mogelijk net-deployed code.
|
||||||
|
|
||||||
|
### 4.3 "actor_scope=organisation maar organisation_id ontbreekt"
|
||||||
|
|
||||||
|
Multi-tenant invariant violation. Per [ARCH §3.3](../ARCH-OBSERVABILITY.md):
|
||||||
|
wanneer `actor_scope=organisation`, MOET `organisation_id` aanwezig
|
||||||
|
zijn als valide ULID. Zo niet: file-level bug in
|
||||||
|
`AuthScopeContextListener::resolveTenantContext()`. P1 — fix in current
|
||||||
|
sprint en breidt
|
||||||
|
`AuthScopeContextListenerTest::test_organisation_id_present_when_actor_scope_is_organisation`
|
||||||
|
uit met de specifieke trigger-route.
|
||||||
|
|
||||||
|
### 4.4 "Frontend events zonder user_id terwijl user is ingelogd"
|
||||||
|
|
||||||
|
Twee mogelijke oorzaken:
|
||||||
|
|
||||||
|
- `actor_scope=portal` — verwacht gedrag voor token-based portal flows.
|
||||||
|
Geen bug.
|
||||||
|
- Pinia auth-store niet geïnitialiseerd op het moment van capture.
|
||||||
|
Vaak na hard-refresh op een protected route waar `useAuthStore` nog
|
||||||
|
niet `setUser()` heeft gehad. Check `useAuthStore().isInitialized`.
|
||||||
|
|
||||||
|
### 4.5 "Backend events zonder organisation_id op een org-scoped route"
|
||||||
|
|
||||||
|
Per ARCH §4.1: auth-listener fires bij `Authenticated` /
|
||||||
|
`TokenAuthenticated`, route-context komt uit `BindSentryRouteContext`.
|
||||||
|
Als de listener vóór route-binding draait, kan organisation_id
|
||||||
|
ontbreken. Realistisch: dit is geen probleem in de huidige
|
||||||
|
implementatie omdat `request()` binnen de listener al de gebonden
|
||||||
|
route ziet — maar als een refactor het pattern breekt, vangt
|
||||||
|
`AuthScopeBindingHttpFlowTest` het.
|
||||||
|
|
||||||
|
### 4.6 "GlitchTip krijgt geen events meer (zwarte stilte)"
|
||||||
|
|
||||||
|
Frontend: check CSP-violation in DevTools Console (zie
|
||||||
|
[ARCH §7](../ARCH-OBSERVABILITY.md)). Bij nieuwe environment / domein
|
||||||
|
moet de ingest-host opnieuw whitelisted worden.
|
||||||
|
|
||||||
|
Backend: check `Integration::handles($exceptions)` in
|
||||||
|
`api/bootstrap/app.php`. Per [BACKLOG OBS-6](../BACKLOG.md) is dit een
|
||||||
|
bekende silent-failure-mode bij sentry-laravel install. Test:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd api && php artisan tinker
|
||||||
|
\Sentry\captureMessage('triage smoke test')
|
||||||
|
```
|
||||||
|
|
||||||
|
Refresh GlitchTip Issues; als er geen event verschijnt, zit het
|
||||||
|
probleem in de SDK-config zelf (DSN, network) niet in
|
||||||
|
`Integration::handles`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §5 Resolutie
|
||||||
|
|
||||||
|
### 5.1 Mark as resolved
|
||||||
|
|
||||||
|
Na fix-deploy: open de issue in GlitchTip, klik **Resolve**.
|
||||||
|
GlitchTip's auto-resolve-on-version werkt niet altijd betrouwbaar in
|
||||||
|
zelf-hosted setups; doe het handmatig zodra je de deploy ziet draaien
|
||||||
|
op de prod-host.
|
||||||
|
|
||||||
|
Als de issue terugkomt op een latere `release` tag: GlitchTip markeert
|
||||||
|
'm automatisch als **regressed**. Behandel als nieuwe P1 (de bug die
|
||||||
|
je dacht gefixed te hebben is niet geheel weg).
|
||||||
|
|
||||||
|
### 5.2 Mark as ignored
|
||||||
|
|
||||||
|
Voor P2/P3 issues die niet in deze sprint worden gefixt: klik
|
||||||
|
**Ignore** met een comment die wijst naar de BACKLOG-entry of een
|
||||||
|
GitHub-issue. Voorkomt dat dezelfde issue opnieuw triage-aandacht
|
||||||
|
krijgt.
|
||||||
|
|
||||||
|
### 5.3 BACKLOG update
|
||||||
|
|
||||||
|
Wanneer de root cause een architectural pattern aanpassing vereist
|
||||||
|
(geen quick fix), maak een nieuwe BACKLOG entry met `OBS-` prefix
|
||||||
|
en cross-link naar de GlitchTip issue. Volg de format van bestaande
|
||||||
|
[OBS-* entries](../BACKLOG.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §6 Audit trail
|
||||||
|
|
||||||
|
Elke triage-actie laat sporen na:
|
||||||
|
|
||||||
|
- **GlitchTip:** issue-state changes (resolved / ignored / regressed)
|
||||||
|
met user (Bert) en timestamp.
|
||||||
|
- **Git:** fix-commits met issue-link in de body indien van
|
||||||
|
toepassing.
|
||||||
|
- **BACKLOG.md:** entries voor architectural follow-ups.
|
||||||
|
- **`runbooks/observability-erasure.md`** als de triage in een
|
||||||
|
GDPR-deletion-verzoek omdraait.
|
||||||
|
|
||||||
|
Voor incidents waar 1+ klant geraakt is, log een externe
|
||||||
|
incident-summary (klant, impact, timeline, fix) in een aparte
|
||||||
|
incident-tracker — niet in GlitchTip zelf, want GlitchTip data wordt
|
||||||
|
na 90 dagen gepurged.
|
||||||
Reference in New Issue
Block a user