docs(observability): add observability gaps analysis document

Document missing monitoring capabilities (distributed tracing, log aggregation,
APM, frontend error monitoring) and potential solutions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-22 19:16:42 +01:00
parent 42b32cb07d
commit 04487ba909

468
docs/OBSERVABILITY_GAPS.md Normal file
View file

@ -0,0 +1,468 @@
# Observability Gaps: Was unserem Monitoring-Stack noch fehlt
> **Stand:** März 2026
> **Bezug:** Bestehendes Setup dokumentiert in [MONITORING.md](MONITORING.md) und [ERROR_TRACKING.md](ERROR_TRACKING.md)
## Übersicht: Was wir haben vs. was fehlt
| Bereich | Haben wir? | Tool | Status |
|---------|-----------|------|--------|
| Metriken (Infra + App) | Ja | VictoriaMetrics + Grafana | Produktionsreif |
| Alerting | Ja | Alertmanager + Telegram/ntfy | 70+ Regeln |
| Error Tracking (Backend) | Ja | GlitchTip | 18 Backends angebunden |
| Web Analytics | Ja | Umami | 15 Apps getracked |
| **Distributed Tracing** | **Nein** | — | Kein Tracing |
| **Log-Aggregation** | **Nein** | — | Nur `docker logs` |
| **APM (Application Performance Monitoring)** | **Nein** | — | Kein Runtime-Profiling |
| **Frontend Error Monitoring** | **Nein** | — | Nur Umami (kein Error Tracking) |
---
## 1. Distributed Tracing
### Was ist das?
Distributed Tracing verfolgt eine einzelne Benutzeranfrage über alle beteiligten Services hinweg — vom Frontend über den Backend-Service bis zur Datenbank und zurück. Jeder Schritt wird als **Span** erfasst, und alle Spans einer Anfrage bilden zusammen einen **Trace**.
### Beispiel: Was passiert ohne Tracing
Ein User öffnet den Chat und die Seite lädt langsam. Im Grafana siehst du, dass die p95-Latenz bei 3 Sekunden liegt. Aber **wo** steckt das Problem?
- Liegt es am Chat-Backend?
- An der Authentifizierung bei mana-core-auth?
- An einer langsamen PostgreSQL-Query?
- Am Redis-Cache, der nicht greift?
- An der Netzwerk-Latenz zwischen Containern?
Ohne Tracing ist das Debugging ein Ratespiel. Du musst `docker logs` von jedem Service einzeln durchsuchen und Timestamps manuell korrelieren.
### Was Tracing liefern würde
Ein Trace für dieselbe Anfrage zeigt dir eine Wasserfall-Ansicht:
```
[Chat-Web → Chat-Backend] ─── 3.2s total ───────────────────────────────
├─ [Auth Validation] ── 45ms ── POST mana-core-auth/validate
├─ [Redis Cache Lookup] ── 2ms ── GET conversation:123
├─ [PostgreSQL Query] ── 2.8s ── SELECT * FROM messages WHERE... ← BOTTLENECK
│ └─ [Index Scan] ── 2.7s ── seq scan on messages (missing index!)
└─ [Response Serialize] ── 12ms ── JSON encoding
```
Sofort sichtbar: Die PostgreSQL-Query braucht 2.8s wegen eines fehlenden Index.
### Relevanz für unser Setup
Unser Stack hat viele Service-zu-Service-Aufrufe:
```
Browser → SvelteKit Web → NestJS Backend → mana-core-auth (Auth-Validierung)
→ PostgreSQL (Daten)
→ Redis (Cache/Sessions)
→ mana-search (Suche)
→ mana-llm (KI-Anfragen)
→ MinIO (Datei-Uploads)
```
Bei 20+ Containern und mehreren Backends, die sich gegenseitig aufrufen, ist es ohne Tracing nahezu unmöglich, Performance-Probleme zu isolieren.
### Tools die in Frage kommen
| Tool | Typ | Passt zu uns? | Aufwand |
|------|-----|--------------|---------|
| **Grafana Tempo** | Self-hosted, Open Source | Sehr gut — integriert sich direkt in bestehendes Grafana | Mittel |
| **Jaeger** | Self-hosted, Open Source | Gut — eigenständige UI, bewährt | Mittel |
| **OpenTelemetry (OTel)** | SDK/Collector (kein Backend) | **Pflicht** — universeller Standard für Instrumentation | Mittel |
| SigNoz | Self-hosted, Open Source | All-in-One (Traces + Metriken + Logs) | Hoch |
| Datadog/New Relic | SaaS | Overkill und teuer für unsere Größe | Gering (aber $$$) |
**Empfehlung:** OpenTelemetry SDK in allen NestJS-Backends + Grafana Tempo als Backend. Tempo ist speziell für Grafana gebaut und braucht keinen zusätzlichen Storage — es nutzt Object Storage (MinIO haben wir bereits).
### Was die Integration bedeutet
**NestJS-Backends (Instrumentation):**
```typescript
// tracing.ts — wird vor dem App-Start geladen
import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
const sdk = new NodeSDK({
serviceName: 'chat-backend',
traceExporter: new OTLPTraceExporter({
url: 'http://tempo:4318/v1/traces',
}),
instrumentations: [
getNodeAutoInstrumentations({
// Automatisch: HTTP, Express, PostgreSQL, Redis, fetch
}),
],
});
sdk.start();
```
Die Auto-Instrumentation fängt automatisch alle HTTP-Requests, DB-Queries und Redis-Aufrufe ab — ohne dass man jeden Endpunkt manuell annotieren muss.
**Docker-Compose Erweiterung:**
```yaml
mana-mon-tempo:
image: grafana/tempo:latest
container_name: mana-mon-tempo
volumes:
- /Volumes/ManaData/tempo:/var/tempo
ports:
- "4318:4318" # OTLP HTTP
- "3200:3200" # Tempo Query API
```
**Aufwand-Schätzung:**
- Tempo aufsetzen: ~1h
- OTel SDK als Shared Package: ~2h
- In alle Backends integrieren: ~30min pro Backend (größtenteils Copy-Paste)
- Grafana Datasource + Dashboard: ~1h
---
## 2. Log-Aggregation
### Was ist das?
Log-Aggregation sammelt die Logs aller Container an einem zentralen Ort, macht sie durchsuchbar, filterbar und korrelierbar. Statt `docker logs mana-chat-backend | grep error` auf dem Server zu tippen, hast du ein Web-UI, in dem du über alle Services gleichzeitig suchen kannst.
### Aktueller Zustand
Unsere Logs sind aktuell:
- **Nur per SSH erreichbar** — du musst dich auf den Mac Mini verbinden
- **Pro Container isoliert**`docker logs <container-name>`
- **Begrenzt** — max 3 Dateien à 50MB pro Container (150MB), danach weg
- **Nicht durchsuchbar** — kein Volltext-Index, kein Filtern nach Severity/Service
- **Nicht korrelierbar** — wenn Auth und Chat-Backend gleichzeitig Fehler loggen, musst du manuell Timestamps vergleichen
### Konkrete Probleme die das verursacht
**Szenario 1: Fehlersuche über Service-Grenzen**
Ein User meldet: "Ich kann keine Nachrichten senden." Du weißt nicht, ob das Problem im Chat-Backend, bei der Auth-Validierung oder in der Datenbank liegt. Aktuell:
```bash
ssh mana-server
docker logs mana-chat-backend --since 10m | grep -i error
docker logs mana-auth | grep -i error
docker logs mana-infra-postgres 2>&1 | grep -i error
# ... manuell Timestamps vergleichen
```
Mit Log-Aggregation:
```
# In Grafana/Loki UI:
{service=~"chat-backend|mana-auth|postgres"} |= "error" | last 10m
```
Ein Query, alle Services, sofort korreliert.
**Szenario 2: Logs gehen verloren**
Container-Restart → Docker rotiert Logs → ältere Logs verschwinden. Wenn ein Problem erst Stunden später gemeldet wird, sind die relevanten Logs möglicherweise schon weg.
**Szenario 3: Post-Mortem Analyse**
Nach einem Ausfall willst du nachvollziehen, was passiert ist. Ohne zentrale Logs rekonstruierst du die Timeline mühsam aus fragmentierten Container-Logs.
### Tools die in Frage kommen
| Tool | Typ | Passt zu uns? | Ressourcenverbrauch |
|------|-----|--------------|-------------------|
| **Grafana Loki** | Self-hosted, Open Source | Ideal — Grafana-nativ, label-basiert, leichtgewichtig | Gering (~256MB RAM) |
| **Promtail** | Log-Shipper für Loki | Gehört zu Loki | Gering (~50MB RAM) |
| ELK Stack (Elasticsearch + Logstash + Kibana) | Self-hosted | Überdimensioniert — Elasticsearch braucht 2-4GB RAM minimum | Hoch |
| Vector/Fluentd | Log-Shipper | Alternative zu Promtail, flexibler aber komplexer | Mittel |
**Empfehlung:** Loki + Promtail. Loki ist speziell als "Prometheus, aber für Logs" gebaut. Es indiziert nur Labels (Service-Name, Level), nicht den vollen Log-Text — dadurch extrem ressourcenschonend. Perfekt für einen Mac Mini mit begrenztem RAM.
### Was die Integration bedeutet
**Docker-Compose Erweiterung:**
```yaml
mana-mon-loki:
image: grafana/loki:3.0.0
container_name: mana-mon-loki
volumes:
- /Volumes/ManaData/loki:/loki
ports:
- "3100:3100"
command: -config.file=/etc/loki/local-config.yaml
mana-mon-promtail:
image: grafana/promtail:3.0.0
container_name: mana-mon-promtail
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
command: -config.file=/etc/promtail/config.yml
```
Promtail liest automatisch alle Docker-Container-Logs und schickt sie an Loki. Kein Code-Change in den Apps nötig.
**Structured Logging (optional, aber empfohlen):**
Damit Loki die Logs besser parsen kann, sollten Backends JSON statt Plain-Text loggen:
```typescript
// NestJS: JSON-Logger statt Default
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
WinstonModule.forRoot({
transports: [
new winston.transports.Console({
format: winston.format.json(),
}),
],
});
```
Ergebnis: `{"level":"error","message":"DB connection failed","service":"chat-backend","timestamp":"2026-03-22T10:00:00Z"}` statt `[Nest] ERROR DB connection failed`.
**Aufwand-Schätzung:**
- Loki + Promtail aufsetzen: ~1-2h
- Grafana Datasource konfigurieren: ~15min
- Log-Dashboard bauen: ~1h
- (Optional) Structured Logging in Backends: ~30min pro Backend
---
## 3. APM (Application Performance Monitoring)
### Was ist das?
APM geht über Metriken und Tracing hinaus: Es liefert **Runtime-Profiling** deiner Anwendungen. Während Metriken dir sagen "die CPU ist bei 80%" und Tracing dir zeigt "diese Query dauert 3s", sagt dir APM **warum** auf Code-Ebene:
- Welche Funktionen verbrauchen die meiste CPU-Zeit?
- Wo entstehen Memory Leaks?
- Wie verhält sich der Garbage Collector?
- Welche Datenbankverbindungen sind idle vs. aktiv?
- Wie groß ist der Event-Loop-Lag in Node.js?
### Was wir aktuell haben (und was fehlt)
**Haben wir:**
- Request-Raten, Latenz-Percentile (p50/p95/p99) via Prometheus-Metriken
- Container-Ressourcen (CPU/RAM) via cAdvisor
- PostgreSQL-Connection-Counts via postgres-exporter
- Alert bei `SlowResponseTime` (p95 > 2s)
**Fehlt uns:**
- **Code-Level Profiling** — Welche Funktion in welchem Service ist der Bottleneck?
- **Node.js Event Loop Monitoring** — Liegt die Latenz an blockierendem Code?
- **Memory Leak Detection** — Wächst der Heap eines Backends über Zeit?
- **Dependency Maps** — Automatische Visualisierung, welche Services voneinander abhängen
- **Transaction-Level Analyse** — Nicht nur "p95 ist 2s", sondern "der `/api/messages` Endpunkt hat p95 von 4s wegen N+1 Query"
### Konkretes Beispiel
Das Chat-Backend wird langsamer über die Zeit. In Grafana siehst du:
- CPU: 40% (nicht auffällig)
- RAM: 650MB → 800MB → 950MB über 3 Tage (steigend!)
- p95 Latenz: 500ms → 1.2s → 2.5s
**Ohne APM:** Du vermutest ein Memory Leak, aber wo? Du müsstest manuell Heap-Snapshots machen, den Code durchlesen, und raten.
**Mit APM:** Du siehst direkt:
- Heap wächst wegen nicht-freigegebener Event-Listener in der WebSocket-Implementierung
- Die Funktion `ChatService.getConversationHistory()` allociert bei jedem Call ein neues Array statt den Cache zu nutzen
- Der GC läuft alle 30s und pausiert den Event Loop für 200ms
### Tools die in Frage kommen
| Tool | Typ | Passt zu uns? | Aufwand |
|------|-----|--------------|---------|
| **Grafana Pyroscope** | Self-hosted, Continuous Profiling | Gut — integriert sich in Grafana, leichtgewichtig | Mittel |
| **Clinic.js** | Open Source, Node.js-spezifisch | Gut für einmalige Diagnose, nicht für Dauerbetrieb | Gering |
| **OpenTelemetry Metrics** | SDK (haben wir noch nicht) | Liefert Event-Loop-Lag, GC-Metriken etc. | Mittel |
| Datadog APM | SaaS | Sehr gut, aber $31/Host/Monat | Gering (aber $$$) |
| Elastic APM | Self-hosted | Braucht Elasticsearch (zu viel RAM) | Hoch |
**Empfehlung:** Zweistufig vorgehen:
1. **Sofort (Low-Effort):** OpenTelemetry Runtime-Metriken in NestJS-Backends aktivieren. Das liefert Event-Loop-Lag, GC-Pause-Dauer, Heap-Size etc. als Prometheus-Metriken → direkt in VictoriaMetrics + Grafana sichtbar.
2. **Später (wenn nötig):** Grafana Pyroscope für Continuous Profiling. Ermöglicht Code-Level Flame Graphs direkt in Grafana — du kannst live sehen, welche Funktionen CPU/Memory fressen.
### Was die Integration bedeutet (Stufe 1: OTel Runtime Metriken)
```typescript
// In jedem NestJS-Backend: runtime-metrics.ts
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
import { MeterProvider } from '@opentelemetry/sdk-metrics';
import { RuntimeNodeInstrumentation } from '@opentelemetry/instrumentation-runtime-node';
const exporter = new PrometheusExporter({ port: 9464 });
const meterProvider = new MeterProvider({ readers: [exporter] });
new RuntimeNodeInstrumentation({ meterProvider });
```
Das liefert automatisch:
- `nodejs.eventloop.delay` — Event-Loop-Lag
- `nodejs.gc.duration` — GC-Pausenzeiten
- `nodejs.heap.size` — Heap-Größe über Zeit
- `process.cpu.utilization` — CPU pro Prozess
Diese Metriken werden von VictoriaMetrics gescraped (ein neuer Scrape-Job pro Backend) und können in Grafana visualisiert werden.
**Aufwand-Schätzung:**
- Stufe 1 (OTel Runtime Metriken): ~2-3h für alle Backends
- Stufe 2 (Pyroscope): ~2h Setup + 1h Grafana-Integration
---
## 4. Frontend Error Monitoring
### Was ist das?
Frontend Error Monitoring fängt Fehler ab, die im Browser des Users passieren — JavaScript-Exceptions, unhandled Promise Rejections, Netzwerk-Fehler, und Performance-Probleme. Diese Fehler sieht dein Backend nie, weil sie client-seitig auftreten.
### Aktueller Zustand
**Umami** trackt nur:
- Seitenaufrufe (welche Seiten werden besucht)
- Referrer (woher kommen Besucher)
- Browser/OS/Gerät
- Länder/Sprachen
**Umami trackt NICHT:**
- JavaScript-Fehler
- Netzwerk-Fehler (fehlgeschlagene API-Calls)
- Performance-Metriken (Largest Contentful Paint, Time to Interactive)
- User-Journeys die abbrechen (z.B. Registrierung startet aber wird nicht abgeschlossen)
- Crashes / White Screens
**GlitchTip** ist nur in den Backends integriert — kein einziges Frontend hat einen Error-Tracking-Client.
### Warum das relevant ist
**Szenario 1: Stille Fehler**
Ein User auf Safari iOS hat einen Fehler in der Todo-App — eine Svelte-Komponente wirft eine Exception und der Screen wird weiß. Der User schließt die App, kommt nicht wieder. Du erfährst davon nie, weil:
- Kein Error wird an ein Backend gesendet
- Umami zeigt nur "1 Seitenaufruf weniger als gestern"
- GlitchTip hat nur Backend-Fehler
**Szenario 2: API-Fehler aus Sicht des Users**
Das Chat-Backend gibt sporadisch 500er zurück. Im GlitchTip siehst du den Backend-Error. Aber was passiert auf der Client-Seite? Sieht der User eine Fehlermeldung? Versucht die App automatisch ein Retry? Oder hängt die UI einfach? Ohne Frontend-Monitoring weißt du das nicht.
**Szenario 3: Performance-Probleme**
Die Calendar-App lädt in deinem Netzwerk in 1.5s. Aber für einen User mit langsamer Verbindung in 8s. Ohne Real User Monitoring (RUM) mit Web Vitals siehst du nur deine eigene Erfahrung.
### Tools die in Frage kommen
| Tool | Typ | Passt zu uns? | Kosten |
|------|-----|--------------|--------|
| **GlitchTip (JS SDK)** | Self-hosted (haben wir schon!) | Ideal — Backend läuft bereits, nur Frontend-SDK fehlt | Kostenlos |
| **Sentry** | SaaS oder Self-hosted | Marktführer, aber teuer in SaaS | Free Tier: 5K Events/Mo |
| **Highlight.io** | Self-hosted, Open Source | All-in-One (Errors + Sessions + Logs) | Kostenlos |
| TrackJS | SaaS | Nur JS-Errors, kein Performance | $$$ |
**Empfehlung:** GlitchTip JS SDK in allen SvelteKit-Frontends. GlitchTip ist Sentry-kompatibel — der `@sentry/svelte` oder `@sentry/browser` SDK funktioniert direkt mit unserem bestehenden GlitchTip-Server. Kein neuer Service nötig.
### Was die Integration bedeutet
**SvelteKit Frontend (hooks.client.ts):**
```typescript
// src/hooks.client.ts
import * as Sentry from '@sentry/svelte';
Sentry.init({
dsn: 'https://key@glitchtip.mana.how/PROJECT_ID',
environment: import.meta.env.MODE,
// Performance Monitoring
tracesSampleRate: 0.1, // 10% der Requests tracen
// Session Replay (optional)
replaysSessionSampleRate: 0,
replaysOnErrorSampleRate: 0.5, // 50% der Error-Sessions aufzeichnen
});
```
**Svelte Error Boundary:**
```svelte
<!-- src/routes/+layout.svelte -->
<script lang="ts">
import * as Sentry from '@sentry/svelte';
// Svelte 5: Fängt unhandled Errors in Child-Komponenten
$effect(() => {
window.addEventListener('unhandledrejection', (event) => {
Sentry.captureException(event.reason);
});
});
</script>
```
**Web Vitals Tracking:**
```typescript
// src/lib/vitals.ts
import { onCLS, onFID, onLCP, onTTFB } from 'web-vitals';
import * as Sentry from '@sentry/svelte';
export function trackWebVitals() {
onLCP((metric) => Sentry.captureMessage(`LCP: ${metric.value}ms`, { level: 'info' }));
onCLS((metric) => Sentry.captureMessage(`CLS: ${metric.value}`, { level: 'info' }));
// ... etc
}
```
**Aufwand-Schätzung:**
- `@sentry/svelte` in ein Frontend integrieren: ~30min
- Auf alle SvelteKit-Apps ausrollen: ~30min pro App (größtenteils gleicher Code)
- Web Vitals ergänzen: ~1h
- GlitchTip-Projekte für Frontends anlegen: ~30min
---
## Zusammenfassung: Prioritäten
| Prio | Maßnahme | Aufwand | Impact |
|------|----------|---------|--------|
| **1** | **Frontend Error Monitoring** (GlitchTip JS SDK) | ~3-4h | Hoch — blinde Flecken bei Client-Fehlern schließen, nutzt bestehende Infra |
| **2** | **Log-Aggregation** (Loki + Promtail) | ~3-4h | Hoch — massiv schnelleres Debugging, kein SSH mehr nötig |
| **3** | **Distributed Tracing** (OTel + Tempo) | ~5-6h | Mittel-Hoch — unverzichtbar wenn Service-übergreifende Probleme auftreten |
| **4** | **APM / Runtime Profiling** (OTel Metriken → Pyroscope) | ~3-5h | Mittel — hilft bei Memory Leaks und Event-Loop-Problemen |
Alle vier Maßnahmen nutzen das **Grafana-Ökosystem**, das wir bereits betreiben. Kein neues Dashboard-Tool, keine neue Infrastruktur — nur Erweiterungen des bestehenden Stacks.
### Gesamtbild nach Umsetzung
```
┌──────────────────────────┐
│ Grafana (haben wir) │
│ Dashboards für alles │
└─────┬───┬───┬───┬────────┘
│ │ │ │
┌───────────────┘ │ │ └───────────────┐
▼ ▼ ▼ ▼
VictoriaMetrics Tempo Loki Pyroscope
(Metriken ✅) (Traces) (Logs) (Profiling)
▲ ▲ ▲ ▲
│ │ │ │
┌────────┴──────┐ OTel SDK Promtail OTel/Pyroscope
│ Exporters │ (in Apps) (Docker) Agent
│ (haben wir) │
└───────────────┘
GlitchTip (Backend-Errors ✅ + Frontend-Errors NEU)
Umami (Web Analytics ✅)
Alertmanager (Alerts ✅)
```