错误监控与可观测性
应用上线后,你看不到用户屏幕上发生了什么——某个页面白屏了?API 响应变慢了?支付流程中断了?没有监控的应用就像闭着眼睛开车。可观测性(Observability)让你在问题发生时第一时间知道、快速定位原因、在用户投诉之前修复。本章系统讲解 Next.js 应用的错误监控、性能追踪、日志管理和告警策略。
1. 可观测性的三大支柱
1.1 Logs、Metrics、Traces
| 支柱 | 定义 | 类比 | 回答的问题 |
|---|---|---|---|
| Logs(日志) | 离散的事件记录 | 病历记录 | "发生了什么?" |
| Metrics(指标) | 聚合的数值度量 | 体检报告 | "整体状态如何?" |
| Traces(链路追踪) | 请求的完整调用链 | 快递追踪 | "请求经过了哪些环节?每个环节耗时多少?" |
三者的关系:
- Metrics 告诉你"有问题"(错误率上升)
- Logs 告诉你"什么问题"(具体错误信息)
- Traces 告诉你"为什么有问题"(哪个环节慢了/出错了)
1.2 Next.js 的可观测性挑战
Next.js 的架构让可观测性比传统 SPA 更复杂:
| 挑战 | 原因 |
|---|---|
| 多运行时 | Server Components(Node.js)+ Client Components(浏览器)+ Middleware(Edge) |
| Serverless | 每个请求可能在不同实例上执行,没有持久进程 |
| SSR + CSR 混合 | 同一个错误可能在服务端和客户端都出现 |
| 流式渲染 | Streaming SSR 的错误可能在渲染中途发生 |
| 构建时 vs 运行时 | 某些错误只在构建时出现(ISR revalidation) |
1.3 可观测性工具全景
| 工具 | 类型 | 特点 | 价格 |
|---|---|---|---|
| Sentry | 错误监控 | 前端 + 后端,Source Maps,Replay | 免费层 |
| Vercel Analytics | 性能指标 | Web Vitals,零配置 | Vercel Pro |
| Datadog | 全栈可观测 | APM + Logs + Metrics + Traces | 付费 |
| Grafana Stack | 开源可观测 | Loki(日志)+ Prometheus(指标)+ Tempo(链路) | 自托管免费 |
| New Relic | APM | 自动探针,AI 分析 | 免费层 |
| OpenTelemetry | 标准协议 | 厂商无关,统一数据格式 | 开源 |
| BetterStack | 日志 + 监控 | Uptime + Logs,漂亮 UI | 免费层 |
| Axiom | 日志分析 | Vercel 集成好,无限保留 | 免费层 |
2. Sentry 集成
2.1 为什么 Sentry 是 Next.js 错误监控的首选
| 原因 | 说明 |
|---|---|
| 官方 SDK | @sentry/nextjs 专为 Next.js 优化 |
| 全栈覆盖 | Client + Server + Edge 三个运行时都支持 |
| Source Maps | 自动上传,生产环境错误定位到源码行 |
| Session Replay | 录制用户操作视频,复现问题 |
| 性能监控 | 自动追踪路由变化、API 调用、数据库查询 |
| App Router 支持 | Server Components、Server Actions 错误自动捕获 |
| 免费额度 | 5K 错误/月、50 Replay/月 |
2.2 Sentry 在 Next.js 中的架构
浏览器(Client Components)
├── 未捕获异常 → Sentry 自动捕获
├── React Error Boundary → Sentry 自动上报
├── Console Errors → 可选捕获
└── Performance → 自动追踪路由、Web Vitals
服务端(Server Components / API Routes)
├── 未捕获异常 → Sentry 自动捕获
├── Server Action 错误 → 自动上报
├── 数据库查询错误 → 手动或自动捕获
└── Performance → 自动追踪 API 耗时
边缘(Middleware)
├── 运行时错误 → Sentry Edge SDK 捕获
└── 超时错误 → 自动上报
2.3 Sentry 核心配置要点
| 配置项 | 推荐值 | 说明 |
|---|---|---|
tracesSampleRate | 0.1(生产) | 采样 10% 的请求做性能追踪 |
replaysSessionSampleRate | 0.1 | 10% 的会话录制 Replay |
replaysOnErrorSampleRate | 1.0 | 出错时 100% 录制 Replay |
tunnel | /api/sentry-tunnel | 绕过广告拦截器 |
| Source Maps | 构建时上传 | 错误定位到源码行 |
| Release | Git commit hash | 关联部署版本 |
2.4 Sentry Tunnel:绕过广告拦截器
很多广告拦截器会屏蔽 sentry.io 的请求,导致客户端错误丢失。解决方案——Sentry Tunnel:
正常模式:
浏览器 ──→ https://o123.ingest.sentry.io/... ← 被广告拦截器屏蔽
Tunnel 模式:
浏览器 ──→ https://yourdomain.com/api/sentry-tunnel ──→ sentry.io
广告拦截器不会屏蔽你自己域名的请求
在 Next.js 中创建 /api/sentry-tunnel Route Handler,将请求转发到 Sentry。
2.5 有效的错误上报
不是所有错误都值得上报——区分信号和噪音:
| 错误类型 | 是否上报 | 原因 |
|---|---|---|
| 未捕获异常 | ✅ 必须 | 可能导致页面崩溃 |
| API 5xx 错误 | ✅ 必须 | 服务端问题 |
| 认证失败(401) | ❌ 不报 | 正常业务行为 |
| 404 请求 | ❌ 不报 | 通常是用户输入错误 |
| 网络超时 | ⚠️ 采样上报 | 可能是用户网络问题 |
| 第三方脚本错误 | ❌ 过滤 | 不是你的代码问题 |
配置 beforeSend 过滤无用错误:
过滤规则:
1. 忽略已知的第三方脚本错误(Google Analytics、Facebook SDK)
2. 忽略网络错误(用户离线)
3. 忽略浏览器扩展引发的错误
4. 忽略爬虫触发的错误
5. 对重复错误去重(相同 fingerprint)
3. 性能监控
3.1 Web Vitals 监控
| 指标 | 全称 | 含义 | 达标值 |
|---|---|---|---|
| LCP | Largest Contentful Paint | 最大内容绘制时间 | ≤ 2.5s |
| INP | Interaction to Next Paint | 交互到下一次绘制 | ≤ 200ms |
| CLS | Cumulative Layout Shift | 累积布局偏移 | ≤ 0.1 |
| FCP | First Contentful Paint | 首次内容绘制 | ≤ 1.8s |
| TTFB | Time to First Byte | 首字节时间 | ≤ 800ms |
3.2 Next.js 内置性能追踪
Next.js App Router 内置了 OpenTelemetry 支持,自动生成以下 Span:
| Span | 说明 |
|---|---|
next.route | 路由处理总时间 |
next.render | Server Component 渲染时间 |
next.data | 数据获取时间 |
next.middleware | Middleware 执行时间 |
fetch | 所有 fetch 请求(自动追踪) |
3.3 Server Timing API
Server Timing 是一个 HTTP 头,允许服务端将性能数据传递到浏览器 DevTools:
Server-Timing: db;dur=23.5, render;dur=45.2, total;dur=120.0
浏览器 DevTools 的 Network 面板中可以直接看到每个环节的耗时,无需额外工具。
3.4 性能预算
| 指标 | 预算值 | 监控方式 |
|---|---|---|
| JS Bundle Size | < 200KB(首屏) | @next/bundle-analyzer |
| LCP | < 2.5s | Sentry / Vercel Analytics |
| TTFB | < 500ms | Server Timing |
| API P99 延迟 | < 1s | APM 工具 |
| 错误率 | < 0.1% | Sentry |
CI 中强制执行预算:构建时检查 Bundle 大小,超出预算则构建失败。
4. 日志管理
4.1 结构化日志
非结构化日志(不推荐):
User john logged in from 192.168.1.1 at 2025-04-12T10:30:00Z
结构化日志(推荐):
{
"level": "info",
"event": "user.login",
"userId": "john",
"ip": "192.168.1.1",
"timestamp": "2025-04-12T10:30:00Z",
"requestId": "req_abc123"
}结构化日志的优势:
- 可以被日志系统索引和搜索
- 可以按字段过滤(
userId = "john") - 可以聚合分析(每小时登录次数)
- 机器可读,支持自动化告警
4.2 日志级别
| 级别 | 用途 | 生产环境 |
|---|---|---|
| error | 影响用户的错误 | ✅ 始终记录 |
| warn | 潜在问题、降级操作 | ✅ 始终记录 |
| info | 关键业务事件(登录、支付) | ✅ 始终记录 |
| debug | 调试信息 | ❌ 生产关闭 |
| trace | 极细粒度的追踪 | ❌ 生产关闭 |
4.3 请求上下文传递
每个请求应该有一个唯一的 requestId,贯穿所有日志——方便从一条错误日志追溯整个请求链路。
请求进入(Middleware)→ 生成 requestId
↓
Server Component → 日志包含 requestId
↓
API 调用 → 日志包含 requestId
↓
数据库查询 → 日志包含 requestId
↓
所有日志都可以通过 requestId 关联起来
4.4 日志平台选型
| 平台 | 特点 | Vercel 集成 | 价格 |
|---|---|---|---|
| Vercel Logs | 零配置、实时 | ✅ 内置 | Vercel Pro |
| Axiom | Vercel 官方推荐 | ✅ 一键集成 | 免费层(500GB/月) |
| BetterStack | 漂亮 UI、告警 | ✅ | 免费层 |
| Datadog Logs | 企业级、全功能 | ⚠️ 需配置 | 付费 |
| Grafana Loki | 开源、自托管 | ❌ | 免费(自托管) |
4.5 Vercel Log Drain
Vercel 的 Log Drain 功能可以将所有日志实时转发到外部平台:
Next.js 应用(Vercel)
├── Serverless Function Logs
├── Edge Function Logs
├── Build Logs
└── Static File Access Logs
↓
Log Drain → 实时转发到
├── Axiom
├── Datadog
├── BetterStack
└── 自定义 HTTP 端点
5. OpenTelemetry
5.1 为什么需要 OpenTelemetry
OpenTelemetry(OTel)是可观测性的开放标准——由 CNCF 维护,厂商无关。
| 问题 | OTel 解决方案 |
|---|---|
| 各厂商数据格式不同 | 统一的数据模型(Span、Metric、Log) |
| 更换监控工具成本高 | 只改 Exporter,不改采集代码 |
| 需要同时用多个工具 | 一份数据发送到多个后端 |
5.2 Next.js 的 OTel 支持
Next.js 内置了 OpenTelemetry 的实验性支持。配置后,Next.js 自动生成:
| 自动生成的 Span | 说明 |
|---|---|
HTTP GET /api/users | API Route 处理 |
next.route /dashboard | 页面路由处理 |
next.render | Server Component 渲染 |
fetch https://api.example.com | 所有 fetch 请求 |
next.middleware | Middleware 执行 |
5.3 OTel 架构
Next.js 应用
├── 自动探针(HTTP、fetch、Next.js 内部)
├── 手动 Span(自定义业务逻辑追踪)
└── OpenTelemetry SDK
↓
OTel Collector(可选,聚合 + 转发)
↓
├── Jaeger(链路追踪可视化)
├── Prometheus(指标存储)
├── Grafana Tempo(链路存储)
├── Datadog(商业 APM)
└── Honeycomb(商业分析)
5.4 手动 Span 的使用场景
自动探针覆盖了 HTTP 和 fetch,但业务逻辑需要手动追踪:
| 场景 | 手动 Span 名称 | 追踪内容 |
|---|---|---|
| 复杂计算 | calculate.pricing | 定价算法耗时 |
| 第三方 API | stripe.create-payment | Stripe 调用耗时和结果 |
| 缓存操作 | cache.get / cache.set | 缓存命中率和耗时 |
| 队列处理 | queue.process-job | 后台任务处理时间 |
| 文件操作 | storage.upload | 文件上传耗时和大小 |
6. 告警策略
6.1 告警金字塔
页面告警(PagerDuty 电话) ← 严重:服务宕机
/ \
紧急告警(Slack + 短信) ← 高:错误率飙升
/ \
警告告警(Slack 通知) ← 中:性能下降
/ \
信息通知(Dashboard) ← 低:趋势变化
6.2 告警规则设计
| 告警 | 条件 | 级别 | 通知渠道 |
|---|---|---|---|
| 服务不可用 | 健康检查连续失败 3 次 | 🔴 严重 | 电话 + Slack |
| 错误率飙升 | 5xx 错误率 > 1%(5 分钟) | 🔴 严重 | Slack + 短信 |
| API 延迟过高 | P99 > 3s(10 分钟) | 🟡 警告 | Slack |
| 内存使用过高 | > 80% | 🟡 警告 | Slack |
| 新错误类型 | 出现从未见过的错误 | 🔵 信息 | Slack |
| 部署完成 | CD 流水线完成 | 🔵 信息 | Slack |
6.3 告警反模式
| 反模式 | 问题 | 解决方案 |
|---|---|---|
| 告警疲劳 | 太多告警导致忽略 | 只告警可行动的事件 |
| 阈值太敏感 | 正常波动触发告警 | 使用动态阈值或百分比变化 |
| 没有分级 | 所有告警同等优先级 | 严格分级(严重/警告/信息) |
| 没有自动恢复通知 | 告警触发后不知道何时恢复 | 配置恢复通知 |
| 没有值班轮换 | 一个人承担所有告警 | PagerDuty 轮换排班 |
6.4 SLO/SLI 驱动的告警
基于 SLO(服务等级目标)设定告警,而非基于绝对阈值:
| SLI | SLO | 告警条件 |
|---|---|---|
| 可用性 | 99.9%(每月停机 < 43 分钟) | 错误预算消耗 > 50% |
| API 延迟 | P99 < 1s | 超出 SLO 的请求 > 0.1% |
| 页面加载 | LCP < 2.5s | P75 LCP > 2.5s |
这种方式的好处:不是"API 慢了就告警",而是"API 慢到影响用户承诺了才告警"。
7. 监控 Dashboard 设计
7.1 四层 Dashboard
| 层级 | 受众 | 关键指标 |
|---|---|---|
| 业务层 | 产品/管理层 | DAU、转化率、收入 |
| 应用层 | 开发团队 | 错误率、P99 延迟、Web Vitals |
| 基础设施层 | 运维/SRE | CPU、内存、磁盘、网络 |
| 依赖层 | 开发团队 | 数据库延迟、第三方 API 状态 |
7.2 Next.js 应用的核心 Dashboard
| 面板 | 指标 |
|---|---|
| 总览 | 请求量、错误率、P50/P95/P99 延迟 |
| Web Vitals | LCP、INP、CLS 的 P75 趋势 |
| API 性能 | 各 Route Handler 的延迟和吞吐量 |
| 错误趋势 | 错误数量、新增错误类型、Top 10 错误 |
| SSR 性能 | Server Component 渲染时间、ISR revalidation |
| 依赖健康 | 数据库连接池、Redis 延迟、第三方 API 状态 |
8. 事故响应
8.1 事故响应流程
告警触发
↓
值班人响应(< 5 分钟)
↓
评估影响范围
├── 全站不可用 → P1(立即全员响应)
├── 核心功能受损 → P2(30 分钟内修复)
├── 非核心功能受损 → P3(4 小时内修复)
└── 性能下降 → P4(下一个工作日修复)
↓
定位原因(Traces + Logs + Metrics)
↓
修复或回滚
↓
事后复盘(Postmortem)
8.2 Postmortem 模板
| 模块 | 内容 |
|---|---|
| 事件摘要 | 什么时间、什么服务、影响了多少用户 |
| 时间线 | 从发现到恢复的完整时间线 |
| 根因分析 | 5 Why 分析法找到根本原因 |
| 影响评估 | 受影响的用户数、收入损失 |
| 修复措施 | 短期修复 + 长期预防 |
| 改进项 | 可执行的改进任务(有负责人和截止日期) |
8.3 混沌工程
主动制造故障来验证监控和告警是否有效:
| 实验 | 方法 | 验证目标 |
|---|---|---|
| API 高延迟 | 注入人工延迟 | 告警是否及时触发 |
| 数据库宕机 | 断开数据库连接 | 应用是否优雅降级 |
| 内存泄漏 | 模拟内存增长 | 是否自动重启 |
| 依赖超时 | 第三方 API 不响应 | 超时和重试是否正常 |
本章小结
- 可观测性三支柱:Logs(发生了什么)+ Metrics(整体状态)+ Traces(为什么出问题)
- Sentry:Next.js 错误监控首选,覆盖 Client + Server + Edge,配置 Tunnel 绕过广告拦截
- 性能监控:Web Vitals(LCP/INP/CLS)+ Server Timing + 性能预算,CI 中强制执行
- 日志管理:结构化日志 + 请求 ID 关联 + Log Drain 到 Axiom/BetterStack
- OpenTelemetry:厂商无关的可观测标准,Next.js 内置支持,自动追踪路由和 fetch
- 告警策略:严格分级、基于 SLO 驱动、避免告警疲劳
- Dashboard:四层设计(业务/应用/基础设施/依赖),核心关注错误率和 P99 延迟
- 事故响应:5 分钟响应 + 分级处理 + Postmortem 持续改进