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.md→ Observability / 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 mitavg_auslastung_pctwidersprä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 D1 — getrennt 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-workersoder 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
| Kandidat | Stärke | Schwäche | EU-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). |
| Sentry | Beste 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-nativ | Kein 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. |
| Honeycomb | Sehr 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
- 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).
- 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.
- Cloudflare-nativ ergänzend für reine Worker-Logs/Logpush sinnvoll, deckt aber Client + Solver nicht → kein Ersatz für ein einheitliches Backend.
- 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-Jobsecurity-audit(npm auditprod-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 mitservice/version/build/trace_id, PII-Scrubbing (sensible Keys →[redacted], E-Mails maskiert; RISK-3), Level-Filter, child-Logger. Transport überLogSinks: 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 viactx.waitUntil(kein Blockieren des Laufzeitpfads). Erste Adopter: Boot-Event + die Kosten-Pull-Pfade (GitHub/Anthropic/Mistral). Dormant ohneOTEL_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-Testnpm 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+ stabileuid⇒ Update-in-place; dormant ohneGRAFANA_URL/GRAFANA_DASHBOARD_TOKEN). Netzfreies Gatetest:grafana-dashboardsvalidiert 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 alstaktano_log_events{level}gezählt (echte Fehler-/Volumen-Rate, nicht nur log-abgeleitet). Zusätzlichtaktano_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 viactx.waitUntil(dormant ohne Endpoint). Dashboardgrafana/um Prometheus-Panels ergänzt. Naming: Code ohne_total(OTel) → PromQL…_total(OTLP→Prometheus ergänzt es). Self-Testtest:obserweitert (Registry/Histogramm/Payload).
Env-Vertrag (dormant-Aktivierung, OP-OBS-1)
| Variable | Zweck | Art |
|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT | Basis-URL des OTLP/HTTP-Collectors (ohne /v1/logs) | Secret/[var] |
OTEL_EXPORTER_OTLP_HEADERS | Auth key=value,… (z. B. Authorization=Basic%20<b64>) | Secret |
LOG_LEVEL | Mindest-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. Viatry/finallygemessen (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);grunderklä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_BUCKETS0,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-Gatescripts/check-no-console.sh(Server-Job) verhindert Rückfälle (Ausnahmen:demo.ts/*-demo.ts/spike.ts+ Zeilen-Markerobs-allow-consolefü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.test→testeObsExport()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 (ohneOTEL_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 Status —
obsStatus(transient je DO-Lebenszeit): jeder reguläreflushObs()-Export und jeder Test ruftmerkeObsExport()(letztes ok/HTTP-Status/Grund/Zeit + ZählerlogsGesendet/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, Scopeslogs:write+metrics:write, FormatAuthorization=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 SecretOTEL_EXPORTER_OTLP_HEADERSfalsch/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) aufLogEvent[](service=taktano-client) und leitet serverseitig viapostOtlpLogsweiter → das Grafana-Token bleibt im Worker. Dormant ohneOTEL_EXPORTER_OTLP_ENDPOINT→204. - Erfassung (Client,
client-obs.service.ts): Angular-ErrorHandler+window.onerror+unhandledrejection(+ manuelleerror/warn/info-API); Ringpuffer (max 50), Batch-Flush alle 10 s + aufpagehide/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-Testtest: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)