Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
61a8af6
add blog post about unroll in the contrib distribution
schmikei Oct 14, 2025
15e753a
npm run fix:all
schmikei Oct 14, 2025
255a46f
Merge branch 'main' into blog/unroll-processor
schmikei Oct 14, 2025
ef4aeda
Apply suggestions from code review
schmikei Oct 15, 2025
c4ae543
remove list with just one item
schmikei Oct 15, 2025
a4bccc3
Update content/en/blog/2025/contrib-unroll-processor.md
schmikei Oct 16, 2025
4a08f22
Merge branch 'main' into blog/unroll-processor
schmikei Oct 16, 2025
6dfa5b8
Merge branch 'main' of github.com:open-telemetry/opentelemetry.io int…
schmikei Oct 21, 2025
a428c83
npm run fix:all
schmikei Oct 21, 2025
f8d438d
Merge branch 'blog/unroll-processor' of github.com:schmikei/opentelem…
schmikei Oct 21, 2025
0ffdb6e
Results from /fix directive
otelbot[bot] Oct 21, 2025
f0d2a5d
Apply suggestions from code review
schmikei Oct 22, 2025
9277613
rename whats next for summary
schmikei Oct 22, 2025
69c73c7
npm run fix:all
schmikei Oct 22, 2025
6a42fa9
Merge branch 'main' of github.com:open-telemetry/opentelemetry.io int…
schmikei Oct 23, 2025
40cf16c
consolidate config snippet
schmikei Oct 23, 2025
d2210a7
Merge branch 'main' into blog/unroll-processor
schmikei Oct 24, 2025
334e58b
address some pr feedback: add sig and clarify what unroll is doing in…
schmikei Oct 29, 2025
44424fe
Merge branch 'blog/unroll-processor' of github.com:schmikei/opentelem…
schmikei Oct 29, 2025
b2a914f
npm run fix:all
schmikei Oct 29, 2025
2f6e1c9
add explicit statement of stance in summary
schmikei Oct 30, 2025
fd3c0a4
npm run fix:all
schmikei Oct 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions content/en/blog/2025/contrib-unroll-processor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
---
title: Contributing the Unroll Processor to the OpenTelemetry Collector Contrib
linkTitle: Adding the Unroll Processor
date: 2025-10-14
author: >-
[Keith Schmitt](https://github.com/schmikei) (Bindplane)
issue: 8039
sig: Collector
cSpell:ignore: Bindplane CloudWatch OTTL schmikei VPC
---

The idea for unrolling bundled logs inside the OpenTelemetry Collector didn't
start with a processor.

When the OpenTelemetry community first discussed the problem of how to handle
logs that contain multiple logical events in a single body, like a JSON array,
the initial instinct was to solve it with an
[OTTL (OpenTelemetry Transform Language) function inside the transform processor](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/41791).

And that made sense to me at first glance. OTTL is powerful, flexible, and can
handle transformations on the record level. But we found deeper challenges.
**The transform processor had a hard time adding new log records
mid-iteration**. It mutates and filters existing data, but expanding one record
into many isn't something it can feasibly do within its role as a single
processor.

That's where we wanted to jump in and help. Back in January of this year I
helped develop a dedicated unroll processor in our distro of the OpenTelemetry
Collector, primarily because our customer base was running into these issues.

The unroll processor expands bundled records in a clean, deterministic way.
After running it for months in production,
[I wanted to help by upstreaming the unroll processor so the OpenTelemetry community could benefit from a shared solution](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/42491).

Let me explain what the unroll processor is, how it works, how it can help you,
and how
[we helped contribute upstream to the Contrib distribution](https://github.com/open-telemetry/opentelemetry-collector-contrib/pull/42500).

## Why unroll?

The core problem is simple. **Some sources deliver multiple events within one
log record**. You want to work with clean, individual log entries.

Before unroll, you had two awkward options:

1. Pre-process logs outside the Collector—if you even could insert the logic.
2. Try to bend OTTL/transform into doing something it was never designed for.

## What the unroll processor does

The unroll processor takes a **list-like log body**, like a JSON array, and
expands it into **one log record per element**, while preserving timestamps, and
both resource and log attributes.

If your input had 10 objects in a JSON array, you get 10 distinct log records
out. Every log record would preserve their metadata and be ready for
transformations, filters, reductions, anything you'd want really.

It's simple, predictable, and production-safe.

## Why decide against OTTL?

We explored this deeply.

On paper, solving this via a transform + OTTL combo seemed simpler. Once we got
into it, we ran into a core limitation: **OTTL can't safely add new records
during iteration**. Trying to generate new entries mid-loop leads to skipped
records, unreliable statement execution, and brittle behavior.

Transform and filter processors are excellent for mutation and suppression. But
**expansion** is a different responsibility. It requires its own semantics,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
**expansion** is a different responsibility. It requires its own semantics,
expansion is a different responsibility. It requires its own semantics,

lifecycle, and guarantees.

The unroll processor cleanly separates the concern of adding records from
transformation logic and operates in a way that's both composable and
predictable.

I helped develop the first version of the unroll processor in the Bindplane
Distro of OpenTelemetry Collector. It was first shipped and in use by customers
in **January 2025 and has been running in production ever since**.

I've seen customers use it across:

- VPC logs
- CloudWatch ingestion pipelines
- Windows + endpoint logs
- Bundled collector telemetry

We observed **very low issue volume** even under real production load, which
gave us the confidence to **propose it upstream**. Specifically when the initial
receiver or source of the log signals is fairly format agnostic.

## How to configure the unroll processor

Drop the unroll processor into your pipeline wherever you need to expand bundled
log payloads. Here's a minimal configuration example to get you started:

```yaml
processors:
unroll:
service:
pipelines:
logs:
receivers: [otlp]
processors: [..., unroll, ...]
exporters: [logging]
```

## Common unroll patterns

The unroll processor only performs work if `log.body` is an iterable list—for
example, a proper JSON array. But in real-world pipelines, log records aren't
always so neatly structured. Sometimes, additional preprocessing is needed to
convert raw log payloads into a format that the unroll processor can operate on.

### Example: multiple JSON objects in a single log record

Consider a case where multiple JSON objects are concatenated into a single log
record, like this:

```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" }
```

Here's how you can do that using the `transform` processor followed by `unroll`:

```yaml
transform:
error_mode: ignore
log_statements:
- context: log
statements:
- set(body, Split(body, "\"},"))
unroll: {}
```

This transform statement uses `Split` to separate the body into chunks using the
`"},"` delimiter, producing a list-like body that the unroll processor can
expand.

```yaml
receivers: ...

processors:
transform:
error_mode: ignore
log_statements:
- context: log
statements:
- set(body, Split(body, "\"},")) where true
unroll: {}
exporters: ...

services:
pipelines:
logs:
receivers: [...]
processors: [transform, unroll]
exporters: [...]
```

## What's Next?

This feature started from a simple need: **make the Collector more versatile and
capable of expanding log records.**

We tried the OTTL route, realized it wouldn't easily work, and upstreamed a
purpose-built, production-tested, and easy-to-use unroll processor. The result
is a small config change that can unblock a huge number of real-world telemetry
ingestion problems.

The unroll processor is now available in
[the official OpenTelemetry Collector Contrib](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/unrollprocessor).
Please feel free to create issues/test it out for your logs pipelines.
20 changes: 20 additions & 0 deletions static/refcache.json
Original file line number Diff line number Diff line change
Expand Up @@ -6979,6 +6979,14 @@
"StatusCode": 206,
"LastSeen": "2025-10-14T17:44:17.684857422Z"
},
"https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/41791": {
"StatusCode": 206,
"LastSeen": "2025-10-14T16:02:31.752477-04:00"
},
"https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/42491": {
"StatusCode": 206,
"LastSeen": "2025-10-14T16:02:33.807944-04:00"
},
"https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22+label%3Adata%3Aprofiles": {
"StatusCode": 206,
"LastSeen": "2025-10-17T21:16:30.350604-04:00"
Expand All @@ -6991,6 +6999,10 @@
"StatusCode": 206,
"LastSeen": "2025-10-14T17:44:16.451356403Z"
},
"https://github.com/open-telemetry/opentelemetry-collector-contrib/pull/42500": {
"StatusCode": 206,
"LastSeen": "2025-10-14T16:02:37.218366-04:00"
},
"https://github.com/open-telemetry/opentelemetry-collector-contrib/releases/tag/v0.111.0": {
"StatusCode": 206,
"LastSeen": "2025-10-14T17:44:09.095879272Z"
Expand Down Expand Up @@ -7483,6 +7495,10 @@
"StatusCode": 206,
"LastSeen": "2025-10-14T17:44:09.840603299Z"
},
"https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/unrollprocessor": {
"StatusCode": 206,
"LastSeen": "2025-10-14T16:02:39.522676-04:00"
},
"https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver": {
"StatusCode": 206,
"LastSeen": "2025-10-17T09:39:52.287342116Z"
Expand Down Expand Up @@ -13595,6 +13611,10 @@
"StatusCode": 206,
"LastSeen": "2025-10-18T19:00:46.156207225Z"
},
"https://github.com/schmikei": {
"StatusCode": 206,
"LastSeen": "2025-10-14T16:02:28.806113-04:00"
},
"https://github.com/scorpionknifes": {
"StatusCode": 206,
"LastSeen": "2025-10-18T19:01:40.680711039Z"
Expand Down