Redacting sensitive attributes in the OpenTelemetry Collector keeps secrets, personal identifiers, and payment-like values out of exported telemetry. The Collector can scrub records after receivers accept them and before exporters send them to an observability backend.
The redaction processor in the Collector Contrib distribution removes attributes that are not explicitly allowed and masks values that match blocked regular expressions. It belongs in the pipeline before the exporter that would otherwise send the original span, log, or metric datapoint.
Use an allowlist that keeps operational fields such as service.name and http.route while removing risky custom fields. Keep the processor summary set to debug only while testing because summary attributes can reveal the names of removed keys; use info or silent for normal traffic.
$ sudoedit /etc/otelcol-contrib/config.yaml
Use the path and binary name from the running distribution. The redaction processor is included in otelcol-contrib and the Kubernetes distribution, not every custom Collector build.
receivers: otlp: protocols: http: endpoint: 0.0.0.0:4318 processors: redaction: allow_all_keys: false allowed_keys: - service.name - http.route - description blocked_values: - "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}" - "4[0-9]{12}(?:[0-9]{3})?" summary: debug exporters: debug: verbosity: detailed service: pipelines: traces: receivers: [otlp] processors: [redaction] exporters: [debug]
allow_all_keys: false makes the processor fail closed. Only keys listed under allowed_keys remain, and blocked_values masks matching substrings inside allowed attribute values.
debug exporter output and summary: debug are for validation only. Both can write telemetry details or removed key names to Collector logs.
$ 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.
$ otelcol-contrib --config=/etc/otelcol-contrib/config.yaml
2026-06-18T07:29:33.165Z info Starting HTTP server {"otelcol.component.id":"otlp","otelcol.component.kind":"receiver","endpoint":"[::]:4318"}
2026-06-18T07:29:33.165Z info Everything is ready. Begin running and processing data.
For a packaged service, restart the otelcol-contrib unit and read the same startup lines from the service logs.
{
"resourceSpans": [
{
"resource": {
"attributes": [
{
"key": "service.name",
"value": {
"stringValue": "checkout-api"
}
}
]
},
"scopeSpans": [
{
"scope": {
"name": "manual-redaction-smoke"
},
"spans": [
{
"traceId": "5b8efff798038103d269b633813fc60c",
"spanId": "eee19b7ec3c1b174",
"name": "checkout",
"kind": 1,
"startTimeUnixNano": "1717260000000000000",
"endTimeUnixNano": "1717260001000000000",
"attributes": [
{
"key": "http.route",
"value": {
"stringValue": "/checkout"
}
},
{
"key": "description",
"value": {
"stringValue": "customer alice@example.com paid with 4111111111111111"
}
},
{
"key": "user.email",
"value": {
"stringValue": "alice@example.com"
}
},
{
"key": "session.token",
"value": {
"stringValue": "Bearer eyJhbGciOiJIUzI1NiJ9.example"
}
}
]
}
]
}
]
}
]
}
The payload uses synthetic values only. service.name and http.route should remain, description should be masked, and user.email plus session.token should be removed.
$ 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 redaction result appears in the Collector terminal because the pipeline exports to debug.
2026-06-18T07:29:54.707Z info ResourceSpans #0
Resource attributes:
-> service.name: Str(checkout-api)
ScopeSpans #0
InstrumentationScope manual-redaction-smoke
Span #0
Trace ID : 5b8efff798038103d269b633813fc60c
ID : eee19b7ec3c1b174
Name : checkout
Attributes:
-> http.route: Str(/checkout)
-> description: Str(customer **** paid with ****)
-> redaction.redacted.keys: Str(session.token,user.email)
-> redaction.redacted.count: Int(2)
-> redaction.masked.keys: Str(description)
-> redaction.masked.count: Int(1)
service.name and http.route passed through, sensitive substrings inside description were masked, and the unapproved session.token and user.email attributes were removed.
processors: redaction: allow_all_keys: false allowed_keys: - service.name - http.route - description blocked_values: - "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}" - "4[0-9]{12}(?:[0-9]{3})?" summary: silent service: pipelines: traces: receivers: [otlp] processors: [redaction] exporters: [otlp]
Keep redaction before every exporter that could send the original data. Add the processor to metrics and logs pipelines separately when those signals can carry sensitive attributes.