#DevOps

监控与日志的最佳实践

服务跑着两眼一抹黑,出事靠用户报告?本文讲清可观测性的日志与监控怎么做:结构化日志(JSON + 请求 ID)、日志级别与采样、监控指标(RED/USE 方法)、告警设计(别告警疲劳)、以及"出事时怎么用日志+指标快速定位"的排查思路。

本文要点

  • 主题聚焦:DevOps
  • 适合人群:独立开发者、全栈工程师、AI 应用开发者
  • 核心价值:快速理解背景、实践路径、常见误区和工程建议
作者 @admin·发布于 2026年5月31日·更新于 2026年7月23日

线上出事,最尴尬的不是"挂了",而是"挂了不知道、用户先发现、客服先来问"。你登机器 tail -f 一通乱翻,日志是散的、没 ID、时间对不上,半小时过去还在猜"这笔订单到底走到哪一步崩的"。这种团队缺的不是某个工具,是可观测性的基本章法。

这篇文章不讲 Prometheus 怎么装,只讲日志和监控的实战做法:日志怎么写才机器可查、指标埋哪些才有用、告警怎么设计不变成噪音、出事时按什么顺序排查。

一、没有可观测的服务就是黑盒

一个没做可观测的服务,本质是个黑盒:你能看到它在跑,但看不到它跑得怎么样。CPU、内存这些"机器视角"的指标,回答不了用户视角的问题——"现在下单慢不慢?""支付有没有在报错?""昨天发的版本是不是把某个接口搞挂了?"

于是出事剧本永远一样:用户打电话说"我下单失败了" → 客服找运营 → 运营找开发 → 开发登机器发现日志没记关键信息 → 等定位到原因,半小时过去了,影响已经扩散。

可观测的核心目的就一句话:把"出事靠人发现"变成"出事靠系统发现",把"靠猜定位"变成"靠数据定位"。 日志和监控是这件事的两条腿,缺一条都走不稳。

二、日志:结构化(JSON)+ 请求 ID

第一条铁律:日志要结构化,别再写人看的句子。

下面这两种写法,看着第二种舒服,实际上是灾难:

text
# 反面教材:人看得爽,机器查不动
2026-07-17 10:23:11 INFO User 8821 placed order #55012, amount=199.00, paid by alipay

# 正确做法:结构化 JSON
{"ts":"2026-07-17T10:23:11.123Z","level":"info","msg":"order_placed","user_id":"8821","order_id":"55012","amount":199.00,"pay_channel":"alipay","request_id":"req-a3f9c2"}

第二种看着啰嗦,但它是 JSON——ELK、Loki、Datadog 任何日志后端都能直接按字段索引、按字段过滤。你想查"金额大于 100 且支付渠道是 alipay 的下单日志",一条查询语句的事。第一种?你只能全文搜"order",搜出来几百条慢慢人肉过滤。

第二条铁律:每一次请求必须带一个 request_id(也叫 trace_id),并且贯穿全程。

js
// 入口中间件生成 request_id,塞进 context
app.use((ctx, next) => {
  ctx.request_id = ctx.headers['x-request-id'] || crypto.randomUUID();
  ctx.logger = logger.child({ request_id: ctx.request_id });
  ctx.set('x-request-id', ctx.request_id);
  return next();
});

关键在于这个 ID 要透传到下游:调数据库、调 Redis、调下游服务,都把它带上(HTTP 头 X-Request-ID、gRPC metadata、消息队列消息体里都塞一个)。这样一来,一笔请求经过五个服务,日志后端里按 request_id=req-a3f9c2 一搜,五个服务的完整调用链全出来了。没有 request_id,日志就是一堆散落的纸片;有了它,日志才是一条链。

三、日志级别与采样:别全量记

级别是日志的"优先级标签",乱用级别等于没级别。一套够用的约定:

  • debug:开发排查用,生产默认关。
  • info:关键业务事件(下单、支付、登录),以及正常但重要的路径。
  • warn:异常但可恢复(重试成功、降级、限流触发)。
  • error:真出错了,需要人介入。

最常见的问题是把 warn/error 当垃圾桶——什么"用户输入校验失败""第三方接口偶发 429"全往 error 里塞。结果 error 日志每天几十万条,真正需要看的埋在里面,告警基于 error 计数也跟着天天响,最后全员屏蔽。校验失败是用户问题,不是系统问题,记 info 就够,甚至不记。

第二个坑是全量打日志。一个 QPS 10 万的接口,每个请求都 log 一条 info,一天就是 86 亿条——存储费比服务器还贵。高 QPS 路径要做采样:正常请求按 1% 比例记一条,异常请求 100% 记。这样既保留了"整体长什么样"的统计样本,又不放过任何一次错误。

js
// 健康请求采样,错误全记
if (status >= 500) {
  logger.error({ path, status, duration_ms, ... });  // 100% 记
} else if (Math.random() < 0.01) {
  logger.info({ path, status, duration_ms, ... });    // 1% 采样
}

四、日志该记什么、不该记什么

该记的:关键路径节点(订单创建、状态流转)、错误及上下文(异常类型、入参摘要、下游响应)、慢操作(超过阈值的 DB 查询、外部调用耗时)、关键业务事件(用户注册、支付成功)。

不该记的(这条比上面更重要):

  • 密码、token、身份证、卡号——任何 PII 和密钥。哪怕你觉得是"内部日志",一旦进了日志系统就有泄漏风险,合规和安全事故两头炸。要做脱敏,而不是"相信没人看"。
  • 全量请求体/响应体——尤其是大对象(图片、文件、长文本)。一条日志几 MB,日志系统半天就炸。
  • 每个循环、每一行都 log——这是把日志当 print 调试,生产环境全是噪音。
  • 第三方库的内部细节——除非你在排查它的 bug,默认调到 warn。

一个朴素的标准:这条日志将来会不会被人搜出来用? 如果想不到"什么场景下我会需要它",就别记。

五、监控指标:RED 与 USE 两套方法

埋指标最容易犯的错是"埋一堆没用的"。两套经典方法帮你聚焦:

RED 方法——给服务(尤其是 HTTP/RPC 接口)用,三个指标:

  • Rate(速率):QPS、每秒请求数。
  • Errors(错误):错误请求数、错误率(5xx、业务失败)。
  • Duration(耗时):延迟分布,看 p50/p95/p99,别只看平均。

任何在线服务,这三个指标先埋上,看板上一摆,健康度一目了然。Redis、MySQL、第三方 API 这些"被调用的依赖",也按 RED 套一遍——它们慢了,你也会慢。

USE 方法——给资源(机器、中间件)用,三个指标:

  • Utilization(使用率):CPU%、内存%、带宽%。
  • Saturation(饱和度):队列深度、连接池等待数、磁盘 IO 等待。
  • Errors(错误):硬件错误、丢包、OOM。

USE 关注"资源快撑不住了"。CPU 80% 还能跑,但队列深度持续上涨、连接池开始排队,才是真正的"快炸了"的信号。饱和度往往比使用率更早报警。

一套组合:对每个服务 RED,对每台机器 USE,基本盘就有了。别一上来就埋几十个自定义指标,先覆盖这两套,缺什么再加。

六、告警设计:基于症状、分级、别告警疲劳

告警是监控的"出口",设计不好整个系统就废了。三条原则:

1. 基于症状,不基于原因。告"下单错误率 > 1%"(用户受影响),别告"支付服务 Pod 重启了"(原因之一,但用户可能没感知)。基于原因的告警会导致:Pod 一抖动就响、磁盘满了 80% 就响,实际啥事没有。只告用户能感知到的问题,把"原因"放到看板上自己看。

2. 分级,且分级要严肃:

  • P0/紧急:核心功能挂了,立刻打电话叫人(下单、支付全挂)。
  • P1/警告:局部异常,影响部分用户,工作时间内处理。
  • P2/信息:统计性波动,记录到看板,不主动通知。

关键是 P0 必须真的紧急。一旦 P0 出了事又不严重,大家就开始忽略它——这就是"狼来了"。

3. 别告警疲劳。一个团队每天收 200 条告警,最后一条都不看,等于没监控。治理办法是定期复盘:过去 30 天的每条告警,问一句"它触发时,真的需要人介入吗?"答否的,降级或删掉。好的告警系统,触发频次低、每次都该看。

七、三支柱关联:用 request_id 把日志、指标、链路串起来

指标、日志、链路(Trace)是可观测三支柱,但单独存在都不够。它们的真正威力在于关联——靠的就是前面讲的 request_id / trace_id。

  • 指标告诉你"有问题":支付错误率从 0.1% 涨到 5%。
  • 链路告诉你"问题在哪一段":支付调银行网关那一段花了 8 秒。
  • 日志告诉你"具体为什么":那一次调用网关返回了 timeout,上游银行的接口在 10:23:11 报了 503。

三者的衔接靠 trace_id:指标的 exemplar 关联到一条 trace,trace 的每个 span 又关联到那段服务的日志。现代可观测栈(OpenTelemetry、Datadog、Grafana Tempo + Loki + Prometheus)都默认支持这种跳转。点一下看板上的异常点,直接跳到那条 trace,再点一下跳到那批日志——这套"跳转"体验,是手搓日志系统给不了的,也是值得投入的地方。

八、出事怎么查:告警 → 指标 → trace → 日志

最后讲实战。凌晨告警响了,你应该按这个顺序走,而不是一上来就登机器瞎翻:

  1. 看告警内容:什么指标、什么阈值、什么服务、什么时候开始。先判断影响范围——是全量用户还是某个地区?是核心链路还是边缘功能?
  2. 看指标定位范围:同一时间的 RED 指标,错误率涨的是哪个接口?p99 延迟涨的是哪段时间?关联的依赖(MySQL、Redis、下游)USE 指标有没有异常?这步是缩小包围圈。
  3. 看 trace 定位节点:挑一条慢请求或失败请求的 trace_id,看整条链路哪个 span 耗时异常、哪个 span 报了错。这步是锁定"罪魁那一跳"。
  4. 看日志定位原因:用 trace_id 搜那一段日志,看具体的错误信息、入参、下游返回。这步是回答"为什么"。
  5. 联动时间线:对照发版时间、配置变更、上游依赖告警——很多时候根因不在你这儿,是上游先动的手。

这套流程熟练之后,一个 P0 从触发到定位根因,正常 10 分钟内。相反,如果跳过指标直接翻日志,你会在几亿条日志里迷失;如果只看指标不看 trace,你会知道"支付慢了"但不知道"慢在哪一跳"。每一步都有它不可替代的作用。

小结

  • 没可观测的服务就是黑盒,出事靠用户报告是团队最大的耻辱。可观测的目的不是"装监控",是让问题在影响用户前被系统自己发现。
  • 日志要结构化(JSON)+ 带 request_id,并且 request_id 要透传全链路。人看的句子是给开发看的,机器能查的 JSON 才是给系统用的。
  • 级别要克制,error 不能当垃圾桶;高 QPS 路径要采样,别让日志费比机器费还贵。密码、token、全量请求体绝对不记。
  • 指标用 RED(服务)+ USE(资源)两套方法先覆盖基本盘,别一上来就埋几十个自定义指标。
  • 告警基于症状(用户受影响),分三级,P0 必须真紧急。告警疲劳的治理就是定期删告警——不响的、响了没用的,果断砍掉。
  • 三支柱靠 trace_id 关联,出事按"告警 → 指标 → trace → 日志"的顺序排查,每一步缩小一圈,10 分钟内定位根因。

一句话收尾:可观测性不是堆工具,是让你在凌晨告警响的那一刻,不至于两眼一抹黑。 真正值钱的是"出事时能快速回答:出了什么、影响谁、慢在哪、为什么"——这四个问题答得越快,你的服务就越接近"可信"。投资日志和监控的回报,从来不在风平浪静的日子,而在下一次半夜炸网的时候。

Related

相关推荐