LinkMesh

Search docs, blog and changelog

ENDE

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.

Signals
LogsTraces
Category
Migration
Source
OTLP
Components
otlpbatchmemory_limitertransformotlphttp
Destination
OTLP/HTTP endpoint

OpenTelemetry Collector

OpenTelemetry Collector YAML
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:

Custom processor body (transform)
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

  1. 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
  2. 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