LinkMesh

Search docs, blog and changelog

ENDE

Snippets · Processors

Enforce required resource attributes (semantic conventions)

Telemetry nobody can attribute is noise you pay to store: dashboards group by service and environment, and a record without them lands in no team's view. This filter processor drops any log record, span or metric whose resource lacks one of the three attributes, so the gap shows up as missing data at the source instead of as unowned data in the backend.

Signals
LogsMetricsTraces
Category
Compliance
Source
OTLP
Components
otlpbatchfiltermemory_limiterotlphttp
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
  # Drops any record whose resource lacks one of the three attributes every
  # dashboard and alert here groups by. A record is dropped when ANY condition
  # is true.
  filter/enforce-semconv:
    error_mode: ignore
    logs:
      log_record:
        - resource.attributes["service.name"] == nil
        - resource.attributes["service.namespace"] == nil
        - resource.attributes["deployment.environment.name"] == nil
    traces:
      span:
        - resource.attributes["service.name"] == nil
        - resource.attributes["service.namespace"] == nil
        - resource.attributes["deployment.environment.name"] == nil
    metrics:
      metric:
        - resource.attributes["service.name"] == nil
        - resource.attributes["service.namespace"] == nil
        - resource.attributes["deployment.environment.name"] == nil
  batch:
    timeout: 5s

exporters:
  otlphttp:
    endpoint: https://your-backend:4318

service:
  pipelines:
    logs:
      receivers: [otlp]
      processors: [memory_limiter, filter/enforce-semconv, batch]
      exporters: [otlphttp]
    traces:
      receivers: [otlp]
      processors: [memory_limiter, filter/enforce-semconv, batch]
      exporters: [otlphttp]
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, filter/enforce-semconv, batch]
      exporters: [otlphttp]

LinkMesh

LinkMesh has no built-in template for this job. Add it as a custom processor in raw mode, with filter as the component type and this body - the same lines as under filter/enforce-semconv in the collector config:

Custom processor body (filter)
error_mode: ignore
logs:
  log_record:
    - resource.attributes["service.name"] == nil
    - resource.attributes["service.namespace"] == nil
    - resource.attributes["deployment.environment.name"] == nil
traces:
  span:
    - resource.attributes["service.name"] == nil
    - resource.attributes["service.namespace"] == nil
    - resource.attributes["deployment.environment.name"] == nil
metrics:
  metric:
    - resource.attributes["service.name"] == nil
    - resource.attributes["service.namespace"] == nil
    - resource.attributes["deployment.environment.name"] == nil

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.

4 log records in, 1 log record out

  1. Before

    body
    complete
    resource.service.name
    shop
    resource.service.namespace
    web
    resource.deployment.environment.name
    prod

    After

    body
    complete
    resource.service.name
    shop
    resource.service.namespace
    web
    resource.deployment.environment.name
    prod
  2. Before

    body
    no namespace
    resource.service.name
    shop
    resource.deployment.environment.name
    prod

    After

    Dropped

  3. Before

    body
    old environment key
    resource.service.name
    shop
    resource.service.namespace
    web
    resource.deployment.environment
    prod

    After

    Dropped

  4. Before

    body
    nothing set

    After

    Dropped

The gotcha

The OpenTelemetry semantic conventions renamed deployment.environment to deployment.environment.name, and anything still sending the old key gets dropped by this filter: in our test a record carrying deployment.environment, but not the new key, was dropped along with the incomplete ones - 4 records in, 1 out - and the collector log said nothing about any of the three drops.

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: Filter records on a route