错误监控、日志与可观测性
代码上线不是终点,而是运维的起点。没有监控的生产环境就像盲飞——用户遇到的错误你不知道,性能退化你不知道,服务宕机你不知道。可观测性(Observability)是指通过**指标(Metrics)、日志(Logs)、链路追踪(Traces)**三个维度理解系统运行状态的能力。本章从 Nuxt4 的错误监控、日志体系、性能监控到告警配置,构建完整的可观测性方案。
1. 错误监控:Sentry
1.1 为什么需要错误监控
console.error 只在开发者自己的浏览器控制台可见。生产环境中用户遇到的错误,你完全看不到——除非用户主动反馈(大部分用户不会)。
错误监控工具(Sentry)解决这个问题:自动捕获前端和服务端的错误,上报到中心平台,附带堆栈、浏览器信息、用户操作轨迹。
1.2 Nuxt4 集成 Sentry
npx nuxi module add @sentry/nuxt// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@sentry/nuxt/module'],
sentry: {
sourceMapsUploadOptions: {
org: 'my-org',
project: 'my-nuxt-app',
},
},
sourcemap: {
client: 'hidden', // 生成 sourcemap 但不暴露给用户
},
})// sentry.client.config.ts
import * as Sentry from '@sentry/nuxt'
Sentry.init({
dsn: useRuntimeConfig().public.sentryDsn,
tracesSampleRate: 0.1, // 10% 的请求采集性能数据
replaysSessionSampleRate: 0, // Session Replay 采样率
replaysOnErrorSampleRate: 1, // 错误时 100% 记录回放
})// sentry.server.config.ts
import * as Sentry from '@sentry/nuxt'
Sentry.init({
dsn: useRuntimeConfig().public.sentryDsn,
tracesSampleRate: 0.1,
})1.3 双端错误捕获
Sentry 在 Nuxt4 中自动捕获两种错误:
客户端错误:
- 未捕获的 JavaScript 异常(
throw new Error()) - Promise rejection(
fetch失败、API 超时) - Vue 组件渲染错误(
onErrorCaptured) - 手动上报(
Sentry.captureException(error))
服务端错误:
- Nitro server routes 中的异常
- SSR 渲染错误
- 中间件错误
1.4 Source Maps
生产环境的 JavaScript 是压缩混淆的——错误堆栈类似 a.js:1:2345,毫无用处。Source Maps 把压缩后的位置映射回源代码位置:
生产环境错误堆栈(无 Source Map):
TypeError: Cannot read property 'name' of undefined
at a.js:1:2345
有 Source Map 后:
TypeError: Cannot read property 'name' of undefined
at getUserProfile (composables/useAuth.ts:42:15)
Sentry 在构建时上传 Source Maps,线上错误自动映射回源代码。sourcemap.client: 'hidden' 确保 Source Maps 不暴露给用户(防止源码泄露)。
1.5 上下文信息
错误本身只是冰山一角。要诊断问题,还需要上下文:
// 设置用户信息
Sentry.setUser({
id: user.value?.id,
email: user.value?.email,
})
// 设置额外上下文
Sentry.setContext('video', {
videoId: route.params.id,
format: 'hls',
quality: '1080p',
})
// 面包屑(用户操作轨迹)
Sentry.addBreadcrumb({
category: 'video',
message: 'User clicked play button',
level: 'info',
})面包屑(Breadcrumbs)自动记录用户在触发错误前的操作轨迹——页面导航、点击事件、网络请求。这让你能还原"用户做了什么导致了这个错误"。
2. 结构化日志
2.1 为什么需要结构化日志
console.log('用户登录成功') 在开发时够用,但在生产环境中毫无用处——你无法搜索、过滤、聚合纯文本日志。
结构化日志是 JSON 格式的日志,每条日志包含标准化的字段:
{
"level": "info",
"message": "User login successful",
"timestamp": "2025-01-15T14:30:00Z",
"userId": "u_123",
"method": "oauth",
"provider": "google",
"duration": 342,
"requestId": "req_abc123"
}结构化日志可以被日志平台(Elasticsearch、Loki、Datadog)索引和查询:
- "查找所有 Google OAuth 登录失败的记录"
- "查找响应时间 > 5s 的 API 请求"
- "查找用户 u_123 的所有操作"
2.2 Nitro 日志配置
Nitro 使用 consola 作为日志库,支持日志分级和格式化:
// server/utils/logger.ts
import { consola } from 'consola'
export const logger = consola.withTag('app')
// 使用
logger.info('Server started', { port: 3000 })
logger.warn('Slow query detected', { query: 'SELECT...', duration: 2500 })
logger.error('Database connection failed', { host: 'db.example.com' })生产环境推荐使用 JSON 格式输出,便于日志平台解析:
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
logLevel: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
},
})2.3 请求级日志
为每个请求生成唯一 ID,所有日志携带该 ID——可以追踪一个请求的完整链路:
// server/middleware/request-id.ts
import { randomUUID } from 'crypto'
export default defineEventHandler((event) => {
const requestId = getHeader(event, 'x-request-id') || randomUUID()
event.context.requestId = requestId
setHeader(event, 'x-request-id', requestId)
})// server/api/videos.get.ts
export default defineEventHandler((event) => {
const requestId = event.context.requestId
logger.info('Fetching videos', { requestId, userId: event.context.userId })
// ...
})3. 日志分级策略
3.1 日志级别
| 级别 | 用途 | 生产环境 |
|---|---|---|
| fatal | 系统无法继续运行 | ✅ 必须记录 |
| error | 业务逻辑错误,需要处理 | ✅ 必须记录 |
| warn | 异常但不影响主流程 | ✅ 建议记录 |
| info | 关键业务事件 | ✅ 选择性记录 |
| debug | 调试信息 | ❌ 生产关闭 |
| trace | 极详细的追踪 | ❌ 仅开发使用 |
3.2 什么该记录
应该记录(info 级别):
- 用户认证事件(登录/登出/注册)
- 关键业务操作(下单、支付、视频上传)
- 外部服务调用(第三方 API、支付网关)
- 系统启动和配置
应该记录(error 级别):
- 未预期的异常
- 外部服务调用失败
- 数据一致性问题
不应该记录:
- 敏感信息(密码、Token、信用卡号)
- 大量重复的心跳日志
- 每个请求的详细参数(info 级别太多日志会产生性能和成本问题)
3.3 日志存储方案
| 方案 | 特点 | 成本 | 适用规模 |
|---|---|---|---|
| 文件 + logrotate | 最简单,本地存储 | 低 | 单服务器 |
| Loki + Grafana | 开源,轻量级 | 低 | 中小规模 |
| Elasticsearch + Kibana | 功能最强,全文搜索 | 高 | 大规模 |
| Datadog / New Relic | SaaS,开箱即用 | 很高 | 预算充足 |
| Cloudflare Logpush | 边缘日志,实时推送 | 按量 | Cloudflare 用户 |
4. 性能监控
4.1 Core Web Vitals
Google 的 Core Web Vitals 是衡量用户体验的核心指标:
| 指标 | 含义 | 好的阈值 |
|---|---|---|
| LCP | 最大内容绘制时间 | < 2.5s |
| FID / INP | 首次/持续交互延迟 | < 200ms |
| CLS | 累积布局偏移 | < 0.1 |
| TTFB | 首字节时间 | < 800ms |
4.2 Lighthouse CI
在 CI 中自动运行 Lighthouse 审计,防止性能退化:
# .github/workflows/lighthouse.yml
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm build
- name: Lighthouse CI
uses: treosh/lighthouse-ci-action@v12
with:
configPath: './lighthouserc.json'
uploadArtifacts: true// lighthouserc.json
{
"ci": {
"assert": {
"assertions": {
"categories:performance": ["error", { "minScore": 0.9 }],
"categories:accessibility": ["warn", { "minScore": 0.9 }],
"categories:seo": ["error", { "minScore": 0.9 }],
"first-contentful-paint": ["error", { "maxNumericValue": 2000 }],
"largest-contentful-paint": ["error", { "maxNumericValue": 2500 }]
}
}
}
}当 Lighthouse 分数低于阈值时,CI 直接失败——性能也是质量门禁的一部分。
4.3 真实用户监控(RUM)
Lighthouse 是合成测试(在标准化环境中测量),真实用户监控(RUM)采集真实用户的性能数据:
// plugins/web-vitals.client.ts
import { onCLS, onFID, onLCP, onTTFB } from 'web-vitals'
export default defineNuxtPlugin(() => {
function reportMetric(metric: { name: string; value: number }) {
// 上报到分析平台
navigator.sendBeacon('/api/metrics', JSON.stringify({
name: metric.name,
value: metric.value,
url: window.location.pathname,
userAgent: navigator.userAgent,
}))
}
onCLS(reportMetric)
onFID(reportMetric)
onLCP(reportMetric)
onTTFB(reportMetric)
})RUM 的价值在于看到真实用户的分布——P50 用户体验可能很好,但 P95 用户(网络差、设备旧)可能很差。
5. 告警配置
5.1 告警原则
- 可操作:每条告警都应该有明确的处理步骤
- 不漏报:关键错误必须触发告警
- 不误报:频繁的无意义告警会导致"告警疲劳"——人们开始忽略所有告警
- 分级:P0(立即处理)、P1(24 小时内处理)、P2(下个迭代处理)
5.2 Sentry 告警规则
规则 1:错误率飙升
条件:5 分钟内同一错误出现 > 10 次
操作:发送 Slack 通知 + PagerDuty
规则 2:新错误
条件:出现之前未见过的错误类型
操作:发送 Slack 通知
规则 3:关键业务错误
条件:标签 business.critical = true
操作:发送 Slack + 邮件 + PagerDuty
5.3 告警渠道
| 渠道 | 适用场景 | 响应时效 |
|---|---|---|
| Slack/飞书 | 团队协作通知 | 工作时间内 |
| 邮件 | 记录和追踪 | 24 小时 |
| PagerDuty/OpsGenie | 紧急事件升级 | 立即 |
| 短信/电话 | P0 级别故障 | 立即 |
5.4 On-Call 轮值
对于需要 24/7 可用的服务,建立 On-Call 轮值制度:
- 每周一位工程师值班,负责处理告警
- 非工作时间通过电话告警
- 如果值班人员 10 分钟未响应,自动升级到下一位
- 事后复盘每次告警,优化告警规则
6. 可观测性全景
6.1 三支柱
| 支柱 | 回答的问题 | 工具 |
|---|---|---|
| Metrics | "系统现在怎么样?" | Prometheus + Grafana |
| Logs | "发生了什么?" | Loki / Elasticsearch |
| Traces | "请求经过了哪些服务?" | Sentry Tracing / Jaeger |
6.2 Nuxt4 可观测性栈推荐
| 层级 | 工具 | 用途 |
|---|---|---|
| 错误监控 | Sentry | 前端 + 服务端错误捕获 |
| 性能监控 | Sentry Performance / web-vitals | Core Web Vitals + API 延迟 |
| 日志 | consola + Loki | 结构化日志收集 |
| CI 性能 | Lighthouse CI | 防止性能退化 |
| 告警 | Sentry Alerts + Slack | 错误率 + 性能阈值告警 |
本章小结
- Sentry 双端集成:客户端自动捕获 JS 异常和 Vue 渲染错误,服务端捕获 Nitro 和 SSR 错误,Source Maps 映射回源代码
- 结构化日志:JSON 格式 + 请求 ID 串联链路,按级别过滤(生产环境 info 及以上)
- 日志纪律:记录关键业务事件和错误,不记录敏感信息和大量重复日志
- Lighthouse CI:在 CI 中自动审计性能,低于阈值阻止合并
- RUM:
web-vitals采集真实用户数据,关注 P95 而非平均值 - 告警:可操作 + 不漏报 + 不误报,分级处理,On-Call 轮值