Snippets · Processors
Set resource attributes the sender left out
Dashboards and alerts group by deployment.environment.name and service.namespace, and not every SDK or source sets them. A collector that serves one environment can stamp them on, in the resource context of the transform processor, as a default for whatever arrives without one.
- Category
- Collection
- Source
- OTLP
- Components
- otlpbatchmemory_limitertransformotlphttp
- Destination
- OTLP/HTTP endpoint
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
# Stamps every record with the environment and namespace it came from, but
# only where the sender did not set them already.
transform/set-resource:
error_mode: ignore
log_statements:
- context: resource
statements:
- set(attributes["deployment.environment.name"], "production") where attributes["deployment.environment.name"] == nil
- set(attributes["service.namespace"], "shop") where attributes["service.namespace"] == nil
trace_statements:
- context: resource
statements:
- set(attributes["deployment.environment.name"], "production") where attributes["deployment.environment.name"] == nil
- set(attributes["service.namespace"], "shop") where attributes["service.namespace"] == nil
metric_statements:
- context: resource
statements:
- set(attributes["deployment.environment.name"], "production") where attributes["deployment.environment.name"] == nil
- set(attributes["service.namespace"], "shop") where attributes["service.namespace"] == nil
batch:
timeout: 5s
exporters:
otlphttp:
endpoint: https://your-backend:4318
service:
pipelines:
logs:
receivers: [otlp]
processors: [memory_limiter, transform/set-resource, batch]
exporters: [otlphttp]
metrics:
receivers: [otlp]
processors: [memory_limiter, transform/set-resource, batch]
exporters: [otlphttp]
traces:
receivers: [otlp]
processors: [memory_limiter, transform/set-resource, 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/set-resource in the collector config:
error_mode: ignore
log_statements:
- context: resource
statements:
- set(attributes["deployment.environment.name"], "production") where attributes["deployment.environment.name"] == nil
- set(attributes["service.namespace"], "shop") where attributes["service.namespace"] == nil
trace_statements:
- context: resource
statements:
- set(attributes["deployment.environment.name"], "production") where attributes["deployment.environment.name"] == nil
- set(attributes["service.namespace"], "shop") where attributes["service.namespace"] == nil
metric_statements:
- context: resource
statements:
- set(attributes["deployment.environment.name"], "production") where attributes["deployment.environment.name"] == nil
- set(attributes["service.namespace"], "shop") where attributes["service.namespace"] == 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.
2 log records in, 2 log records out
Before
- body
- nothing set by the SDK
- resource.service.name
- checkout
After
- body
- nothing set by the SDK
- resource.service.name
- checkout
- resource.deployment.environment.name
- production
- resource.service.namespace
- shop
Before
- body
- SDK already says staging
- resource.service.name
- checkout
- resource.deployment.environment.name
- staging
After
- body
- SDK already says staging
- resource.service.name
- checkout
- resource.deployment.environment.name
- staging
- resource.service.namespace
- shop
The gotcha
Without the where clause, set() overwrites. In our test the guarded config left a record's staging environment alone; the same statements without where turned it into production. Guard every default with == nil, or the collector silently rewrites what the application told you.
Used in these pipelines
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
- Kubernetes pod logs to Loki, enriched with deployment and clusterEvery pod's logs into Loki over OTLP, labelled with namespace, pod, container, deployment, node and cluster.
- Receive OTLP over gRPC and HTTPThe front door: accept OTLP from SDKs and other collectors, on both transports, for all three signals.
- Parse Apache access logs into semantic conventionsTurn Apache combined-format access log lines into semantic-convention attributes, with typed status codes and sizes.
- Parse nginx access logs into semantic conventionsTurn nginx combined-format access log lines into OpenTelemetry semantic-convention attributes, with typed status codes.