Transforming attributes in the OpenTelemetry Collector normalizes telemetry before it leaves a pipeline. Use the transform processor when incoming spans, metrics, or logs carry missing resource metadata, older semantic convention names, or custom keys that need to match a backend query model.
The transform processor uses OpenTelemetry Transformation Language, or OTTL, statements inside signal sections such as trace_statements, metric_statements, and log_statements. Each statement targets a telemetry context, so explicit paths such as resource.attributes and span.attributes keep the rule tied to the field being changed.
A local smoke test can receive one OTLP/HTTP trace, add resource attributes, rename a span attribute from http.method to http.request.method, and derive a service.tier value from the route. Keep the debug exporter only while checking the result because detailed exporter output can write service names, routes, and other telemetry attributes to Collector logs.
$ sudoedit /etc/otelcol-contrib/config.yaml
The transform processor is included in the otelcol-contrib and Kubernetes Collector distributions. Custom Collector builds must include the processor before this configuration can load.
receivers: otlp: protocols: http: endpoint: 0.0.0.0:4318 processors: transform/attributes: error_mode: ignore trace_statements: - context: resource statements: - set(resource.attributes["service.namespace"], "shop") - set(resource.attributes["deployment.environment.name"], "staging") - context: span statements: - set(span.attributes["http.request.method"], span.attributes["http.method"]) where span.attributes["http.method"] != nil - delete_key(span.attributes, "http.method") - set(span.attributes["service.tier"], "frontend") where span.attributes["http.route"] == "/checkout" exporters: debug: verbosity: detailed service: pipelines: traces: receivers: [otlp] processors: [transform/attributes] exporters: [debug]
error_mode: ignore keeps processing the record when a statement fails. Use propagate only when dropping the affected payload is safer than exporting it with the original attributes.
$ otelcol-contrib validate --config=/etc/otelcol-contrib/config.yaml
No output with a zero exit status means the file parsed and the referenced receiver, processor, exporter, and pipeline exist in the Collector build.
$ sudo systemctl restart otelcol-contrib
For a foreground smoke test, run otelcol-contrib --config=/etc/otelcol-contrib/config.yaml in one terminal and leave it open while sending telemetry from another terminal.
{
"resourceSpans": [
{
"resource": {
"attributes": [
{
"key": "service.name",
"value": {
"stringValue": "checkout-api"
}
}
]
},
"scopeSpans": [
{
"scope": {
"name": "manual-transform-smoke"
},
"spans": [
{
"traceId": "5b8efff798038103d269b633813fc60c",
"spanId": "eee19b7ec3c1b174",
"name": "checkout",
"kind": 1,
"startTimeUnixNano": "1717260000000000000",
"endTimeUnixNano": "1717260001000000000",
"attributes": [
{
"key": "http.method",
"value": {
"stringValue": "POST"
}
},
{
"key": "http.route",
"value": {
"stringValue": "/checkout"
}
}
]
}
]
}
]
}
]
}
The payload starts with service.name, http.method, and http.route. The processor should add service.namespace and deployment.environment.name, rename http.method, and add service.tier.
$ curl --silent --show-error --include --request POST http://127.0.0.1:4318/v1/traces \
--header 'Content-Type: application/json' \
--data @trace.json
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 21
{"partialSuccess":{}}
The 200 OK response confirms that the receiver accepted the trace. The transformed fields appear in the Collector log because the test pipeline exports to debug.
$ journalctl -u otelcol-contrib --since "5 minutes ago"
Jun 18 07:44:48 host otelcol-contrib[1234]: ResourceSpans #0
Resource attributes:
-> service.name: Str(checkout-api)
-> service.namespace: Str(shop)
-> deployment.environment.name: Str(staging)
ScopeSpans #0
InstrumentationScope manual-transform-smoke
Span #0
Trace ID : 5b8efff798038103d269b633813fc60c
ID : eee19b7ec3c1b174
Name : checkout
Attributes:
-> http.request.method: Str(POST)
-> http.route: Str(/checkout)
-> service.tier: Str(frontend)
The old http.method key is absent, http.request.method is present, and the resource block contains the new namespace and deployment attributes.
exporters: otlp: endpoint: otel-gateway.example.com:4317 service: pipelines: traces: receivers: [otlp] processors: [transform/attributes] exporters: [otlp]
Review metric attribute transforms separately before changing metric names or labels. Removing or renaming metric-identifying attributes can split or merge time series in the backend.
$ rm trace.json