TT Lab
开始
学习 学习路径 课程

OTCA — OpenTelemetry 认证助理

七种 instrument、日志记录的十二个字段,以及 schema URL

在 TT Lab 中继续学习

一句话总结

指标 API 的 Instrument 分为 Counter、UpDownCounter、Gauge、Histogram 及其异步(callback)变体,SDK 导出的 Sum 带有 delta 或 cumulative 这种 temporality。日志信号由含十二个字段的 LogRecord 数据模型,以及把现有日志库连接到该模型的桥接 API 构成。schema URL 是一种版本标记,让语义约定即使发生变化,消费者也能解读数据。出处是指标 API、指标数据模型、日志数据模型、遥测 schema这几份规范。

为什么需要它

“请求数”“队列长度”“房间温度”都是数字,性质却不同。请求数只增不减,队列长度时增时减,温度相加则没有意义。Instrument 分成不同类型,就是为了在 API 阶段把这种性质说明白,让 SDK 选出合适的默认聚合。规范概览把这一点表述为“记录原始测量值,聚合方式由最终用户选择”。

日志则是相反方向的问题。所有语言里早已有日志库,不能要求丢掉这些日志、用新 API 重写。因此日志信号从“如何把现有日志转入公共模型并与跟踪关联”出发。schema 也是同类问题:语义约定中的属性名称一旦改变,期待该名称的仪表板和后端就会出问题。

工作原理

七种 Instrument

Instrument 由名称、类型、单位(可选)、说明(可选)定义。综合概念文档与 API 规范的说明,如下所示。

Instrument 同步/异步 性质 记录操作
Counter 同步 单调递增(只增不减) Add
Asynchronous Counter 异步 单调递增,上报观察时刻的累积值 回调
UpDownCounter 同步 可增可减(队列长度) Add
Asynchronous UpDownCounter 异步 可以相加汇总的值(进程堆大小) 回调
Gauge 同步 不能相加的值(噪声水平),变化时记录 Record
Asynchronous Gauge 异步 不能相加的值(房间温度),在观察时刻上报 回调
Histogram 同步 分布有意义的值(请求延迟) Record

同步 Instrument 直接在应用逻辑中调用,其测量值可以与 Context(即当前 span)关联。异步 Instrument 先注册回调,再由 SDK 只在采集(collection)时刻调用,规范中的例子是每 15 秒读取一次传感器温度。异步测量值不与 Context 关联。规范补充说,这里的同步与异步和编程中的 async 模式无关。UpDownCounter 的说明中有一条指引:“如果值是单调递增的,就使用 Counter”。

每个 Instrument 都有默认聚合,可以用 View 修改、忽略特定 Instrument,或选择要上报的属性。概念文档还说明了基数(cardinality)限制:每个指标流的唯一属性组合默认上限为 2000,超出后不会丢弃测量值,而是折叠成一个 otel.metric.overflow=true 属性。合计是对的,但被折叠的测量值会丢失其他属性,所以按该属性过滤的查询结果会偏少。

temporality:delta 与 cumulative

在数据模型中,Sum 由数据点构成,数据点带有 AggregationTemporality(delta 或 cumulative)、单调(monotonic)标志,以及起止时间。delta 在互不重叠的时间窗口内,上报该区间的值;cumulative 上报从起点(通常是进程启动)起的总和。单调 delta 的和不得为负,单调 cumulative 的和不得小于之前的值。

规范列出两者之间的取舍:进程重启检测、速率(rate)计算、推送/拉取方式;并说明 OTLP 两者都支持,必要时可以做 Delta-to-Cumulative 或 Cumulative-to-Delta 转换。直方图也有 temporality,min、max 在 delta 中更有用,delta→cumulative 可以转换,反过来则不行。概念文档还指出它与基数的关系:delta 每个周期清空状态,只统计一个周期内的组合;cumulative 则保留状态,所以一旦触及上限,就会持续溢出直到进程重启。Gauge 是某个时刻的采样值,所以没有 temporality。

日志记录的字段

日志数据模型出发于这样一个要求:“必须能把现有的日志格式无歧义地映射到这个模型”,常用字段作为有名字的顶层字段,其余放入 Attributes。顶层字段共十二个。

字段 含义
Timestamp 事件发生的时间(源头时钟)
ObservedTimestamp 采集系统观察到的时间。由 SDK 创建的日志中与 Timestamp 相同
TraceId / SpanId / TraceFlags W3C Trace Context 的标识符与标志。附加在处理请求期间的日志上
SeverityText 源头的日志级别字符串
SeverityNumber 标准化的严重程度数字
Body 正文
Resource 产生日志的实体
InstrumentationScope 产生日志的范围(库、模块)
Attributes 附加信息
EventName 表示事件种类的名称

转到只支持一个时间的地方时,有 Timestamp 就用它,没有就用 ObservedTimestamp。SeverityNumber 的范围是 1–24:1–4 为 TRACE,5–8 为 DEBUG,9–12 为 INFO,13–16 为 WARN,17–20 为 ERROR,21–24 为 FATAL,0 表示未指定。如规范中的例子所示,17 比 20 是更轻的错误。Events 是这种 LogRecord 的标准化形式,日志语义约定是以 Event 形式定义的。

桥接 API

日志 API在第一句话中就说明了对象:“提供给日志库作者,用来创建日志 appender;appender 通过这个 API,把现有日志库与 OpenTelemetry 日志数据模型连接起来。”结构就是从 LoggerProvider 获取 Logger(name,可选的 version、schema_url、attributes),再由 Logger 来 Emit LogRecord。Emit 接收 Timestamp、ObservedTimestamp、Context、SeverityNumber、SeverityText、Body、Attributes、EventName,不传 Context 时使用当前 Context——这就是日志自动带上 TraceId、SpanId 的原理。Enabled 只是在创建 LogRecord 开销很大时先行询问的优化,并非必须调用。

日志信号概览把采集方式分为两类。一类是由 Collector 读取并解析输出到文件或标准输出的日志(对应用几乎没有改动,但解析困难);另一类是挂上 appender,通过 OTLP 直接发送(结构化好,文件、轮转、解析都不再需要,但需要能接收 OTLP 的目的地)。无论哪种,应用开发者只需要在启动时配置 appender 和 SDK。

schema URL

语义约定在演进,遥测的来源与消费者的变化速度各不相同。为了把三方解耦,schema 规范规定:schema 有版本(MAJOR.MINOR.PATCH),版本之间的转换(例如属性重命名)由 schema 文件明确写出,每个版本都由唯一的 Schema URL 标识。URL 的最后一段路径是版本,前面是 schema 系列。schema 文件一经发布就不可变,所以可以永久缓存。来源(埋点库)会把 schema URL 写入导出的遥测——获取 Tracer、Meter、Logger 时的 schema_url 参数就是它的位置——消费者则查看收到的 schema 版本,必要时转换为自己期望的版本。文档中的例子是:1.2.0 中的 deployment.environment,被期望 1.1.0 的后端转换为 environment 后存储;如果后端不了解 schema,则由 Collector 的 schema 转换处理器代劳。OpenTelemetry 自己的 schema 发布在 /schemas/<version> 路径下。

在现场相遇的样子

一个团队把“当前活跃连接数”记成了 Counter,所以图表只会上升。连接关闭时数值应该减少,所以正确的是 UpDownCounter;而实际上,从连接池读取值的回调更自然,于是改成了 Asynchronous UpDownCounter。

另一个团队说发往 Prometheus 的指标和发往厂商后端的指标数值不同,原因是一边配置成 cumulative,另一边配置成 delta。同样是 Counter,temporality 不同,一个点上的数字所代表的区间就不同。

下一项测验要确认什么

测验会问:把 API 与 SDK 分离的原因、Batching processor 的默认值、同步与异步 Instrument 的区别及与 Context 的关联、delta 与 cumulative 的含义、LogRecord 的 Timestamp 与 ObservedTimestamp、桥接 API 的对象,以及 schema URL 的作用。