一、引言
「系统出问题了,但不知道出在哪」是运维最痛的事。可观测性(Observability)解决的就是这个:通过日志、指标、链路追踪三支柱,让你能回答「发生了什么、现在状态如何、某个请求经历了什么」。而在 Serverless/边缘架构下,函数无状态、实例瞬息万变,可观测性更是唯一能「看见」分布式行为的窗口。
本文系统讲可观测性:先建立三支柱框架,再深入结构化日志与上下文注入、OpenTelemetry 集成、Serverless 的可观测性挑战(日志聚合/冷启动/跨边缘追踪)、错误追踪与 Sourcemap 还原、告警与 SLO,最后给出可观测性闭环设计与成本控制。
关联:https://plumephp.com/tools-frontend-monitoring-rum/(前端监控 RUM)、https://plumephp.com/tools-edge-cache-cdn-strategy/(边缘缓存)、https://plumephp.com/tools-serverless-cold-start/(Serverless 形态)、https://plumephp.com/vercel-analytics-speed-insights/(Vercel 监控)。
二、三支柱:日志、指标、链路追踪
2.1 三个支柱的分工
| 支柱 | 回答的问题 | 典型工具 |
|---|---|---|
| 日志(Logs) | 发生了什么(事件明细) | Datadog/Loki/CloudWatch |
| 指标(Metrics) | 现在状态如何(数值聚合) | Prometheus/StatsD |
| 追踪(Traces) | 一个请求经历了什么(调用链) | Jaeger/Tempo/OTel |
三者互补:
指标 → 发现「服务慢了」(报警)
日志 → 查「哪个请求慢」(明细)
追踪 → 追「慢在哪一环」(调用链)
2.2 从「监控」到「可观测性」
监控:你知道要问什么(预设指标)→ 看仪表盘
可观测性:你不知道要问什么 → 随时下钻(日志+追踪+上下文)
关键:上下文(context)——每个日志带 requestId、user、service 维度
一句话总结:日志管事件、指标管状态、追踪管调用链——三支柱合起来让你能「从报警下钻到某一行代码」。
三、结构化日志与上下文注入
3.1 结构化日志(不要 print 字符串)
// 反例:无法检索
console.log('order created: ' + orderId)
// 正例:结构化 + 上下文
console.log(JSON.stringify({
level: 'info',
event: 'order.created',
orderId,
userId,
requestId, // 贯穿全链路
service: 'orders-api',
latencyMs: 123
}))
3.2 上下文注入(Context Propagation)
// 中间件生成并传播 requestId
export async function middleware(request: Request) {
const requestId = request.headers.get('x-request-id') || crypto.randomUUID()
request.headers.set('x-request-id', requestId)
return NextResponse.next({ request })
}
// 每个函数日志都带 requestId → 全链路可串
3.3 日志分级与采样
debug:开发期用,生产不开(量大)
info :关键事件(创建/更新/登出)
warn :可恢复异常(重试/降级)
error:真实故障(带 stack + 上下文)
采样:高频 info 按比例采样,error 全量
一句话总结:结构化日志 + requestId 上下文 = 可检索、可串链路;分级控制量与采样降成本。
四、OpenTelemetry 集成
4.1 OTel 的核心概念
统一采集:Logs/Metrics/Traces 统一 API 与协议
自动埋点:SDK 自动捕获 HTTP/DB/消息
导出:OTLP 协议导出到任意后端(Jaeger/Tempo/Datadog/自建)
// 初始化 OTel(Node.js)
import { NodeSDK } from '@opentelemetry/sdk-node'
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter({ url: process.env.OTEL_EXPORTER }),
instrumentations: [getNodeAutoInstrumentations()]
})
sdk.start()
4.2 手动埋点(关键业务)
import { trace } from '@opentelemetry/api'
const tracer = trace.getTracer('payment')
export async function processOrder(orderId: string) {
const span = tracer.startSpan('order.process')
span.setAttribute('order.id', orderId)
try {
// ... 业务
span.setStatus({ code: SpanStatusCode.OK })
} catch (e) {
span.setStatus({ code: SpanStatusCode.ERROR })
span.recordException(e)
throw e
} finally {
span.end()
}
}
4.3 供应商中立
OTel 的价值是「一次埋点、任意后端」——避免锁定供应商
迁移后端只改 exporter 配置,埋点代码不动
一句话总结:OTel 统一三支柱采集、自动埋点、OTLP 导出——供应商中立,迁移后端只换 exporter。
五、Serverless 可观测性的挑战
5.1 无状态与日志聚合
挑战:函数实例瞬灭,日志散落各地
解法:平台日志(CloudWatch/Workers Logs)+ 结构化 → 聚合到集中存储
用 requestId/version 维度检索
5.2 冷启动的观测
// 记录冷启动(模块级一次性)
const started = Date.now()
let coldStart = false
if (!globalThis.__warm) {
globalThis.__warm = true
coldStart = true
}
// 日志带 coldStart 标记 → 聚合看冷启动占比
5.3 跨边缘/跨服务追踪
边缘函数 → API → DB/第三方,每跳都是独立实例
需要 trace context(W3C traceparent)传播:
HTTP 头带 traceparent → 下游续接 span
边缘平台(Vercel/Cloudflare)逐步内置 trace 支持
一句话总结:Serverless 可观测的难点是「无状态 + 跨边缘」——结构化日志聚合、显式标记冷启动、trace context 跨跳传播。
六、错误追踪与 Sourcemap 还原
6.1 前端错误与堆栈还原
前端打包后堆栈全是压缩代码 → 用 Sourcemap 还原源码
步骤:
1. 构建时生成 .map(不上传到 CDN,仅上传监控平台)
2. 错误上报带堆栈 → 平台用 map 还原
3. 关联用户/版本/路径 → 定位
// 前端错误上报(示意)
window.addEventListener('error', (e) => {
fetch('/api/track', {
method: 'POST',
body: JSON.stringify({
message: e.message,
stack: e.error?.stack, // 压缩堆栈 → 平台还原
path: location.pathname,
version: __BUILD_ID__
})
})
})
6.2 服务端错误聚合
后端 error 日志 → 聚合平台 → 按「签名」分组(同错误合并计数)
签名 = 堆栈首几行哈希 → 快速看「哪个错误影响多少人」
6.3 错误分级的响应
P0(核心功能全挂)→ 立即告警 + 自动回滚
P1(局部功能异常)→ 告警 + 排查
P2(低影响) → 日报合并处理
P3(噪音) → 采样/静默
一句话总结:错误追踪 = 前端 Sourcemap 还原源码 + 后端按签名聚合 + 分级响应;堆栈还原与分组计数是「快速定位影响面」的关键。
七、告警与 SLO
7.1 SLO 定义
SLO(服务目标):如「P95 延迟 < 500ms,99% 请求成功」
SLI(度量):错误率、延迟、可用性
SLO 是「可量化的质量契约」→ 触发时即告警
7.2 告警规则设计
好的告警:指向问题、可行动、不噪音
规则示例:
错误率 5xx > 1%(5 分钟)→ 告警
P95 延迟 > 基线 2 倍(10 分钟)→ 告警
SLO 烧钱率 > 阈值 → 告警
反模式:告警疲劳(阈值过敏感)→ 会被忽略
7.3 告警闭环
告警 → 定位(日志+追踪下钻)→ 修复 → 验证 → 复盘
闭环要求:每条告警有「runbook」(如何排查)
一句话总结:SLO 定质量目标、告警按目标触发、闭环到复盘——告警的关键是「可行动、不噪音、有 runbook」。
八、可观测性闭环设计
观测层:日志 + 指标 + 追踪(OTel 统一采集)
分析层:聚合、分组、下钻(requestId 串链路)
决策层:告警 + SLO + 看板
行动层:回滚 / 降级 / 扩容 / 修复
反馈层:复盘 → 补指标 / 补告警 → 持续改进
核心链路:用户请求 → 边缘(trace)→ 函数(log+metric)→ 数据库(span)
→ 平台聚合 → 报警 → 定位 → 修复
一套最小闭环:
1. 全站埋点(OTel + RUM)
2. 集中日志(结构化 + requestId)
3. 关键指标看板(错误率/P95/可用性)
4. SLO + 分级告警
5. Sourcemap 还原 + 签名聚合
6. 告警 runbook + 自动回滚兜底
一句话总结:可观测性闭环 = 采集(OTel+RUM)→ 分析(聚合+下钻)→ 决策(SLO+告警)→ 行动(回滚/修复)→ 反馈(复盘补测)。
九、成本与治理
9.1 可观测性成本怎么爆
日志量指数增长 → 存储贵
Trace 全量采样 → 昂贵
告警噪音 → 人效下降
9.2 成本控制策略
| 手段 | 做法 |
|---|---|
| 采样 | trace 按比例采样(头部采样),error 全量 |
| 日志分级 | debug 不存储、info 限 TTL |
| 压缩/裁剪 | 去敏感字段、限制字段数 |
| 告警收敛 | 分组、去抖、分级 |
| 存储分层 | 热 7 天、温 30 天、冷归档 |
9.3 隐私与合规
日志可能含 PII(用户信息)→ 脱敏后再存储
保留策略符合合规(GDPR 等)→ 限 TTL
敏感字段不进日志(密码/token)
一句话总结:可观测性成本 = 采样控 trace、分级控日志、收敛控告警;PII 脱敏与保留策略是合规底线。
十、速查表
| 需求 | 方案 |
|---|---|
| 事件明细 | 结构化日志 + requestId |
| 状态数值 | 指标(Prometheus 等) |
| 调用链 | OTel Trace + traceparent |
| 统一采集 | OpenTelemetry |
| 前端还原 | Sourcemap 上传监控平台 |
| 错误聚合 | 签名分组 + 计数 |
| 质量目标 | SLO/SLI |
| 告警 | 错误率/延迟阈值 + runbook |
| 冷启动观测 | 显式标记 + 日志维度 |
| 成本控制 | 采样 + 分级 + TTL |
一句话记忆:可观测性 = 日志(事件)+ 指标(状态)+ 追踪(调用链)三支柱,OTel 统一采集、供应商中立;结构化日志带 requestId 才能串链路,Sourcemap 还原前端堆栈、签名聚合后端错误;SLO 定目标、分级告警可行动、闭环到复盘;Serverless 的难点是跨边缘 trace 与冷启动观测;成本靠采样、分级、TTL 控制——观测不是堆工具,是能「从报警下钻到代码」的能力。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。