Grafana Tempo 中的 OpenTelemetry Transformation Language(OTTL):语句、条件、路径与实战过滤
发布时间:2026/9/19 14:41:26 作者:尧图编辑部 阅读量:1,286
:语句、条件、路径与实战过滤)
Grafana Tempo 中的 OpenTelemetry Transformation LanguageOTTL语句、条件、路径与实战过滤【免费下载链接】tempoGrafana Tempo is a high volume, minimal dependency distributed tracing backend.项目地址: https://gitcode.com/GitHub_Trending/tempo1/tempoOpenTelemetry Transformation LanguageOTTL是 OpenTelemetry Collector 生态中的一种小型领域特定语言DSL用于以 OpenTelemetry 原生概念处理遥测数据。本文以 Tempo 仓库内置的 OTTL 包vendor/github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl为骨架系统讲解 OTTL 的语句结构、路径体系、语法要素与调试手段并结合 Tempo 中真实使用 OTTL 的转发过滤链路modules/distributor/forwarder给出可复制、可运行的实战配置。读完本文你将掌握编写 OTTL 语句与条件、理解各信号 Path 上下文、以及利用 OTTL 在 Tempo 中按需过滤 Span 的方法。OTTL 是什么OTTLOpenTelemetry Transformation Language是一种小型、领域特定的编程语言其设计目标是用 OpenTelemetry 原生概念与构造来处理遥测数据。它不是一个通用编程语言而是专门服务于遥测数据的**变更mutation与生成generation**场景。当前仓库以 vendor 形式内置了该语言完整实现位于vendor/github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/其中包括parser.goOTTL 语句与条件的解析入口grammar.go、LANGUAGE.md语言文法与完整语法说明paths.go、contexts/路径Path解析与各信号上下文ottlfuncs/预置的函数库Editors 与 Convertersexpression.go、factory.go、functions.go表达式、工厂与函数注册机制。Tempo 自己并不重新发明 OTTL而是在转发器模块中直接引用该包github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl将其作为 Span 过滤条件的执行引擎。因此理解 OTTL 语法是正确配置 Tempo 转发过滤的前提。OTTL 语句的两要素一个 OTTL 语句Statement由两部分组成一个函数Function对遥测数据进行变换一个可选的条件Condition决定函数是否执行。官方 README 给出的经典示例set(span.attributes[test], pass) where span.attributes[test] nil函数部分是set(span.attributes[test], pass)set用第二个参数的值设置第一个参数指向的字段条件部分是where span.attributes[test] nil仅当该 Span 尚不存在名为test的属性时才执行设置。函数Editors 与 Converters在 OTTL 文法中函数分为两类Editors编辑器直接变换底层遥测数据通常不返回值。语句中必须有且仅有一个 Editor 调用见LANGUAGE.md的 Editors 一节。编辑器标识符必须以小写字母开头Converters转换器在把数据作为函数参数或用于布尔表达式之前将数据转换为新格式可返回任意类型。转换器标识符必须以大写字母开头且后面可以追加零个或多个字符串键[key]或整数键[0]进行索引。例如Int()、IsMatch(field, .*)、Split(field, ,)[1]都是转换器。当对返回值进行索引时OTTL 支持pcommon.Map/map[string]any用字符串键索引、pcommon.Slice/[]any用整数键索引若返回值不支持索引或键类型不匹配OTTL 会报错。需要特别强调OTTL 没有内置的 Editors 或 Converters。调用方必须提供一个标识符到函数实现的映射表OTTL 在执行语句时根据该映射调用对应实现。预置函数库位于vendor/github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottlfuncs/大多数 Collector 组件中的 OTTL 语句都使用这套函数。函数参数类型OTTL 函数单值参数支持Setter、Getter、GetSetter、PMapGetter、FloatGetter、FloatLikeGetter、StringGetter、StringLikeGetter、IntGetter、IntLikeGetter、BoolGetter、BoolLikeGetter、ByteSliceLikeGetter、Enum以及 Go 原生类型string、float64、int64、bool等。切片参数则支持各类*Getter与string、float64、int64、uint8字节切片字面量按字节切片解析等。参数可按需设为可选使用Optional[T]包装底层类型如Optional[string]且所有可选参数必须排在必选参数之后。参数可以按Arguments结构体中定义的顺序传参也可以使用命名参数名称是结构体字段名的 snake_case 形式以任意顺序传参未命名时跳过某个可选参数必须把其前面的可选参数一并传完。通过 Path 访问遥测数据语句内部通过OTTL Paths访问遥测字段。Path 由小写标识符、点.以及方括号中的字符串键或整数键组成例如metric.name span.value_double resource.name resource.attributes[key] log.attributes[nested][values] datapoint.cache[slice][1]Path 的语义约定如下标识符映射到遥测字段点.用于分隔嵌套字段首个 Path 段被 OTTL 解释为上下文标识符方括号与键[key]、[0]用于访问 map/slice 中的值。当访问 map 中不存在的键时返回nil因此可以用attributes[custom-attr] ! nil这样的布尔表达式检查键是否存在。Path 的解释不由 OTTL 实现而是由调用方提供PathExpressionParser完成。这个解析器所在的包通常称为上下文Context。各信号上下文与 Path 列表针对每一种 OpenTelemetry 信号仓库都提供了对应的 OTTL 上下文实现位于vendor/github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/下遥测信号OTTL 上下文目录Resource资源contexts/ottlresourceInstrumentation Scope插桩作用域contexts/ottlscopeSpan跨度contexts/ottlspanSpan Event跨度事件contexts/ottlspaneventMetric指标contexts/ottlmetricDatapoint数据点contexts/ottldatapointLog日志contexts/ottllogProfile性能剖析contexts/ottlprofile例如contexts/ottlspan/span.go定义了 Span 上下文的PathExpressionParser使得span.attributes[key]、span.name、span.status.code等路径可以被正确解析。OTTL 当前不支持跨信号交互所以不能写出把日志体塞进 Span 属性这样的语句set(span.attributes[log body], log.body)OTTL 语法要素详解LANGUAGE.md位于vendor/github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/LANGUAGE.md完整描述了 OTTL 文法。除 Path 外Value 还可以是以下形式。字面量Literals字符串双引号包裹如a string整数任意数字可带/-前缀内部统一为int64浮点数数字加小数点可带符号内部统一为float64如1.5、-.5布尔精确字符串true/false空值精确字符串nil字节切片以0x开头的十六进制串如0x0001。列表与映射列表List由逗号分隔的值序列组成仅能在函数参数或条件中使用语法不提供对单个元素的访问器[] [1] [1, 2, 3] [a, attributes[key], Concat([a, b], -)]映射Map是键值对的集合值可以是嵌套映射或表达式{} {foo: bar} {foo: {a: 2}} {foo: {a: attributes[key]}}枚举Enums枚举是大写标识符在解析阶段被EnumParser转换为int64数值在解析时而非执行时确定因此枚举符号可以当作整数使用。若 OTTL 函数需要接收枚举参数参数类型必须声明为Enum而非int64。数学表达式Math Expressions数学表达式支持、-、*、/以及括号分组可作用于int64、float64、time.Time与time.Durationtime.Time - time.Time得到time.Durationtime.Duration ± time.Time得到time.Timetime.Time - time.Duration得到time.Timetime.Duration ± time.Duration得到time.Duration。注意事项*与/优先级高于与-时间类型只能使用/-int64与float64混用会报错除零会以错误形式被优雅处理整数除法遵循 Go 的整数除法规则。因为数学表达式可以引用 Path 与 Converter所以它们在数据处理期间求值——这也意味着函数若想接受数学表达式作为参数必须声明为Getter类型。示例1 1 end_time_unix_nano - end_time_unix_nano sum([1, 2, 3, 4]) (10 / 1) - 1布尔表达式Boolean Expressions布尔表达式以字面量where开头后接一个或多个布尔值决定 Editor 是否执行它总是求值为 true 或 false。多个布尔可用and、or连接用not取反。优先级从高到低为notandor可用括号覆盖优先级。布尔值可以是字面量布尔值、返回布尔值的 Converter或由左值、运算符、右值组成的比较Comparison。比较运算符包括相等!不等小于大于小于等于大于等于。not取反示例not true not name foo not (IsMatch(name, http_.*) and kind 0)比较规则Comparison Rules两个值比较时若数值类型不同则按float64比较数值与字符串的比较遵循 Go 的带符号比较规则对布尔值false小于true。非基本类型的值只允许与!使用 Go 标准运算符实现。nil字节数组与nil等价time.Time相等性用time.Equal()time.Duration用time.Before()/time.After()比较map、[]any、pcommon.Map、pcommon.Slice等类型则分别通过reflect.DeepEqual或各自的Equal方法比较。典型条件示例name a name 1 2 attributes[custom-attr] ! nil IsMatch(resource.attributes[host.name], pod-*)Getter / Setter 与函数内日志遥测数据的读写通过TransformContext由调用方创建并在求值时传入以及Getter、Setter、GetSetter接口完成Getter 用于读取 Path、枚举、字面量与 Converter 解析出的值Setter 用于更新字段值GetSetter 同时提供读写能力。若需在 OTTL 函数内部输出日志只需在函数签名中加入component.TelemetrySettings类型的参数OTTL 会把NewParser时传入的 TelemetrySettings 注入函数供其发日志。在 Tempo 中实战用 OTTL 过滤转发 SpanOTTL 不仅用于数据变换也被广泛用于过滤。README 明确给出了 OTTL 的典型使用场景用 transform processor 修改管道中的数据、用 filter processor 删除管道中的数据、用 tail sampling processor 选择采样目标、用 routing connector 在管道间路由数据。Tempo 正是把 OTTL 用在了转发forwarder的过滤上在把接收到的 Trace 转发给外部后端如另一套 OTLP gRPC 端点之前先用 OTTL 条件剔除掉不需要的 Span。转发过滤的配置结构Tempo 转发器配置在modules/distributor/forwarder/config.go中定义name: 转发器名称 backend: otlpgrpc otlpgrpc: # OTLP gRPC 后端连接配置 filter: traces: span: [OTTL Span 条件] spanevent: [OTTL Span Event 条件]对应源码中的结构体Config包含Name、Backend、OTLPGRPC、Filter四个字段FilterConfig.Traces即TraceFiltersConfig含SpanConditionsYAML 键span与SpanEventConditionsYAML 键spanevent两个字符串数组。Config.Validate()会校验转发器名称非空、backend 必须为受支持的otlpgrpc。过滤链路的实现原理在modules/distributor/forwarder/forwarder.go的New()中如果配置了任何 Span 或 Span Event 条件就会返回一个FilterForwarder通过filterprocessor.NewFactory()创建 Collector 的 filter processor 工厂用factory.CreateDefaultConfig()得到OTTL 函数已正确初始化的默认配置并将ErrorMode设为ottl.IgnoreError出错时忽略而非丢弃数据把配置中的SpanConditions、SpanEventConditions填入filterprocessor.TraceFilters将下游转发器包装成consumerToForwarderAdapter作为 filter processor 的消费端启动 processor 后ForwardTraces会先把 Trace 复制一份避免改动原始数据再交给 filter processor 过滤剩余数据才转发出去。测试modules/distributor/forwarder/forwarder_test.go验证了这一行为配置SpanConditions: []string{name to-filter}时名为to-filter的 Span 被剔除、to-keep的 Span 被保留而当条件为name to-filter-1 or name to-filter-2且两个 Span 都被命中时下游转发器一次都不会被调用。条件是如何求值的Tempo 使用的 filter processor 位于vendor/github.com/open-telemetry/opentelemetry-collector-contrib/processor/filterprocessor/。其internal/condition/traces.go展示了 OTTL 条件的执行链路条件被编译为ottlspan.NewConditionSequence(...)与ottlspanevent.NewConditionSequence(...)分别处理span与spanevent条件执行时对每个 Span 创建ottlspan.NewTransformContextPtr(rs, ss, span)上下文调用spanExpr.Eval(ctx, spanCtx)求值若条件为真则该 Span 通过Spans().RemoveIf(...)被移除Span Event 同理在span.Events().RemoveIf(...)中处理。也就是说你在配置里写的每一条span条件本质上都是一条独立的 OTTL 布尔表达式最终被编译为可对每个 Span 上下文求值的Condition。与官方文档的调试方法配合README 提供了非常实用的调试手段当 OTTL 语句表现不符合预期时可在 Collector 中开启 debug 日志OTTL 会打印出当前语句/条件以及完整的TransformContext让你准确看到 OTTL 眼中的底层数据service: telemetry: logs: level: debug开启后会输出类似如下的日志parser.go中的initial TransformContext与TransformContext after statement execution2024-05-29T16:38:09.600-0600 debug ottlv0.101.0/parser.go:265 initial TransformContext {kind: processor, name: transform, pipeline: logs, TransformContext: {resource: {attributes: {}, ...}, scope: {...}, log_record: {...}, cache: {}}} 2024-05-29T16:38:09.600-0600 debug ottlv0.101.0/parser.go:268 TransformContext after statement execution {kind: processor, name: transform, pipeline: logs, statement: set(resource.attributes[\test\], \pass\), condition matched: true, TransformContext: {resource: {attributes: {test: pass}, ...}}}注意该特性日志量非常大非常 verbose但它能提供OTTL 如何理解底层数据的准确视图适合在条件不生效时定位是路径写错还是数据本身不符合预期。在 Tempo 的转发过滤场景中同样适用把日志级别调到 debug即可看到每条条件求值时的condition matched结果与TransformContext快照。进阶学习路径完整语法阅读vendor/github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/LANGUAGE.md覆盖文法、比较规则、Getter/Setter 等全部细节预置函数库vendor/github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottlfuncs/中的 Editors 与 Converters 列表set、IsMatch、Concat、Split等各信号上下文与 Pathvendor/github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/下每个上下文目录的 README 与*.go实现Tempo 中的落地用法modules/distributor/forwarder/forwarder.go与modules/distributor/forwarder/config.go以及modules/distributor/forwarder/forwarder_test.go中的过滤行为测试filter processor 内部机制vendor/github.com/open-telemetry/opentelemetry-collector-contrib/processor/filterprocessor/internal/condition/traces.go了解 Span/Span Event 条件从字符串编译到逐 Span 求值、删除的完整链路。总结OTTL 是一套围绕 OpenTelemetry 数据模型设计的轻量 DSL语句 编辑器函数 可选条件值可以是 Path、字面量、列表、映射、枚举、Converter 或数学表达式条件则基于where引导的布尔表达式与严格定义的比较规则。Tempo 仓库将其内置为 vendor 依赖并在 distributor 的转发过滤链路中把用户配置的 OTTL 条件编译为逐 Span 求值的 Condition实现转发前的按需剔除。掌握了 OTTL 的语句、Path 与语法要素再配合 debug 日志这一利器你就能在 Tempo 及更广泛的 OpenTelemetry Collector 生态中编写准确、可维护的遥测处理规则。【免费下载链接】tempoGrafana Tempo is a high volume, minimal dependency distributed tracing backend.项目地址: https://gitcode.com/GitHub_Trending/tempo1/tempo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考