跳转至

监控与可观测 — 三大支柱、OpenTelemetry 与追踪瀑布图

这一章讲什么: 健康检查只能告诉你「死了没」,不能告诉你「为什么慢」。 这一章给应用装上「内窥镜」:OpenTelemetry 自动给 Express、HTTP、 Prisma 的每次调用生成追踪,汇进 Grafana,让你在一个瀑布图里 看见一次请求在每一层花的时间。 读完你能分清监控与可观测,并能亲手用追踪定位一个慢请求的根因。

1. 监控 vs 可观测:能预知的和不能预知的

监控回答:「系统在工作吗?」——预先决定量什么、阈值定多少, 越过预设阈值(界限值)就报警。第 16 章的 /healthz、/readyz、CPU 面板、 「错误率超 5% 就告警」全是监控。它擅长已知问题:库挂了→ readiness 失败→报警响→你知道发生了什么1。

可观测回答:「为什么不行了?」——不部署新代码、不加新检查, 光靠应用已产出的数据,就能回答你没预想过的问题。对比一针见血: 监控告诉你「API 响应从 50ms 跳到 800ms,发生在 14:32」; 可观测让你追问「哪个端点慢了、哪条数据库查询该负责」—— 你不需要预测这个故障场景,数据早就在那里等你问1。

监控 可观测
问题 在工作吗? 为什么不在?
手法 预定检查与阈值 探索数据找未知问题
数据 指标、健康检查 日志+指标+追踪协同
适用 已知故障模式 未知/意外故障

两个都要:监控抓得住你预知的,可观测救得了你预想不到的1。

2. 三大支柱与它们的乘法

日志(logs):事件记录。结构化 JSON 日志里那个 traceId 字段, 就是把一条日志接到某次请求追踪上的钩子——相关性不会自动发生, 要_logger_主动从 OTel 上下文里取 traceId 写进每条日志。 原则:足够重建现场,但不泄敏感数据(原始 email、令牌、完整请求体 都不进日志;span 属性同理)2。

指标(metrics,随时间收集的数字测量)。只记数字,所以能存百万级数据点; RED 方法一个框架吃全:Rate(每秒请求数)、Errors(失败率)、 Duration(延迟分布,看 p50/p95/p99)。「只记一个框架就记 RED」3。

追踪(traces):一次请求的完整旅程,拆成一串 span—— 每段 span 有开始时间、时长、父 span(树状结构由此而来)和 属性(HTTP 方法、SQL 语句)。书里那张示例图一眼读完全部:

  [Trace 4bf92f3577b34da6]
   |-- HTTP POST /api/orders (245ms)
   |-- express.middleware (2ms)
   |-- prisma:client:operation - order.create (198ms)
   |     |-- prisma:engine:query - INSERT INTO "Order"... (185ms)
   |-- express.response (3ms)

  总耗时 245ms,其中 185ms 在那条 INSERT 上 —— 慢,也知道慢在哪。

3

乘法在相关性:日志带 traceId → 点进去就是整条追踪; 追踪里看到慢 span → 跳到同时段指标看是孤例还是趋势。 OpenTelemetry 的全部价值,就是一个框架产数据、把三种信号缝起来3。

3. OpenTelemetry:插一次队,换所有后端

OTel 是 CNCF 毕业项目,核心卖点是厂商中立:在它之前, 发给 Jaeger 要用 Jaeger 的库,换 Datadog 就得拆掉重写; 用了 OTel,插桩一次,后端随便换——Grafana、Jaeger、Honeycomb、 Datadog,凡是说 OTLP 协议的都能接。换后端=改配置,不改代码4。

三层架构:应用(装几个 npm 包 + 一个配置文件;auto-instrumentation 自动捕获 HTTP 请求、Express 路由、数据库查询——追踪代码一行不用写) → Collector(独立进程:接收、批处理、过滤、采样、转发;可加可不加, 加了就多一层解耦——后端挂了它缓冲重试,还能不改应用就换目的地) → 后端(本地开发用 grafana/otel-lgtm 一个镜像打包 Loki+Grafana+Tempo+Prometheus+Collector)4。

包清单里值得知道:auto-instrumentations-node 是元包, 检测到 Express/pg/fetch 就自动建 span;Prisma 不在其列—— Prisma 团队自己维护 @prisma/instrumentation,要单独注册5。

4. 落地:一个文件 + 加载顺序的军规

src/instrumentation.ts 三件事:NodeSDK 配好 OTLP 导出器 ( traces/metrics 都走 OTEL_EXPORTER_OTLP_ENDPOINT 环境变量, 代码里不写死 serviceName——配置全走环境变量,同一份代码 跨环境复用);instrumentations 关掉文件系统噪音、滤掉 /healthz 和 /readyz(健康检查的重复 span 只会淹没信号);SIGTERM/SIGINT 时 sdk.shutdown() 把缓冲的遥测冲刷出去(用 process.once, 第二次信号直接快速退出,与第 16 章优雅关停协作)6。

军规只有一条,却是「装了 OTel 却看不到 trace」的头号原因: instrumentation 必须在应用代码之前加载。OTel 靠 import 时打补丁; 如果你的应用先 import 了 Express,补丁没打上,一切白搭。 ESM 项目用 --import ./src/instrumentation.ts + --experimental-loader=@opentelemetry/instrumentation/hook.mjs; CommonJS 项目用 --require 即可。连 dev 脚本(tsx)都要带上 --import,否则「生产有 trace、开发没有」7。

Compose 里加一个 otel-stack 服务(grafana/otel-lgtm,版本钉死); OTLP 端口用 expose 而非 ports(容器网内部通信,不暴露宿主); API 侧两个环境变量 OTEL_EXPORTER_OTLP_ENDPOINT / OTEL_SERVICE_NAME。路由处理器一行没改——这就是 auto-instrumentation 的语义:代码不动,全栈有图8。

5. 主线例子:故障注入——瀑布图上一眼定位 800ms

教程的高光设计:先看健康 trace(52ms,Prisma 查询占大头), 然后人为制造故障——给 users 路由加 await new Promise(r => setTimeout(r, 800)),再打几次请求, 回 Tempo 按时长降序排9:

  正常:   [handler 52ms]──[prisma 40ms]           ← DB 是主耗时
  注入后: [handler 850ms]  ←─────大段空白─────→ [prisma 40ms]

  瀑布图把「时间丢在哪」画成了形状:
  850ms 的处理器 span 里,Prisma 缩在最右还是 40ms ——
  800ms 消失在处理器内部、数据库调用之前。
  没有加一行调试日志,没有重现环境。

9

反过来若 Prisma span 自己变成 750ms,瀑布图立刻指认数据库, span 属性里还躺着那条确切的 SQL。这就是开篇那句话的兑现: 「你不需要预知这个故障,数据早已在那里」9。

跨服务的追踪靠 W3C Trace Context:API 对外发 HTTP 请求时, OTel 自动注入 traceparent 头,接收方取出来接续同一条 trace—— 单机只见 API 内部,加了后台 worker/通知服务,图自动连起来。 浏览器到 API 则要双边配置:前端 @opentelemetry/sdk-trace-web + propagateTraceHeaderCorsUrls 白名单,后端 CORS allowedHeaders 显式放行 traceparent/tracestate/baggage——缺一边,断层10。

6. 生产化:采样、性能、告警与排障表

采样——生产别追踪一切。1000 QPS 全量导出,数据量大到全是噪声。 头部采样(请求开头按比率掷骰子)起步:ParentBasedSampler 包 TraceIdRatioBasedSampler(0.1)——父 span 已采样的强制跟随 (否则下游给已丢弃的上游续命,产生孤儿 trace),根 span 按 10% 来; 环境变量 OTEL_TRACES_SAMPLER=parentbased_traceidratio 同样生效。 进阶是尾部采样(Collector 看完全部 span 再定夺:错误和慢 trace 全留,正常流量低比率)——配置在 Collector,应用代码不用动11。

开销:默认的 BatchSpanProcessor 在内存里缓冲、攒批一起发送,中小流量 基本无感;真出现延迟,先查 Collector 是否可达(导出器阻塞是首因)11。 lgtm 一体化镜像是开发用的;生产把 Loki/Grafana/Tempo/Mimir 拆成独立服务,Collector 单独部署(sidecar 或独立服务)当缓冲12。

自定义 span:tracer.startActiveSpan('order.create', …) 给 「验证+多写+发邮件」这类跨多个调用的业务操作包一层——回调内的子 span 自动认它当父级,finally 里必须 span.end()。属性只放低基数 值(条目数、是/否标记);用户 ID、订单号这类高基数标识会撑爆存储, 还可能在可观测后台泄露隐私——「遥测数据的访问面往往比生产库还宽」13。

告警回到监控那一半:Grafana 统一告警三件套——alert rule (查询+条件+定时评估)、contact point(邮件/Slack/PagerDuty)、 notification policy(按标签路由)。第一个该建的规则:p95 延迟 (IS ABOVE 0.5)或错误率——用户真能感到的指标;for 持续几分钟 再响,免得一次尖刺半夜把人叫醒14。

排障表(装完看不到 trace 的对号入座):没有任何 trace → --import 缺失(最常见);生产有开发无 → dev 脚本漏了 --import; 只有 HTTP span 没有 Prisma → @prisma/instrumentation 没注册; 导出器连接错 → Docker 网内端点写错15。

7. 作者的判断与证据

  • 「监控与可观测都要」+ 两者的「预知/未知」分界,是本章的纲领性判断1。
  • 「RED 方法只记一个就够」是明确的取舍建议3。
  • 「lgtm 一体镜像是开发用的,生产要拆开」是务实的边界声明12。
  • 「遥测数据访问面比生产库宽,按日志的标准对待属性」是 2026 年的 隐私口径,值得全文背诵13。

8. 边界与局限

  • 前端可观测只有最小示例(sdk-trace-web 一段), hydration、 bundle 分析等大话题明说「够单独一章」;
  • 日志支柱只到概念(Node.js 侧的 OTel 日志支持「仍在成熟」, 本章聚焦 traces+metrics);
  • tail-based sampling 只在 Collector 层面提及,无落地配置;
  • 包名、镜像版本(otel-lgtm 0.28.0)、指标名随版本漂移,书中明说 「以 metric browser 为准」——本节为 2026 年中时点。

判断(我们的,不是书里的): 「故障注入看瀑布图」是全书后段的 教学高光之一:它把可观测从口号变成一次亲眼所见的定位—— 先给你看正常的形状(52ms,DB 占大头),再破坏它(800ms 的空白段), 认知落差自己完成了教学。这比十条「可观测的好处」都有效。 如果错,会错在: 单 API + 单库的拓扑下,追踪的价值被低估的 风险很小、被高估的风险存在——真正的追踪红利在微服务和异步队列里; 本书的单体演示是正确起点,但读者容易把「瀑布图」当成追踪的全部, 而忽略了它在跨服务断点上的真正杀手锏(上下文传播一节只是预告)。

9. 可带走的

  1. 监控=「在工作吗」(已知故障、阈值);可观测=「为什么不在」(未知故障、探索数据)。
  2. 三大支柱:日志(事件+traceId 接线)、指标(数字,RED=Rate/Errors/Duration)、追踪(span 树)。
  3. 相关性是乘法:日志里的 traceId → 点进追踪;慢 span → 跳同时段指标。
  4. OTel:插桩一次、后端随便换(OTLP);三层=应用/Collector/后端。
  5. auto-instrumentation 覆盖 Express/HTTP/pg/fetch;Prisma 要单独 @prisma/instrumentation。
  6. 军规:instrumentation 必须最先加载(--import / --require),dev 脚本也不例外。
  7. 健康检查的 span 要滤掉,别淹没信号。
  8. 采样:ParentBased 包 Ratio,10% 起步;错误/慢 trace 全留要 tail-based(Collector 层)。
  9. 自定义 span 给业务操作包层;属性低基数,遥测当日志一样做隐私审查。
  10. 第一个告警:p95 延迟 + for 持续几分钟;排障先查 --import。

10. 原文地图

主题 原书章 原文位置
监控 vs 可观测 Chapter 14: Monitoring and Observability text/167-ch14-chapter-14-monitoring-and-observability.txt:67(搜「whether the system is working」) · text/167-ch14-chapter-14-monitoring-and-observability.txt:117(搜「50ms to 800ms」)
日志与 traceId The Three Pillars of Observability text/167-ch14-chapter-14-monitoring-and-observability.txt:220(搜「does not happen automatically」)
RED 方法 The Three Pillars of Observability text/167-ch14-chapter-14-monitoring-and-observability.txt:310(搜「RED method」)
span 示例图 The Three Pillars of Observability text/167-ch14-chapter-14-monitoring-and-observability.txt:338(搜「4bf92f3577b34da6」)
OTel 厂商中立 Introducing OpenTelemetry text/167-ch14-chapter-14-monitoring-and-observability.txt:400(搜「If you later switched to Datadog, you ri」)
三层架构与 auto-instrumentation Introducing OpenTelemetry text/167-ch14-chapter-14-monitoring-and-observability.txt:410(搜「three layers」) · text/167-ch14-chapter-14-monitoring-and-observability.txt:546(搜「do not need to add tracing code」)
Prisma 单独注册 Introducing OpenTelemetry text/167-ch14-chapter-14-monitoring-and-observability.txt:548(搜「not covered」)
instrumentation.ts 配置 Adding OpenTelemetry to Our Project text/167-ch14-chapter-14-monitoring-and-observability.txt:643(搜「NodeSDK」) · text/167-ch14-chapter-14-monitoring-and-observability.txt:715(搜「ignoreIncomingRequestHook: (req) => {」)
加载顺序军规 Adding OpenTelemetry to Our Project text/167-ch14-chapter-14-monitoring-and-observability.txt:811(搜「before your application code」) · text/167-ch14-chapter-14-monitoring-and-observability.txt:825(搜「single most common」)
故障注入瀑布图 Exploring Traces in Grafana text/167-ch14-chapter-14-monitoring-and-observability.txt:1267(搜「artificial delay」) · text/167-ch14-chapter-14-monitoring-and-observability.txt:1300(搜「800ms sits」)
上下文传播与浏览器侧 Exploring Traces in Grafana text/167-ch14-chapter-14-monitoring-and-observability.txt:1324(搜「traceparent」) · text/167-ch14-chapter-14-monitoring-and-observability.txt:1363(搜「allowedHeaders: [」)
头部采样与 ParentBased Production actions text/167-ch14-chapter-14-monitoring-and-observability.txt:1424(搜「head-based」) · text/167-ch14-chapter-14-monitoring-and-observability.txt:1448(搜「orphan traces」)
尾部采样 Production actions text/167-ch14-chapter-14-monitoring-and-observability.txt:1482(搜「tail-based」)
lgtm 是开发用的 Production actions text/167-ch14-chapter-14-monitoring-and-observability.txt:1556(搜「separate service」)
自定义 span 与低基数 Production actions text/167-ch14-chapter-14-monitoring-and-observability.txt:1730(搜「startActiveSpan」) · text/167-ch14-chapter-14-monitoring-and-observability.txt:1758(搜「high-cardinality」)
告警三件套 Production actions text/167-ch14-chapter-14-monitoring-and-observability.txt:1844(搜「alert」) · text/167-ch14-chapter-14-monitoring-and-observability.txt:1918(搜「p95 latency」)
排障表 Production actions text/167-ch14-chapter-14-monitoring-and-observability.txt:1944(搜「No traces in Tempo」)


  1. 出处:「Chapter 14: Monitoring and Observability」第 59–171 段(text/167-ch14-chapter-14-monitoring-and-observability.txt:67,搜「whether the system is working」;:117,搜「50ms to 800ms」;:119,搜「which specific database query」)。 ↩↩↩↩

  2. 出处:同上第 183–286 段(text/167-ch14-chapter-14-monitoring-and-observability.txt:220,搜「does not happen automatically」;:262,搜「reconstruct what happened」)。 ↩

  3. 出处:同上第 288–382 段(text/167-ch14-chapter-14-monitoring-and-observability.txt:310,搜「RED method」;:338,搜「4bf92f3577b34da6」;:350,搜「185 ms」)。 ↩↩↩↩

  4. 出处:同上第 384–458 段(text/167-ch14-chapter-14-monitoring-and-observability.txt:400,搜「If you later switched to Datadog, you ri」;:404,搜「vendor-agnostic」;:410,搜「three layers」)。 ↩↩

  5. 出处:同上第 460–558 段(text/167-ch14-chapter-14-monitoring-and-observability.txt:524,搜「metapackage」;:548,搜「not covered」)。 ↩

  6. 出处:同上第 560–781 段(text/167-ch14-chapter-14-monitoring-and-observability.txt:643,搜「NodeSDK」;:711,搜「ignoreIncomingRequestHook」;:731,搜「noise」;:767,搜「SIGTERM」)。 ↩

  7. 出处:同上第 783–968 段(text/167-ch14-chapter-14-monitoring-and-observability.txt:811,搜「before your application code」;:825,搜「single most common」;:851,搜「--experimental-loader」)。 ↩

  8. 出处:同上第 970–1177 段(text/167-ch14-chapter-14-monitoring-and-observability.txt:1052,搜「expose」;:1114,搜「did not change our route handlers」)。 ↩

  9. 出处:同上第 1179–1318 段(text/167-ch14-chapter-14-monitoring-and-observability.txt:1267,搜「artificial delay」;:1298,搜「850ms」;:1304,搜「without adding any debug logging」)。 ↩↩↩

  10. 出处:同上第 1320–1400 段(text/167-ch14-chapter-14-monitoring-and-observability.txt:1324,搜「traceparent」;:1360,搜「allowedHeaders」;:1394,搜「propagateTraceHeaderCorsUrls」)。 ↩

  11. 出处:同上第 1402–1552 段(text/167-ch14-chapter-14-monitoring-and-observability.txt:1424,搜「head-based」;:1448,搜「orphan traces」;:1482,搜「tail-based」;:1548,搜「Collector is reachable」)。 ↩↩

  12. 出处:同上第 1554–1606 段(text/167-ch14-chapter-14-monitoring-and-observability.txt:1556,搜「separate service」;:1574,搜「development and testing」)。 ↩↩

  13. 出处:同上第 1705–1790 段(text/167-ch14-chapter-14-monitoring-and-observability.txt:1730,搜「startActiveSpan」;:1758,搜「high-cardinality」;:1768,搜「broader access」)。 ↩↩

  14. 出处:同上第 1826–1928 段(text/167-ch14-chapter-14-monitoring-and-observability.txt:1844,搜「alert」;:1898,搜「IS ABOVE 0.5」;:1912,搜「brief spike」)。 ↩

  15. 出处:同上第 1930–2000 段(text/167-ch14-chapter-14-monitoring-and-observability.txt:1944,搜「No traces in Tempo」)。 ↩