Files
HiveGrid-Wireframes/hivegrid-cloud/docs/improvement-plan/02-observability/sentry-wiring-api-worker.md
T

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:86 ruft createErrorHandler({ ... }) auf, ohne den reporter-Parameter zu setzen → Errors gehen nirgendwohin außer in den Logger.
  • apps/worker/src/index.ts:10 ruft startWorkerRuntime({ logger }) auf, ohne den reporter zu 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 vor request.headers.authorization, aber nicht vor user.email, nicht vor breadcrumbs[].data.body, nicht vor contexts.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ür boilerplate-api und boilerplate-worker
  • 01-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 Wiring
  • 02-observability/opentelemetry.md — OTel-Tracer registriert sich neben dem Sentry-Tracer; sauberer ist, Sentry zuerst zu wiren
  • 03-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 default 0.1, dev 1.0 (steuerbar via SENTRY_TRACES_SAMPLE_RATE)
  • profilesSampleRate: prod 0.0 (off), dev 0.0 (Profiling separat freischalten via SENTRY_PROFILES_SAMPLE_RATE)

Implementation Steps

  1. sanitizeErrorReportValue rekursiv machen — prüfen ob die Funktion schon existiert, falls ja erweitern, falls nicht in packages/error-reporter/src/sanitize.ts anlegen. 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
  2. beforeSend-Hook updaten — packages/error-reporter/src/sentry.ts:78-84 so umbauen, dass sanitizeErrorReportValue auf event.request, event.user, event.breadcrumbs, event.tags, event.contexts, event.extra lä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
  3. 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 aus packages/error-reporter)
    • Liest DSN aus process.env.SENTRY_DSN_API, Release aus process.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
  4. API: createErrorHandler verdrahten — apps/api/src/app.ts:86 so ändern dass reporter: apiErrorReporter übergeben wird.

    • Files: apps/api/src/app.ts
    • Commit: 02-observability/sentry-wiring-api: wire apiErrorReporter into createErrorHandler
  5. 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.captureException mit 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
  6. 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
  7. 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/node v8's Sentry.bunIntegration oder 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
  8. Worker: startWorkerRuntime verdrahten — apps/worker/src/index.ts:10 so ändern dass reporter: workerErrorReporter übergeben wird.

    • Files: apps/worker/src/index.ts
    • Commit: 02-observability/sentry-wiring-worker: wire workerErrorReporter into runtime
  9. Worker: BullMQ-Job-Tracing — falls keine offizielle Integration, manuelle Wrapper in apps/worker/src/runtime.ts:

    • Pro Worker-Instance einen processor-Wrapper, der Sentry.startSpan({ op: 'queue.process', name: '<queue-name>/<job-name>' }) startet
    • Job-ID, Attempts, Tenant-ID (wenn vorhanden) als Tags
    • Bei Job-Error: Sentry.captureException mit 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
  10. Env-Defaults + .env.example — neue Variablen dokumentieren:

    • SENTRY_DSN_API, SENTRY_DSN_WORKER
    • SENTRY_ENVIRONMENT (= production | staging | dev)
    • SENTRY_RELEASE (= $GIT_SHA in CI)
    • SENTRY_TRACES_SAMPLE_RATE (default 0.1 prod, 1.0 dev)
    • SENTRY_PROFILES_SAMPLE_RATE (default 0.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
  11. 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

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 Endlosschleife
    • packages/error-reporter/src/sentry.test.ts — beforeSend redactet vollständig; gibt null zurück wenn event.exception.values[0].type auf der Drop-Liste (z.B. AbortError)
    • apps/api/src/sentry.test.ts — No-Op-Reporter wenn keine DSN; richtige Integrations geladen wenn DSN gesetzt
    • apps/api/src/middleware/sentryTracing.test.ts — Span wird gestartet, status_code korrekt gesetzt, Error führt zu captureException mit Span-Context
    • apps/worker/src/runtime.test.ts — Job-Wrapper startet Span, setzt Tags, fängt Errors
  • Integration-Test:
    • In-Memory-Sentry-Transport (offizielles @sentry/core testkit) prüft End-to-End: API-Request → Span → captureException → Event hat erwartetes Payload
  • 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ür apps/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

  • apiErrorReporter und workerErrorReporter in app.ts / index.ts verdrahtet
  • beforeSend macht 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_RELEASE env 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 test grün
  • .env.example erweitert
  • Detail-MD Progress Log aktualisiert
  • PROGRESS.md Status auf completed

Rollback Strategy

  • Reporter abklemmen: SENTRY_DSN_API / SENTRY_DSN_WORKER leer 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.body kann 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-cli an Sentry hochgeladen, NICHT mit dem Build-Output publiziert (siehe sentry-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 ob sanitizeErrorReportValue schon existiert, sonst Neu-Anlage-Plan
  • Research: Explore-Subagent mit Brief: "Welche @sentry/node v8 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-reviewer nach Task-Completion

Staging-Deployment-Steps

  1. Voraussetzung: sentry-selfhost-setup.md ist completed, DSNs für boilerplate-api und boilerplate-worker existieren in Sentry-UI
  2. 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)
  3. Branch agent/phase-02-sentry-wiring-backend pushen → Coolify auto-deploys
  4. 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"
    
  5. 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
    
  6. PII-Check: manuell ein Issue öffnen, prüfen dass request.headers.authorization, user.email, breadcrumbs[].data.body.password NICHT im Klartext stehen → [Redacted] erwarten
  7. Tag-Check: Issue hat release: <git-sha>, environment: staging, runtime: node-<version>
  8. Wenn alles grün: PR → main

Deviations from Plan

  • packages/config/src/env.ts zod schema: repo verwendet keinen zentralen Zod-Env-Schema-File. Stattdessen tracken wir Env-Variablen in packages/config/src/catalog.ts und env-examples.test.ts enforced Drift. Statt einen neuen Zod-Schema einzuführen (Scope-Creep), wurden die neuen Env-Variablen in den existierenden Catalog eingetragen, plus die historisch undokumentierten SENTRY_URL und AUTH_PASSWORD_*-Keys mitkatalogisiert.
  • @sentry/node v10.53.1 statt v8.x: Lockfile zog v10 ein (über den parallelen apps/web Sentry-Wiring-Track). API ist mit v8/v10 kompatibel (gleiche startSpan/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 die apps/api/src/sentry.ts zu einer schweren Abhängigkeit macht (statt einem leichten Lazy-Reporter). Die Sentry-@sentry/node v10 httpIntegration ist 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 deshalb c.error zusätzlich zu try/catch, damit beide Pfade abgedeckt sind.
  • Worker index.ts Top-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.md A10 verifies the per-app Sentry DSNs are live (SENTRY_DSN_API, SENTRY_DSN_WEB, SENTRY_DSN_WORKER) and PROGRESS.md marks #30 completed. This supersedes the stale staging-smoke-pending caveat. 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/node dep 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: add apps/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 completed row in PROGRESS.md.

Lessons Learned

(Optional am Task-Ende.)