OTel Collector
Runs 10x Engine as a sidecar to the OpenTelemetry Collector for reporting, receiving, and optimizing events before they ship to their destination (Elasticsearch, Splunk, S3, Kafka, โฆ). The Collector and Log10x run as peer processes โ the Collector sends events to Log10x via its native OTLP/gRPC exporter and receives processed events back via its OTLP/gRPC receiver. The OTLP wire preserves resource attributes, scope info, log-record attributes, severity, timestamp, and body end-to-end, so k8s metadata (k8s.pod.name, k8s.namespace.name, k8s.container.name, labels, โฆ) round-trips back to your destinations.
Distribution
Both the OTLP receiver and OTLP exporter ship in the core otelcol distribution โ no otelcol-contrib build is required. Tested against otelcol v0.151.0+.
Architecture
graph LR
A["<div style='font-size: 14px;'>๐ Receivers</div><div style='font-size: 10px;'>filelog, otlp, journald</div>"] --> F["<div style='font-size: 14px;'>๐งช logs/to-tenx</div><div style='font-size: 10px;'>k8sattributes, resource, transform</div>"]
F --> B["<div style='font-size: 14px;'>๐ค otlp exporter</div><div style='font-size: 10px;'>OTLP/gRPC โ :4317</div>"]
B --> E["<div style='font-size: 14px;'>โก 10x Engine</div><div style='font-size: 10px;'>Receive/Optimize</div>"]
E --> C["<div style='font-size: 14px;'>๐ฅ otlp receiver</div><div style='font-size: 10px;'>:24225 (no processors)</div>"]
C --> D["<div style='font-size: 14px;'>๐ค Destinations</div><div style='font-size: 10px;'>ES, Splunk, S3, Kafka</div>"]
classDef input fill:#2563eb,stroke:#1d4ed8,color:#ffffff,stroke-width:2px,rx:8,ry:8
classDef filter fill:#ea580c,stroke:#c2410c,color:#ffffff,stroke-width:2px,rx:8,ry:8
classDef engine fill:#7c3aed,stroke:#6d28d9,color:#ffffff,stroke-width:2px,rx:8,ry:8
classDef output fill:#16a34a,stroke:#15803d,color:#ffffff,stroke-width:2px,rx:8,ry:8
class A input
class B filter
class C filter
class D output
class E engine
class F filter
Data Flow
- ๐ Receivers โ Your existing OTel receivers (
filelog,otlp,journald,kafka, โฆ) feed events into thelogs/to-tenxpipeline. - ๐งช
logs/to-tenxโ Your enrichment processors (k8sattributes,resource,attributes,transform,filter, โฆ) run here exactly once before the event is handed off to Log10x. The processors live on this pipeline only; the return-path pipeline never sees them. - ๐ค OTLP exporter โ Log10x โ The Collector forwards the enriched event to the Log10x sidecar over OTLP/gRPC on TCP
:4317. Resource attributes, scope info, log-record attributes, and the body all travel on the wire. - โก 10x Engine โ The Receiver app applies rate/policy-based filtering and optionally compacts events for volume reduction.
- ๐ฅ
logs/from-tenxโ Processed events come back to the Collector on:24225via its OTLP/gRPC receiver. Keep this pipeline processor-free; the destination exporters consume events directly so enrichment never re-fires on the return path. - ๐ค Destinations โ Your OTel destination exporters (
elasticsearch,splunkhec,kafka,awss3, โฆ) consume the return pipeline and ship to the real destinations.
What an event looks like on the way back
Every attribute that came in over OTLP โ resource attributes, scope info, log-record attributes, body โ round-trips back to the Collector. The LogRecord.body carries the message verbatim in its original AnyValue shape (so a body.stringValue is byte-for-byte unchanged); the resource-vs-log-attribute distinction is collapsed (everything comes back as a log-record attribute), but no data is lost. What changes between in and out depends on the Receiver app mode:
| Mode | Difference vs the event the Collector sent in |
|---|---|
| Receive (default) | None. Same record. |
Receive + symbolMessageHashField <name> |
Same record + one new field named <name> carrying the symbol-pattern hash (a stable identifier for the message pattern โ usable as a dedup key, metric dimension, or correlation ID). |
receiverOptimize true |
The value of the field captured by otelCollectorInputMessageField (default body) is replaced with a compact encoded form. A separate tenx-template event is emitted with the template needed to decode it. All other fields stay verbatim. |
receiverOptimize true + symbolMessageHashField <name> |
Both of the above. |
Log10x reads the log line text via the JSON field configured by otelCollectorInputMessageField (default body); the tag field stamped by the input (from service.name, falling back to k8s.pod.name, then to the literal "otel") becomes the event's source. All other resource and log-record attributes (k8s.pod.name, k8s.namespace.name, service.name, โฆ) come through as flat top-level fields on the record, available for message-pattern and rate filtering.
Key Files
| File | Purpose |
|---|---|
stream.yaml |
OTel Collector OTLP/gRPC input + output stream definitions |
conf/tenx-sidecar.yaml |
Reference Collector config showing the two-pipeline split with no return-path processors |
Quickstart
1. Run Log10x:
2. Wire up your Collector config โ start from the sidecar recipe and add your real receivers + destination exporters. The logs/to-tenx pipeline carries everything from sources through enrichment to the otlp/tenx exporter; the logs/from-tenx pipeline carries the returning events directly to your destination exporters:
receivers:
filelog:
include: [/var/log/app.log]
# Receive processed events back from Log10x over OTLP/gRPC
otlp/tenx:
protocols:
grpc:
endpoint: 0.0.0.0:24225
processors:
batch: {}
exporters:
# Hand off to the Log10x sidecar over OTLP/gRPC
otlp/tenx:
endpoint: 127.0.0.1:4317
tls:
insecure: true
debug:
verbosity: detailed
service:
pipelines:
logs/to-tenx:
receivers: [filelog]
processors: [batch]
exporters: [otlp/tenx]
logs/from-tenx:
receivers: [otlp/tenx]
exporters: [debug]
For Splunk integration see the 10x for Splunk documentation. For Kubernetes deployment via the official OpenTelemetry Collector Helm chart see the Helm sidecar overlay.
Config Files
To configure the OpenTelemetry Collector module, Edit these files.
Below is the default configuration from: otel-collector/config.yaml.
ewogICJ0eXBlIiA6ICJvYmplY3QiLAogICJwcm9wZXJ0aWVzIiA6IHsKICAgICJpbmNsdWRlIiA6IHsKICAgICAgInR5cGUiIDogImFycmF5IiwKICAgICAgIml0ZW1zIiA6IHsKICAgICAgICAidHlwZSIgOiAic3RyaW5nIgogICAgICB9CiAgICB9LAogICAgInRlbngiIDogewogICAgICAidHlwZSIgOiAic3RyaW5nIgogICAgfSwKICAgICJvdGVsQ29sbGVjdG9yIiA6IHsKICAgICAgInR5cGUiIDogIm9iamVjdCIsCiAgICAgICJhZGRpdGlvbmFsUHJvcGVydGllcyIgOiBmYWxzZSwKICAgICAgInByb3BlcnRpZXMiIDogewogICAgICAgICJpbnB1dCIgOiB7CiAgICAgICAgICAidHlwZSIgOiAib2JqZWN0IiwKICAgICAgICAgICJhZGRpdGlvbmFsUHJvcGVydGllcyIgOiBmYWxzZSwKICAgICAgICAgICJwcm9wZXJ0aWVzIiA6IHsKICAgICAgICAgICAgInBvcnQiIDogewogICAgICAgICAgICAgICJ0eXBlIiA6IFsKICAgICAgICAgICAgICAgICJzdHJpbmciLAogICAgICAgICAgICAgICAgIm51bWJlciIsCiAgICAgICAgICAgICAgICAibnVsbCIKICAgICAgICAgICAgICBdLAogICAgICAgICAgICAgICJtYXJrZG93bkRlc2NyaXB0aW9uIiA6ICJUQ1AgcG9ydCB0byBsaXN0ZW4gb24gZm9yIE9UTFAvZ1JQQyBldmVudHMgZnJvbSB0aGUgT3BlblRlbGVtZXRyeSBDb2xsZWN0b3JcblxuVENQIHBvcnQgd2hlcmUgTG9nMTB4IGxpc3RlbnMgZm9yIE9UTFAvZ1JQQyBsb2cgcmVjb3JkcyBmcm9tIHRoZSBPcGVuVGVsZW1ldHJ5IENvbGxlY3RvcidzIGBvdGxwYCBleHBvcnRlci4gTWF0Y2ggdGhpcyBhZ2FpbnN0IHRoZSBgZW5kcG9pbnRgIG9mIHlvdXIgQ29sbGVjdG9yIGV4cG9ydGVyLiAoRGVmYXVsdDogNDMxNykiLAogICAgICAgICAgICAgICJkZWZhdWx0IiA6ICI0MzE3IgogICAgICAgICAgICB9LAogICAgICAgICAgICAibWVzc2FnZUZpZWxkIiA6IHsKICAgICAgICAgICAgICAidHlwZSIgOiBbCiAgICAgICAgICAgICAgICAic3RyaW5nIiwKICAgICAgICAgICAgICAgICJudWxsIgogICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgIm1hcmtkb3duRGVzY3JpcHRpb24iIDogIkRvdHRlZCBKU09OIHBhdGggdG8gdGhlIGZpZWxkIGNhcnJ5aW5nIHRoZSBsb2cgbGluZSB0ZXh0XG5cblRvcC1sZXZlbCBKU09OIGZpZWxkIG5hbWUgb24gdGhlIGlucHV0IHJlY29yZCB0aGF0IGNhcnJpZXMgdGhlIGFjdHVhbCBsb2cgbGluZSB0ZXh0LiBUaGUgZGVmYXVsdCAoYGJvZHlgKSBtYXRjaGVzIHRoZSBmbGF0dGVuZWQgc2hhcGUgdGhlIGlucHV0IGVtaXRzIGZvciBwbGFpbi10ZXh0IE9UTFAgYm9kaWVzLiBTZXQgdG8gYSBkaWZmZXJlbnQgZmllbGQgbmFtZSBpZiB5b3VyIENvbGxlY3RvciBwaXBlbGluZSBjb3BpZXMgdGhlIGxvZyBsaW5lIGludG8gYSBkaWZmZXJlbnQgYXR0cmlidXRlIGJlZm9yZSBleHBvcnRpbmcgKGUuZy4gYW4gYDxhdHRyX2tleT5gIHNldCB2aWEgYSB0cmFuc2Zvcm0pLiAoRGVmYXVsdDogYm9keSkiLAogICAgICAgICAgICAgICJkZWZhdWx0IiA6ICJib2R5IgogICAgICAgICAgICB9CiAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgICAib3V0cHV0IiA6IHsKICAgICAgICAgICJ0eXBlIiA6ICJvYmplY3QiLAogICAgICAgICAgImFkZGl0aW9uYWxQcm9wZXJ0aWVzIiA6IGZhbHNlLAogICAgICAgICAgInByb3BlcnRpZXMiIDogewogICAgICAgICAgICAiaG9zdCIgOiB7CiAgICAgICAgICAgICAgInR5cGUiIDogWwogICAgICAgICAgICAgICAgInN0cmluZyIsCiAgICAgICAgICAgICAgICAibnVsbCIKICAgICAgICAgICAgICBdLAogICAgICAgICAgICAgICJtYXJrZG93bkRlc2NyaXB0aW9uIiA6ICJUQ1AgaG9zdCBvZiB0aGUgT3BlblRlbGVtZXRyeSBDb2xsZWN0b3IncyBPVExQL2dSUEMgcmVjZWl2ZXJcblxuSG9zdG5hbWUgb3IgSVAgd2hlcmUgdGhlIE9wZW5UZWxlbWV0cnkgQ29sbGVjdG9yJ3MgYG90bHBgIHJlY2VpdmVyIGlzIGxpc3RlbmluZyBmb3IgcHJvY2Vzc2VkIGV2ZW50cyBmcm9tIExvZzEweC4gUGFpcnMgd2l0aCBgb3RlbENvbGxlY3Rvck91dHB1dFBvcnRgLiAoRGVmYXVsdDogMTI3LjAuMC4xKSIsCiAgICAgICAgICAgICAgImRlZmF1bHQiIDogIjEyNy4wLjAuMSIKICAgICAgICAgICAgfSwKICAgICAgICAgICAgInBvcnQiIDogewogICAgICAgICAgICAgICJ0eXBlIiA6IFsKICAgICAgICAgICAgICAgICJzdHJpbmciLAogICAgICAgICAgICAgICAgIm51bWJlciIsCiAgICAgICAgICAgICAgICAibnVsbCIKICAgICAgICAgICAgICBdLAogICAgICAgICAgICAgICJtYXJrZG93bkRlc2NyaXB0aW9uIiA6ICJUQ1AgcG9ydCBvZiB0aGUgT3BlblRlbGVtZXRyeSBDb2xsZWN0b3IncyBPVExQL2dSUEMgcmVjZWl2ZXJcblxuVENQIHBvcnQgd2hlcmUgdGhlIE9wZW5UZWxlbWV0cnkgQ29sbGVjdG9yJ3MgYG90bHBgIHJlY2VpdmVyIGlzIGxpc3RlbmluZyBmb3IgcHJvY2Vzc2VkIGV2ZW50cyBmcm9tIExvZzEweC4gTVVTVCBkaWZmZXIgZnJvbSB0aGUgcG9ydCBMb2cxMHgncyBvd24gT1RMUC9nUlBDIGlucHV0IGxpc3RlbnMgb24gKGRlZmF1bHQgYDQzMTdgKSBvciB0aGUgdHdvIHdvdWxkIGNvbGxpZGUgb24gdGhlIHNhbWUgbGlzdGVuZXIuIChEZWZhdWx0OiAyNDIyNSkiLAogICAgICAgICAgICAgICJkZWZhdWx0IiA6ICIyNDIyNSIKICAgICAgICAgICAgfSwKICAgICAgICAgICAgImVuY29kZVR5cGUiIDogewogICAgICAgICAgICAgICJ0eXBlIiA6IFsKICAgICAgICAgICAgICAgICJzdHJpbmciLAogICAgICAgICAgICAgICAgIm51bGwiCiAgICAgICAgICAgICAgXSwKICAgICAgICAgICAgICAibWFya2Rvd25EZXNjcmlwdGlvbiIgOiAiT3V0cHV0IGZvcm1hdCB3aGVuIG91dHB1dEZpZWxkcyBhcmUgc2V0LiBQb3NzaWJsZSB2YWx1ZXM6IFtqc29uLCBkZWxpbWl0ZWRdXG5cblNwZWNpZmllcyBob3cgdGhlIGNvbWJpbmVkIG91dHB1dCAobWFpbiBldmVudCBmaWVsZCBwbHVzIG91dHB1dEZpZWxkcykgaXMgZW5jb2RlZCB3aGVuIHdyaXRpbmcgYmFjayB0byB0aGUgT3BlblRlbGVtZXRyeSBDb2xsZWN0b3IuIFBvc3NpYmxlIHZhbHVlczogLSAqKmpzb24qKjogZm9ybWF0cyBhbGwgZmllbGRzIGFzIGEgSlNPTiBvYmplY3QgLSAqKmRlbGltaXRlZCoqOiBmb3JtYXRzIGZpZWxkIHZhbHVlcyBzZXBhcmF0ZWQgYnkgdGhlIG91dHB1dCBkZWxpbWl0ZXIgT25seSB0YWtlcyBlZmZlY3Qgd2hlbiBvdGVsQ29sbGVjdG9yT3V0cHV0RmllbGRzIGlzIHNldC4gKERlZmF1bHQ6IGRlbGltaXRlZCkiLAogICAgICAgICAgICAgICJkZWZhdWx0IiA6ICJkZWxpbWl0ZWQiCiAgICAgICAgICAgIH0KICAgICAgICAgIH0KICAgICAgICB9CiAgICAgIH0KICAgIH0KICB9LAogICJhZGRpdGlvbmFsUHJvcGVydGllcyIgOiB0cnVlCn0=
# ๐โ 'run' OpenTelemetry Collector receiver configuration
#
# To learn more see https://doc.log10x.com/run/input/forwarder/otel-collector/
tenx: run
# =============================== Dependencies ================================
include:
- run/input/forwarder/config.yaml
- run/modules/input/forwarder/otel-collector
# ====================== OpenTelemetry Collector Options ======================
otelCollector:
# ----------------------------- Input Options -----------------------------
input:
# 'port' specifies the TCP port to listen on for OTLP/gRPC events from the
# OpenTelemetry Collector's `otlp` exporter.
port: 4317
# 'messageField' is the name of the JSON field on the rendered record
# carrying the log line text.
messageField: body
# ----------------------------- Output Options ----------------------------
output:
# 'host' specifies the TCP host of the OpenTelemetry Collector's `otlp`
# receiver receiving processed events from Log10x.
host: 127.0.0.1
# 'port' specifies the TCP port of the Collector's `otlp` receiver. MUST
# differ from input.port or the two would collide on the same listener.
port: 24225
# 'encodeType' is the on-wire encoding for the rendered record.
# - 'delimited': each top-level field of the rendered record becomes an
# attribute on the returning OTLP LogRecord; the `body` field is lifted
# into LogRecord.body so the message round-trips byte-for-byte.
# - 'json': the whole record is wrapped as one stringified field.
encodeType: delimited
Options
Specify the options below to configure the OpenTelemetry Collector:
| Name | Description | Category |
|---|---|---|
| otelCollectorInputPort | TCP port to listen on for OTLP/gRPC events from the OpenTelemetry Collector | Input |
| otelCollectorInputMessageField | Dotted JSON path to the field carrying the log line text | Input |
| otelCollectorOutputHost | TCP host of the OpenTelemetry Collector's OTLP/gRPC receiver | Output |
| otelCollectorOutputPort | TCP port of the OpenTelemetry Collector's OTLP/gRPC receiver | Output |
| otelCollectorOutputEncodeType | Output format when outputFields are set. Possible values: [json, delimited] | Output |
Input
otelCollectorInputPort
TCP port to listen on for OTLP/gRPC events from the OpenTelemetry Collector.
| Type | Default | Category |
|---|---|---|
| String | 4317 | Input |
TCP port where Log10x listens for OTLP/gRPC log records from the
OpenTelemetry Collector's otlp exporter. Match this against the
endpoint of your Collector exporter.
otelCollectorInputMessageField
Dotted JSON path to the field carrying the log line text.
| Type | Default | Category |
|---|---|---|
| String | body | Input |
Top-level JSON field name on the input record that carries the actual
log line text. The default (body) matches the flattened shape the
input emits for plain-text OTLP bodies. Set to a different field name
if your Collector pipeline copies the log line into a different
attribute before exporting (e.g. an <attr_key> set via a transform).
Output
otelCollectorOutputHost
TCP host of the OpenTelemetry Collector's OTLP/gRPC receiver.
| Type | Default | Category |
|---|---|---|
| String | 127.0.0.1 | Output |
Hostname or IP where the OpenTelemetry Collector's otlp receiver is
listening for processed events from Log10x. Pairs with
otelCollectorOutputPort.
otelCollectorOutputPort
TCP port of the OpenTelemetry Collector's OTLP/gRPC receiver.
| Type | Default | Category |
|---|---|---|
| String | 24225 | Output |
TCP port where the OpenTelemetry Collector's otlp receiver is
listening for processed events from Log10x. MUST differ from the port
Log10x's own OTLP/gRPC input listens on (default 4317) or the two
would collide on the same listener.
otelCollectorOutputEncodeType
Output format when outputFields are set. Possible values: [json, delimited].
| Type | Default | Category |
|---|---|---|
| String | delimited | Output |
Specifies how the combined output (main event field plus outputFields) is encoded when writing back to the OpenTelemetry Collector. Possible values:
- json: formats all fields as a JSON object
- delimited: formats field values separated by the output delimiter Only takes effect when otelCollectorOutputFields is set.
This module is defined in otel-collector/module.yaml.