SaaS Monitoring and Observability Stack: A Solo Dev's 2026 Field Guide

Der Monitoring- und Observability-Stack, den ich in über 12 Produktions-SaaS-Apps auf Cloudflare Workers betreibe – Logs, Metriken, Traces, Alarm-Leiter, Dashboards, Kosten-Disziplin.

Huifer
Huifer
18. Juli 202613 min read
Auch verfügbar auf:English · 中文

Geschrieben von Huifer, Solo-Entwickler und Maintainer von TanStack Ship. Ich betreibe denselben Monitoring- und Observability-Stack in über 12 Produktions-TanStack-Start-SaaS-Anwendungen auf Cloudflare Workers – eine Abrechnungsplattform mit ca. 95.000 Anfragen/Tag, ein Multi-Tenant-Analysetool, drei B2B-Integrationsprodukte und mehrere kleinere Apps. Ich habe persönlich jeden schmerzhaften Fehlermodus debugged, den dieser Leitfaden adressiert: ein D1-Schreibsperren-Vorfall, der mich um 3 Uhr nachts pagte; eine Webhook-Retry-Schleife, die meine Analytics-Engine-Rechnung in 48 Stunden verdoppelte; ein Tail Worker, der zwei Tage lang stillschweigend Logs fallen ließ; ein Grafana-Dashboard, das grün zeigte, obwohl der kundenorientierte Flow unterbrochen war. Jedes unten beschriebene Muster ist das, was ich tatsächlich ausliefere; jede Sprosse der Alarm-Leiter ist ein Vorfall, den ich triagiert habe; jede Kostenangabe stammt aus meinen eigenen Abrechnungs-Dashboards.

Verifizierte Quellen: Cloudflare Workers Analytics Engine · Cloudflare Workers Logs · Cloudflare Workers Tail Workers · Workers Trace Events (OpenTelemetry) · OpenTelemetry specification · Grafana Cloud · TanStack Start documentation · TanStack Ship features · TanStack Ship GitHub Organization

Letztes Update: 2026-07-18 · Changelog


Kurz zusammengefasst: Ein SaaS-Monitoring- und Observability-Stack im Jahr 2026 besteht aus drei Säulen (Logs, Metriken, Traces) plus einer fünfstufigen Alarm-Leiter, und die größte Determinante der Operabilität ist, ob die Alarm-Leiter quantifiziert ist, bevor der Vorfall eintritt. Ich betreibe Cloudflare Workers Analytics Engine für sowohl Logs als auch Metriken, OpenTelemetry-Trace-Events für verteilte Traces, Grafana Cloud für Dashboards und Better Stack für On-Call-Rotation. Die Alarm-Leiter umfasst P0 Kundenimpact (Page), P1 Zuverlässigkeit (Slack in 5 Min.), P2 Kosten-Guardrail (Slack-Digest), P3 Rauschen (verwerfen), P4 Changelog (aufzeichnen). Kosten-Disziplin ist in den Stack eingebaut: jede Log-Zeile wird gesampled, jede Metrik hat eine Retention-Policy, jeder Trace wird Head-gesampled. Der vollständige Stack kostet ca. 40–80 $/Monat pro App, nicht 400–800 $ – der günstige Stack ist derjenige, der den nächsten Ausfall übersteht. Für den Kontext der Authentifizierungsschicht, vgl. den SaaS-Authentifizierungsleitfaden; für die Deployment-Pipeline, die Observability als Release-Kriterium definiert, vgl. den Deployment-Pipeline-Leitfaden; für die API-Design-Muster, die der Stack einhalten muss, vgl. den SaaS-API-Design-Leitfaden.


Der Drei-Säulen-Stack: Was ich tatsächlich betreibe

Das „Drei-Säulen"-Modell (Logs, Metriken, Traces) ist nichts Neues, aber die Disziplin, die einen funktionierenden Stack von einem rauschenden unterscheidet, liegt darin, welche Säule welche Frage beantwortet. Logs beantworten was ist passiert; Metriken beantworten wie ist der Trend; Traces beantworten warum ist es passiert. Jeder Stack, den ich betreibe, ordnet jede Säule einem Tool zu, das zur Kostenstruktur der Frage passt – hochkardinale Per-Event-Daten für Logs, niedrigkardinale aggregierte Daten für Metriken, gesampelte Per-Request-Daten für Traces.

SäuleFrageToolRetentionKostenstruktur
LogsWas ist passiert?Workers Tail + Analytics Engine7–30 TagePer-Event, niedrig
MetrikenWie ist der Trend?Workers Analytics Engine + Grafana3–12 MonateAggregiert, mittel
TracesWarum ist es passiert?Workers Trace Events (OTel)1–7 TageGesampelt, niedrig

Logs: Workers Tail + Analytics Engine

Jede TanStack-Start-App, die ich ausliefere, erzeugt strukturierte JSON-Logs am Edge. Das Format ist festgelegt: Timestamp, Level, Nachricht, Request-ID, Tenant-ID, Route, Status, duration_ms, plus ein Metadata-Bag für applikationsspezifische Felder. Der Cloudflare-cf-ray-Header wird zur Request-ID; er propagiert vom Edge zum Worker und zu jedem Downstream-Aufruf und ist die einzelne Zeichenkette, die ich in ein Support-Ticket einfüge.

typescript
// src/lib/logger.ts
type LogLevel = "debug" | "info" | "warn" | "error"

interface LogEntry {
  timestamp: string
  level: LogLevel
  message: string
  requestId: string
  tenantId?: string
  route?: string
  status?: number
  duration_ms?: number
  error?: { name: string; message: string; stack?: string }
  metadata?: Record<string, unknown>
}

export function createLogger(request: Request, ctx: ExecutionContext) {
  const requestId = request.headers.get("cf-ray") ?? crypto.randomUUID()

  const write = (entry: Omit<LogEntry, "timestamp" | "requestId">) => {
    const full: LogEntry = { timestamp: new Date().toISOString(), requestId, ...entry }
    console.log(JSON.stringify(full))
    ctx.env.LOGS_ANALYTICS.writeDataPoint({
      blobs: [full.level, full.message, full.requestId, full.tenantId ?? "", full.route ?? ""],
      doubles: [full.status ?? 0, full.duration_ms ?? 0],
      indexes: [full.requestId],
    })
  }

  return {
    info: (message: string, metadata?: Record<string, unknown>) =>
      write({ level: "info", message, metadata }),
    warn: (message: string, metadata?: Record<string, unknown>) =>
      write({ level: "warn", message, metadata }),
    error: (message: string, error?: Error, metadata?: Record<string, unknown>) =>
      write({ level: "error", message, error: error ? { name: error.name, message: error.message, stack: error.stack } : undefined, metadata }),
    getRequestId: () => requestId,
  }
}

Der duale Schreibvorgang (console + Analytics Engine) ist die Disziplin, die den Silent-Drop-Bug abfängt. Workers Logs (console.log) ist das menschenlesbare Tail, das ich bei Vorfällen live scanne; Analytics Engine ist der abfragbare Speicher für Retention und Aggregationen. Den Fehler, den ich 2024 gemacht habe: Ich habe mich nur auf Workers Logs verlassen. Ein falsch konfigurierter Tail Worker ließ zwei Tage lang Logs für eine App fallen, und ich bemerkte es erst, als ein Kunde ein Problem meldete. Seitdem schreibt jede App an beide Ziele, und die Analytics-Engine-Bindung ist Teil der Deployment-Checkliste.

Metriken: Workers Analytics Engine + Grafana

Die Metrik-Pipeline, die ich betreibe, nutzt Analytics Engine für die Aufnahme und Grafana Cloud für Abfragen und Dashboards. Jeder Worker erzeugt 3–7 Metriken pro Anfrage (Request-Zähler, Fehlerzähler, Latenz p50/p95/p99, plus 1–3 Business-Metriken wie „Checkout erfolgreich" oder „Stripe-Webhook zugestellt"). Die Aggregation erfolgt serverseitig; das Grafana-Panel liest aus demselben Speicher über die Grafana-Analytics-Engine-Datenquelle.

Die Kardinalitätsdisziplin ist das, was die Rechnung unter Kontrolle hält. Jede Metrik-Index-Spalte (das indexes-Array in writeDataPoint) ist niedrigkardinal – route, status_class, tenant_tier, niemals tenant_id oder request_id. Eine App mit 100.000-Tenants und einem Per-Tenant-Metrik-Index sprengt jedes Analytics-Engine-Kontingent; ein 3-Index-Schema bleibt komfortabel im Freitier. Das Muster, das ich ausliefere:

typescript
// src/lib/metrics.ts — drei Metriken pro Anfrage, alle niedrigkardinal
ctx.env.METRICS_ANALYTICS.writeDataPoint({
  blobs: [route, statusClass, tenantTier],   // route, "2xx"/"4xx"/"5xx", "free"/"pro"/"enterprise"
  doubles: [1, duration_ms],                // Request-Zähler, Latenz in ms
  indexes: [route, statusClass],            // abfragbare Dimensionen, NICHT tenant_id
})

Das Format ist in der Workers Analytics Engine SQL-Referenz dokumentiert – jede Metrik ist eine Zeile in einer zeitindizierten Tabelle, jede Abfrage ist SQL, jedes Dashboard ist ein Grafana-Panel, das von einer SQL-Abfrage gespeist wird. Ich habe dasselbe Format mit Datadog, Better Stack und selbst gehostetem Prometheus + Grafana ausgeliefert. Der Workers-Analytics-Engine-Pfad ist der, den ich für Cloudflare-only-Deployments bevorzuge, weil es keine zweite Vendor-Beziehung gibt.

Traces: Workers Trace Events (OpenTelemetry)

Traces sind die Säule, die ich zuletzt hinzugefügt habe (Q1 2026) und die sich innerhalb eines Monats bezahlt gemacht hat. Die Workers-Trace-Events-Integration erzeugt OpenTelemetry-konforme Spans aus jeder Worker-Invocation. Das Format: Eine eingehende HTTP-Anfrage wird zum Root-Span; jede D1-Abfrage, jedes KV-Read, jeder fetch()-Aufruf an eine Downstream-API wird zum Child-Span mit Timing und Metadaten. Die Fehlermodus-Traces fangen das, was Logs und Metriken verpassen: Eine langsame D1-Abfrage innerhalb eines 3-Sekunden-Checkout-Flows, bei dem jede Metrik „Checkout ist in Ordnung" sagt, weil p95 über den gesamten Flow berechnet wird.

Die Initialisierung ist ein Block in wrangler.jsonc plus ein Import im Entrypoint:

typescript
// src/instrumentation.ts — OpenTelemetry-Initialisierung für Cloudflare Workers
import { trace, SpanKind } from "@opentelemetry/api"
import { CloudflareTraceExporter } from "@/lib/cf-otel-exporter"

const tracer = trace.getTracer("tanstack-ship-app")

export async function withTrace<T>(request: Request, fn: () => Promise<T>): Promise<T> {
  const url = new URL(request.url)
  return tracer.startActiveSpan(`${request.method} ${url.pathname}`, { kind: SpanKind.SERVER }, async (span) => {
    try {
      const result = await fn()
      span.setStatus({ code: 1 }) // OK
      return result
    } catch (err) {
      span.recordException(err as Error)
      span.setStatus({ code: 2, message: (err as Error).message }) // ERROR
      throw err
    } finally {
      span.end()
    }
  })
}

Der Exporter liefert Spans an ein Backend – ich nutze Grafana Cloud Tempo für die SaaS-Apps, wo Grafana bereits das Dashboard-Tool ist. Die Workers-Trace-Events-Dokumentation deckt das native Binding-Format ab; die OpenTelemetry-Spezifikation deckt das Wire-Format ab. Die Head-Sampling-Regel: 10 % der erfolgreichen Anfragen, 100 % der Fehler, 100 % der langsamen Anfragen (>p95). Sampling-Mathematik ist die einzige Disziplin, die das Trace-Volumen davon abhält, das Log-Volumen zu überholen.

Die Fünfstufige Alarm-Leiter

Die Alarm-Leiter ist der Teil des Observability-Stacks, der entscheidet, ob 3 Uhr nachts ruhig oder miserabel ist. Die Leiter, die ich betreibe, hat fünf Stufen, jede mit einem quantifizierten Auslöser, einem Benachrichtigungskanal und einer dokumentierten Reaktionszeit. Die Regel: Jede Alarmsprosse wird vor dem Vorfall benannt; Bauchgefühl pagt nicht.

P0 Kundenimpact: sofort pagen

Ein P0-Alarm ist eines von drei Dingen: eine anhaltende 5xx-Rate über 1 % über 5 Minuten, eine anhaltende Zahlungsflow-Fehlerrate über 0,5 % über 5 Minuten, oder ein vollständiger Checkout-Flow-Ausfall, erkannt durch einen synthetischen Check. Der Benachrichtigungskanal ist PagerDuty (oder Better Stack On-Call); die Reaktion ist der On-Call-Engineer innerhalb von 15 Minuten; die dokumentierte Aktion ist „Rollback auf letzten grünen Deploy, dann untersuchen". Die P0-Alarme, auf die ich reagiert habe: der D1-Schreibsperren-Vorfall (220 req/s für 12 Minuten), der Stripe-Webhook-Doppelbelastungs-Vorfall (47 doppelt abgerechnete Kunden), die Tail-Worker-Fehlkonfiguration, die zwei Tage lang Logs fallen ließ.

Die Auslöseschwellen sind quantifiziert, nicht geschätzt. Eine 5xx-Rate von 1 % über 5 Minuten auf der Abrechnungsplattform ist P0; dieselbe Rate auf der Marketing-Site ist P2 (Slack-Digest) – dieselbe Metrik, unterschiedliche Sprengkraft. Jeder P0-Alarm hat einen Runbook-Link aus der Benachrichtigung; jedes Runbook benennt das Rollback-Ziel, die On-Call-Rotation und die Postmortem-Vorlage.

P1 Zuverlässigkeit: Slack innerhalb von 5 Minuten

P1-Alarme sind Zuverlässigkeitssignale, die noch nicht kundenrelevant sind: Fehlerrate steigt über den Basiswert, p95-Latenz steigt über den Basiswert, Dependency-Health-Degradation. Der Benachrichtigungskanal ist ein dedizierter Slack-Kanal (#alerts-reliability); die Reaktion ist Quittierung innerhalb von 30 Minuten; die dokumentierte Aktion ist Untersuchung, nicht Rollback. P1-Alarme, die ich ausgeliefert habe: eine langsame D1-Abfrage, die Checkout p95 von 320 ms auf 1,8 s über 90 Minuten trieb (erkannt durch den p95-Alarm, bevor ein P0 auslöste), ein Workers-KV-Read-Timeout-Cluster, der eine Region betraf.

P2 Kosten-Guardrails: Slack-Digest

P2-Alarme sind Kostensignale. Ein täglicher Digest um 09:00 zeigt: gesamtes Log-Volumen, gesamtes Metrik-Volumen, gesamtes Trace-Volumen, Top-10-Routen nach Request-Zähler, Top-10-Tenants nach Request-Zähler, Woche-über-Woche-Delta, projizierte Monatsendkosten. Der Digest ist das Alarmsignal, das mich aufweckt, wenn etwas stillschweigend teuer wird – die Webhook-Retry-Schleife, die die Rechnung in 48 Stunden verdoppelte, wurde durch einen Mittwochs-Digest erkannt, nicht durch einen P0-Alarm.

P3 Rauschen-Filter

P3 ist kein Alarm; es ist ein Filter. Jeder Alarm, der zweimal in 30 Tagen auslöst und keine Aktion produziert, wird auf P3 herabgestuft (keine Benachrichtigung, für Musteranalyse behalten) oder gelöscht.

P4 Changelog

P4 ist das Audit-Log. Jeder Deploy, jede Konfigurationsänderung, jede D1-Migration, jede Webhook-Secret-Rotation wird in dieselbe Grafana-Datenquelle geloggt und ist als Zeitreihe abfragbar. Der P4-Eintrag beweist „Das Rate-Limit hat sich um 14:23 geändert und der 5xx-Spike begann um 14:25" – der Beweis, der eine 90-minütige Untersuchung in eine 10-minütige verwandelt.

Vom Pager zur Grundursache: Der On-Call-Playbook

Das On-Call-Playbook ist die Brücke zwischen der Alarm-Leiter und den Dashboards. Das Playbook hat drei Phasen: Orientieren, Hypothese bilden, Beheben.

Erste 5 Minuten: Dashboards, nicht Code

Die ersten 5 Minuten eines Vorfalls werden mit Dashboards verbracht, nicht mit Code. Die vier Panels, die ich in dieser Reihenfolge öffne: (1) Plattform-Health (5xx-Rate, p95-Latenz, Request-Zähler), (2) Per-Route-Performance (welche Routen sind beeinträchtigt), (3) Dependency-Health (D1, KV, R2, Stripe, Drittanbieter-APIs), (4) das Panel für kürzliche Deploys (P4-Changelog). Die Hypothesenphase beginnt mit diesen vier Panels, nicht mit dem Lesen von Code.

5–30 Minuten: Traces und Korrelation

Sobald das Dashboard mir sagt, welche Route beeinträchtigt ist und welche Dependency langsam ist, beantwortet der Trace-View warum. Der Trace zeigt den langsamen Span – eine D1-Abfrage, die 1,4 s innerhalb eines 1,8-s-Checkouts dauerte – und die Trace-Metadaten (die SQL-Abfrage, die Bind-Parameter, die Tabelle) sagen mir, was zu reparieren ist. Die requestId-Korrelation über Logs und Traces hinweg ist die einzelne Zeichenkette, die ich in das Support-Ticket einfüge, wenn der Kunde fragt: „Was ist mit meinem Checkout um 14:23 passiert?"

Die 30-Minuten-Marke ist der Eskalationspunkt: Wenn ich bis dahin keine Hypothese habe, eskaliere ich zu einem Kollegen (wenn einer verfügbar ist) oder rolle auf den letzten grünen Stand zurück. Die Regel: Nie länger als 30 Minuten debuggen ohne Rollback. Jeder Vorfall, den ich länger als 30 Minuten ohne Rollback debuggte, dauerte länger als ein Rollback + Fix-Forward gebraucht hätte.

Kosten-Disziplin in der Observability-Schicht

Die Observability-Rechnung ist der Posten, den die meisten Solo-Devs unterschätzen. Ich habe Observability-Stacks ausgeliefert, die 40 $/Monat kosteten, und Stacks, die 800 $/Monat für dieselbe App kosteten – der Unterschied liegt bei Sampling, Retention und der Disziplin „Keine Logs schreiben, die man nicht abfragen wird."

Sampling, Retention und der Unit-Economics-Check

Der Unit-Economics-Check ist die Mathematik, die ich quartalsweise durchführe: Kosten pro 1.000 Anfragen. Ein gesunder Stack läuft bei 0,01–0,05 $ pro 1.000 Anfragen; ein aufgeblähter Stack bei 0,20–0,50 $. Die Mathematik fängt die stillen Kostenleckagen ab: Eine Debug-Log-Zeile, die bei jeder Anfrage ausgegeben wird; ein Trace-Exporter, der auf 100 % Sampling eingestellt ist; ein Analytics-Engine-Datensatz ohne TTL.

Die Sampling-Disziplin ist konkret. Logs: 100 % der Fehler und Warnungen, 10 % der Info, 0 % des Debug in der Produktion. Metriken: 100 % der Aggregationen (kein Sampling – Aggregation ist das Sampling). Traces: 100 % der Fehler, 100 % der langsamen Anfragen (über p95), 1 % von allem anderen. Retention: 7 Tage für Traces, 30 Tage für Rohlogs, 12 Monate für aggregierte Metriken, für immer für das Changelog. Die TTL wird auf dem Analytics-Engine-Datensatz gesetzt; der Cron-Job ist Teil der Deployment-Pipeline.

Wann einen Vendor hinzufügen vs. selbst bauen

Die ehrliche Empfehlung: Selbst bauen, bis der Stack mehr als 3 Stunden Engineering pro Monat an Wartung kostet. Der Workers-Analytics-Engine-+-Grafana-Cloud-Pfad kostet ca. 40–80 $/Monat pro App und erfordert ca. 1 Stunde Wartung pro Monat. Der Datadog- oder Better-Stack-Pfad kostet ca. 200–400 $/Monat pro App und erfordert ca. 10 Minuten Wartung pro Monat. Der Break-Even-Punkt liegt bei App #3 oder #4, abhängig vom Traffic. Ich baue selbst für Apps unter ca. 50.000 Anfragen/Tag; ich bezahle für Datadog oder Better Stack für Apps über ca. 200.000 Anfragen/Tag, wo die On-Call-Ergonomie und die vorgefertigten Integrationen mehr sparen als die Rechnung kostet.

Den Fehler in beiden Richtungen habe ich gemacht: zu früh für einen Vendor bezahlt, als der Stack noch nicht groß genug war, und zu lange gezögert zu zahlen, nachdem er es war. Der Entscheidungsrahmen ist konkret: Wenn ich mehr als 1 Stunde pro Woche für Observability-Wartung aufwende, amortisiert sich der Vendor selbst.

Dashboard-Stack und wofür jeder zuständig ist

Der Dashboard-Stack, den ich betreibe, umfasst sechs Panels pro App, nicht mehr. Jedes Panel hat ein benanntes Publikum (On-Call-Engineer, Gründer, Customer Success) und eine benannte Frage. Die sechs Panels:

  1. Platform Health (On-Call) – Request-Zähler, 5xx-Rate, p95-Latenz, Dependency-Health.
  2. Per-Route Performance (On-Call) – Request-Zähler und p95 pro Top-20-Route.
  3. Dependency Health (On-Call) – D1 Read/Write-Latenz, KV Read-Latenz, Stripe-Webhook-Zustellrate, Drittanbieter-API-Fehlerraten.
  4. Cost Digest (Gründer) – tägliche/wöchentliche/monatliche Kosten pro Säule, Monatsendprojektion.
  5. Customer-Impact-Funnel (Gründer) – Checkout-Conversion, Anmelde-Conversion, Retention-Kurve.
  6. Deploy Changelog (Alle) – jeder Deploy, jede Migration, jede Konfigurationsänderung, mit Zeitstempel.

Die Regel: Ein Dashboard, das niemand ansieht, ist eine Kosten, kein Asset. Ich lösche Panels jedes Quartal.

Wo dieser Leitfaden aufhört

Dies ist die Gestalt eines produktionsreifen SaaS-Observability-Stacks im Jahr 2026: strukturierte Logs in Workers Analytics Engine, aggregierte Metriken in Grafana über denselben Speicher, Head-gesampelte OpenTelemetry-Traces in Tempo, eine fünfstufige quantifizierte Alarm-Leiter und ein Sechs-Panel-Dashboard-Set pro App. Der Stack ist dauerhaft; die Vendors sind ersetzbar. Grafana gegen Datadog tauschen, Tempo gegen Honeycomb, Workers Analytics Engine gegen selbst gehostetes ClickHouse – jedes Muster überträgt sich. Die Disziplinen, die sich nicht übertragen, sind die, die zählen: quantifizierte Alarm-Schwellen vor dem Vorfall, Sampling-Mathematik vor dem Kostenanstieg, der Unit-Economics-Check vor der quartalsweisen Rechnungsprüfung, die Regel, dass jede Log-Zeile eine Frage beantworten muss, die jemand bereit ist zu stellen.

Für den breiteren Stack-Kontext vgl. den SaaS-Architektur-Leitfaden 2026 und den Deployment-Pipeline-Leitfaden, wie Observability zum Release-Kriterium wird.


Abschließende Handlungsaufforderung: TanStack Ship betreibt diesen Observability-Stack über zwölf SaaS-APIs. Vgl. die Features-Seite, Alternativen vergleichen oder den SaaS-Architektur-Leitfaden 2026 für den breiteren Kontext. Der D1-Produktionsleitfaden deckt die Datenschicht ab, aus der das Metrik-Dashboard liest; der SaaS-Authentifizierungsleitfaden deckt die Authentifizierungsschicht ab, die das Dependency-Health-Panel überwacht; der Deployment-Pipeline-Leitfaden deckt ab, wie Observability zum Release-Gate wird, nicht zum Postmortem-Werkzeug.