TL;DR: 可观测性是 SaaS 应用运维信心的基础。没有它,你只能在黑暗中调试。本文覆盖可观测性的三大支柱——日志、指标和链路追踪——并给出在 Cloudflare Workers 和 TanStack Start 上的实战实现,以及生产环境监控、告警和根因分析的实用模式。
可观测性的三大支柱
| 支柱 | 回答的问题 | 存储 | 保留时间 | 成本 |
|---|---|---|---|---|
| 日志 | "发生了什么?" | Workers Analytics Engine | 7-30 天 | 低 |
| 指标 | "趋势是什么?" | Analytics Engine + Grafana | 3-12 个月 | 中 |
| 链路追踪 | "为什么会发生?" | Workers Trace(Tail Worker) | 1-7 天 | 低(采样) |
日志:边缘网络上的结构化日志
日志格式
结构化日志以一致的 JSON 格式捕获每个请求,包含级别、消息、请求 ID、耗时和元数据。这种结构化方式使得跨所有服务进行过滤、告警和调试成为可能:
// 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(数值)和索引,支持高效过滤:
// 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、TTFB | Web Vitals API + Analytics |
收集性能指标
请求级指标——延迟、状态码和缓存命中率——是任何可观测实践的基础。此中间件捕获每个请求的时序数据,并记录到 Analytics Engine 供后续分析:
// 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 路径提取百分位分布,清晰展示哪些端点需要优化:
// 查询 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 部署:
// 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 调用关联:
// 将追踪上下文从服务器函数传递到下游服务
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 的错误率和延迟百分位,然后对照阈值判断是否需要行动:
// 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 和邮件通知团队(针对高严重级别),并在可能的情况下尝试自动修复。此服务器函数集中了响应逻辑:
// 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 生产指南 解释了如何监控数据库性能。