OTTL Metric Context 完整解析:在 OpenTelemetry Collector 中以 OTTL 读写 pdata 指标数据
2026/9/19 6:11:26 网站建设 项目流程

OTTL Metric Context 完整解析:在 OpenTelemetry Collector 中以 OTTL 读写 pdata 指标数据

【免费下载链接】tempoGrafana Tempo is a high volume, minimal dependency distributed tracing backend.项目地址: https://gitcode.com/GitHub_Trending/tempo1/tempo

导读

本文围绕 OpenTelemetry Collector Contrib 中的Metric Context(ottlmetric)展开,讲解如何在 OTTL(OpenTelemetry Transformation Language)语句与条件中访问、校验和改写单条 OTLP 指标(Metric)及其关联的 Resource、Instrumentation Scope 与缓存。文章覆盖 Metric Context 的全部受支持 Path、枚举符号,并结合本仓库 vendor 目录下的pkg/ottl/contexts/ottlmetric源码与 filter/transform 处理器的真实调用链,帮助读者在配置 filter processor、transform processor 等组件时,准确编写可用的指标级 OTTL 表达式,并理解其底层实现。

一、Metric Context 是什么

Metric Context 是 OTTL 针对pdata Metric的一种 Context 实现,pdata(pmetric)是 OpenTelemetry Collector 内部对 OTLP 指标(opentelemetry/proto/metrics/v1/metrics.proto)的内存表示。它适用于“与单条 OTLP 指标交互”的场景,例如:

  • filter processormetric_conditions中按指标名、指标类型、Resource 属性等条件丢弃指标;
  • transform processor中改写指标描述、单位、类型等字段;
  • 在自定义 Collector 组件中,基于该 Context 构造布尔表达式,判断指标是否应被处理。

在 OTTL 体系里,每个信号(Signal)都有自己的 Context:Resource、Instrumentation Scope、Span、Span Event、Metric、DataPoint、Log、Profile。Metric Context 处于指标信号的中间层级,其层级关系为resource → scope → metric → datapoint(该层级也体现在 filter processor 的文档中:vendor/github.com/open-telemetry/opentelemetry-collector-contrib/processor/filterprocessor/README.md)。

版本说明:Metric Context 文档仅适用于0.120.0及以后的 opentelemetry-collector-contrib 版本,早期版本行为请查阅对应的 release 分支文档。

二、源码定位与上下文结构

Metric Context 的实现位于仓库 vendor 目录:

  • 文档:vendor/github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlmetric/README.md
  • 实现:vendor/github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlmetric/metrics.go
  • 内部指标上下文:vendor/github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/internal/ctxmetric/context.go

metrics.go的源码可以看到,TransformContext携带一条指标及其完整层级:

type TransformContext struct { resourceMetrics pmetric.ResourceMetrics scopeMetrics pmetric.ScopeMetrics metric pmetric.Metric cache pcommon.Map }

它同时实现了ctxresource.Contextctxscope.Contextctxmetric.Context三个内部接口,因此同一个 TransformContext 既能暴露resource.*instrumentation_scope.*路径,也能暴露metric.*路径。ctxmetric.Context接口只要求一个方法GetMetric() pmetric.Metric,对应metric.*路径的字段访问都由此展开。

2.1 上下文的生命周期管理

值得注意的源码细节是,Metric Context 使用sync.Pool复用 TransformContext 对象:

  • NewTransformContextPtr(resourceMetrics, scopeMetrics, metric, options...)从对象池取出实例并填充数据;
  • Close()清空字段并放回池中(tcPool.Put(tCtx))。

这意味着在 filter/transform 处理器内部,每个指标都会创建一个上下文、求值后立即归还,避免高频指标处理场景下的频繁分配。调用方(例如filterprocessor/metrics.go中的processSkipExpression)会严格按照NewTransformContextPtr(...)Eval(...)Close()的顺序使用:

tCtx := ottlmetric.NewTransformContextPtr(rm, smetrics, metric) skip, err := fmp.skipMetricExpr.Eval(ctx, tCtx) tCtx.Close()

2.2 解析器的组装

NewParser(functions, telemetrySettings, options...)通过ctxcommon.NewParser组装路径解析器,将metricresourcescope(含instrumentation_scope旧名)、otelcol等多个子上下文注册进统一路径表:

map[string]ottl.PathExpressionParser[*TransformContext]{ ctxresource.Name: ctxresource.PathGetSetter[*TransformContext], ctxscope.Name: ctxscope.PathGetSetter[*TransformContext], ctxscope.LegacyName: ctxscope.PathGetSetter[*TransformContext], ctxmetric.Name: ctxmetric.PathGetSetter[*TransformContext], ctxotelcol.Name: ctxotelcol.PathGetSetter[*TransformContext], }

因此,在 metric 上下文中,你可以同时使用metric.*resource.*instrumentation_scope.*(以及旧写法scope.*)和otelcol.*路径,而解析器会依据路径前缀把它们分发到对应的 GetSetter 实现上。

三、完整 Path 参考表

Metric Context 支持通过 metrics proto 中的字段名来访问 pdata 数据。所有整数值以int64读取/写入,所有浮点值以float64读取/写入。下表为当前文档声明的全部受支持路径:

path访问的字段类型
metric.cache当前转换上下文临时缓存的值,可在复杂转换中作为数据占位pcommon.Map
metric.cache[""]cache 中某个条目的值,支持多重索引访问嵌套字段string,bool,int64,float64,pcommon.Map,pcommon.Slice,[]bytenil
resource被处理指标所属的 resourcepcommon.Resource
resource.attributes被处理指标的 resource attributespcommon.Map
resource.attributes[""]resource 属性的值,支持多重索引访问嵌套字段string,bool,int64,float64,pcommon.Map,pcommon.Slice,[]bytenil
instrumentation_scope被处理指标的 instrumentation scopepcommon.InstrumentationScope
instrumentation_scope.nameinstrumentation scope 的名称string
instrumentation_scope.versioninstrumentation scope 的版本string
instrumentation_scope.attributesinstrumentation scope 的属性pcommon.Map
instrumentation_scope.attributes[""]instrumentation scope 属性的值,支持多重索引访问嵌套字段string,bool,int64,float64,pcommon.Map,pcommon.Slice,[]bytenil
metric.name指标名称string
metric.description指标描述string
metric.unit指标单位string
metric.type指标数据类型int64
metric.metadata与指标关联的元数据pcommon.Map
metric.aggregation_temporality指标的聚合时态(temporality)int64
metric.is_monotonic指标的单调性bool
metric.data_points指标的数据点pmetric.NumberDataPointSlicepmetric.HistogramDataPointSlicepmetric.ExponentialHistogramDataPointSlicepmetric.SummaryDataPointSlice
otelcol.*ottlotelcol 上下文暴露的全部路径视具体字段而定

3.1 使用要点

  • metric.cachemetric.cache["key"]是本上下文独有的临时缓存能力:缓存从 OTTL 上下文创建时初始化(sync.PoolNew函数中cache: pcommon.NewMap()),每次Close()时通过cache.Clear()清空。它适合在多个语句之间暂存中间计算结果。
  • 属性/缓存的多重索引语法形如resource.attributes["a"]["b"],可用于访问嵌套的pcommon.Mappcommon.Slice
  • metric.data_points的类型是切片(slice),具体是 Number、Histogram、ExponentialHistogram 还是 Summary 取决于metric.type的取值,因此在条件中通常需要先判断metric.type再使用对应类型的数据点。
  • otelcol.*将 otelcol 上下文(pkg/ottl/contexts/ottlotlcol)暴露的全部路径注入本上下文,可用于访问 Collector 自身的运行时信息(例如组件 ID、配置信息等,具体路径以 ottlotelcol 文档为准)。

3.2 底层实现:枚举解析与 SymbolTable

metrics.go中的parseEnum负责将 OTTL 语句里的枚举符号解析为数值,它直接查询ctxmetric.SymbolTable

func parseEnum(val *ottl.EnumSymbol) (*ottl.Enum, error) { if val != nil { if enum, ok := ctxmetric.SymbolTable[*val]; ok { return &enum, nil } return nil, fmt.Errorf("enum symbol, %s, not found", *val) } return nil, errors.New("enum symbol not provided") }

也就是说,Metric Context 中可用的枚举名称并非魔法字符串,而是由内部ctxmetric包维护的符号表集中定义,语句中写错枚举名会在解析阶段直接报enum symbol, X, not found

四、枚举(Enums)参考表

Metric Context 支持来自 metrics proto 的枚举名,同时也支持 pdata 定义的指标数据类型枚举(其数值定义见go.opentelemetry.io/collector/pdata/pmetric/metrics.go)。完整枚举清单如下:

枚举符号
AGGREGATION_TEMPORALITY_UNSPECIFIED0
AGGREGATION_TEMPORALITY_DELTA1
AGGREGATION_TEMPORALITY_CUMULATIVE2
METRIC_DATA_TYPE_NONE0
METRIC_DATA_TYPE_GAUGE1
METRIC_DATA_TYPE_SUM2
METRIC_DATA_TYPE_HISTOGRAM3
METRIC_DATA_TYPE_EXPONENTIAL_HISTOGRAM4
METRIC_DATA_TYPE_SUMMARY5

4.1 实践建议

  • 聚合时态(Aggregation Temporality)枚举用于与metric.aggregation_temporality(int64)比较,例如判断 Sum/Histogram 是 Delta 还是 Cumulative:metric.aggregation_temporality == AGGREGATION_TEMPORALITY_CUMULATIVE
  • 数据类型枚举用于与metric.type(int64)比较,例如只处理 Sum:metric.type == METRIC_DATA_TYPE_SUM
  • 这两个枚举类别不可混用:METRIC_DATA_TYPE_*只对metric.type有意义,AGGREGATION_TEMPORALITY_*只对metric.aggregation_temporality有意义。

五、Metric Context 在 Collector 处理器中的真实应用

5.1 filter processor 中的 metric_conditions

filter processor 使用 OTTL 条件决定丢弃哪些遥测数据,条件之间是 OR 关系,任一条件满足即丢弃。在metric_conditions列表中可以使用的上下文包括resourcescopemetricdatapoint(见 filterprocessor/README.md)。

典型配置(来源于该 README 的 Basic Config 示例):

processors: filter: error_mode: propagate metric_conditions: - metric.name == "my.metric" and resource.attributes["my_label"] == "abc123" - metric.type == METRIC_DATA_TYPE_HISTOGRAM - resource.attributes["service.name"] == "my_service_name"
底层求值调用链

在 filter processor 的实现中(vendor/github.com/open-telemetry/opentelemetry-collector-contrib/processor/filterprocessor/metrics.go),filterMetricProcessor同时持有三个表达式:

skipResourceExpr expr.BoolExpr[*ottlresource.TransformContext] skipMetricExpr expr.BoolExpr[*ottlmetric.TransformContext] skipDataPointExpr expr.BoolExpr[*ottldatapoint.TransformContext]

处理指标时按层级自顶向下执行:先对ResourceMetrics求值skipResourceExpr,再对每个Metric求值skipMetricExpr(即本篇文章的 Metric Context),最后对 DataPoint 求值skipDataPointExpr。每层丢弃后,上层会检查剩余数量,若全部被丢弃则连带移除上层对象——这与 README 中“如果某个 metric 的所有 datapoint 都被丢弃,则该 metric 也会被丢弃”的行为完全一致。processMetrics中还通过metricDataPointCountBeforeFilters - metricDataPointCountAfterFilters统计被过滤的数据点数量并记录到处理器遥测中。

条件序列的构建

filterottl.NewBoolExprForMetric(见vendor/github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterottl/filter.go)负责把配置中的条件字符串解析为可求值的条件序列:

func NewBoolExprForMetric(conditions []string, functions map[string]ottl.Factory[*ottlmetric.TransformContext], errorMode ottl.ErrorMode, set component.TelemetrySettings) (*ottl.ConditionSequence[*ottlmetric.TransformContext], error) { parser, err := ottlmetric.NewParser(functions, set, parserOptions...) statements, err := parser.ParseConditions(conditions) c := ottlmetric.NewConditionSequence(statements, set, ottlmetric.WithConditionSequenceErrorMode(errorMode)) return &c, nil }

即:ottlmetric.NewParser(解析路径/枚举)→ParseConditions(语法解析)→NewConditionSequence(组合为条件序列)。解析、构造失败会在配置加载阶段直接报错。

5.2 error_mode 的三种行为

filter processor 的error_mode决定条件求值出错时的处理策略:

error_mode说明
ignore忽略条件返回的错误、记录日志并继续执行下一条条件(推荐,且在多数发行版为默认)
silent忽略错误且不记录日志,继续执行下一条条件
propagate将错误向上传递,导致该批次数据被丢弃

对应到代码,WithConditionSequenceErrorMode(errorMode)把该模式注入ConditionSequence,条件求值时按此策略处理错误。

5.3 transform processor 与语句序列

在 transform processor 中,Metric Context 同样被用作语句求值上下文。与 filter 不同,transform 以Statement(函数 + 可选条件)形式改写指标。metrics.go提供了配套的语句序列 API:

  • NewStatementSequence(statements, telemetrySettings, options...):创建语句序列;
  • NewConditionSequence(conditions, telemetrySettings, options...):创建条件序列;
  • WithStatementSequenceErrorMode/WithConditionSequenceErrorMode:分别设置语句/条件求值的错误模式。

一个典型的 transform 语句形如:

set(metric.description, "new description") where metric.name == "http.server.duration"

5.4 调试技巧

当 OTTL 语句或条件行为不符合预期时,可在 Collector 配置中把日志级别调为 debug(参考 pkg/ottl/README.md):

service: telemetry: logs: level: debug

此时 OTTL 会输出初始 TransformContext、每条语句执行后的 TransformContext(包含 resource、scope、metric 与 cache 的快照,序列化逻辑见metrics.goMarshalLogObject),帮助你准确判断 OTTL 视角下底层数据的真实形态。

六、Metric Context 相关的 OTTL 背景知识

6.1 OTTL 语句的两部分结构

OTTL 语句由两部分组成:一个转换函数 + 一个可选的执行条件。例如:

set(span.attributes["test"], "pass") where span.attributes["test"] == nil

在 metric 场景下,对应地:

set(metric.unit, "s") where metric.name == "http.server.duration"

其中set是函数,where之后是条件,条件中引用的metric.*resource.*等路径即由 Metric Context 提供。OTTL 目前不支持跨信号引用,因此不能在 metric 上下文中访问 span 或 log 的字段。

6.2 条件的层级推断与执行顺序

filter processor 会基于条件中出现的路径前缀自动推断上下文(Context Inference)。例如metric.name == "x"推断为 metric 上下文,resource.attributes["host"] == "localhost"推断为 resource 上下文。同一条信号下的多个条件按层级顺序执行:resource → scope → metric → datapoint;高层对象被丢弃后,低层条件不会再被检查。若单条条件混用了不同层级的路径(如metric.name == "a" or resource.attributes["b"] == "c"),处理器会在较低层级(metric)上求值——因为高层信息在低层上下文中总是可见的。

6.3 可用的 OTTL 函数

filter processor 与 transform processor 可访问全部 OTTL 转换函数(Converter),并额外提供少量处理器自有函数。与 metric 相关的有两个:

  • HasAttrKeyOnDatapoint(key):若指标的任一 datapoint 的属性 Map 中存在给定 key,返回true
  • HasAttrOnDatapoint(key, value):若指标的任一 datapoint 的属性 Map 中存在给定 key 且值为给定 value(非字符串属性值按""比较),返回true

例如:

HasAttrOnDatapoint("http.method", "GET")

需要说明的是,这两个函数在实现中要求使用 metric 上下文,并且依赖对指标 datapoint 的遍历——从源码结构看,这类“指标级但涉及 datapoint 内容”的函数正是 Metric Context 的典型使用场景:过滤粒度是整条指标,判断依据来自其数据点。

七、在 Grafana Tempo 中的关联位置

本仓库(Grafana Tempo)将 opentelemetry-collector-contrib 作为 vendor 依赖引入,pkg/ottl及其上下文实现位于 vendor/github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl 下。从仓库源码结构看,Tempo 的modules/distributor/forwarder(forwarder.go)引用了pkg/ottlprocessor/filterprocessor,用于在 trace 转发链路中基于 OTTL 表达式进行过滤;同目录的测试 ottl_ismatch_test.go 也印证了 OTTL 表达式在 Tempo 分发链路中的实际运用。因此,本仓库中的 OTTL 相关文档与实现,对于在 Tempo 及其依赖的 Collector 组件中编写指标级过滤/转换规则具有直接的参考价值。

八、小结

Metric Context(ottlmetric)是 OTTL 访问 pdata Metric 的标准入口,本文覆盖了它的全部受支持路径与枚举、上下文对象结构、解析器组装方式,以及它在 filter/transform 处理器中的真实调用链与配置示例。实践时可遵循以下要点:

  1. 字段路径以 metrics proto 字段名为准,整数用int64、浮点用float64
  2. 判断指标类型用metric.type == METRIC_DATA_TYPE_*,判断聚合时态用metric.aggregation_temporality == AGGREGATION_TEMPORALITY_*
  3. 可同时使用metric.*resource.*instrumentation_scope.*otelcol.*路径;
  4. 临时数据放metric.cache,跨语句传递;
  5. 出错处理优先使用error_mode: ignore,配合 debug 日志排查条件行为。

【免费下载链接】tempoGrafana Tempo is a high volume, minimal dependency distributed tracing backend.项目地址: https://gitcode.com/GitHub_Trending/tempo1/tempo

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询