为 OpenTelemetry Collector Contrib 贡献 Unroll Processor
在 OpenTelemetry Collector 中解压(unrolling)捆绑日志的想法并非始于一个 processor。
“解压”的意思是,将单个日志记录中包含的多个逻辑事件(例如,包含十个日志条目的 JSON 数组)展开成十个单独的日志记录,每个记录对应一个事件。这样,您就可以处理单个日志条目,而不是捆绑的有效负载。
当 Collector SIG 首次讨论如何处理包含多个逻辑事件的日志(如 JSON 数组)的主体时,最初的想法是使用 `transform` processor 中的 OTTL(OpenTelemetry Transform Language)函数 来解决。
乍一看,这似乎是合理的。OTTL 功能强大、灵活,并且可以处理记录级别的转换。但我们发现了更深层次的挑战。`transform` processor 在迭代过程中添加新日志记录时遇到了困难。它可以修改和过滤现有数据,但将一条记录扩展成多条记录并非它作为单个 processor 的职责范围内可以有效完成的任务。
这时,我们想要介入并提供帮助。今年一月份,我帮助开发了 OpenTelemetry Collector 发行版中的一个专用解压 processor,主要是因为我们的客户遇到了这些问题。
Unroll processor 以一种清晰、确定的方式展开捆绑的记录。在生产环境中运行了数月后,我想通过 upstream 贡献 unroll processor,以便 OpenTelemetry 社区能够从共享解决方案中受益。
让我解释一下 unroll processor 是什么,它是如何工作的,它如何帮助您,以及我们如何 upstream 贡献到 Contrib 发行版中。
为何要解压?
核心问题很简单:一些源以一个日志记录的形式传递多个事件。您希望处理干净、独立的日志条目。
在 unroll 之前,您有两种选择:
- 在 Collector 之外预处理日志——如果您能够插入逻辑。
- 尝试让 OTTL/transform 去做它从未被设计用来做的事情。
这两种方法都未能正确解决问题。
Unroll Processor 的作用
Unroll processor 会接收一个列表状的日志主体(例如 JSON 数组),并将其展开成每个元素一条日志记录,同时保留时间戳、资源和日志属性。
如果您的输入是 JSON 数组中的十个对象,您将得到十条独立的日志记录。每条日志记录都将保留其元数据,并为转换、过滤、归约等操作做好准备。
它简单、可预测且生产安全。
为何选择放弃 OTTL?
我们对此进行了深入探讨。
理论上,使用 transform + OTTL 组合解决此问题似乎更简单。但当我们深入研究时,遇到了一个核心限制:OTTL 无法在迭代过程中安全地添加新记录。尝试在循环中生成新条目会导致记录跳过、语句执行不可靠以及行为脆弱。
Transform 和 filter processor 在变异和抑制数据方面表现出色。但展开是另一项职责。它需要自己的语义、生命周期和保证。
Unroll processor 将添加记录的关注点与转换逻辑清晰地分开,并以一种可组合且可预测的方式运行。
我帮助开发了 Bindplane Distro for OpenTelemetry Collector 中 unroll processor 的第一个版本。它于 2025 年 1 月首次发布并被客户使用,此后一直在生产环境中运行。
我看到客户在以下方面使用了它:
- VPC 日志
- CloudWatch 摄入管道
- Windows + 端点日志
- 捆绑的 collector 遥测数据
即使在真实的生产负载下,我们也观察到极低的故障率,尤其是在初始接收器或日志信号源对格式不太敏感的情况下。这让我们有信心将该组件 upstream。
如何配置 Unroll Processor
将 unroll processor 放入您的管道中,以便在需要展开捆绑的日志有效负载的地方使用。这是一个基本的配置示例,供您开始使用:
processors:
unroll:
service:
pipelines:
logs:
receivers: [otlp]
processors: [..., unroll, ...]
exporters: [logging]
常见的解压模式
Unroll processor 仅在 `log.body` 是可迭代列表(例如,一个有效的 JSON 数组)时才执行操作。但在实际的管道中,日志记录并非总是如此结构化。有时需要额外的预处理,将原始日志有效负载转换为 unroll processor 可以操作的格式。
示例:单个日志记录中的多个 JSON 对象
考虑一个将多个 JSON 对象连接成单个日志记录的场景,如下所示:
{"@timestamp":"2025-09-19T02:20:17.920Z", "log.level": "INFO", "message":"initialized", "ecs.version": "1.2.0","service.name":"ES_ECS","event.dataset":"elasticsearch.server","process.thread.name":"main","log.logger":"org.elasticsearch.node.Node","elasticsearch.node.name":"es-test-3","elasticsearch.cluster.name":"elasticsearch"},{"type": "server", "timestamp": "2025-09-18T20:44:01,838-04:00", "level": "INFO", "component": "o.e.n.Node", "cluster.name": "elasticsearch", "node.name": "es-test", "message": "initialized" }
以下是如何使用 `transform` processor 然后是 `unroll` 进行预处理:
receivers: ...
processors:
transform:
error_mode: ignore
log_statements:
- context: log
statements:
- set(body, Split(body, "\"},"))
unroll: {}
exporters: ...
services:
pipelines:
logs:
receivers: [...]
processors: [transform, unroll]
exporters: [...]
此转换语句使用 `Split` 函数,以 `"},"` 作为分隔符来分割 `body`,生成一个 unroll processor 可以展开的列表状 `body`。
摘要
此功能源于一个简单的需求:使 Collector 更通用,能够展开日志记录。
我们尝试了 OTTL 路线,发现它不容易实现,然后 upstream 了专用的、经过生产测试的、易于使用的 unroll processor。结果是一个小的配置更改,可以解决大量现实世界中的遥测数据摄入问题。
我们目前不建议将记录展开添加到 OTTL。展开会改变数据流的基数,可能需要不同的生命周期和正确性保证,因此专用 processor 目前是合适的选择。将职责分开,让 OTTL 专注于转换现有日志记录,而 unroll processor 则负责展开。
Unroll processor 现在已在官方的 OpenTelemetry Collector Contrib 中可用。请随时创建 issues 并测试它,以用于您的日志管道。