> ## Documentation Index
> Fetch the complete documentation index at: https://otel.fyi/llms.txt
> Use this file to discover all available pages before exploring further.

# Routing

> OpenTelemetry connector for Routing

# Routing Connector

![Status](https://img.shields.io/badge/status-alpha-red)

**Available in:** `contrib`, `k8s`

**Maintainers:** [@TylerHelmuth](https://github.com/TylerHelmuth), [@evan-bradley](https://github.com/evan-bradley), [@edmocosta](https://github.com/edmocosta), [@bogdandrutu](https://github.com/bogdandrutu), [@mwear](https://github.com/mwear)

**Source:** [opentelemetry-collector-contrib](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/connector/routingconnector)

## Overview

Routes logs, metrics or traces based on resource attributes to specific pipelines using [OpenTelemetry Transformation Language (OTTL)](../../pkg/ottl/README.md) statements as routing conditions.

## Configuration

If you are not already familiar with connectors, you may find it helpful to first visit the [Connectors README].

The following settings are available:

* `table (required)`: the routing table for this connector.
* `table.condition`: the routing condition provided as the [OTTL] condition. Required if `table.statement` is not provided. Use context-qualified paths (e.g., `resource.attributes["key"]`, `span.attributes["key"]`) to automatically infer the context (see [Context Inference](#context-inference)).
* `table.statement`: the routing condition provided as the [OTTL] statement. Required if `table.condition` is not provided. Generally `condition` is preferred since it is more terse. May not be used with the deprecated `request` context.
* `table.context (optional)`: the [OTTL Context](#supported-contexts) in which the condition/statement will be evaluated. **Deprecated:** `request` — use `otelcol.client.metadata` or `otelcol.grpc.metadata` paths instead (see [Limitations](#limitations)). In most cases this field should be omitted; the context is inferred automatically from context-qualified paths. If specified, it takes precedence over inference.
* `table.action (optional, default: move)`: determines what happens to the data when the routing condition is met. Valid values are `move` and `copy`.
  * `move`: Matched data is moved to the target pipeline(s) and removed from subsequent route evaluation. This is the default behavior.
  * `copy`: Matched data is copied to the target pipeline(s) but remains available for evaluation by subsequent routes. This allows the same data to be routed to multiple pipelines.
* `table.pipelines (required)`: the list of pipelines to use when the routing condition is met.
* `default_pipelines (optional)`: contains the list of pipelines to use when a record does not meet any of specified conditions.
* `error_mode (optional)`: determines how errors returned from OTTL statements are handled. Valid values are `propagate`, `ignore` and `silent`. If `ignore` or `silent` is used and a statement's condition has an error then the payload will be routed to the default pipelines. When `silent` is used the error is not logged. If not supplied, `propagate` is used.

### Context Inference

The routing connector supports OTTL context inference, allowing you to write clearer and more maintainable routing conditions using context-qualified paths. This is the recommended approach for specifying routing conditions.

```yaml theme={null}
- condition: resource.attributes["env"] == "prod"
  pipelines: [logs/prod]
- condition: span.attributes["http.method"] == "GET"
  pipelines: [traces/http]
- condition: log.severity_text == "ERROR"
  pipelines: [logs/errors]
```

This approach makes it immediately clear which attributes you're accessing without needing a separate `context` field.

### Supported contexts

| Context     | Path prefix  | Example                                                                          |
| ----------- | ------------ | -------------------------------------------------------------------------------- |
| [Resource]  | `resource.`  | `resource.attributes["service.name"]`                                            |
| [Span]      | `span.`      | `span.attributes["http.method"]`                                                 |
| [Log]       | `log.`       | `log.body`, `log.attributes["level"]`                                            |
| [Metric]    | `metric.`    | `metric.name`                                                                    |
| [Datapoint] | `datapoint.` | `datapoint.attributes["host"]`                                                   |
| [OtelCol]   | `otelcol.`   | `otelcol.client.metadata["X-Tenant"][0]`, `otelcol.grpc.metadata["x-tenant"][0]` |

[resource]: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/pkg/ottl/contexts/ottlresource/README.md

[span]: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/pkg/ottl/contexts/ottlspan/README.md

[metric]: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/pkg/ottl/contexts/ottlmetric/README.md

[datapoint]: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/pkg/ottl/contexts/ottldatapoint/README.md

[log]: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/pkg/ottl/contexts/ottllog/README.md

[otelcol]: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/pkg/ottl/contexts/ottlotelcol/README.md

The `otelcol.client.metadata` and `otelcol.grpc.metadata` paths provide access to incoming HTTP and gRPC request metadata respectively, and are valid in all signal contexts.

### Limitations

* **Deprecated:** The `request` context is deprecated. Use `otelcol.client.metadata["key"]` (HTTP/client metadata) or `otelcol.grpc.metadata["key"]` (gRPC metadata) paths instead. These are supported in all signal contexts. A warning is logged when the `request` context is used. The `request` context only supports the `condition` field with a very limited grammar: `request["key"] == "value"` or `request["key"] != "value"`.
* When using context inference without an explicit `context` field, the inferred context must be compatible with the pipeline signal type (e.g., `span` context can only be used in traces pipelines).

### Supported [OTTL] functions

* [Standard OTTL Converter Functions](../../pkg/ottl/ottlfuncs/README.md#converters)
* [delete\_key](../../pkg/ottl/ottlfuncs/README.md#delete_key)
* [delete\_matching\_keys](../../pkg/ottl/ottlfuncs/README.md#delete_matching_keys)

## Additional Settings

The full list of settings exposed for this connector are documented in [config.go](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/connector/routingconnector/config.go) with detailed sample configuration files:

* [logs](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/connector/routingconnector/testdata/config/logs.yaml)
* [metrics](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/connector/routingconnector/testdata/config/metrics.yaml)
* [traces](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/connector/routingconnector/testdata/config/traces.yaml)

## Examples

> \[!NOTE]
> The examples below use context-qualified paths, which is the recommended configuration style. The explicit `context` field is still supported for backward compatibility but is no longer the primary documentation style. See [Context Inference](#context-inference) for details.

### Route logs based on tenant

```yaml theme={null}
receivers:
    otlp:

exporters:
  file/other:
    path: ./other.log
  file/acme:
    path: ./acme.log
  file/ecorp:
    path: ./ecorp.log

connectors:
  routing:
    default_pipelines: [logs/other]
    table:
      - context: resource
        condition: otelcol.client.metadata["X-Tenant"][0] == "acme"
        pipelines: [logs/acme]
      - context: resource
        condition: otelcol.client.metadata["X-Tenant"][0] == "ecorp"
        pipelines: [logs/ecorp]

service:
  pipelines:
    logs/in:
      receivers: [otlp]
      exporters: [routing]
    logs/acme:
      receivers: [routing]
      exporters: [file/acme]
    logs/ecorp:
      receivers: [routing]
      exporters: [file/ecorp]
    logs/other:
      receivers: [routing]
      exporters: [file/other]
```

### Route logs based on region

```yaml theme={null}
receivers:
    otlp:

exporters:
  file/other:
    path: ./other.log
  file/east:
    path: ./east.log
  file/west:
    path: ./west.log

connectors:
  routing:
    default_pipelines: [logs/other]
    table:
      - condition: log.attributes["region"] == "east"
        pipelines: [logs/east]
      - condition: log.attributes["region"] == "west"
        pipelines: [logs/west]

service:
  pipelines:
    logs/in:
      receivers: [otlp]
      exporters: [routing]
    logs/east:
      receivers: [routing]
      exporters: [file/east]
    logs/west:
      receivers: [routing]
      exporters: [file/west]
    logs/other:
      receivers: [routing]
      exporters: [file/other]
```

### Route low-severity logs to cheap storage, remainder by service name

```yaml theme={null}
receivers:
    otlp:

exporters:
  file/cheap:
    path: ./cheap.log
  file/service1:
    path: ./service1-important.log
  file/service2:
    path: ./service2-important.log

connectors:
  routing:
    table:
      - condition: log.severity_number < SEVERITY_NUMBER_ERROR
        pipelines: [logs/cheap]
      - condition: resource.attributes["service.name"] == "service1"
        pipelines: [logs/service1]
      - condition: resource.attributes["service.name"] == "service2"
        pipelines: [logs/service2]

service:
  pipelines:
    logs/in:
      receivers: [otlp]
      exporters: [routing]
    logs/cheap:
      receivers: [routing]
      exporters: [file/cheap]
    logs/service1:
      receivers: [routing]
      exporters: [file/service1]
    logs/service2:
      receivers: [routing]
      exporters: [file/service2]
```

### Route low-severity logs to cheap storage, remainder by tenant

```yaml theme={null}
receivers:
    otlp:

exporters:
  file/cheap:
    path: ./cheap.log
  file/acme:
    path: ./acme.log
  file/ecorp:
    path: ./ecorp.log

connectors:
  routing:
    table:
      - condition: log.severity_number < SEVERITY_NUMBER_ERROR
        pipelines: [logs/cheap]
      - context: resource
        condition: otelcol.client.metadata["X-Tenant"][0] == "acme"
        pipelines: [logs/acme]
      - context: resource
        condition: otelcol.client.metadata["X-Tenant"][0] == "ecorp"
        pipelines: [logs/ecorp]

service:
  pipelines:
    logs/in:
      receivers: [otlp]
      exporters: [routing]
    logs/cheap:
      receivers: [routing]
      exporters: [file/cheap]
    logs/acme:
      receivers: [routing]
      exporters: [file/acme]
    logs/ecorp:
      receivers: [routing]
      exporters: [file/ecorp]
```

### Route all logs to an archive, while also routing errors using `action: copy`

Conditions with no OTTL paths (such as the literal `"true"`) cannot be inferred, so an explicit `context` field is required.

```yaml theme={null}
receivers:
    otlp:

exporters:
  file/archive:
    path: ./archive.log
  file/errors:
    path: ./errors.log
  file/other:
    path: ./other.log

connectors:
  routing:
    default_pipelines: [logs/other]
    table:
      - context: resource
        condition: "true"
        action: copy
        pipelines: [logs/archive]
      - condition: log.severity_number >= SEVERITY_NUMBER_ERROR
        pipelines: [logs/errors]

service:
  pipelines:
    logs/in:
      receivers: [otlp]
      exporters: [routing]
    logs/archive:
      receivers: [routing]
      exporters: [file/archive]
    logs/errors:
      receivers: [routing]
      exporters: [file/errors]
    logs/other:
      receivers: [routing]
      exporters: [file/other]
```

In this example:

* All logs are first copied to the archive pipeline (using `action: copy`), which means the original data remains available for subsequent route evaluation.
* Error logs are then moved to the errors pipeline (using the default `action: move`).
* Any remaining logs (non-errors) go to the default pipeline.

### Route traces to multiple pipelines using `action: copy`

```yaml theme={null}
receivers:
    otlp:

exporters:
  file/prod:
    path: ./prod.json
  file/high-latency:
    path: ./high-latency.json
  file/other:
    path: ./other.json

connectors:
  routing:
    default_pipelines: [traces/other]
    table:
      - condition: resource.attributes["env"] == "prod"
        action: copy
        pipelines: [traces/prod]
      - condition: span.attributes["http.duration_ms"] > 1000
        pipelines: [traces/high-latency]

service:
  pipelines:
    traces/in:
      receivers: [otlp]
      exporters: [routing]
    traces/prod:
      receivers: [routing]
      exporters: [file/prod]
    traces/high-latency:
      receivers: [routing]
      exporters: [file/high-latency]
    traces/other:
      receivers: [routing]
      exporters: [file/other]
```

In this example:

* Production traces are copied to the prod pipeline. Since `action: copy` is used, the traces remain available for subsequent evaluation.
* High-latency spans (>1000ms) are then moved to the high-latency pipeline. A production trace with high latency will appear in both the prod and high-latency pipelines.
* Remaining traces go to the default pipeline.

## `match_once`

The `match_once` field was deprecated as of `v0.116.0` and removed in `v0.120.0`.

The following examples demonstrate some strategies for migrating a configuration from `match_once`.

### Example without `default_pipelines`

If not using `default_pipelines`, you may be able to split the router into multiple parallel routers.
In the following example, the `"env"` and `"region"` are not directly related.

```yaml theme={null}
routing:
  match_once: false
  table:
    - condition: attributes["env"] == "prod"
       pipelines: [ logs/prod ]
    - condition: attributes["env"] == "dev"
       pipelines: [ logs/dev ]
    - condition: attributes["region"] == "east"
       pipelines: [ logs/east ]
    - condition: attributes["region"] == "west"
       pipelines: [ logs/west ]

service:
  pipelines:
    logs/in::exporters: [routing]
    logs/prod::receivers: [routing]
    logs/dev::receivers: [routing]
    logs/east::receivers: [routing]
    logs/west::receivers: [routing]
```

Therefore, the same behavior can be achieved using separate routers. Listing both routers in the pipeline configuration will
result in each receiving an independent handle to the data. The same data can then match routes in both routers.

```yaml theme={null}
routing/env:
  table:
    - condition: resource.attributes["env"] == "prod"
       pipelines: [ logs/prod ]
    - condition: resource.attributes["env"] == "dev"
       pipelines: [ logs/dev ]
routing/region:
  table:
    - condition: resource.attributes["region"] == "east"
       pipelines: [ logs/east ]
    - condition: resource.attributes["region"] == "west"
       pipelines: [ logs/west ]

service:
  pipelines:
    logs/in::exporters: [routing/env, routing/region]
    logs/prod::receivers: [routing/env]
    logs/dev::receivers: [routing/env]
    logs/east::receivers: [routing/region]
    logs/west::receivers: [routing/region]
```

### Example with `default_pipelines`

The following example demonstrates strategies for migrating from `match_once: true` while using `default_pipelines`.

```yaml theme={null}
routing:
  match_once: true
  default_pipelines: [ logs/default ]
  table:
    - condition: resource.attributes["env"] == "prod"
      pipelines: [ logs/prod ]
    - condition: resource.attributes["env"] == "dev"
      pipelines: [ logs/dev ]
    - condition: resource.attributes["region"] == "east"
      pipelines: [ logs/east ]
    - condition: resource.attributes["region"] == "west"
      pipelines: [ logs/west ]

service:
  pipelines:
    logs/in::exporters: [routing]
    logs/default::receivers: [routing]
    logs/prod::receivers: [routing]
    logs/dev::receivers: [routing]
    logs/east::receivers: [routing]
    logs/west::receivers: [routing]
```

If the number of routes are limited, you may be able to articulate a route for each combination of conditions. This avoids the need to change any pipelines.

```yaml theme={null}
routing:
  default_pipelines: [ logs/default ]
  table:
    - condition: resource.attributes["env"] == "prod" and resource.attributes["region"] == "east"
       pipelines: [ logs/prod, logs/east ]
    - condition: resource.attributes["env"] == "prod" and resource.attributes["region"] == "west"
       pipelines: [ logs/prod, logs/west ]
    - condition: resource.attributes["env"] == "dev" and resource.attributes["region"] == "east"
       pipelines: [ logs/dev, logs/east ]
    - condition: resource.attributes["env"] == "dev" and resource.attributes["region"] == "west"
       pipelines: [ logs/dev, logs/west ]

service:
  pipelines:
    logs/in::exporters: [routing]
    logs/default::receivers: [routing]
    logs/prod::receivers: [routing]
    logs/dev::receivers: [routing]
    logs/east::receivers: [routing]
    logs/west::receivers: [routing]
```

A more general solution is to use a layered approach. In this design, the first layer is a single router that sorts data according to whether it matches
*any route* or *no route*. This allows the second layer to work without `default_pipelines`. The downside to this approach is that the set of conditions
in the first and second layers must be kept in sync.

```yaml theme={null}

routing:
  default_pipelines: [ logs/default ]
  table: # all routes forward to second layer
    - condition: resource.attributes["env"] == "prod"
       pipelines: [ logs/env, logs/region ] 
    - condition: resource.attributes["env"] == "dev"
       pipelines: [ logs/env, logs/region ]
    - condition: resource.attributes["region"] == "east"
       pipelines: [ logs/env, logs/region ]
    - condition: resource.attributes["region"] == "west"
       pipelines: [ logs/env, logs/region ]

# Second layer routes logs based on environment and region
routing/env:
  table:
    - condition: resource.attributes["env"] == "prod"
       pipelines: [ logs/prod ]
    - condition: resource.attributes["env"] == "dev"
       pipelines: [ logs/dev ]
routing/region:
  table:
    - condition: resource.attributes["region"] == "east"
       pipelines: [ logs/east ]
    - condition: resource.attributes["region"] == "west"
       pipelines: [ logs/west ]

service:
  pipelines:
    logs/in::exporters: [routing]
    logs/prod::receivers: [routing/env]
    logs/dev::receivers: [routing/env]
    logs/east::receivers: [routing/region]
    logs/west::receivers: [routing/region]
```

[Connectors README]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/connector/README.md

[OTTL]: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/pkg/ottl/README.md

[OTTL Context]: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/pkg/ottl/LANGUAGE.md#contexts

***

*Last generated: 2026-07-06*
