Snippets · Processors
Rename old HTTP attributes to current semantic conventions
Instrumentation written before the HTTP semantic conventions were stabilised sends http.method, http.status_code and http.target; current instrumentation sends http.request.method, http.response.status_code, url.path and url.query. Mixed, every dashboard needs both. This config renames the old names on spans and log records, splitting http.target into path and query.
OpenTelemetry Collector
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
processors:
memory_limiter:
check_interval: 1s
limit_percentage: 80
spike_limit_percentage: 25
# Moves pre-1.21 HTTP attribute names to the current semantic conventions,
# on spans and log records, without touching records that already use them.
# http.target held path and query together; they become url.path and
# url.query.
transform/rename:
error_mode: ignore
trace_statements:
- context: span
statements:
- set(attributes["http.request.method"], attributes["http.method"]) where attributes["http.method"] != nil
- set(attributes["http.response.status_code"], attributes["http.status_code"]) where attributes["http.status_code"] != nil
- set(attributes["url.path"], attributes["http.target"]) where attributes["http.target"] != nil
- set(attributes["url.query"], attributes["http.target"]) where attributes["http.target"] != nil and IsMatch(attributes["http.target"], "\\?")
- replace_pattern(attributes["url.path"], "\\?.*$", "") where attributes["http.target"] != nil
- replace_pattern(attributes["url.query"], "^[^?]*\\?", "") where attributes["http.target"] != nil
- delete_matching_keys(attributes, "^http\\.(method|status_code|target)$")
log_statements:
- context: log
statements:
- set(attributes["http.request.method"], attributes["http.method"]) where attributes["http.method"] != nil
- set(attributes["http.response.status_code"], attributes["http.status_code"]) where attributes["http.status_code"] != nil
- set(attributes["url.path"], attributes["http.target"]) where attributes["http.target"] != nil
- set(attributes["url.query"], attributes["http.target"]) where attributes["http.target"] != nil and IsMatch(attributes["http.target"], "\\?")
- replace_pattern(attributes["url.path"], "\\?.*$", "") where attributes["http.target"] != nil
- replace_pattern(attributes["url.query"], "^[^?]*\\?", "") where attributes["http.target"] != nil
- delete_matching_keys(attributes, "^http\\.(method|status_code|target)$")
batch:
timeout: 5s
exporters:
otlphttp:
endpoint: https://your-backend:4318
service:
pipelines:
logs:
receivers: [otlp]
processors: [memory_limiter, transform/rename, batch]
exporters: [otlphttp]
traces:
receivers: [otlp]
processors: [memory_limiter, transform/rename, batch]
exporters: [otlphttp]
LinkMesh
LinkMesh has no built-in template for this job. Add it as a custom processor in raw mode, with transform as the component type and this body - the same lines as under transform/rename in the collector config:
error_mode: ignore
trace_statements:
- context: span
statements:
- set(attributes["http.request.method"], attributes["http.method"]) where attributes["http.method"] != nil
- set(attributes["http.response.status_code"], attributes["http.status_code"]) where attributes["http.status_code"] != nil
- set(attributes["url.path"], attributes["http.target"]) where attributes["http.target"] != nil
- set(attributes["url.query"], attributes["http.target"]) where attributes["http.target"] != nil and IsMatch(attributes["http.target"], "\\?")
- replace_pattern(attributes["url.path"], "\\?.*$", "") where attributes["http.target"] != nil
- replace_pattern(attributes["url.query"], "^[^?]*\\?", "") where attributes["http.target"] != nil
- delete_matching_keys(attributes, "^http\\.(method|status_code|target)$")
log_statements:
- context: log
statements:
- set(attributes["http.request.method"], attributes["http.method"]) where attributes["http.method"] != nil
- set(attributes["http.response.status_code"], attributes["http.status_code"]) where attributes["http.status_code"] != nil
- set(attributes["url.path"], attributes["http.target"]) where attributes["http.target"] != nil
- set(attributes["url.query"], attributes["http.target"]) where attributes["http.target"] != nil and IsMatch(attributes["http.target"], "\\?")
- replace_pattern(attributes["url.path"], "\\?.*$", "") where attributes["http.target"] != nil
- replace_pattern(attributes["url.query"], "^[^?]*\\?", "") where attributes["http.target"] != nil
- delete_matching_keys(attributes, "^http\\.(method|status_code|target)$")
How custom components are validated and rolled out: create a custom processor.
Download config.yamlValidated with otelcol-contrib v0.161.0 on 2026-10-26
Complete config files, each checked with the collector's own validate command (otelcol-contrib v0.161.0). Replace https://your-backend:4318 with your own OTLP endpoint before you run it.
Before and after
Example telemetry in, and what this exact config sent out when we ran it in otelcol-contrib v0.161.0. CI re-runs it on every change and fails if the output differs. Changed values are highlighted.
2 spans in, 2 spans out
Before
- span
- GET /invoices
- attributes.http.method
- GET
- attributes.http.status_code
- 200
- attributes.http.target
- /invoices?year=2026
- resource.service.name
- legacy-billing
After
- span
- GET /invoices
- attributes.http.request.method
- GET
- attributes.http.response.status_code
- 200
- attributes.url.path
- /invoices
- attributes.url.query
- year=2026
- resource.service.name
- legacy-billing
Before
- span
- POST /orders
- attributes.http.request.method
- POST
- attributes.http.response.status_code
- 201
- attributes.url.path
- /orders
- resource.service.name
- checkout
After
- span
- POST /orders
- attributes.http.request.method
- POST
- attributes.http.response.status_code
- 201
- attributes.url.path
- /orders
- resource.service.name
- checkout
The gotcha
Guard every rename with where <old> != nil. In our test the same statements without it set http.request.method, http.response.status_code and url.path to null on the span that already used the current names - the rename wiped the data it was meant to align with.
Manage this across a fleet
LinkMesh manages collector configuration across a fleet from one self-hosted control plane, so a change like this is made once rather than host by host. The first 25 Collectors are free after a no-card registration in the OpenSight Customer Portal (5 without one).
Doing this in LinkMesh: Processors
Related snippets
- Receive logs from Fluent Bit and Fluentd (forward)Take logs from existing Fluent Bit or Fluentd agents over the forward protocol, without replacing them first.
- Scrape Prometheus metrics endpointsScrape any Prometheus /metrics endpoint - node_exporter, an application, an exporter - into OpenTelemetry metrics.
- Receive StatsD metricsAccept StatsD counters, gauges and timers from applications that already emit them, and send them on as OpenTelemetry metrics.
- Drop sensitive attributes before exportDelete named attributes, and every captured HTTP request header, from logs, spans and metrics before they are exported.