Zum Hauptinhalt springen

Observability / Logging-Backend — Architektur-Design (OP-OBS-1)

Status: in Umsetzung — Prinzip beschlossen (OTel/OTLP, User 06-22), Backend = Grafana gewählt (User 07-02; Cloud EU ↔ self-hosted LGTM code-frei umschaltbar, live); Slices 1 · 2 · A · A2 · B · C1 + Verbindungstest gebaut (zuletzt v0.51.0 — s. §6a), Folge-Slices offen · Bezug: OP-OBS-1 (Owning-OP, HANDOFF.md §4) · OP-LOG-1 · Root-CLAUDE.mdObservability / Logging · docs/konventionen/agents.md §5.1 · grafana/README.md · Zielgruppe: IT-Ops · DevOps · True North: nur indirekt — technische Betriebs-Telemetrie hält die App verlässlich am Laufen, die die drei Leitfragen beantwortet (Telemetrie ≠ Fachsicht, §1).

Kernaussage. OTLP/Grafana ist DER zentrale Log-Sink (Leitlinie 07-03): alle drei Laufzeiten (Worker/DO · Angular-Client · Python-Solver) und Tooling-/CI-Schritte melden Logs/Traces/Metrics über das OpenTelemetry-SDK und einen dünnen Logger-Wrapper an Grafana (Cloud EU ↔ self-hosted LGTM code-frei umschaltbar) — best-effort und dormant ohne Endpoint, bricht nie den Deploy-Pfad. Weil alles über OTLP exportiert, bleibt die Backend-Wahl vendor-neutral austauschbar (kein Lock-in).

1. Zweck & Abgrenzung (was hierher gehört — und was nicht)

OP-OBS-1 ist die technische Betriebs-Telemetrie: Anwendungs-Logs, Traces und Metrics über das OpenTelemetry-SDK (OTLP), pro Laufzeit über einen dünnen Logger-Wrapper, mit trace_id-Korrelation. Dieses Dokument klärt die offene Frage „welches Backend/Collector".

Leitlinie (Nutzer 07-03, Root-CLAUDE.md §Observability): OTLP/Grafana ist DER zentrale Log-Sink. Nicht nur die Laufzeiten, auch Tooling-/CI-Schritte (z. B. der Doku-Index-Push, service.name= taktano-ci) melden operativ relevante Warnungen/Fehler dorthin — Actions-Annotationen/console sind Kontext-Ergänzung, nie Ersatz. Best-effort + dormant ohne Endpoint (bricht nie den Deploy-Pfad).

Bewusst NICHT hier (häufige Verwechslung — Scope-Leitplanke):

  • True-North-/Cockpit-Dashboards (Auslastung · Wo stehen die Fahrzeuge · freie Kapazität) gehören in die App — Leitprinzip Selbsterklärbarkeit, deutsche Domänenbegriffe, --tk-*-Tokens. Ein Telemetrie-Tool-Panel mit avg_auslastung_pct widerspräche dem. Telemetrie ≠ Fachsicht.
  • Kosten-Dashboard (OP-COST-1) ist eine eigene App-Sicht; Infra-Spend wird in der App aggregiert (server/src/kosten/infra.ts), nicht aus einem Observability-Tool gespiegelt.
  • Audit-Log (OP-AUDIT-1) ist die fachliche, berechtigungs-gefilterte Änderungshistorie auf D1getrennt von der technischen OTel-Telemetrie (Compliance-Historie ≠ Betriebs-Logs).

2. Ausgangslage

Drei Laufzeiten, heute alle console/print-basiert (CLI-Demos & Self-Tests dürfen das bleiben):

  • Worker/DO (Cloudflare) — OTel via @microlabs/otel-cf-workers oder native Workers-Observability (Workers Logs / Tail-Worker / Logpush) → OTLP.
  • Client (Angular/Browser) — @opentelemetry/sdk-logs + sdk-trace-web, OTLP/HTTP an einen Collector hinter Access; Frontend-Fehler/RUM.
  • Solver (Python/Fly.io) — opentelemetry-sdk + OTLP-Exporter.

Weil alle drei über OTLP exportieren, ist die Backend-Wahl vendor-neutral austauschbar — ein Wechsel kostet keine Code-Änderung, nur Endpoint/Config. Das entschärft die Entscheidung (kein Lock-in).

3. Backend-Kandidaten

KandidatStärkeSchwächeEU-Datenresidenz
Grafana (LGTM)Volle Plattform: Loki (Logs) · Tempo (Traces) · Mimir/Prometheus (Metrics) · Faro (Frontend-RUM) · Alerting/OnCall. OSS, ein Pane über alle 3 Laufzeiten.Mehr Setup/Ops (self-host) bzw. Cloud-Kosten; Crash-Triage roher als Sentry.Stark — self-hostbar (Daten bleiben bei uns) oder Grafana Cloud EU (Frankfurt).
SentryBeste Crash-/Exception-DX, schnelle Triage; SDKs @sentry/cloudflare · @sentry/angular · sentry-sdk (Python); ingestiert OTLP → kann das OP-OBS-1-Backend sein.Error-zentriert (Logs/Metrics schwächer als LGTM); SaaS.Mittel — EU-Region nötig + AVV/DPA + PII-Scrubbing.
Cloudflare-nativKein Drittanbieter, Worker-Logs/Tail/Logpush „eingebaut", am nächsten an der Worker-Laufzeit.Deckt Client + Python-Solver nicht ab → kein einheitliches Pane; begrenzte Trace-/Metrics-Tiefe.Gut (CF-Verträge), aber nur ein Teil des Stacks.
HoneycombSehr starke Trace-/High-Cardinality-Analyse (Debugging verteilter Requests).SaaS, US-zentriert; primär Traces (Logs/RUM nachrangig).Schwach für DE/AT/CH (US-Residenz).

Grafana vs. Sentry — die eigentliche Abwägung

Sie überlappen, sind aber unterschiedlich akzentuiert. Entweder-oder, nicht additiv:

  • Sentry wenn schnelle Crash-Visibility mit minimalem Aufwand Vorrang hat.
  • Grafana wenn Datenhoheit (DE/AT/CH), Vendor-Neutralität und eine durchgehende Logs+Traces+Metrics+RUM-Plattform für die ISO-/Audit-Story schwerer wiegen.

4. DSGVO / EU-Residenz (verbindliche Leitplanke)

Zielmärkte DE/AT/CH → jedes Backend, das Telemetrie an Dritte sendet, braucht EU-Residenz, AVV/DPA und PII-Scrubbing (keine Klartext-PII in Logs, OP-LOG-1, RISK-3). Grafana self-hosted ist hier am stärksten (Telemetrie bleibt vollständig in unserer Hand) — der Preis ist Betriebsaufwand. Grafana Cloud EU bzw. Sentry EU-Region sind der pragmatische Mittelweg; Honeycomb (US) ist dafür schwach.

5. Empfehlung

  1. Grafana als bevorzugtes OP-OBS-1-Backend — bevorzugt self-hosted (LGTM) wegen EU-Datenhoheit (Compliance + Verkaufsargument, analog zur Better-Auth-Entscheidung OP-AUTH-1), alternativ Grafana Cloud EU als Ops-armer Start mit späterer Migration (dank OTLP code-frei).
  2. Sentry als gleichwertige Alternative, falls Crash-DX/„schnell ohne Ops" höher gewichtet wird — dann mit EU-Region + Scrubbing als harten Vorbedingungen. Sentry kann denselben OTLP-Strom annehmen.
  3. Cloudflare-nativ ergänzend für reine Worker-Logs/Logpush sinnvoll, deckt aber Client + Solver nicht → kein Ersatz für ein einheitliches Backend.
  4. Honeycomb zurückgestellt (US-Residenz).

Entscheidung noch offen (User-Wahl Grafana vs. Sentry); die OTLP-Verdrahtung selbst ist davon unabhängig und kann unabhängig vorbereitet werden.

6. Verwandte Werkzeug-Frage (SonarQube · Snyk · Sentry · Grafana)

Aus derselben Diskussion (User 06-27 „Hilft uns SonarQube/Sentry/Snyk? Wie könnte Grafana helfen?"):

  • Sentry / Grafana → siehe oben, OP-OBS-1-Backend-Kandidaten (kein zusätzliches Tool, sondern die Backend-Wahl für eine bereits getroffene Entscheidung).
  • Snyk → adressiert RISK-4 (Supply-Chain); Lücke 06-27 gratis & GitHub-nativ geschlossen: Dependabot (.github/dependabot.yml) ✅ + CI-Job security-audit (npm audit prod-Deps high+ / pip-audit) ✅. Snyk erst später als Komfort (Lizenz-/Audit-Dashboard).
  • SonarQube / CodeQL → die Lint-/Static-/SAST-Lücke füllt CodeQL ✅ aktiv via GitHub Default Setup (06-27): GitHub fährt CodeQL selbst (TS+Python automatisch, PR/Push, null Wartung); bewusst kein eigener Workflow (advanced + default schließen sich aus). Ergänzend offen: ESLint + ruff/mypy. SonarQube/SonarCloud weiter nur später als audit-fähiges Quality-Dashboard (Datenresidenz-Vorbehalt).

→ Festgehalten in docs/betrieb/Compliance.md (SBOM/Security) + docs/betrieb/Risikoregister.md (RISK-4).

6a. Stand der Umsetzung (OP-OBS-1 / OP-LOG-1)

Backend gewählt (User 07-02): Grafana. Cloud EU vs. self-hosted LGTM bleibt offen, ist aber dank OTLP code-frei umschaltbar (nur Endpoint/Token).

Slice 1 — Logger-Wrapper + OTLP-Log-Export (Worker/DO) — gebaut (v0.45.0)

  • Logger-Wrapper server/src/obs/logger.ts (OP-LOG-1): strukturierte Events mit service/version/ build/trace_id, PII-Scrubbing (sensible Keys → [redacted], E-Mails maskiert; RISK-3), Level-Filter, child-Logger. Transport über LogSinks: console (interim, deploy-sicher) + optional OTLP-Puffer.
  • OTLP/HTTP-Export server/src/obs/otlp.ts: baut Standard-resourceLogs-JSON und POSTet best-effort; dependency-frei, nimmt jeder OTLP-Collector (Grafana Alloy / OTel-Collector / Grafana-Cloud-Gateway).
  • DO-Verdrahtung (server/party/leitstand.ts): Logger mit console + (falls Endpoint) OTLP-Puffer; flushObs() schickt den Puffer via ctx.waitUntil (kein Blockieren des Laufzeitpfads). Erste Adopter: Boot-Event + die Kosten-Pull-Pfade (GitHub/Anthropic/Mistral). Dormant ohne OTEL_EXPORTER_OTLP_ENDPOINT.
  • Dashboards-as-Code: grafana/dashboards/taktano-technik.json (Log-Volumen/Level, Fehler/Warnungen, Kosten-Pull-Ergebnisse, letzte Fehler) + grafana/README.md. Metriken in Slice 1 aus Logs abgeleitet (LogQL); native OTLP-Metrics + Traces = Folge-Slices. Self-Test npm run test:obs (in CI). Auto-Provisioning (07-02): Dashboards erscheinen nicht von selbst über OTLP (OTLP transportiert nur Daten) — der CI-deploy-Job spielt sie deshalb per Grafana-API ein (scripts/push-grafana-dashboards.mjs, POST /api/dashboards/db, overwrite + stabile uid ⇒ Update-in-place; dormant ohne GRAFANA_URL/GRAFANA_DASHBOARD_TOKEN). Netzfreies Gate test:grafana-dashboards validiert die JSONs je PR. Repo = Single Source; Hand-Edits in Grafana werden beim nächsten Deploy überschrieben.

Slice 2 — native OTLP-Metrics (Worker/DO) — gebaut (v0.46.0)

  • Metrik-Registry server/src/obs/metrics.ts (MetricsRegistry): Counter + explizite-Bucket- Histogramme, in-memory je DO-Lebenszeit, cumulative (mappt sauber auf Prometheus-Counter). Reiner OTLP/HTTP-JSON-Payload-Builder (resourceMetrics, sum/histogram) + best-effort POST an /v1/metrics.
  • metricsLogSink: hängt am Logger → jeder Log wird zusätzlich als taktano_log_events{level} gezählt (echte Fehler-/Volumen-Rate, nicht nur log-abgeleitet). Zusätzlich taktano_infra_pull{dienst,ergebnis} an den Auto-Pull-Pfaden. Histogramm-Fähigkeit (ms-Buckets) — in Slice A verdrahtet (s. u.).
  • flushObs() schickt Logs + Metrik-Snapshot via ctx.waitUntil (dormant ohne Endpoint). Dashboard grafana/ um Prometheus-Panels ergänzt. Naming: Code ohne _total (OTel) → PromQL …_total (OTLP→Prometheus ergänzt es). Self-Test test:obs erweitert (Registry/Histogramm/Payload).

Env-Vertrag (dormant-Aktivierung, OP-OBS-1)

VariableZweckArt
OTEL_EXPORTER_OTLP_ENDPOINTBasis-URL des OTLP/HTTP-Collectors (ohne /v1/logs)Secret/[var]
OTEL_EXPORTER_OTLP_HEADERSAuth key=value,… (z. B. Authorization=Basic%20<b64>)Secret
LOG_LEVELMindest-Level (debug/info/warn/error), Default info[var]

Bereitstellung wie die übrigen Secrets über GitHub-Actions-Secrets (CI-deploy spiegelt via wrangler secret put, Everything-as-Code). Ohne Endpoint = No-op (console-only), deploy-sicher.

Slice A — Dauer-Histogramme (Worker/DO) — gebaut (v0.47.0)

  • taktano_solve_dauer{modus,ergebnis} (CP-SAT-Aufruf) + taktano_assistent_turn_dauer{frontend,ergebnis} (Assistent-Turn), ms-Buckets → P50/P95/P99. Via try/finally gemessen (auch Fehl-/Timeout-Fälle, ergebnis-Label).

Slice A2 — RAG-Wirkungs-Metriken (OP-DOCS-2) — gebaut (v0.51.0)

Macht die Wirkung der semantischen Doku-Suche (Vectorize-RAG, In-App-Assistent.md §8.1) messbar — je dokuSuche-Aufruf:

  • taktano_doku_suche{pfad,grund,ergebnis} (Counter): pfad = vectorize/keyword (wer hat bedient); grund erklärt den Fallback (ok · dormant = Binding/Embed-Modell fehlt · leer = Vectorize ohne berechtigte Treffer · fehler = Embed/Query-Ausnahme); ergebnis=leer = Frage ganz ohne Treffer → das Recall-Signal („Assistent findet X nicht"), vor der Aktivierung das Aktivierungskriterium, jetzt der Erfolgs-Monitor.
  • taktano_doku_suche_dauer{pfad} (Histogramm, ms): Kosten des semantischen Pfads (Embed + Vectorize-Query) vs. Keyword (~0 ms) — der „Pain" aus der Pain/Gain-Analyse, live gemessen.
  • taktano_doku_suche_score (Histogramm, SCORE_BUCKETS 0,4–0,95): Cosine-Top-Score der semantischen Treffer — Match-Qualität über Zeit (sinkende Scores = Index veraltet/Korpus-Drift).
  • Logzeile assistent.dokuSuche (Wrapper, trace-korreliert): pfad · grund · anzahl · topScore · dauerMs · frageLaenge — bewusst ohne Fragetext (OP-LOG-1, kein Klartext-Inhalt ins Ops-Log).
  • Drei Grafana-Panels (grafana/dashboards/taktano-technik.json): RAG-Anteil & Fallback-Gründe · Latenz P95 je Pfad · Top-Score P50/P90.

Slice B — console→Wrapper-Migration + Gate — gebaut (v0.47.0)

  • Verbliebene Laufzeit-console.* im DO auf den Wrapper migriert (Boot/Kosten/Assistent/Purge/Employee) → alle DO-Laufzeit-Logs strukturiert in Grafana. CI-Gate scripts/check-no-console.sh (Server-Job) verhindert Rückfälle (Ausnahmen: demo.ts/*-demo.ts/spike.ts + Zeilen-Marker obs-allow-console für den consoleSink-Transport + die OTLP-Fehlversand-Pfade).

Verbindungstest + Status in der App — gebaut (v0.48.0)

Damit sich die Weiterleitung belegen lässt (nicht nur „konfiguriert"), gibt es einen aktiven Test und einen laufenden Status — sichtbar im Kosten-Workspace (Tab „Infra", admin-gegated):

  • Aktiver Test — DO-Handler obs.testtesteObsExport() sendet synchron (await) einen synthetischen Log + eine Test-Metrik (taktano_obs_test) an den OTLP-Endpoint und gibt das echte HTTP-Ergebnis (Log-/Metrik-Status) zurück. End-to-end-Beleg, dass Grafana annimmt. Dormant (ohne OTEL_EXPORTER_OTLP_ENDPOINT) → ehrliches {ok:false, grund:"nicht konfiguriert"}. Der reguläre Export bleibt fire-and-forget (waitUntil); nur der explizite Test wartet auf die Antwort.
  • Laufender StatusobsStatus (transient je DO-Lebenszeit): jeder reguläre flushObs()-Export und jeder Test ruft merkeObsExport() (letztes ok/HTTP-Status/Grund/Zeit + Zähler logsGesendet/metrikenGesendet). obsStatusDTO() liefert dem Client nur Host + letztes Ergebnis (Region sichtbar, kein Token/Secret), ausschließlich im Admin-Broadcast. Kein persistenter State (DTO ist reine Ableitung — „derived state nie speichern").
  • UI — Karte „Telemetrie · Grafana": Ampel (grün=ok · amber=Fehler/dormant · faint=noch keiner), Host/Region, „Letzter Export" (rel. Zeit + HTTP-Status), Zähler Logs/Metrik-Exporte, Button „↻ Verbindung testen" (Toast-Quittung). Bei Fehler zusätzlich ein selbsterklärender Klartext-Hinweis je Status (obsHinweis(), v0.48.1): 401/403 → Auth (Token/Instance-ID im OTLP-Header, Scopes logs:write+metrics:write, Format Authorization=Basic base64(InstanceID:Token)) · 404 → Endpoint (…/otlp ohne /v1/…) · 5xx → vorübergehend. Praxis-Befund 07-02: erster echter Test lieferte HTTP 401 → Export scheiterte bis dahin still (Credential im Secret OTEL_EXPORTER_OTLP_HEADERS falsch/scope-arm), vom Test sofort sichtbar gemacht — genau der Zweck der Karte.

Slice C1 — Client-Telemetrie (Angular, Variante A) — gebaut (v0.49.0)

Unbehandelte Client-Fehler landen jetzt in Grafana — vorher Blindflug. Bewusst minimal & dependency-frei (Variante A); volles RUM (Web-Vitals/Sessions) = Grafana Faro, Folge-Schnitt C2.

  • Kein Token im Browser (Kern-Entscheidung): der Browser POSTet same-origin an den Access-gegateten Worker-Endpoint POST /api/obs/client; der Worker mappt (rein/gehärtet, server/src/obs/client-logs.ts) auf LogEvent[] (service=taktano-client) und leitet serverseitig via postOtlpLogs weiter → das Grafana-Token bleibt im Worker. Dormant ohne OTEL_EXPORTER_OTLP_ENDPOINT204.
  • Erfassung (Client, client-obs.service.ts): Angular-ErrorHandler + window.onerror + unhandledrejection (+ manuelle error/warn/info-API); Ringpuffer (max 50), Batch-Flush alle 10 s + auf pagehide/visibilitychange (sendBeacon). Best-effort, kein Re-Queue bei Sendefehler (keine Endlosschleife). Konsole zeigt Fehler weiterhin (Dev-Erfahrung unverändert).
  • Härtung (Payload ist unvertrauenswürdig): Level-Whitelist, Event-Anzahl gedeckelt (50), msg/url-Längen begrenzt, Content-Length-Limit (128 KB), PII-Scrubbing wie serverseitige Logs. Self-Test test:obs-client.
  • Verifikation in Grafana: Loki {service_name="taktano-client"}.

EU-Residenz (RISK-16) — erfüllt

Stack auf Grafana Cloud EU umgestellt (otlp-gateway-prod-eu-west-2, 07-02) → Telemetrie bleibt in der EU (kurzzeitiger US-Test-Stack abgelöst). Rest-offen: AVV/DPA mit Grafana in Compliance.md belegen (analog RISK-15). Region-/Backend-Wechsel bleibt code-frei via OTLP.

Offene Folge-Slices

  • Client (Angular) C2 — volles RUM: Grafana Faro (Web-Vitals LCP/CLS/INP, Session-Tracking, Auto-Instrumentierung) bzw. @opentelemetry/sdk-trace-web. (C1 = Fehler-Forwarder ist gebaut, s. o.)
  • Solver (Python): opentelemetry-sdk + OTLP-Exporter (Request-/Solve-Timings, trace_id-Korrelation).
  • Traces über OTLP/Tempo (verteilte Request-/Solve-Spans, trace_id-Korrelation).
  • Weitere Metriken: Re-Opt-Läufe, Request-Dauer je Route.
  • Backend-Option: self-hosted LGTM als Alternative zu Grafana Cloud EU (code-frei via OTLP).

7. Bezüge

OP-OBS-1 (dieses Dokument) · OP-LOG-1 (Logger-Wrapper/PII-Scrubbing) · OP-AUDIT-1 (getrennt, fachlich) · OP-COST-1/OP-COST-2 (Infra-Spend-Pull, eigene App-Sicht) · OP-SEC-1/OP-SBOM-1 (Snyk/CodeQL-Kontext) · OP-AUTH-1 (EU-Residenz-Präzedenz) · RISK-3 (PII in Logs) · RISK-4 (Supply-Chain) · A-5-Stack · True North.


↩ Zurück zur Doku-Landkarte · Lesepfade · Register (alle OPs)