SuperAPI AI 工具集

observability-and-instrumentation

部署与运维

查看项目

更新于 10-15 11:33

技能介绍

用途

无法观测的代码无法运维。可观测性指仅凭代码发出的遥测数据,从外部回答“系统正在做什么以及为什么”。埋点不是发布后的附加项,而应与功能、测试同步编写:若功能上线时没有遥测,第一个用户报障就会变成考古,而非一次查询。

何时使用

  • 构建任何将在生产环境运行的功能
  • 新增服务、端点、后台任务或外部集成
  • 生产事故排查耗时过长(“我们无法判断发生了什么”)
  • 设置或审查告警规则
  • 审查引入 I/O、重试、队列或跨服务调用的 PR

不适用于:

  • 诊断当前正在发生的故障——应使用调试与错误恢复技能(可观测性是让该技能下次更快的工具)
  • 对已测量的慢速进行性能分析与优化
  • 发布日监控检查清单和回滚触发条件——本技能覆盖的是为其提供数据的埋点

流程

1. 埋点前先定义“正常”

没有问题的遥测就是噪声。添加任何埋点前,先写下值班工程师会就本功能提出的2到4个问题。示例问题涉及支付重试成功率、永久失败原因、支付提供商是否变慢。若无法列出问题,说明还没准备好埋点,结果只会是记下一切却学不到任何东西。

2. 为每个问题选择正确的信号

  • 结构化日志:回答“这个具体案例发生了什么”,按事件记录,随流量增长;
  • 指标:回答“总体频率或速度是多少”,每个序列固定,查询成本低;
  • 追踪:回答“跨服务的时间花在哪里”,按请求记录,通常采样。

经验规则:指标告诉你出了问题,追踪告诉你哪里出问题,日志告诉你为什么。

3. 结构化日志

记录事件而非散文。每条日志都是带有稳定事件名和机器可读字段的 JSON 对象,避免字符串插值。

日志级别应一致使用:error 表示不变量被破坏、可能需人工处理;warn 表示降级但已处理(重试成功、使用了回退);info 表示重要业务事件(订单已下、任务完成);debug 为诊断细节,生产默认关闭。

关联 ID 是强制要求。在系统边界生成或接受请求 ID,并附加到每条日志、span 和出站调用上,否则无法从交错的日志中重建单个请求。示例中展示了 Express 中间件为每个请求创建子 logger 并向下游传播 ID 的做法。

当多个入口点写入同一日志时,需命名入口点。关联 ID 标识一次运行,但不说明由哪条代码路径启动。同一个任务经由调度器、重放端点或手动 CLI 运行时,在一个日志接收端会产生可互换的行,归因只能靠排除法,且依赖外部记录仍然存在。应在运行开始处、关联 ID 旁标记入口点,并以与关联 ID 相同的方式传播两者。示例提供了统一的 runLog 辅助函数,字段名用 entryPoint 而非 source(ECS 保留 source.* 给网络字段)。

绝不要记录密钥、令牌、密码或完整 PII。这是安全加固技能的硬性规则,遥测管道是典型的数据泄露路径。应使用字段允许列表,不要记录整个请求体。

4. 指标

对于请求驱动的服务,在每个端点和每个外部依赖上埋 RED 指标:Rate(请求/秒)、Errors(失败率)、Duration(延迟直方图,而非平均值)。对于资源(队列、连接池、主机),使用 USE:Utilization、Saturation、Errors。

与追踪一样,厂商中立路径是 OpenTelemetry 指标 API(同一 SDK 和上下文)。示例使用 Prometheus 的 prom-client,仅是一种常见后端选择,不是唯一选择;RED/USE 和基数规则两者相同。

基数才是失败模式。每个唯一标签组合都是独立的时间序列。标签必须来自小而固定的集合(路由模板、状态类别、提供商名称)。绝不要用用户 ID、原始 URL、错误消息或其他无界值作为标签,这些属于日志和追踪。

平均值永远不要跟踪,百分位数始终要跟踪:平均值会掩盖那1%处境糟糕的用户。使用直方图并读取 p50/p95/p99。

5. 分布式追踪

使用 OpenTelemetry,它是厂商中立标准,自动埋点能以近乎零代码覆盖 HTTP、gRPC 和常见数据库客户端。示例展示了 tracing.ts 需在其他任何模块之前导入。仅在有意义的内部分工作单元(如 applyDiscounts、chargeProvider)添加手动 span,并附加值班人员会用来筛选的属性。在每个异步边界传播上下文(HTTP 头、队列消息元数据),否则追踪会在断层处中断。默认按低比例进行头部采样;若后端支持尾部采样,保留100%的错误。

6. 告警

对用户可感知的症状告警,而不是原因。症状类(可触发寻呼)示例:错误率5分钟内超过1%、p99延迟超过2秒、队列年龄超过10分钟。原因类(应放仪表盘而非寻呼)示例:CPU达85%、一个 Pod 重启、磁盘使用70%。

每条告警的规则:必须可操作,若响应是“忽略,它会自愈”,则删除该告警;必须链接到运行手册,哪怕只有三行:含义、首先运行的查询、升级路径;阈值和持续时间必须由 SLO 或历史数据论证,而不是猜测;仅使用两种严重级别:page(面向用户,立即行动)和 ticket(降级,本周处理)。第三层会变成噪声,训练人们忽略一切。

编写运行手册

每条告警都链接到运行手册,存于 docs/runbooks/,以告警命名。运行手册要回答三个问题而无需读者思考:发生了什么、先检查什么、若未解决该联系谁。示例给出了最小可行运行手册三行格式,并说明何时扩展:仅当首次检查不足以决定时才添加步骤。运行手册需保持最新,在关闭每次使用过它的事故时一并更新;过时的手册会构建虚假信心。

7. 验证遥测本身

埋点也是代码,可能出错。完成前应触发路径并查看实际输出:在预发环境强制出错,按 requestId 在日志中找到它,确认字段是结构化的;发送测试流量,确认指标序列以预期标签和合理值出现;在追踪 UI 中跨服务跟随一个请求,确认没有断开的 span;将每条新告警触发一次(临时降低阈值),确认到达正确渠道且运行手册链接有效。

常见合理化借口

技能列出了常见借口及其现实:如“等它能工作后再加日志”——“之后”会变成“第一次事故之后”,那是发现盲区最昂贵的时刻;应边构建边埋点。“更多日志=更好可观测性”——非结构化噪声让事故更慢,三个可查询事件胜过三百行散文。“console.log 暂时够用”——非结构化输出无法过滤、关联或告警,结构化 logger 一次性多花五分钟。等等。

红旗信号

  • 含有重试、队列或外部调用且零新增遥测的功能 PR
  • 用字符串插值而非结构化字段构建的日志行
  • 没有关联/请求 ID,每条日志都是孤儿
  • 一个日志流由调度器、Webhook 和手动运行共同写入,却没有字段说明哪次产生了该行
  • 指标标签使用用户 ID、原始 URL 或错误消息文本(基数炸弹)
  • 延迟以平均值跟踪且没有百分位数
  • 每天触发并被确认却不采取行动的告警
  • 对原因(CPU、内存)告警并寻呼人类,而面向用户的错误率未被监控
  • 日志中出现密钥、令牌或完整请求体
  • 以“在我机器上能用”作为生产功能健康的唯一证据

验证

埋点功能后,确认:本功能的值班问题已写下,且每个信号映射到其中一个;所有日志输出结构化(JSON),含稳定事件名和每行的关联 ID;每个由多个入口点写入的日志接收端都携带入口点字段,在运行开始处设置并与关联 ID 一起传播,而非在下游推断;任何日志行中无密钥、令牌或未脱敏 PII;每个新端点和每个外部依赖都有 RED 指标,标签集有界;延迟为直方图,p95/p99 可查询;单个请求可在追踪 UI 中端到端跟随且无断开 span;每条新告警基于症状,有运行手册链接,并已测试触发过一次;预发环境中诱导的故障仅通过遥测即可定位,无需读源码。

快速列表和发布前埋点门禁见 references/observability-checklist.md。