全栈手账 订阅 RSS

监控日志与告警体系

可观测性建设指南:讲解三支柱(指标/日志/链路)、日志分级与结构化、采集与轮转、监控指标体系、前端错误追踪、告警分级与设计、故障排查与复盘,让问题被发现得早、定位得快。

读了 20 分钟 更新于 11 月 3 日

一、可观测性三支柱

支柱 回答什么问题 典型工具
Metrics(指标) 「整体是否健康?」 Prometheus、云监控、宝塔监控
Logs(日志) 「具体发生了什么?」 文件日志、ELK、Loki
Traces(链路) 「慢在哪一环?」 Jaeger、SkyWalking、OpenTelemetry

三者的分工:

文本
指标异常(CPU 90%、错误率 5%)      ↓ 触发告警查日志(哪个接口、什么错误)      ↓ 定位到具体请求看链路(请求经过了哪些服务、各耗时多少)      ↓ 找到瓶颈

小团队不必一次上齐三件套。最低配是「指标 + 结构化日志」,已经能覆盖 80% 的排查场景。


二、日志规范

2.1 日志分级

级别 含义 生产环境 示例
DEBUG 调试细节 ❌ 关闭 变量值、SQL 语句
INFO 正常业务事件 ✅ 开 用户登录、订单创建
WARN 可恢复的异常 ✅ 开 重试成功、降级触发
ERROR 需要处理的错误 ✅ 开 接口 500、第三方调用失败
FATAL 进程无法继续 ✅ 开 数据库连接池耗尽、启动失败

常见错误:全用 INFO,导致真正的错误淹没在噪音里;或 ERROR 满天飞,失去告警意义。

2.2 结构化日志(关键)

javascript
// ❌ 纯文本:无法检索与聚合console.log('用户 1024 下单失败,错误:库存不足')// ✅ 结构化(JSON):可查询、可统计logger.error({  event: 'order.create.failed',  userId: 1024,  orderId: 'ORD20260101001',  reason: 'STOCK_INSUFFICIENT',  requestId: ctx.state.requestId,  duration: 156,})

必须包含的字段:

字段 作用
timestamp 时间(含时区)
level 级别
event 事件名(用点分层级,便于聚合)
requestId 贯穿一次请求的追踪 ID
userId 受影响用户(脱敏后)
duration 耗时(ms)
error.stack 错误堆栈(仅 ERROR)

2.3 requestId:贯穿全链路

javascript
// 中间件:为每个请求生成唯一 IDapp.use(async (ctx, next) => {  ctx.state.requestId = ctx.get('X-Request-Id') || crypto.randomUUID()  ctx.set('X-Request-Id', ctx.state.requestId)  await next()})

价值:用户报障时提供 requestId,就能直接筛出这一次请求的所有相关日志,不必靠时间戳猜。

2.4 该记与不该记

✅ 应记录 ❌ 禁止记录
登录成功/失败(IP、UA、时间) 明文密码
权限变更、敏感操作 完整身份证、银行卡号、CVV
关键状态流转(订单状态变化) 完整 token / session 值
第三方调用(接口、耗时、结果) 数据库连接串、密钥
异常堆栈(含 requestId) 用户完整手机号(应脱敏)
javascript
// 脱敏工具const maskPhone = (p) => p?.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')const maskIdCard = (s) => s?.replace(/(\d{4})\d{10}(\w{4})/, '$1**********$2')

日志一旦写入磁盘、进入日志平台,就很难彻底删除。脱敏要在写入前完成,而不是事后处理。


三、日志采集与轮转

3.1 落盘位置规范

code
/var/log/app/├── app.log            # 全量日志(INFO 及以上)├── app-error.log      # 仅 ERROR└── archive/           # 归档    ├── app-2026-01-01.log.gz    └── ...

3.2 轮转(必须做,否则磁盘会满)

bash
# /etc/logrotate.d/app/var/log/app/*.log {    daily                 # 每天轮转    rotate 14             # 保留 14 天    compress              # gzip 压缩    delaycompress    missingok    notifempty    copytruncate          # 不重启进程也能轮转}

为什么必须轮转:单个日志文件增长到几 GB 后,会拖慢写入、占满磁盘,甚至导致服务崩溃。磁盘满是最常见的线上故障之一。

bash
# 手动检查日志目录占用du -sh /var/log/app/du -h /var/log/app/* | sort -rh | head -10

3.3 应用层日志滚动(不依赖系统 logrotate)

javascript
// winston + 按天/按大小滚动const { createLogger, format, transports } = require('winston')require('winston-daily-rotate-file')const logger = createLogger({  format: format.combine(format.timestamp(), format.json()),  transports: [    new transports.DailyRotateFile({      filename: '/var/log/app/app-%DATE%.log',      datePattern: 'YYYY-MM-DD',      maxSize: '100m',      maxFiles: '14d',      level: 'info',    }),    new transports.DailyRotateFile({      filename: '/var/log/app/app-error-%DATE%.log',      datePattern: 'YYYY-MM-DD',      level: 'error',      maxFiles: '30d',    }),  ],})

容器环境应输出到 stdout,由容器运行时统一收集,不要在容器内写文件。


四、日志分析常用命令

bash
# 实时跟踪tail -f /var/log/app/app.log# 过滤错误并统计 TOP 10grep '"level":"error"' app.log | jq -r '.event' | sort | uniq -c | sort -rn | head -10# 按 requestId 追踪一次请求grep 'a1b2c3d4-xxxx' app.log# 统计各接口 P95 耗时(jq 处理 JSON 日志)jq -r 'select(.duration) | "\(.event) \(.duration)"' app.log \  | awk '{a[$1]=a[$1]" "$2} END {for (k in a) print k, a[k]}'# 统计每分钟错误数grep '"level":"error"' app.log | jq -r '.timestamp' | cut -c1-16 | uniq -c# Nginx 日志:TOP 10 访问 IPawk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10# Nginx 日志:TOP 10 慢接口awk '{print $NF, $7}' /var/log/nginx/access.log | sort -rn | head -10

五、监控指标体系

5.1 四个黄金信号

信号 含义 关键指标
延迟(Latency) 请求处理快慢 P50/P95/P99 响应时间
流量(Traffic) 负载大小 QPS、并发连接数
错误(Errors) 失败比例 5xx 率、业务失败率、超时率
饱和度(Saturation) 资源使用程度 CPU、内存、磁盘、连接池使用率

5.2 分层监控指标

系统层

指标 告警阈值建议
CPU 使用率 持续 5 分钟 > 85%
内存使用率 > 90%,或 Swap 持续使用
磁盘使用率 > 85%(写满会直接导致故障)
负载(load) > CPU 核数 × 2
网络带宽 接近上限的 80%

应用层

指标 告警阈值建议
接口 P95 耗时 > 目标值(如 500ms)
5xx 错误率 5 分钟内 > 1%
进程存活 进程消失立即告警
队列积压 > 阈值或持续增长
数据库连接池 使用率 > 80%

业务层(最容易被忽略,但最有价值)

指标 为什么重要
下单量 / 支付成功率 技术指标正常但业务指标暴跌,说明有隐藏故障
注册转化率 突然归零通常是注册链路坏了
关键接口调用量 掉零意味着上游没流量或路由错了
第三方回调到达量 掉零可能对方出问题或回调地址失效

技术监控与业务监控缺一不可:服务活着但「什么都办不成」,技术指标可能完全正常。

5.3 缓存与数据库专项指标

对象 指标
Redis 内存使用率、命中率、连接数、慢命令、被驱逐 key 数
MySQL 连接数、慢查询数、QPS、主从延迟、InnoDB 缓冲池命中率

缓存命中率掉到 80% 以下就要警惕——可能大量 key 过期或缓存服务异常。


六、前端错误监控

6.1 需要捕获的三类错误

javascript
// 1. JS 运行时错误window.addEventListener('error', (e) => {  report({    type: 'js-error',    message: e.message,    source: e.filename,    line: e.lineno,    stack: e.error?.stack,    url: location.href,  })})// 2. 未处理的 Promise 拒绝window.addEventListener('unhandledrejection', (e) => {  report({ type: 'unhandled-rejection', reason: String(e.reason) })})// 3. 资源加载失败(图片/JS/CSS)window.addEventListener('error', (e) => {  if (e.target?.tagName) {    report({ type: 'resource-error', tag: e.target.tagName, src: e.target.src || e.target.href })  }}, true)   // 捕获阶段才能拿到资源错误

6.2 上报要点

要点 说明
采样 大流量下按比例采样(如 10%),否则上报本身成为负担
去重 相同错误按 message + stack 首行 聚合
带上版本号 区分是哪个版本引入的问题
带上用户环境 UA、屏幕尺寸、网络类型
失败不影响业务 上报请求要 try/catch + 无阻塞,绝不因为上报失败影响页面

6.3 性能数据采集

javascript
// 采集 Core Web Vitalsnew PerformanceObserver((list) => {  for (const entry of list.getEntries()) {    report({ type: 'lcp', value: entry.startTime, url: location.href })  }}).observe({ type: 'largest-contentful-paint', buffered: true })

七、告警体系

7.1 告警分级

级别 定义 通知方式 响应时限
P0 服务不可用、数据丢失 电话 + 短信 + IM 立即
P1 核心功能受损、错误率高 短信 + IM 30 分钟内
P2 非核心功能异常、性能下降 IM 当天
P3 潜在风险、容量预警 邮件/日报 本周

关键:级别定义要写明「什么情况算 P0」,否则所有告警都会变成 P0。

7.2 告警设计三原则

原则一:告警必须可执行

不可执行的告警等于噪音:

❌ 无效告警 ✅ 有效告警
CPU 瞬时 80% CPU 持续 5 分钟 > 85%
出现 1 次错误 5 分钟内 5xx 率 > 1%
磁盘 60% 磁盘 > 85%

原则二:避免告警风暴

yaml
# 重复抑制:同一告警 30 分钟内只发一次# 告警聚合:同一时刻多条告警合并为一条# 依赖收敛:数据库挂了不要再报「接口报错」——根因优先

原则三:分级降噪

  • 生产环境才开高优先级告警
  • 已知问题临时静默(并设置静默到期时间)
  • 定期清理从未触发或永远在触发的告警规则

7.3 一个可用的最小告警集合

小项目只需先配这几条:

告警 条件
服务进程消失 进程数 = 0(持续 1 分钟)
站点不可访问 健康检查连续 3 次失败
磁盘将满 使用率 > 85%
内存吃紧 可用内存 < 10% 或 Swap 持续增长
错误率飙升 5xx 率 5 分钟内 > 1%
证书即将过期 剩余天数 < 15

八、故障排查流程

8.1 标准顺序(由外到内)

文本
1. 用户侧:确认现象(全站还是局部?所有用户还是部分?)2. DNS / 证书:域名解析是否正常、证书是否过期3. 网络 / 网关:能否连通、Nginx 是否在跑4. 应用进程:进程是否存活、端口是否监听5. 应用日志:最近 ERROR 是什么、有无异常堆栈6. 依赖服务:数据库、Redis、第三方接口是否可用7. 资源:CPU / 内存 / 磁盘 / 连接数8. 最近变更:是否有发布、配置修改、依赖升级

8.2 十分钟快速定位清单

bash
# 1. 端口与进程ss -tlnp | grep :80ps aux | grep -E "node|php-fpm|java" | grep -v grep# 2. 本机能否访问curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" http://127.0.0.1/health# 3. 资源free -h; df -h; uptime# 4. 最近错误日志tail -100 /var/log/app/app-error.log# 5. 依赖服务redis-cli pingmysqladmin status -u user -p# 6. 最近变更git log --oneline -5

8.3 「最近改了什么」是第一嫌疑

统计上,线上故障大部分由变更引起。排查时优先确认:

  • 今天是否发布过?
  • 是否改过配置(Nginx、数据库参数、环境变量)?
  • 是否升级过依赖或系统包?
  • 是否改过 DNS / 证书?

九、值班与复盘

9.1 故障复盘模板

markdown
## 故障复盘:{标题}**时间**:2026-01-01 10:00 ~ 10:35(35 分钟)**影响**:{哪些功能、多少用户、是否数据丢失}**级别**:P1### 时间线- 10:00 监控告警触发(错误率 8%)- 10:03 值班确认,开始排查- 10:15 定位到 {根因}- 10:28 执行 {修复动作}- 10:35 指标恢复,确认无残留### 根因{一句话说明直接原因 + 深层原因}### 改进项| 项 | 负责人 | 截止 ||----|-------|------|| 补充 {某场景} 的监控告警 | — | — || 修复 {某代码缺陷} | — | — || 为该问题增加回归测试 | — | — |

复盘的原则:讨论「系统为什么允许这个错误发生」,而不是「谁犯了错」。追责会让人隐瞒问题,改进项才是价值所在。

9.2 改进项要闭环

复盘最常见的失效模式:写了一份很好的报告,改进项没人跟进。

建议:

  • 每个改进项必须有负责人 + 截止时间
  • 下次复盘先检查上次改进项的完成情况
  • 只在「同类问题重复出现」时才升级到流程层面

十、常见问题

现象 原因 处理
磁盘突然满 日志未轮转 配 logrotate,清理归档;同时检查是否有异常大量日志
告警太多没人看 阈值过低、无抑制 收紧阈值 + 加抑制 + 分级
出问题查不到日志 只记 INFO、无 requestId 改结构化日志 + 引入 requestId
日志里发现明文密码 未脱敏 立即改代码,并评估是否需要轮换凭证
前端报错无法复现 无版本号与用户环境 上报时带版本、UA、URL
监控显示正常但用户说不能用 只监控了技术指标 补业务指标(下单量、成功率)
凌晨告警无人响应 无分级与值班安排 定义 P0 用电话,其余用 IM 次日处理

十一、检查清单

日志

  • 日志分级使用正确,生产关闭 DEBUG
  • 结构化 JSON 输出,含 timestamp/level/event/requestId
  • 每个请求有唯一 requestId 并回传响应头
  • 敏感字段已脱敏(密码、手机号、证件号、token)
  • 已配置轮转(按天 + 按大小 + 压缩 + 保留天数)
  • 日志目录占用有监控

监控

  • 已覆盖四个黄金信号(延迟/流量/错误/饱和度)
  • 系统指标:CPU、内存、磁盘、负载
  • 应用指标:接口 P95、错误率、进程存活
  • 依赖指标:数据库连接与慢查询、Redis 命中率
  • 业务指标:核心业务量、成功率
  • HTTPS 证书到期时间有监控

告警

  • 告警分了 P0–P3 且明确定义触发条件
  • 每条告警都可执行(持续时长 + 阈值明确)
  • 有重复抑制与聚合,避免告警风暴
  • P0 能触达本人(电话/短信)

前端

  • JS 错误、Promise 拒绝、资源加载失败均已捕获
  • 上报带版本号、页面 URL、用户环境
  • 有采样策略,上报失败不影响业务
  • Core Web Vitals 有采集

流程

  • 有标准故障排查顺序(由外到内)
  • 故障复盘有模板,改进项有负责人与截止时间
  • 上次复盘的改进项已闭环

十二、总结

可观测性建设的优先级,从高到低是:

  1. 结构化日志 + requestId——成本最低、收益最直接,没有它排查基本靠猜。
  2. 核心指标监控 + 少量精准告警——先配「服务不可用、磁盘满、错误率飙升、证书过期」这几条,比堆几十条无效告警有用得多。
  3. 业务指标监控——技术指标正常但业务量暴跌,是最隐蔽也最危险的故障。
  4. 链路追踪——服务拆分到一定复杂度后才需要。

两条最容易忽视的原则:

  • 告警必须可执行:不能指导行动的告警,是在训练大家忽略告警。
  • 故障优先怀疑变更:先问「最近改了什么」,比从代码里找 bug 快得多。

完

前端、后端、部署、客服都是我一个人。写这里的东西都是真踩过的坑,不是抄来的教程。