18 KiB
Sentry Wiring — API + Worker
Status: completed (api/worker/web Sentry DSNs live per operator checklist A10; original staging-smoke caveat superseded) Task-ID: #30 Phase: 02 Estimated Effort: 1 Tag Owner: unassigned
Problem
packages/error-reporter/src/sentry.ts enthält bereits einen Sentry-Reporter-Adapter, der die ErrorReporter-Interface aus dem gleichen Package implementiert. Aber:
apps/api/src/app.ts:86ruftcreateErrorHandler({ ... })auf, ohne denreporter-Parameter zu setzen → Errors gehen nirgendwohin außer in den Logger.apps/worker/src/index.ts:10ruftstartWorkerRuntime({ logger })auf, ohne denreporterzu setzen → Worker-Job-Fehler bleiben nur in den Logs.- Performance/Tracing ist gar nicht initialisiert — keine Hono-Middleware, keine Prisma-Wrap, keine BullMQ-Hooks.
packages/error-reporter/src/sentry.ts:78-84(beforeSend) macht nur eine flache Sanitization — schützt vorrequest.headers.authorization, aber nicht voruser.email, nicht vorbreadcrumbs[].data.body, nicht vorcontexts.custom.*. PII kann durchsickern.- Kein Release-Tagging — Errors lassen sich nicht auf einen Git-SHA zurückführen.
Depends on
02-observability/sentry-selfhost-setup.md— wir brauchen funktionierende DSNs fürboilerplate-apiundboilerplate-worker01-tech-debt-foundation/dep-updates.md— Sentry-SDK soll auf v8.x sein (v7-API ist anders, würde die hier beschriebene Wiring brechen)
Blocks
02-observability/sentry-ci-integration.md— CI-Release-Tagging braucht funktionierendes Wiring02-observability/opentelemetry.md— OTel-Tracer registriert sich neben dem Sentry-Tracer; sauberer ist, Sentry zuerst zu wiren03-scaling-infrastructure/*— Refactorings wollen Backend-Sichtbarkeit haben
Approach
Gewählt: Die bestehende ErrorReporter-Interface in packages/error-reporter/ bleibt der einzige Berührungspunkt zwischen App-Code und Sentry. Wir initialisieren Sentry.init in zwei dedizierten Bootstrap-Modulen (apps/api/src/sentry.ts, apps/worker/src/sentry.ts), exportieren von dort jeweils einen vorkonfigurierten apiErrorReporter / workerErrorReporter und verdrahten die in app.ts bzw. index.ts. Damit bleibt der App-Code Reporter-agnostisch.
Performance/Tracing: Sentry's @sentry/node v8 Auto-Instrumentations für HTTP + Prisma + BullMQ aktivieren, plus eine kleine Hono-Middleware in der API für Request-Tracing (Hono ist nicht offiziell auto-instrumentiert).
beforeSend-Hardening: Statt zwei Top-Level-Felder zu redacten, eine rekursive sanitizeErrorReportValue-Funktion (existiert bereits in packages/error-reporter/src/sanitize.ts — falls nicht, neu anlegen) auf das gesamte Event-Objekt anwenden. Allowlist-Ansatz für Header (content-type, user-agent, accept-language durch — alles andere raus).
Release-Tagging: SENTRY_RELEASE env aus CI (= $GIT_SHA) in Sentry.init durchreichen. Default-Fallback ist dev-<short-sha> in pnpm dev.
Sample-Rates:
tracesSampleRate: prod default0.1, dev1.0(steuerbar viaSENTRY_TRACES_SAMPLE_RATE)profilesSampleRate: prod0.0(off), dev0.0(Profiling separat freischalten viaSENTRY_PROFILES_SAMPLE_RATE)
Implementation Steps
-
sanitizeErrorReportValuerekursiv machen — prüfen ob die Funktion schon existiert, falls ja erweitern, falls nicht inpackages/error-reporter/src/sanitize.tsanlegen. Soll für Objekte/Arrays rekursiv arbeiten, Keys einer Denylist redacten (password,token,secret,authorization,cookie,set-cookie,email,phone, etc.), Strings ab > 8 KB truncaten.- Files:
packages/error-reporter/src/sanitize.ts,packages/error-reporter/src/sanitize.test.ts - Commit:
02-observability/sentry-wiring-api: extend sanitizeErrorReportValue with recursive PII redaction
- Files:
-
beforeSend-Hook updaten —packages/error-reporter/src/sentry.ts:78-84so umbauen, dasssanitizeErrorReportValueaufevent.request,event.user,event.breadcrumbs,event.tags,event.contexts,event.extraläuft. Header-Allowlist statt Denylist.- Files:
packages/error-reporter/src/sentry.ts,packages/error-reporter/src/sentry.test.ts - Commit:
02-observability/sentry-wiring-api: harden beforeSend with recursive sanitization
- Files:
-
API-Bootstrap-Modul — neu
apps/api/src/sentry.ts:Sentry.init({ dsn, environment, release, tracesSampleRate, integrations: [httpIntegration, prismaIntegration({ client: prisma }), ...] })- Exportiert
apiErrorReporter: ErrorReporter(Sentry-Adapter auspackages/error-reporter) - Liest DSN aus
process.env.SENTRY_DSN_API, Release ausprocess.env.SENTRY_RELEASE - Wenn keine DSN gesetzt → No-Op-Reporter zurückgeben (für lokale Dev ohne Sentry)
- Files:
apps/api/src/sentry.ts,apps/api/src/sentry.test.ts - Commit:
02-observability/sentry-wiring-api: add api sentry bootstrap module
-
API:
createErrorHandlerverdrahten —apps/api/src/app.ts:86so ändern dassreporter: apiErrorReporterübergeben wird.- Files:
apps/api/src/app.ts - Commit:
02-observability/sentry-wiring-api: wire apiErrorReporter into createErrorHandler
- Files:
-
API: Hono-Tracing-Middleware — Hono hat keine offizielle Sentry-Integration. Eigene Middleware in
apps/api/src/middleware/sentryTracing.ts:- Startet
Sentry.startSpan({ op: 'http.server', name: '<METHOD> <route-pattern>' })pro Request - Setzt
transaction.setTag('http.method', ...),transaction.setTag('http.status_code', ...) - Catched Errors, ruft
Sentry.captureExceptionmit Span-Context - Files:
apps/api/src/middleware/sentryTracing.ts,apps/api/src/middleware/sentryTracing.test.ts - Commit:
02-observability/sentry-wiring-api: add hono tracing middleware
- Startet
-
API: Middleware registrieren — die neue Middleware als erste Middleware in der Hono-App registrieren (in
apps/api/src/app.ts).- Files:
apps/api/src/app.ts - Commit:
02-observability/sentry-wiring-api: register sentry tracing middleware in hono app
- Files:
-
Worker-Bootstrap-Modul — analog zu Step 3, neu
apps/worker/src/sentry.ts:- Eigene DSN (
SENTRY_DSN_WORKER) - Integrations:
httpIntegration(für outbound HTTP),prismaIntegration,bullMQIntegration(über@sentry/nodev8'sSentry.bunIntegrationoder Manuelle BullMQ-Hooks falls nicht offiziell) - Exportiert
workerErrorReporter: ErrorReporter - Files:
apps/worker/src/sentry.ts,apps/worker/src/sentry.test.ts - Commit:
02-observability/sentry-wiring-worker: add worker sentry bootstrap module
- Eigene DSN (
-
Worker:
startWorkerRuntimeverdrahten —apps/worker/src/index.ts:10so ändern dassreporter: workerErrorReporterübergeben wird.- Files:
apps/worker/src/index.ts - Commit:
02-observability/sentry-wiring-worker: wire workerErrorReporter into runtime
- Files:
-
Worker: BullMQ-Job-Tracing — falls keine offizielle Integration, manuelle Wrapper in
apps/worker/src/runtime.ts:- Pro
Worker-Instance einenprocessor-Wrapper, derSentry.startSpan({ op: 'queue.process', name: '<queue-name>/<job-name>' })startet - Job-ID, Attempts, Tenant-ID (wenn vorhanden) als Tags
- Bei Job-Error:
Sentry.captureExceptionmit Span-Context - Files:
apps/worker/src/runtime.ts,apps/worker/src/runtime.test.ts - Commit:
02-observability/sentry-wiring-worker: add bullmq job tracing wrapper
- Pro
-
Env-Defaults +
.env.example— neue Variablen dokumentieren:SENTRY_DSN_API,SENTRY_DSN_WORKERSENTRY_ENVIRONMENT(=production|staging|dev)SENTRY_RELEASE(=$GIT_SHAin CI)SENTRY_TRACES_SAMPLE_RATE(default0.1prod,1.0dev)SENTRY_PROFILES_SAMPLE_RATE(default0.0)- Files:
.env.example,packages/config/src/env.ts(Zod-Schema),packages/config/src/env.test.ts - Commit:
02-observability/sentry-wiring-api: add sentry env vars to config schema
-
Smoke-Test-Skript —
apps/api/scripts/smoke-sentry.ts: triggert manuell einen Fake-Error + eine Fake-Transaction, prüft via Sentry-API ob beides angekommen ist.- Files:
apps/api/scripts/smoke-sentry.ts - Commit:
02-observability/sentry-wiring-api: add smoke script
- Files:
Test Requirements
- Unit-Tests:
packages/error-reporter/src/sanitize.test.ts— alle Denylist-Keys werden in nested objects/arrays redacted; lange Strings truncated; circular refs werden nicht zur Endlosschleifepackages/error-reporter/src/sentry.test.ts—beforeSendredactet vollständig; gibtnullzurück wennevent.exception.values[0].typeauf der Drop-Liste (z.B.AbortError)apps/api/src/sentry.test.ts— No-Op-Reporter wenn keine DSN; richtige Integrations geladen wenn DSN gesetztapps/api/src/middleware/sentryTracing.test.ts— Span wird gestartet, status_code korrekt gesetzt, Error führt zucaptureExceptionmit Span-Contextapps/worker/src/runtime.test.ts— Job-Wrapper startet Span, setzt Tags, fängt Errors
- Integration-Test:
- In-Memory-Sentry-Transport (offizielles
@sentry/coretestkit) prüft End-to-End: API-Request → Span → captureException → Event hat erwartetes Payload
- In-Memory-Sentry-Transport (offizielles
- E2E (manuell auf Staging):
- 1 Fake-500-Endpoint hochtreten, schauen ob Issue in Sentry-UI auftaucht
- 1 Worker-Job fehlschlagen lassen, schauen ob Issue mit
queue+job_name-Tag auftaucht
- Coverage-Target: ≥ 90% für
packages/error-reporter/src/, ≥ 80% fürapps/api/src/sentry.ts+apps/worker/src/sentry.ts+ Middleware - Test-Files: wie in Implementation Steps gelistet
NICHT als completed markieren bevor:
- alle Unit-Tests grün
- ein echtes Test-Event in Staging-Sentry sichtbar ist (für api UND worker)
Acceptance Criteria
apiErrorReporterundworkerErrorReporterinapp.ts/index.tsverdrahtetbeforeSendmacht rekursive PII-Sanitization, Header-Allowlist statt -Denylist- Hono-Tracing-Middleware aktiv, Spans für jeden Request
- BullMQ-Job-Tracing aktiv, Spans für jeden Job
- Release-Tagging via
SENTRY_RELEASEenv funktioniert - Sample-Rates env-konfigurierbar, Defaults korrekt
- Lokal ohne
SENTRY_DSN_*läuft alles (No-Op-Reporter) - Staging: Test-Error in API + Test-Job-Error in Worker landen in Sentry-UI, mit korrekten Tags
pnpm lint,pnpm typecheck,pnpm testgrün.env.exampleerweitert- Detail-MD Progress Log aktualisiert
- PROGRESS.md Status auf
completed
Rollback Strategy
- Reporter abklemmen:
SENTRY_DSN_API/SENTRY_DSN_WORKERleer setzen → No-Op-Reporter, kein Error reporting, App läuft weiter. - Sample-Rate auf 0:
SENTRY_TRACES_SAMPLE_RATE=0→ kein Tracing-Overhead. - Code-Revert der Wiring-Commits (jeder Step ist ein Commit, einzeln revertierbar) — Implementation-Steps 4, 6, 8, 9 sind die kritischen Wirings.
- Middleware-Disable: Hono-Middleware mit
if (!process.env.SENTRY_DSN_API) return next()ist self-disablend.
Security Notes
- PII-Risiko ist real:
event.request.bodykann Passwörter, Tokens, Stripe-Card-Details enthalten. Die Sanitization MUSS rekursiv sein, sonst leaken wir DSGVO-relevante Daten an Sentry (auch wenn Sentry self-hosted ist — Log-Files, Backups, Sentry-Web-UI-Nutzer). - Header-Allowlist statt Denylist: nur explizit erlaubte Header durchlassen (
content-type,user-agent,accept-language,x-request-id). Authorization/Cookie/Set-Cookie/X-Api-Key/X-CSRF-Token werden so sicher gar nicht erst weitergegeben. - Breadcrumbs standardmäßig kappen auf 50 Einträge (
maxBreadcrumbs: 50), sonst Memory-Leak bei langen Sessions. - Stack-Traces sind nicht sensitiv (kein Source-Code-Leak), aber Source-Maps ja — Source-Maps werden NUR via
sentry-clian Sentry hochgeladen, NICHT mit dem Build-Output publiziert (siehesentry-ci-integration.md). - Tracing-Sample-Rate prod = 0.1 — bei 100% riskieren wir Performance-Regression UND erhöhtes PII-Leak-Risiko (mehr Spans = mehr potentielle Payload-Snippets in Tags).
SENTRY_DSN_*ist semi-public (Frontend-DSN ist eh public sichtbar) — kein Geheimnis aber auch nicht in Logs ausgeben.
Subagent Recommendations
- Planning:
Plan-Subagent mit dieser Detail-MD — soll prüfen obsanitizeErrorReportValueschon existiert, sonst Neu-Anlage-Plan - Research:
Explore-Subagent mit Brief: "Welche@sentry/nodev8 Integrations sind für Hono + Prisma + BullMQ verfügbar; welche brauchen manuelle Wrapper" - Implementation
packages/error-reporter/:general-purpose-Subagent für Steps 1-2 (atomare Commits) - Implementation API-Side:
general-purpose-Subagent für Steps 3-6 - Implementation Worker-Side:
general-purpose-Subagent für Steps 7-9 - Tests: separater
general-purpose-Subagent (kann erste Test-Files schon parallel zur Implementation schreiben, da Specs eindeutig) - Verification:
general-purpose-Subagent für Acceptance-Criteria-Check + Staging-Smoke-Test - Code-Review:
gsd-code-reviewernach Task-Completion
Staging-Deployment-Steps
- Voraussetzung:
sentry-selfhost-setup.mdistcompleted, DSNs fürboilerplate-apiundboilerplate-workerexistieren in Sentry-UI - Coolify-Env-Variables für api + worker setzen:
SENTRY_DSN_API,SENTRY_DSN_WORKER,SENTRY_ENVIRONMENT=staging,SENTRY_RELEASE=<git-sha>,SENTRY_TRACES_SAMPLE_RATE=1.0(für Staging vollständiges Tracing) - Branch
agent/phase-02-sentry-wiring-backendpushen → Coolify auto-deploys - Smoke-Test API:
curl https://api.<staging-domain>/__test/throw # 500 erzwingen # In Sentry-UI: Project boilerplate-api → Issues → Eintrag binnen 30s # Span sichtbar in Performance → Transaktion "GET /__test/throw" - Smoke-Test Worker:
# Triggere einen Job, der gewollt fehlschlägt (z.B. via Test-Queue): pnpm --filter @apps/worker exec node scripts/smoke-sentry.ts # In Sentry-UI: Project boilerplate-worker → Issues + Transactions - PII-Check: manuell ein Issue öffnen, prüfen dass
request.headers.authorization,user.email,breadcrumbs[].data.body.passwordNICHT im Klartext stehen →[Redacted]erwarten - Tag-Check: Issue hat
release: <git-sha>,environment: staging,runtime: node-<version> - Wenn alles grün: PR → main
Deviations from Plan
packages/config/src/env.tszod schema: repo verwendet keinen zentralen Zod-Env-Schema-File. Stattdessen tracken wir Env-Variablen inpackages/config/src/catalog.tsundenv-examples.test.tsenforced Drift. Statt einen neuen Zod-Schema einzuführen (Scope-Creep), wurden die neuen Env-Variablen in den existierenden Catalog eingetragen, plus die historisch undokumentiertenSENTRY_URLundAUTH_PASSWORD_*-Keys mitkatalogisiert.@sentry/nodev10.53.1 statt v8.x: Lockfile zog v10 ein (über den parallelenapps/webSentry-Wiring-Track). API ist mit v8/v10 kompatibel (gleichestartSpan/captureException/requestDataIntegration-Signaturen); kein Downgrade.prismaIntegration({ client: prisma }): in den API/Worker Bootstrap-Modulen aktuell NICHT registriert, weil das die Prisma-Client-Instanz importieren würde, was dieapps/api/src/sentry.tszu einer schweren Abhängigkeit macht (statt einem leichten Lazy-Reporter). Die Sentry-@sentry/nodev10httpIntegrationist defaultmäßig aktiv und reicht für API-Span-Coverage; Prisma-Spans können in einem Follow-up phase 02 task aktiviert werden.- Hono "throw"-Pfad: Hono leitet thrown Errors über
app.onError→ der Throw propagiert nicht durch die Middleware-Kette. Die Tracing-Middleware liest deshalbc.errorzusätzlich zu try/catch, damit beide Pfade abgedeckt sind. - Worker
index.tsTop-Level-Await: Lazy-Import des Sentry-SDK braucht Top-Level-Await; tsconfig ist NodeNext, also funktional. Sollte das produktiv Probleme machen, lässt sich der Import in eine async-IIFE wickeln. - 2026-05-27 status reconciliation:
docs/improvement-plan/OPERATOR-CHECKLIST.mdA10 verifies the per-app Sentry DSNs are live (SENTRY_DSN_API,SENTRY_DSN_WEB,SENTRY_DSN_WORKER) andPROGRESS.mdmarks #30completed. This supersedes the stalestaging-smoke-pendingcaveat. Keep the staging smoke steps above as the repeatable verification procedure for new deployments and releases.
Progress Log
- 2026-05-17 ~03:54 —
2493a76e— Step 1: extract sanitizeErrorReportValue to dedicated sanitize.ts with expanded denylist + 8 KB truncation + circular refs (packages/error-reporter/src/sanitize.ts+ tests). - 2026-05-17 ~04:09 —
b85e3ac9— Step 2: harden beforeSend with recursive sanitization, header allowlist, AbortError drop, init plumbing for tracesSampleRate/profilesSampleRate/maxBreadcrumbs/integrations (packages/error-reporter/src/sentry.ts+ tests). - 2026-05-17 ~04:14 —
71c165a0— Step 3: add API Sentry bootstrap module (apps/api/src/sentry.ts) +@sentry/nodedep on api/worker. - 2026-05-17 ~04:16 —
61f4f207— Step 4: wire apiErrorReporter into createErrorHandler. - 2026-05-17 ~04:20 —
18310c61— Step 5: add Hono tracing middleware (apps/api/src/middleware/sentryTracing.ts+ tests). - 2026-05-17 ~04:22 —
7c6576f4— Step 6: register Sentry tracing middleware as first middleware in app.ts. - 2026-05-17 ~04:25 —
507464ad— Step 7: add Worker Sentry bootstrap module (apps/worker/src/sentry.ts+ tests). - 2026-05-17 ~04:28 —
60ed7b70— Step 8: wire workerErrorReporter into startWorkerRuntime + shutdownWorkerRuntime. - 2026-05-17 ~04:41 —
d71ed026— Step 9: BullMQ job tracing wrapper with queue.process spans + tenant tags + captureException (apps/worker/src/runtime.ts+ tests + index.ts lazy-load). - 2026-05-17 ~04:43 —
750d4653— Step 10: SENTRY_DSN_API/SENTRY_DSN_WORKER/SENTRY_RELEASE/SENTRY_TRACES_SAMPLE_RATE/SENTRY_PROFILES_SAMPLE_RATE in.env.example+ catalog; additionally catalog SENTRY_URL + AUTH_PASSWORD_* to fix the existing env-examples drift gate. - 2026-05-17 ~04:45 —
a8b00f56— Step 11: addapps/api/scripts/smoke-sentry.ts(manual exception + transaction trigger). - 2026-05-27 — docs/status reconciliation — A10 verifies DSNs live
across api/worker/web, matching the #30
completedrow inPROGRESS.md.
Lessons Learned
(Optional am Task-Ende.)