Routing telemetry by attributes in the OpenTelemetry Collector sends matching records to different downstream pipelines before they reach a backend. It is useful when production traffic, tenant-specific data, or selected services need separate exporters while unmatched telemetry still follows a default path.
The routing connector sits between Collector pipelines as an exporter on the incoming pipeline and a receiver on the routed pipelines. Its route table evaluates OTTL conditions against resource, span, metric, datapoint, log, or request context, then forwards matching telemetry to the pipeline names listed for that route.
A local smoke test can route OTLP/HTTP log records with deployment.environment.name set to production to debug/prod and send all other records to debug/default. Replace the debug exporters with real backend exporters after the split is proven because detailed debug output can contain service names, attributes, and log bodies.
$ sudoedit /etc/otelcol-contrib/config.yaml
The routing connector is available in Collector builds that include contrib connectors, such as otelcol-contrib or a custom distribution built with that connector.
receivers: otlp: protocols: http: endpoint: 0.0.0.0:4318 exporters: debug/prod: verbosity: detailed debug/default: verbosity: detailed connectors: routing: default_pipelines: [logs/default] table: - context: resource condition: 'attributes["deployment.environment.name"] == "production"' pipelines: [logs/prod] service: telemetry: logs: level: info pipelines: logs/in: receivers: [otlp] exporters: [routing] logs/prod: receivers: [routing] exporters: [debug/prod] logs/default: receivers: [routing] exporters: [debug/default]
logs/in receives the original data and exports it to the connector. logs/prod and logs/default receive from the connector, so each routed pipeline can use its own exporters.
$ otelcol-contrib validate --config=/etc/otelcol-contrib/config.yaml
No output with a zero exit status means the receiver, connector, exporters, and pipeline references are valid for the Collector build.
$ otelcol-contrib --config=/etc/otelcol-contrib/config.yaml
2026-06-18T07:39:47.586Z info Starting HTTP server {"otelcol.component.id":"otlp","otelcol.component.kind":"receiver","endpoint":"[::]:4318"}
2026-06-18T07:39:47.586Z info Everything is ready. Begin running and processing data.
Leave this terminal open while sending the smoke payloads from another terminal.
{
"resourceLogs": [
{
"resource": {
"attributes": [
{
"key": "service.name",
"value": {
"stringValue": "checkout-api"
}
},
{
"key": "deployment.environment.name",
"value": {
"stringValue": "production"
}
}
]
},
"scopeLogs": [
{
"scope": {
"name": "manual-routing-smoke"
},
"logRecords": [
{
"timeUnixNano": "1717260000000000000",
"severityText": "INFO",
"body": {
"stringValue": "route smoke production"
}
}
]
}
]
}
]
}
$ curl --silent --show-error --include --request POST http://127.0.0.1:4318/v1/logs \
--header 'Content-Type: application/json' \
--data @log-production.json
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 21
{"partialSuccess":{}}
2026-06-18T07:40:01.473Z info Logs {"otelcol.component.id":"debug/prod","otelcol.component.kind":"exporter","otelcol.signal":"logs","resource logs":1,"log records":1}
Resource attributes:
-> service.name: Str(checkout-api)
-> deployment.environment.name: Str(production)
InstrumentationScope manual-routing-smoke
LogRecord #0
SeverityText: INFO
Body: Str(route smoke production)
The debug/prod component ID proves that the production resource attribute matched the route table entry.
{
"resourceLogs": [
{
"resource": {
"attributes": [
{
"key": "service.name",
"value": {
"stringValue": "checkout-api"
}
},
{
"key": "deployment.environment.name",
"value": {
"stringValue": "staging"
}
}
]
},
"scopeLogs": [
{
"scope": {
"name": "manual-routing-smoke"
},
"logRecords": [
{
"timeUnixNano": "1717260001000000000",
"severityText": "INFO",
"body": {
"stringValue": "route smoke staging"
}
}
]
}
]
}
]
}
$ curl --silent --show-error --include --request POST http://127.0.0.1:4318/v1/logs \
--header 'Content-Type: application/json' \
--data @log-staging.json
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 21
{"partialSuccess":{}}
2026-06-18T07:40:01.499Z info Logs {"otelcol.component.id":"debug/default","otelcol.component.kind":"exporter","otelcol.signal":"logs","resource logs":1,"log records":1}
Resource attributes:
-> service.name: Str(checkout-api)
-> deployment.environment.name: Str(staging)
InstrumentationScope manual-routing-smoke
LogRecord #0
SeverityText: INFO
Body: Str(route smoke staging)
The debug/default component ID proves that telemetry without a matching production value used default_pipelines.
exporters: otlp/prod: endpoint: telemetry-prod.example.net:4317 tls: insecure: false otlp/default: endpoint: telemetry-default.example.net:4317 tls: insecure: false service: pipelines: logs/prod: receivers: [routing] exporters: [otlp/prod] logs/default: receivers: [routing] exporters: [otlp/default]
Keep debug exporters and verbosity: detailed out of normal traffic unless logs are protected and the output is intentionally temporary.
$ rm log-production.json log-staging.json