Development
可观测性与埋点
为代码添加埋点,使生产环境中的行为可见且可诊断。在添加日志、指标、链路追踪或告警时使用。
无法观测的代码就是无法运维的代码。埋点应当与功能一同编写,而不是上线后再补充。
使用场景
- 构建任何将在生产环境运行的功能
- 新增服务、端点、后台任务或外部集成
- 某次生产事故诊断耗时过长
- 设置或审查告警规则
流程
1. 在埋点之前先定义“正常工作”的含义
在添加任何埋点之前,先写下一名值班工程师将会提出的 2–4 个问题:
FEATURE: checkout payment retry
QUESTIONS ON-CALL WILL ASK:
1. What fraction of payments succeed on first attempt vs after retry?
2. When a payment fails permanently, why?
3. Is the payment provider slower than usual?
→ Every signal below must help answer one of these.
2. 为每个问题选择正确的信号
| 信号 | 回答的问题 | 成本特征 |
|---|---|---|
| 结构化日志 | “这个具体场景里发生了什么?” | 按事件计 |
| 指标 | “总体上多频繁 / 多快?” | 每序列固定 |
| 链路追踪 | “时间消耗在跨服务的哪一环?” | 按请求计 |
指标告诉你出了问题,链路追踪告诉你问题出在哪里,日志告诉你为什么。
3. 结构化日志
记录事件,而非散文:
// BAD: string interpolation — unqueryable
logger.info(`Payment ${id} failed for user ${userId} after ${n} retries`);
// GOOD: stable event name + structured fields
logger.warn({
event: 'payment_failed',
paymentId: id,
provider: 'stripe',
errorCode: err.code,
attempt: n,
}, 'payment failed');
日志级别:
| 级别 | 含义 | 值班动作 |
|---|---|---|
error |
不变量被破坏;可能需要有人处理 | 调查 |
warn |
已降级但已被处理 | 关注趋势 |
info |
重要业务事件 | 无 |
debug |
诊断细节 | 生产环境关闭 |
关联 ID(Correlation ID)是必需的:
app.use((req, res, next) => {
req.id = req.headers['x-request-id'] ?? crypto.randomUUID();
req.log = logger.child({ requestId: req.id });
res.setHeader('x-request-id', req.id);
next();
});
4. 指标
对于请求驱动的服务,在每个端点上埋设 RED 指标:速率(Rate)、错误(Errors)、时延(Duration)。
import { Histogram } from 'prom-client';
const httpDuration = new Histogram({
name: 'http_request_duration_seconds',
help: 'HTTP request duration',
labelNames: ['method', 'route', 'status_class'],
buckets: [0.05, 0.1, 0.25, 0.5, 1, 2.5, 5],
});
高基数(Cardinality)是失败的根源。 切勿将用户 ID、原始 URL 或错误信息用作标签。
永远不要追踪平均值,始终追踪百分位。
5. 分布式链路追踪
使用 OpenTelemetry——它是厂商中立的标准:
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
const sdk = new NodeSDK({
serviceName: 'checkout-service',
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
6. 告警
针对用户能感受到的症状告警,而非针对原因:
SYMPTOM (page-worthy): CAUSE (dashboard, not a page):
error rate > 1% for 5 min CPU at 85%
p99 latency > 2s one pod restarted
queue age > 10 min disk at 70%
每条告警的规则:
- 它必须是可处置的。 如果应对方式是“忽略它”,那就删掉这条告警。
- 它要链接到一份运维手册(runbook)。
- 它要有由 SLO 论证的阈值和持续时间。
验证
为功能埋点之后:
- 值班问题已写下,且每个信号都对应到其中一个问题
- 所有日志输出均为结构化(JSON)并带有关联 ID
- 任何日志行中都没有密钥或未脱敏的 PII
- 每个新端点都有 RED 指标
- 时延以直方图记录;p95/p99 可查询
- 单个请求可以在链路追踪 UI 中端到端追踪
- 每条新告警都基于症状并带有运维手册链接
- 仅凭遥测数据即可定位在预发环境中诱发的故障