SaaS Monitoring Observability Guide: 我们如何在 10 分钟内解决生产问题

真正的可观测性配置。使用 Cloudflare RUM、Grafana 和自定义指标,我们将调试时间从数小时缩短到数分钟。完整实现方案。

Maya Patel
Maya Patel
June 8, 202613 min read
其他语言:Deutsch · English

TL;DR: 可观测性是 SaaS 应用运维信心的基础。没有它,你只能在黑暗中调试。本文覆盖可观测性的三大支柱——日志、指标和链路追踪——并给出在 Cloudflare Workers 和 TanStack Start 上的实战实现,以及生产环境监控、告警和根因分析的实用模式。


可观测性的三大支柱

支柱回答的问题存储保留时间成本
日志"发生了什么?"Workers Analytics Engine7-30 天低
指标"趋势是什么?"Analytics Engine + Grafana3-12 个月中
链路追踪"为什么会发生?"Workers Trace(Tail Worker)1-7 天低(采样)

日志:边缘网络上的结构化日志

日志格式

结构化日志以一致的 JSON 格式捕获每个请求,包含级别、消息、请求 ID、耗时和元数据。这种结构化方式使得跨所有服务进行过滤、告警和调试成为可能:

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,
  }
}

日志记录器为每条日志附加唯一的 requestId(来自 Cloudflare 的 cf-ray 头),使关联单个请求的所有日志成为可能。createLogger 工厂函数嵌入此 ID,开发者无需手动传递。

向 Analytics Engine 发送日志

Cloudflare Workers Analytics Engine 为日志数据提供可查询的长期存储。每条日志作为数据点写入,包含 blobs(字符串)、doubles(数值)和索引,支持高效过滤:

typescript
// src/lib/logger.ts — 写入 Workers Analytics Engine
function log(entry: LogEntry) {
  // 写入 Analytics Engine 以便查询
  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],
  })

  // 同时输出到 Workers console 供 wrangler tail 使用
  console.log(JSON.stringify(entry))
}

同时写入 Analytics Engine 和 console.log 兼顾两者优势:持久化可查询存储用于仪表盘,以及通过 wrangler tail 实时流式传输用于交互式调试。


指标:测量什么

SaaS 指标分类

类别指标如何测量
性能请求延迟(p50/p95/p99)、响应大小、缓存命中率Analytics Engine
业务注册量、MRR、流失率、转化率数据库查询
基础设施Worker CPU 时间、内存、子请求数Workers Runtime API
用户体验LCP、CLS、INP、TTFBWeb Vitals API + Analytics

收集性能指标

请求级指标——延迟、状态码和缓存命中率——是任何可观测实践的基础。此中间件捕获每个请求的时序数据,并记录到 Analytics Engine 供后续分析:

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

    // 请求完成后记录指标
    context.waitUntil(
      context.env.ANALYTICS.writeDataPoint({
        blobs: [
          request.url,
          request.method,
          String(context.response?.status ?? 500),
        ],
        doubles: [
          Date.now() - startTime, // 耗时
          request.cf?.colo ?? 0, // 边缘节点位置
        ],
        indexes: [request.url.split("?")[0]], // 路径
      })
    )
  }
)

使用 context.waitUntil 确保响应发送给客户端后仍能记录指标,不会为面向用户的请求增加延迟,同时收集完整数据。

查询指标

收集指标后,需要 SQL 访问 Analytics Engine 来查询。此模式按 API 路径提取百分位分布,清晰展示哪些端点需要优化:

typescript
// 查询 Analytics Engine 获取仪表盘数据
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
  }
)

quantile 函数直接在数据库中计算百分位延迟,避免将原始数据拉取到应用层处理的开销。过滤到最近 24 小时保持查询快速且聚焦于当前性能。


链路追踪:分布式请求追踪

设置 Tail Worker

Tail Worker 在每个请求完成后从主 Worker 接收事件,非常适合在不影响请求性能的情况下采样追踪数据。作为处理事件流的独立 Worker 部署:

typescript
// tail-worker.ts — 部署为独立 Worker
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]],
        })

        // 异常时触发告警
        if (event.exceptions?.length > 0 || event.crons?.length > 0) {
          await sendIncidentAlert(event)
        }
      }
    }
  },
}

Tail Worker 捕获每个请求的耗时、异常和机器人分数。检测到异常时,可立即触发事故告警——将可观测性管道转变为自动化事件响应系统。

注入追踪上下文

对于与外部服务交互的请求(Stripe、Resend、OpenAI),传播 trace ID 使你能将服务器函数执行与下游 API 调用关联:

typescript
// 将追踪上下文从服务器函数传递到下游服务
export const handleWebhook = createServerFn({ method: "POST" }).handler(
  async ({ request, context }) => {
    const traceId = crypto.randomUUID()

    // 向下游调用传递 trace ID
    await fetch("https://api.stripe.com/v1/events", {
      headers: {
        "X-Trace-Id": traceId,
      },
    })

    // 附带追踪上下文记录日志
    context.logger.info("Webhook received", { traceId, eventType: event.type })
  }
)

X-Trace-Id 头传递给下游服务,相同的 trace ID 也包含在日志条目中。这创建了端到端链路,连接了传入请求、服务器函数逻辑和所有外部 API 调用。


告警:何时叫醒相关人员

告警严重级别

级别响应时间示例
紧急15 分钟p99 延迟 > 5s 持续 5 分钟
高1 小时错误率 > 1% 持续 10 分钟
中24 小时磁盘使用率 > 80%
低7 天检测到已废弃 API 调用

告警配置

告警应基于已收集的指标驱动,而非独立的监控基础设施。此服务器函数查询 Analytics Engine 的错误率和延迟百分位,然后对照阈值判断是否需要行动:

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

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

    // 1. 检查错误率
    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: `Error rate ${(rate * 100).toFixed(1)}% > 1%`,
      })
    }

    // 2. 检查延迟
    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 latency ${p99}ms > 5000ms`,
      })
    }

    return alerts
  }
)

告警检查器按 cron 计划运行,评估错误率和延迟阈值。通过查询最近 5 分钟的数据,能快速捕获峰值,同时避免短暂瞬时错误引发的误报。


仪表盘:可视化可观测性数据

理想的 SaaS 可观测性仪表盘包含三个视图:

运维视图(实时)

┌──────────────┬──────────────┬──────────────┬──────────────┐
│ 请求速率     │ 错误率       │ p50 延迟     │ p99 延迟     │
│   1,234/min │   0.02%     │    45ms      │    210ms    │
├──────────────┼──────────────┼──────────────┼──────────────┤
│ 活跃用户     │ 注册量(24h)  │ 流失(30d)    │ MRR         │
│   847       │   23         │    3.2%      │  $12,450    │
└──────────────┴──────────────┴──────────────┴──────────────┘

业务视图(周)

Cohort 留存表
注册漏斗(访问 → 注册 → 订阅 → 活跃)
功能采用热力图
用户主要行为

调试视图(按需)

请求链路追踪浏览器
错误日志流
Worker CPU 性能分析
数据库查询性能

自动化事件响应

告警触发时,事件响应处理器记录事件、通过 Slack 和邮件通知团队(针对高严重级别),并在可能的情况下尝试自动修复。此服务器函数集中了响应逻辑:

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

    // 2. 通知(仅高及以上严重级别)
    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. 在可能的情况下自动修复
    if (data.message.includes("rate limit")) {
      // 扩大速率限制窗口
      console.log("Auto-remediation: rate limit exceeded")
    }
  }
)

事件处理器采用基于严重级别的通知策略:低和中级别告警仅静默记录,而高和紧急级别触发即时通知。自动修复操作——如调整速率限制——内置于同一流程,使恢复无需人工干预。


可观测性预算

明智分配可观测性资源:

组件可观测性预算占比原因
请求日志(采样)20%核心运维数据
错误/异常日志30%调试价值最高
业务指标25%营收与增长
性能指标15%用户体验
分布式链路追踪10%复杂问题调试

生产可观测性检查清单

  • 所有请求产生结构化 JSON 日志
  • 日志包含请求 ID、用户 ID 和耗时
  • 错误日志包含堆栈追踪
  • 所有关键业务事件作为指标追踪
  • 收集 Worker 性能指标(CPU、内存、子请求数)
  • 配置 p99 延迟 > 5s 和错误率 > 1% 的告警
  • Tail Worker 捕获采样请求的追踪数据
  • 仪表盘覆盖运维、业务和调试视图
  • 日志有自动化保留和归档策略
  • 日志访问有审计和角色限制

结语

对于生产级 SaaS 应用,可观测性不是可选项。日志、指标和链路追踪三大支柱各司其职,成熟的可观测性实践三者缺一不可。

对于 Cloudflare Workers 上的 TanStack Start,原生可观测性工具(Workers Analytics Engine、Ta il Workers、Trace Workers)提供了坚实基础,无需第三方服务即可收集、查询和告警运维数据。

关键是从简单开始:先上结构化日志,再为核心业务和性能指标添加指标,最后在调试复杂问题时引入链路追踪。等到有足够数据设定有意义的阈值后再添加告警。如需更广泛的安全视角,参见 SaaS 安全最佳实践,了解如何将性能指标与用户体验关联请阅读 Core Web Vitals 优化指南。真实用户监控 指南涵盖如何从真实用户收集现场数据,而 Cloudflare D1 生产指南 解释了如何监控数据库性能。