SaaS Observability Stack: Wir debuggen Produktionsprobleme jetzt in 10 Minuten

Echte Observability-Einrichtung. Debugging-Zeit von Stunden auf Minuten reduziert mit Cloudflare RUM, Grafana und Metriken. Volle Implementierung.

Maya Patel
Maya Patel
8. Juni 202613 min read
Auch verfügbar auf:English · 中文

TL;DR: Observability ist das Fundament der operativen Sicherheit in einer SaaS-Anwendung. Ohne sie debuggen Sie blind. Dieser Leitfaden behandelt die drei Säulen der Observability — Logs, Metriken und Traces — implementiert für SaaS-Anwendungen auf Cloudflare Workers und TanStack Start, mit praktischen Mustern für Produktionsmonitoring, Alarmierung und Root-Cause-Analyse.


Die drei Säulen der Observability

SäuleSie beantwortetSpeicherAufbewahrungKosten
Logs„Was ist passiert?"Workers Analytics Engine7–30 TageNiedrig
Metriken„Wie ist der Trend?"Analytics Engine + Grafana3–12 MonateMittel
Traces„Warum ist es passiert?"Workers Trace (Tail Worker)1–7 TageNiedrig (gesampelt)

Logging: Strukturiertes Logging am Edge

Log-Format

Strukturiertes Logging erfasst jeden Request in einem konsistenten JSON-Format mit Level, Nachricht, Request-ID, Dauer und Metadaten. Dieser strukturierte Ansatz ermöglicht Filtering, Alarmierung und Debugging über alle Services hinweg:

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

interface LogEntry {
  timestamp: string
  level: LogLevel
  message: string
  requestId: string
  userId?: string
  duration?: number
  error?: string
  metadata?: Record<string, unknown>
}

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

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

Der Logger hängt eine eindeutige requestId (aus Cloudflares cf-ray-Header) an jeden Log-Eintrag an, wodurch alle Logs eines einzelnen Requests korreliert werden können. Die createLogger-Factory bettet diese ID ein, sodass Entwickler sie nie manuell übergeben müssen.

Logs an die Analytics Engine senden

Cloudflare Workers Analytics Engine bietet abfragbaren, langfristigen Speicher für Ihre Log-Daten. Jeder Log-Eintrag wird als Datenpunkt mit Blobs (Strings), Doubles (numerische Werte) und Indizes für effizientes Filtern geschrieben:

typescript
// src/lib/logger.ts — Schreiben in Workers Analytics Engine
function log(entry: LogEntry) {
  // In Analytics Engine schreiben für abfragbaren Speicher
  context.env.ANALYTICS.writeDataPoint({
    blobs: [
      entry.level,
      entry.message,
      entry.requestId,
      entry.userId ?? "",
      entry.error ?? "",
      JSON.stringify(entry.metadata ?? {}),
    ],
    doubles: [entry.duration ?? 0],
    indexes: [entry.requestId],
  })

  // Auch an Workers-Konsole für wrangler tail ausgeben
  console.log(JSON.stringify(entry))
}

Durch das Schreiben sowohl in die Analytics Engine als auch in console.log erhalten Sie das Beste aus beiden Welten: persistenten, abfragbaren Speicher für Dashboards und Echtzeit-Streaming über wrangler tail für interaktives Debugging.


Metriken: Was Sie messen sollten

SaaS-Metriken-Taxonomie

KategorieMetrikenMessmethode
PerformanceRequest-Latenz (p50/p95/p99), Antwortgröße, Cache-Hit-RateAnalytics Engine
BusinessSignups, MRR, Churn-Rate, Conversion-RateDatenbankabfragen
InfrastrukturWorker-CPU-Zeit, Speicher, Subrequest-AnzahlWorkers Runtime API
User ExperienceLCP, CLS, INP, TTFBWeb Vitals API + Analytics

Performance-Metriken erfassen

Request-Level-Metriken — Latenz, Statuscodes und Cache-Hit-Rates — sind das Fundament jeder Observability-Praxis. Diese Middleware erfasst Zeitdaten für jeden Request und zeichnet sie in der Analytics Engine zur späteren Analyse auf:

typescript
// server/middleware/metrics.ts
export const metricsMiddleware = createServerFn({ method: "GET" }).handler(
  async ({ request, context }) => {
    const startTime = Date.now()

    // Nach Abschluss des Requests Metriken aufzeichnen
    context.waitUntil(
      context.env.ANALYTICS.writeDataPoint({
        blobs: [
          request.url,
          request.method,
          String(context.response?.status ?? 500),
        ],
        doubles: [
          Date.now() - startTime, // Dauer
          request.cf?.colo ?? 0, // Edge-Standort
        ],
        indexes: [request.url.split("?")[0]], // Pfad
      })
    )
  }
)

Die Verwendung von context.waitUntil stellt sicher, dass die Metrik auch nach dem Senden der Antwort an den Client aufgezeichnet wird, sodass keine Latenz zu user-facing Requests hinzugefügt wird, während gleichzeitig umfassende Daten gesammelt werden.

Metriken abfragen

Sobald Metriken erfasst sind, erfordert ihre Abfrage SQL-Zugriff auf die Analytics Engine. Dieses Muster extrahiert Percentile-Aufschlüsselungen nach API-Pfad und gibt Ihnen einen klaren Überblick darüber, welche Endpunkte optimiert werden müssen:

typescript
// Analytics Engine für Dashboard-Daten abfragen
export const getApiLatency = createServerFn({ method: "GET" }).handler(
  async ({}, { context }) => {
    const result = await context.env.ANALYTICS.query({
      sql: `
        SELECT
          blob3 as path,
          quantile(0.50)(double1) as p50,
          quantile(0.95)(double1) as p95,
          quantile(0.99)(double1) as p99,
          count() as requests
        FROM analytics_engine_table
        WHERE timestamp > now() - INTERVAL '1' DAY
        GROUP BY blob3
        ORDER BY p99 DESC
        LIMIT 20
      `,
    })

    return result.rows
  }
)

Die quantile-Funktionen berechnen Percentile-Latenzen direkt in der Datenbank und vermeiden so den Aufwand, rohe Daten zur Verarbeitung in die Anwendung zu ziehen. Die Filterung auf die letzten 24 Stunden hält Abfragen schnell und auf die aktuelle Performance fokussiert.


Tracing: Verteiltes Request-Tracing

Einen Tail Worker einrichten

Ein Tail Worker empfängt Events von Ihrem primären Worker, nachdem jeder Request abgeschlossen wurde, was ihn ideal zum Samplen von Trace-Daten macht, ohne die Request-Performance zu beeinträchtigen. Deployen Sie ihn als separaten Worker, der Event-Streams verarbeitet:

typescript
// tail-worker.ts — als separater Worker deployed
export default {
  async tail(events: TraceEvent[]) {
    for (const event of events) {
      if (event.duration && event.event.request) {
        await ANALYTICS.writeDataPoint({
          blobs: [
            event.event.request.method,
            event.event.request.url,
            String(event.exceptions?.[0]?.message ?? ""),
          ],
          doubles: [
            event.duration ?? 0,
            event.event.request.cf?.botManagement?.score ?? 0,
          ],
          indexes: [event.event.request.url.split("?")[0]],
        })

        // Bei Exceptions Alarm auslösen
        if (event.exceptions?.length > 0 || event.crons?.length > 0) {
          await sendIncidentAlert(event)
        }
      }
    }
  },
}

Der Tail Worker erfasst Dauer, Exceptions und Bot-Scores für jeden Request. Wenn Exceptions erkannt werden, kann er sofort Incident Alerts auslösen und verwandelt damit Ihre Observability-Pipeline in ein automatisiertes Incident-Response-System.

Trace-Kontext injizieren

Für Requests, die mit externen Services interagieren (Stripe, Resend, OpenAI), ermöglicht das Propagieren einer Trace-ID die Korrelation der Server-Funktionsausführung mit nachgelagerten API-Calls:

typescript
// Trace-Kontext von Server-Funktion an nachgelagerte Services übergeben
export const handleWebhook = createServerFn({ method: "POST" }).handler(
  async ({ request, context }) => {
    const traceId = crypto.randomUUID()

    // Trace-ID an nachgelagerte Calls übergeben
    await fetch("https://api.stripe.com/v1/events", {
      headers: {
        "X-Trace-Id": traceId,
      },
    })

    // Mit Trace-Kontext loggen
    context.logger.info("Webhook erhalten", { traceId, eventType: event.type })
  }
)

Der X-Trace-Id-Header wird an nachgelagerte Services übergeben und dieselbe Trace-ID in Log-Einträgen eingefügt. Dies erzeugt einen Ende-zu-Ende-Trace, der den eingehenden Request, Ihre Server-Funktionslogik und alle externen API-Calls verbindet.


Alarmierung: Wann jemand geweckt werden sollte

Alarm-Schweregrade

LevelReaktionszeitBeispiel
Kritisch15 Minutenp99-Latenz > 5s für 5 Minuten
Hoch1 StundeFehlerrate > 1 % für 10 Minuten
Mittel24 StundenSpeichernutzung > 80 %
Niedrig7 TageDeprecated API-Calls erkannt

Alarm-Konfiguration

Alarme sollten von den Metriken getrieben werden, die Sie bereits erfassen, nicht von separater Monitoring-Infrastruktur. Diese Server-Funktion fragt die Analytics Engine nach Fehlerraten und Latenz-Perzentilen und wertet sie gegen Schwellenwerte aus, um zu bestimmen, ob Handlungsbedarf besteht:

typescript
// server/monitoring/alerts.ts
import { createServerFn } from "@tanstack/react-start"

export const checkAlerts = createServerFn({ method: "GET" }).handler(
  async ({}, { context }) => {
    const alerts = []

    // 1. Fehlerrate prüfen
    const errorRate = await context.env.ANALYTICS.query({
      sql: `
        SELECT count() as total,
               countIf(blob0 = 'error') as errors
        FROM analytics_engine_table
        WHERE timestamp > now() - INTERVAL '5' MINUTE
      `,
    })

    const rate = Number(errorRate.rows[0]?.errors ?? 0) /
                 Math.max(Number(errorRate.rows[0]?.total ?? 1), 1)

    if (rate > 0.01) {
      alerts.push({
        severity: "high",
        message: `Fehlerrate ${(rate * 100).toFixed(1)}% > 1%`,
      })
    }

    // 2. Latenz prüfen
    const latency = await context.env.ANALYTICS.query({
      sql: `
        SELECT quantile(0.99)(double1) as p99
        FROM analytics_engine_table
        WHERE timestamp > now() - INTERVAL '5' MINUTE
      `,
    })

    const p99 = Number(latency.rows[0]?.p99 ?? 0)
    if (p99 > 5000) {
      alerts.push({
        severity: "critical",
        message: `p99-Latenz ${p99}ms > 5000ms`,
      })
    }

    return alerts
  }
)

Der Alert-Checker läuft auf einem Cron-Schedule und wertet sowohl Fehlerraten- als auch Latenz-Schwellenwerte aus. Durch das Abfragen der letzten 5 Minuten werden Spitzen schnell erkannt, während falsch-positive Ergebnisse durch kurze transiente Fehler vermieden werden.


Dashboard: Observability-Daten visualisieren

Das ideale SaaS-Observability-Dashboard hat drei Ansichten:

Operative Ansicht (Echtzeit)

┌──────────────┬──────────────┬──────────────┬──────────────┐
│ Request-Rate │ Fehlerrate   │ p50-Latenz  │ p99-Latenz  │
│  1.234/min   │   0,02 %     │   45ms      │   210ms     │
├──────────────┼──────────────┼──────────────┼──────────────┤
│ Aktive User  │ Signups (24h)│ Churn (30d) │ MRR         │
│    847       │     23       │   3,2 %     │ $12.450     │
└──────────────┴──────────────┴──────────────┴──────────────┘

Business-Ansicht (wöchentlich)

Kohorten-Retention-Tabelle
Signup-Trichter (Besuch → Signup → Subscribe → Aktiv)
Feature-Adoption-Heatmap
Top-User-Aktionen

Debug-Ansicht (on-demand)

Request Trace Explorer
Error Log Stream
Worker-CPU-Profil
Datenbank-Performance

Automatisierte Incident-Response

Wenn Alarme ausgelöst werden, protokolliert der Incident-Response-Handler das Event, benachrichtigt das Team über Slack und E-Mail (bei hoher Priorität) und versucht, wo möglich, automatische Behebung. Diese Server-Funktion zentralisiert die Response-Logik:

typescript
// server/monitoring/incident.ts
export const handleIncident = createServerFn({ method: "POST" }).handler(
  async ({ data }: { data: { severity: string; message: string } }) => {
    // 1. Incident loggen
    await logIncident(data)

    // 2. Benachrichtigen (nur für hohe+ Priorität)
    if (["high", "critical"].includes(data.severity)) {
      await sendSlackNotification(
        `[${data.severity.toUpperCase()}] ${data.message}`
      )
      await sendEmail({
        to: "ops@tanstackship.com",
        subject: `[${data.severity}] ${data.message}`,
      })
    }

    // 3. Wo möglich automatisch beheben
    if (data.message.includes("rate limit")) {
      // Rate-Limit-Fenster hochskalieren
      console.log("Auto-Remediation: Rate-Limit überschritten")
    }
  }
)

Der Incident-Handler nutzt eine schwellenwertbasierte Benachrichtigungsstrategie: niedrige und mittlere Alarme werden still protokolliert, während hohe und kritische Alarme sofortige Benachrichtigungen auslösen. Automatische Gegenmaßnahmen — wie das Anpassen von Rate-Limits — sind in denselben Flow integriert, damit die Wiederherstellung ohne menschliches Eingreifen erfolgt.


Observability-Budget

Allocieren Sie Ihre Observability-Ressourcen sinnvoll:

Komponente% des Observability-BudgetsWarum
Request-Logging (gesampelt)20 %Operative Kerndaten
Error-/Exception-Logging30 %Höchster Debugging-Wert
Business-Metriken25 %Umsatz und Wachstum
Performance-Metriken15 %User Experience
Verteilte Traces10 %Komplexe Probleme debuggen

Produktions-Observability-Checkliste

  • Alle Requests erzeugen strukturierte JSON-Logs
  • Logs enthalten Request-ID, User-ID und Dauer
  • Error-Logs enthalten Stack Traces
  • Alle kritischen Business-Events werden als Metriken erfasst
  • Worker-Performance-Metriken erfasst (CPU, Speicher, Subrequests)
  • Alarme für p99-Latenz > 5s und Fehlerrate > 1 % konfiguriert
  • Tail Worker erfasst Trace-Daten für gesampelte Requests
  • Dashboard deckt operative, Business- und Debug-Ansichten ab
  • Logs haben automatisierte Aufbewahrungs- und Archivierungspolicy
  • Zugriff auf Logs wird auditiert und rollenbasiert eingeschränkt

Fazit

Observability ist für eine Produktions-SaaS-Anwendung keine Option. Die drei Säulen — Logs, Metriken und Traces — erfüllen jeweils unterschiedliche Zwecke, und eine ausgereifte Observability-Praxis braucht alle drei.

Für TanStack Start auf Cloudflare Workers bieten die nativen Observability-Tools (Workers Analytics Engine, Tail Workers, Trace Workers) eine solide Grundlage, ohne dass Drittanbieter-Services erforderlich sind. Sie können operative Daten vollständig innerhalb des Cloudflare-Ökosystems erfassen, abfragen und alarmieren.

Der Schlüssel liegt darin, einfach zu beginnen: zuerst strukturierte Logs, dann Metriken für die wichtigsten geschäftlichen und performance-bezogenen Indikatoren, dann Traces zum Debuggen komplexer Probleme. Fügen Sie Alarmierung erst hinzu, wenn Sie genügend Daten haben, um aussagekräftige Schwellenwerte zu definieren. Für eine breitere Sicherheitsperspektive lesen Sie unsere Best Practices für SaaS-Sicherheit, und wie Sie Performance-Metriken mit User Experience verknüpfen, erfahren Sie im Core Web Vitals Optimization Guide. Der Leitfaden Real User Monitoring behandelt die Erfassung von Felddaten aus echten Nutzern, während der Cloudflare D1 Production Guide erklärt, wie Sie Ihre Datenbank-Performance überwachen.