三支柱与 OTLP 详解:Metrics、Logs、Traces 的统一
基于 OpenTelemetry 1.x(2026-05 CNCF 毕业) · 核于 2026-08
速查
- 三支柱数据模型:①Metrics——时序数值,由
MeterAPI 创建(Counter 递增计数、Histogram 分布、Gauge 瞬时值、UpDownCounter 可增可减);②Logs——结构化事件,由LoggerAPI 发射(带 severity、body、attributes、trace_id 关联);③Traces——span 树,由TracerAPI 创建(span 有 trace_id/span_id/parent_span_id/name/起止时间/attributes/status)。 - 三支柱共享 trace_id:这是 OTel 的精髓。一个请求的 trace_id 会同时关联到它的 trace span、metric 数据点(exemplar)、log 记录——从 metric 异常跳到 trace、从 trace span 跳到 log,形成完整排障链路。
- OTLP(OpenTelemetry Protocol):统一传输协议。①数据模型用 protobuf 定义(Metrics/Logs/Traces 各有规范结构);②传输支持 OTLP/gRPC(默认)和 OTLP/HTTP(POST protobuf 或 JSON);③后端无关——后端实现 OTLP 接收端即可消费,这是终结厂商锁定的根基。
- Instrumentation 两种方式:①自动埋点——加载 instrumentation library 自动拦截主流框架(HTTP/gRPC/Express/Kafka/Redis/DB 驱动),零代码改动;②手动埋点——用 OTel API 显式创建业务 span/metric/log。生产实践:自动埋点覆盖通用调用 + 手动埋点补充业务语义。
- API vs SDK:
@opentelemetry/api只定义接口(不产生数据,零依赖,业务代码只依赖它);@opentelemetry/sdk-node等是实现(真正产生数据、配置导出)。库代码只用 API,应用入口配 SDK——这样库不绑死具体实现。 - Resource(资源):描述数据来源的属性集(host.name、service.name、service.version、deployment.environment、cloud.provider),附加在所有遥测数据上,标识「哪个服务哪个环境产生的」。在 SDK 启动时配置一次,全局生效。
- Context Propagation(上下文传播):trace_id/span_id 跨服务跨进程传递。W3C Trace Context(
traceparentheader:00-<trace-id>-<span-id>-<flags>)是标准,OTel 默认用它。A 调 B 时 OTel 自动把 trace_id 注入请求 header,B 的 OTel 自动提取——B 的 span 挂到 A 的 trace 树上。 - Sampler(采样器):决定哪些 trace/span 被记录。高频服务全量记录开销大,用采样(如 ParentBased/TraceIDRatioBased/AlwaysOn/AlwaysOff)控制数据量。Metrics 通常不采样(全量),Traces 常采样(如 10%)。
- Exemplar(范例):Metrics 的数据点可以关联一个 trace——当指标出现高延迟异常值时,exemplar 指向那个高延迟请求的 trace,从 metric 直接跳到 trace 排障。
一、三支柱的数据模型
Metrics(指标):时序数值
OTel 的 Metrics API(Meter)提供四种仪器:
| 仪器 | 行为 | 典型用途 |
|---|---|---|
| Counter | 只增不减(累加) | 请求总数、错误总数、字节发送量 |
| UpDownCounter | 可增可减 | 队列长度、活跃连接数 |
| Histogram | 分布(值 + 桶) | 延迟分布(P50/P90/P99)、响应大小 |
| Gauge | 瞬时值(可涨可跌) | CPU 使用率、温度、内存 |
javascript
const meter = metrics.getMeter('order-service');
const requestCounter = meter.createCounter('orders.total', { description: '订单总数' });
const latencyHistogram = meter.createHistogram('orders.duration', { unit: 'ms' });
requestCounter.add(1, { status: 'success', region: 'cn' });
latencyHistogram.record(150, { endpoint: '/api/order' });- 属性(attributes/labels):每个数据点带多维标签,可切片聚合(按 status/region/endpoint)。
- 与 Prometheus 的关系:OTel 的 Histogram 概念和 Prometheus 一致(桶 + 总和 + 计数),OTel Collector 可把 OTel metric 转成 Prometheus exposition format。
Logs(日志):结构化事件
OTel 的 Logs API(Logger)发射结构化日志记录,带:severity(级别)、body(正文)、attributes(结构化字段)、trace_id/span_id(关联追踪):
javascript
const logger = logs.getLogger('order-service');
logger.emit({
severity: SeverityNumber.ERROR,
body: '订单处理失败',
attributes: { orderId: 123, reason: 'insufficient_stock' },
// trace_id 自动从当前 context 关联(如有)
});- 日志关联 trace:OTel 的精髓——日志自动带上当前请求的 trace_id,在 trace 视图里能看到对应日志,反之亦然。
- 成熟度:Logs 信号在多数语言 SDK 2024-2025 陆续 GA(Metrics/Traces 更早 GA)。
Traces(追踪):span 树
OTel 的 Traces API(Tracer)创建 span——一个操作单元,有 trace_id(整条链路)、span_id(本段)、parent_span_id(父段)、name、起止时间、attributes、status、events:
javascript
const tracer = trace.getTracer('order-service');
const span = tracer.startSpan('process_order', { attributes: { orderId: 123 } });
try {
const childSpan = tracer.startSpan('charge_payment'); // 子 span
await chargePayment(order);
childSpan.end();
} finally {
span.end();
}- span 树:一个请求的多个服务/操作形成树(根 span 是入口),通过 parent_span_id 串联。
- attributes:span 的结构化属性(如 orderId、http.method、db.statement),用于检索过滤。
- events:span 内的离散事件(如「缓存未命中」「重试第 2 次」)。
二、三支柱联动:trace_id 贯穿
OTel 的精髓是三支柱共享 trace_id,形成排障闭环:
Metrics 告警:orders.duration P99 突增到 2000ms
│
│ exemplar 指向慢请求的 trace_id
▼
Traces 定位:该请求的 span 树 → 发现 DB 查询 span 耗时 1800ms
│
│ span 的 trace_id 关联 logs
▼
Logs 细节:DB 查询的日志 → 「慢查询,缺索引」
│
▼
根因:缺索引 → 加索引修复没有 trace_id 关联,三支柱各自孤立——看 metric 知道「有问题」、看 trace 知道「卡在哪」、看 log 知道「为什么」,三者串联才能高效排障。
三、OTLP:统一传输协议
OTLP 让三支柱用同一协议传输,是后端无关的根基:
| 维度 | 说明 |
|---|---|
| 数据模型 | protobuf 定义 Metrics/Logs/Traces 的规范结构 |
| 传输 | OTLP/gRPC(默认,TCP 长连接)或 OTLP/HTTP(POST protobuf 或 JSON) |
| 端口 | gRPC 默认 4317,HTTP 默认 4318 |
| 后端接入 | 后端实现 OTLP receiver 即可消费(或用 Collector 转码) |
- 为什么统一协议重要:以前 Metrics 走 Prometheus pull、Logs 走 Syslog/Fluentd、Traces 走 Jaeger thrift——三套。OTLP 一套搞定,应用只发 OTLP,Collector 扇出到任意后端。
- OTLP/JSON:HTTP + JSON 格式,方便调试(curl 能直接发)和不支持 gRPC 的环境(如某些 Serverless)。
四、Instrumentation:自动 vs 手动
自动埋点(Automatic Instrumentation)
加载 instrumentation library,零代码改动接入主流框架:
javascript
// Node.js 入口注册 instrumentation
import { NodeSDK } from '@opentelemetry/sdk-node';
import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
import { PgInstrumentation } from '@opentelemetry/instrumentation-pg';
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter(),
instrumentations: [
new HttpInstrumentation(), // 自动埋 HTTP 调用
new ExpressInstrumentation(), // 自动埋 Express 路由
new PgInstrumentation(), // 自动埋 PostgreSQL 查询
],
});
sdk.start();- 覆盖范围:HTTP/gRPC 服务器与客户端、Web 框架(Express/Koa/Spring/Django)、DB 驱动(pg/mysql/redis)、消息队列(Kafka/RabbitMQ)、云 SDK。
- 优势:零业务代码改动,快速接入,覆盖通用调用链。
- 局限:只能埋「框架级」调用,业务语义(如「处理支付」)要手动。
手动埋点(Manual Instrumentation)
用 OTel API 在业务代码里显式创建 span/metric/log:
javascript
// 业务 span:自动埋点覆盖不到的语义
const span = tracer.startSpan('business.validate_order');
span.setAttribute('order.amount', order.amount);
try {
validateOrder(order);
} catch (e) {
span.recordException(e);
span.setStatus({ code: 2, message: e.message });
throw e;
} finally {
span.end();
}- 何时手动:业务关键操作(下单/支付/风控)、自动埋点覆盖不到的逻辑、需要业务属性(orderId)的 span。
- 生产实践:自动埋点打基础(覆盖通用调用)+ 手动埋点补业务语义(关键流程的 span/metric)。
五、API vs SDK:库与应用的分工
| 角色 | 依赖 | 职责 |
|---|---|---|
| 库代码(如 npm 包) | 只依赖 @opentelemetry/api | 用 API 创建 span/metric(接口,不产生数据) |
| 应用入口 | 配置 SDK(如 @opentelemetry/sdk-node) | 注册 exporter、配 resource、加载 instrumentation(真正产生+导出数据) |
- 为什么这么分:库只依赖 API(零依赖、轻量),不绑死具体 SDK 实现。应用在入口决定用哪个 SDK、发哪个后端——库的埋点代码在 SDK 注入后自动生效。这就是「库不绑后端」的设计。
六、Resource 与 Context 传播
- Resource(资源):在 SDK 启动时配置,标识数据来源:
javascript
const resource = new Resource({
'service.name': 'order-service',
'service.version': '1.2.0',
'deployment.environment': 'production',
'host.name': os.hostname(),
});这个 resource 附加在该应用产生的所有 metrics/logs/traces 上——后端能按 service/environment 切片聚合。
- Context Propagation(上下文传播):trace_id 跨服务传递。OTel 默认用 W3C Trace Context(
traceparentheader):
A 服务调 B 服务:
A 的 OTel 自动在请求 header 注入:
traceparent: 00-<trace-id>-<span-id>-01
B 的 OTel 自动从 header 提取 trace-id
B 创建的 span 的 parent_span_id = A 传来的 span-id
→ B 的 span 挂到 A 的 trace 树上,形成完整链路- W3C 标准:
traceparent是 W3C 推荐标准(2020),主流框架/语言都支持,保证跨语言跨服务传播追踪上下文。 - Baggage:另一个 W3C 标准,用于跨服务传播业务键值对(如 tenant-id),不进 trace 但随请求流转。
下一步
三支柱与 OTLP 讲完后,下一步是Collector 与生态采用——Collector 的接收-处理-导出架构、与 Prometheus/Jaeger 的关系、2026 CNCF 毕业的意义。