otel-ottl
OpenTelemetry Transformation Language (OTTL)
Use OTTL to transform, filter, and manipulate telemetry data inside the OpenTelemetry Collector — without changing application code.
Components that use OTTL
OTTL is not limited to the transform and filter processors. The following Collector components accept OTTL expressions in their configuration.
Processors
| Component | Use case |
|---|---|
| transform | Modify, enrich, or redact telemetry (set attributes, rename fields, truncate values) |
| filter | Drop telemetry entirely (discard metrics by name, drop spans by status, remove noisy logs) |
| attributes | Insert, update, delete, or hash resource and record attributes |
| span | Rename spans and set span status based on attribute values |
| tailsampling | Sample traces based on OTTL conditions (e.g., keep error traces, drop health checks) |
| cumulativetodelta | Convert cumulative metrics to delta temporality with OTTL-based metric selection |
| logdedup | Deduplicate log records using OTTL conditions |
| lookup | Enrich telemetry by looking up values from external tables using OTTL expressions |
Connectors
| Component | Use case |
|---|---|
| routing | Route telemetry to different pipelines based on OTTL conditions |
| count | Count spans, metrics, or logs matching OTTL conditions and emit as metrics |
| sum | Sum numeric values from telemetry matching OTTL conditions and emit as metrics |
| signaltometrics | Generate metrics from spans or logs using OTTL expressions for attribute extraction |
Receivers
| Component | Use case |
|---|---|
| hostmetrics | Filter host metrics at collection time using OTTL conditions |
OTTL syntax
Path expressions
Navigate telemetry data using dot notation:
span.name
span.attributes["http.method"]
resource.attributes["service.name"]
Contexts (first path segment) map to OpenTelemetry signal structures:
resource- Resource-level attributesscope- Instrumentation scopespan- Span data (traces)spanevent- Span eventsmetric- Metric metadatadatapoint- Metric data pointslog- Log records
Enumerations
Several fields accept int64 values exposed as global constants:
span.status.code == STATUS_CODE_ERROR
span.kind == SPAN_KIND_SERVER
Operators
| Category | Operators |
|---|---|
| Assignment | = |
| Comparison | ==, !=, >, <, >=, <= |
| Logical | and, or, not |
Functions
Converters (uppercase, pure functions that return values):
ToUpperCase(span.attributes["http.request.method"])
Substring(log.body.string, 0, 1024)
Concat(["prefix", span.attributes["request.id"]], "-")
IsMatch(metric.name, "^k8s\\..*$")
Editors (lowercase, functions with side-effects that modify data):
set(span.attributes["region"], "us-east-1")
delete_key(resource.attributes, "internal.key")
limit(log.attributes, 10, [])
Conditional statements
The where clause applies transformations conditionally:
span.attributes["db.statement"] = "REDACTED" where resource.attributes["service.name"] == "accounting"
Nil checks
OTTL uses nil for absence checking (not null):
resource.attributes["service.name"] != nil
Common patterns
Set attributes
set(resource.attributes["k8s.cluster.name"], "prod-aws-us-west-2")
Redact sensitive data
Replace known sensitive attributes with a fixed placeholder.
Always guard with a nil check to avoid creating the attribute when it does not exist.
set(span.attributes["http.request.header.authorization"], "REDACTED") where span.attributes["http.request.header.authorization"] != nil
Redact authorization and session headers
processors:
transform/redact-headers:
error_mode: ignore
trace_statements:
- context: span
statements:
- set(span.attributes["http.request.header.authorization"], "REDACTED") where span.attributes["http.request.header.authorization"] != nil
- set(span.attributes["http.request.header.cookie"], "REDACTED") where span.attributes["http.request.header.cookie"] != nil
- set(span.attributes["http.response.header.set-cookie"], "REDACTED") where span.attributes["http.response.header.set-cookie"] != nil
log_statements:
- context: log
statements:
- set(log.attributes["http.request.header.authorization"], "REDACTED") where log.attributes["http.request.header.authorization"] != nil
- set(log.attributes["http.request.header.cookie"], "REDACTED") where log.attributes["http.request.header.cookie"] != nil
Mask credit card numbers in log bodies
Use replace_pattern to replace patterns matching sensitive data while preserving the rest of the field.
For example, the following pattern matches 13–19 digit sequences (covering Visa, Mastercard, Amex, and others) and replaces them with a masked value.
processors:
transform/redact-credit-cards:
error_mode: ignore
log_statements:
- context: log
statements:
- replace_pattern(log.body["string"], "\\b(\\d{4})\\d{5,11}(\\d{4})\\b", "$$1****$$2")
Replace email addresses with a hash
Use a hash function to replace email addresses with a non-reversible identifier that still allows correlation across records.
processors:
transform/hash-emails:
error_mode: ignore
trace_statements:
- context: span
statements:
- set(span.attributes["user.email"], SHA256(span.attributes["user.email"])) where span.attributes["user.email"] != nil
log_statements:
- context: log
statements:
- set(log.attributes["user.email"], SHA256(log.attributes["user.email"])) where log.attributes["user.email"] != nil
Delete attributes that should never be exported
Use delete_key to remove attributes entirely rather than replacing them with a placeholder.
processors:
transform/delete-sensitive:
error_mode: ignore
trace_statements:
- context: span
statements:
- delete_key(span.attributes, "credit-card.number") where IsMatch(span.attributes["credit-card.number"], "(?i)(password|secret|token)")
log_statements:
- context: log
statements:
- delete_key(log.attributes, "request.body")
Drop log records containing sensitive data
Use the filter processor to drop entire log records that match a sensitive pattern, when redaction is not sufficient.
processors:
filter/drop-sensitive-logs:
error_mode: ignore
logs:
log_record:
- 'IsMatch(log.body["string"], "(?i)-----BEGIN (RSA |EC )?PRIVATE KEY-----")'
Pipeline placement
Place redaction processors after enrichment processors (resourcedetection, k8sattributes, resource) and before exporters.
Redaction must run after all attributes have been set, but before telemetry leaves the Collector.
See processor ordering for the full ordering guidance.
Application-level sanitization is the first line of defence.
Use Collector-side redaction as a safety net, not a substitute for source-level prevention.
See the sensitive data rule in the otel-instrumentation skill for application-level guidance.
Drop telemetry by pattern
In a filter processor, matching expressions cause data to be dropped:
IsMatch(metric.name, "^k8s\\.replicaset\\..*$")
Drop stale data
time_unix_nano < UnixNano(Now()) - 21600000000000
Backfill missing timestamps
processors:
transform:
log_statements:
- context: log
statements:
- set(log.observed_time, Now()) where log.observed_time_unix_nano == 0
- set(log.time, log.observed_time) where log.time_unix_nano == 0
Filter processor example
processors:
filter:
metrics:
datapoint:
- 'IsMatch(ConvertCase(String(metric.name), "lower"), "^k8s\\.replicaset\\.")'
service:
pipelines:
metrics:
receivers: [otlp]
processors: [filter, batch]
exporters: [debug]
Transform processor example
processors:
transform:
trace_statements:
- context: span
statements:
- set(span.status.code, STATUS_CODE_ERROR) where span.attributes["http.response.status_code"] >= 500
- set(span.attributes["env"], "production") where resource.attributes["deployment.environment"] == "prod"
service:
pipelines:
traces:
receivers: [otlp]
processors: [transform, batch]
exporters: [debug]
Defensive nil checks
Always check for nil before operating on optional attributes:
resource.attributes["service.namespace"] != nil
and
IsMatch(ConvertCase(String(resource.attributes["service.namespace"]), "lower"), "^platform.*$")
Normalize high-cardinality attributes
High-cardinality attributes — URL paths with embedded IDs, long freeform strings, or unbounded attribute maps — inflate storage costs and degrade query performance. Use OTTL in a transform processor to normalize these attributes before export.
Replace dynamic path segments
Replace numeric IDs and UUIDs in url.path and http.route with fixed placeholders to collapse cardinality.
processors:
transform/normalize-paths:
error_mode: ignore
trace_statements:
- context: span
statements:
- replace_pattern(span.attributes["url.path"], "/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", "/{uuid}") where span.attributes["url.path"] != nil
- replace_pattern(span.attributes["url.path"], "/\\d+", "/{id}") where span.attributes["url.path"] != nil
- replace_pattern(span.attributes["http.route"], "/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", "/{uuid}") where span.attributes["http.route"] != nil
- replace_pattern(span.attributes["http.route"], "/\\d+", "/{id}") where span.attributes["http.route"] != nil
Mask IP addresses to subnet
Replace the last octet of IPv4 addresses with 0 to reduce cardinality while preserving subnet-level information.
processors:
transform/mask-ips:
error_mode: ignore
trace_statements:
- context: span
statements:
- replace_pattern(span.attributes["client.address"], "(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})\\.\\d{1,3}", "$$1.0") where span.attributes["client.address"] != nil
log_statements:
- context: log
statements:
- replace_pattern(log.attributes["client.address"], "(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})\\.\\d{1,3}", "$$1.0") where log.attributes["client.address"] != nil
Limit attribute count and value length
Use limit and truncate_all to enforce bounds on attribute maps that may grow unboundedly.
processors:
transform/limit-attributes:
error_mode: ignore
trace_statements:
- context: span
statements:
- limit(span.attributes, 64, [])
- truncate_all(span.attributes, 256)
log_statements:
- context: log
statements:
- limit(log.attributes, 64, [])
- truncate_all(log.attributes, 256)
Enrich telemetry with static attributes
When resourcedetection or k8sattributes processors are not available — for example, in non-Kubernetes deployments or when the Collector runs outside the cluster — set resource attributes explicitly.
processors:
resource/static-env:
attributes:
- key: deployment.environment.name
value: production
action: upsert
- key: k8s.cluster.name
value: prod-us-west-2
action: upsert
Use the resource processor (not the transform processor) for static resource attributes.
The resource processor operates at the resource level directly, while transform requires a resource context and explicit path expressions.
To copy a resource attribute down to the span or log level — for example, when the backend does not propagate resource context — use the transform processor:
processors:
transform/copy-resource:
error_mode: ignore
trace_statements:
- context: span
statements:
- set(span.attributes["deployment.environment.name"], resource.attributes["deployment.environment.name"]) where resource.attributes["deployment.environment.name"] != nil
Error handling
Compilation errors
Occur during processor initialization and prevent Collector startup:
- Invalid syntax (missing quotes)
- Unknown functions
- Invalid path expressions
- Type mismatches
Runtime errors
Occur during telemetry processing:
- Accessing non-existent attributes
- Type conversion failures
- Function execution errors
Error mode configuration
Always set error_mode explicitly.
The default (propagate) stops processing the current item on any error, which can silently drop telemetry in production.
| Mode | Behavior | When to use |
|---|---|---|
propagate (default) |
Stops processing current item | Development and strict environments where you want to catch every error |
ignore |
Logs error, continues processing | Production — set this unless you have a specific reason not to |
silent |
Ignores errors without logging | High-volume pipelines with known-safe transforms where error logs are noise |
processors:
transform:
error_mode: ignore
trace_statements:
- context: span
statements:
- set(span.attributes["parsed"], ParseJSON(span.attributes["json_body"]))
Performance
OTTL statements compile once at startup and execute as optimized function chains at runtime.
There is no need to optimize for compilation speed — focus on reducing the number of statements that evaluate per telemetry item.
Use where clauses to skip items early rather than applying unconditional transforms.
Function reference
Editors
Editors modify telemetry data in-place. They are lowercase.
| Function | Signature | Description |
|---|---|---|
append |
append(target, Optional[value], Optional[values]) |
Appends single or multiple values to a target field, converting scalars to arrays if needed |
delete_key |
delete_key(target, key) |
Removes a key from a map |
delete_matching_keys |
delete_matching_keys(target, pattern) |
Removes all keys matching a regex pattern |
flatten |
flatten(target, Optional[prefix], Optional[depth]) |
Flattens nested maps to the root level |
keep_keys |
keep_keys(target, keys[]) |
Removes all keys NOT in the supplied list |
keep_matching_keys |
keep_matching_keys(target, pattern) |
Keeps only keys matching a regex pattern |
limit |
limit(target, limit, priority_keys[]) |
Reduces map size to not exceed limit, preserving priority keys |
merge_maps |
merge_maps(target, source, strategy) |
Merges source into target (strategy: insert, update, upsert) |
replace_all_matches |
replace_all_matches(target, pattern, replacement) |
Replaces matching string values using glob patterns |
replace_all_patterns |
replace_all_patterns(target, mode, regex, replacement) |
Replaces segments matching regex (mode: key or value) |
replace_match |
replace_match(target, pattern, replacement) |
Replaces entire string if it matches a glob pattern |
replace_pattern |
replace_pattern(target, regex, replacement) |
Replaces string sections matching a regex |
set |
set(target, value) |
Sets a telemetry field to a value |
truncate_all |
truncate_all(target, limit) |
Truncates all string values in a map to a max length |
Converters: type checking
| Function | Signature | Description |
|---|---|---|
IsBool |
IsBool(value) |
Returns true if value is boolean |
IsDouble |
IsDouble(value) |
Returns true if value is float64 |
IsInt |
IsInt(value) |
Returns true if value is int64 |
IsMap |
IsMap(value) |
Returns true if value is a map |
IsList |
IsList(value) |
Returns true if value is a list |
IsMatch |
IsMatch(target, pattern) |
Returns true if target matches regex pattern |
IsRootSpan |
IsRootSpan() |
Returns true if span has no parent |
IsString |
IsString(value) |
Returns true if value is a string |
Converters: type conversion
| Function | Signature | Description |
|---|---|---|
Bool |
Bool(value) |
Converts value to boolean |
Double |
Double(value) |
Converts value to float64 |
Int |
Int(value) |
Converts value to int64 |
String |
String(value) |
Converts value to string |
Converters: string manipulation
| Function | Signature | Description |
|---|---|---|
Concat |
Concat(values[], delimiter) |
Concatenates values with a delimiter |
ConvertCase |
ConvertCase(target, toCase) |
Converts to lower, upper, snake, or camel |
HasPrefix |
HasPrefix(value, prefix) |
Returns true if value starts with prefix |
HasSuffix |
HasSuffix(value, suffix) |
Returns true if value ends with suffix |
Index |
Index(target, value) |
Returns first index of value in target, or -1 |
Split |
Split(target, delimiter) |
Splits string into array by delimiter |
Substring |
Substring(target, start, length) |
Extracts substring from start position |
ToCamelCase |
ToCamelCase(target) |
Converts to CamelCase |
ToLowerCase |
ToLowerCase(target) |
Converts to lowercase |
ToSnakeCase |
ToSnakeCase(target) |
Converts to snake_case |
ToUpperCase |
ToUpperCase(target) |
Converts to UPPERCASE |
Trim |
Trim(target, Optional[char]) |
Removes leading/trailing characters |
TrimPrefix |
TrimPrefix(value, prefix) |
Removes leading prefix |
TrimSuffix |
TrimSuffix(value, suffix) |
Removes trailing suffix |
Converters: Hashing
| Function | Signature | Description |
|---|---|---|
FNV |
FNV(value) |
Returns FNV hash as int64 |
MD5 |
MD5(value) |
Returns MD5 hash as hex string |
Murmur3Hash |
Murmur3Hash(target) |
Returns 32-bit Murmur3 hash as hex string |
Murmur3Hash128 |
Murmur3Hash128(target) |
Returns 128-bit Murmur3 hash as hex string |
SHA1 |
SHA1(value) |
Returns SHA1 hash as hex string |
SHA256 |
SHA256(value) |
Returns SHA256 hash as hex string |
SHA512 |
SHA512(value) |
Returns SHA512 hash as hex string |
Converters: encoding and decoding
| Function | Signature | Description |
|---|---|---|
Decode |
Decode(value, encoding) |
Decodes string (base64, base64-raw, base64-url, IANA encodings) |
Hex |
Hex(value) |
Returns hexadecimal representation |
Converters: Parsing
| Function | Signature | Description |
|---|---|---|
ExtractPatterns |
ExtractPatterns(target, pattern) |
Extracts named regex capture groups into a map |
ExtractGrokPatterns |
ExtractGrokPatterns(target, pattern, Optional[namedOnly], Optional[defs]) |
Parses unstructured data using grok patterns |
ParseCSV |
ParseCSV(target, headers, Optional[delimiter], Optional[headerDelimiter], Optional[mode]) |
Parses CSV string to map |
ParseInt |
ParseInt(target, base) |
Parses string as integer in given base (2-36) |
ParseJSON |
ParseJSON(target) |
Parses JSON string to map or slice |
ParseKeyValue |
ParseKeyValue(target, Optional[delimiter], Optional[pair_delimiter]) |
Parses key-value string to map |
ParseSeverity |
ParseSeverity(target, severityMapping) |
Maps log level value to severity string |
ParseSimplifiedXML |
ParseSimplifiedXML(target) |
Parses XML string to map (ignores attributes) |
ParseXML |
ParseXML(target) |
Parses XML string to map (preserves structure) |
Converters: Time and Date
| Function | Signature | Description |
|---|---|---|
Day |
Day(value) |
Returns day component from time |
Duration |
Duration(duration) |
Parses duration string (e.g. "3s", "333ms") |
FormatTime |
FormatTime(time, format) |
Formats time to string using Go layout |
Hour |
Hour(value) |
Returns hour component from time |
Hours |
Hours(value) |
Returns duration as floating-point hours |
Minute |
Minute(value) |
Returns minute component from time |
Minutes |
Minutes(value) |
Returns duration as floating-point minutes |
Month |
Month(value) |
Returns month component from time |
Nanosecond |
Nanosecond(value) |
Returns nanosecond component from time |
Nanoseconds |
Nanoseconds(value) |
Returns duration as nanosecond count |
Now |
Now() |
Returns current time |
Second |
Second(value) |
Returns second component from time |
Seconds |
Seconds(value) |
Returns duration as floating-point seconds |
Time |
Time(target, format, Optional[location], Optional[locale]) |
Parses string to time |
TruncateTime |
TruncateTime(time, duration) |
Truncates time to multiple of duration |
Unix |
Unix(seconds, Optional[nanoseconds]) |
Creates time from Unix epoch |
UnixMicro |
UnixMicro(value) |
Returns time as microseconds since epoch |
UnixMilli |
UnixMilli(value) |
Returns time as milliseconds since epoch |
UnixNano |
UnixNano(value) |
Returns time as nanoseconds since epoch |
UnixSeconds |
UnixSeconds(value) |
Returns time as seconds since epoch |
Weekday |
Weekday(value) |
Returns day of week from time |
Year |
Year(value) |
Returns year component from time |
Converters: Collections
| Function | Signature | Description |
|---|---|---|
ContainsValue |
ContainsValue(target, item) |
Returns true if item exists in slice |
Format |
Format(formatString, args[]) |
Formats string using fmt.Sprintf syntax |
Keys |
Keys(target) |
Returns all keys from a map |
Len |
Len(target) |
Returns length of string, slice, or map |
SliceToMap |
SliceToMap(target, Optional[keyPath], Optional[valuePath]) |
Converts slice of objects to map |
Sort |
Sort(target, Optional[order]) |
Sorts array (asc or desc) |
ToKeyValueString |
ToKeyValueString(target, Optional[delim], Optional[pairDelim], Optional[sort]) |
Converts map to key-value string |
Values |
Values(target) |
Returns all values from a map |
Converters: IDs and Encoding
| Function | Signature | Description |
|---|---|---|
ProfileID |
ProfileID(bytes|string) |
Creates ProfileID from 16 bytes or 32 hex chars |
SpanID |
SpanID(bytes|string) |
Creates SpanID from 8 bytes or 16 hex chars |
TraceID |
TraceID(bytes|string) |
Creates TraceID from 16 bytes or 32 hex chars |
UUID |
UUID() |
Generates a new UUID |
UUIDv7 |
UUIDv7() |
Generates a new UUIDv7 |
Converters: XML
| Function | Signature | Description |
|---|---|---|
ConvertAttributesToElementsXML |
ConvertAttributesToElementsXML(target, Optional[xpath]) |
Converts XML attributes to child elements |
ConvertTextToElementsXML |
ConvertTextToElementsXML(target, Optional[xpath], Optional[name]) |
Wraps XML text content in elements |
GetXML |
GetXML(target, xpath) |
Returns XML elements matching XPath |
InsertXML |
InsertXML(target, xpath, value) |
Inserts XML at XPath locations |
RemoveXML |
RemoveXML(target, xpath) |
Removes XML elements matching XPath |
Converters: Miscellaneous
| Function | Signature | Description |
|---|---|---|
CommunityID |
CommunityID(srcIP, srcPort, dstIP, dstPort, Optional[proto], Optional[seed]) |
Generates network flow hash |
IsValidLuhn |
IsValidLuhn(value) |
Returns true if value passes Luhn check |
Log |
Log(value) |
Returns natural logarithm as float64 |
URL |
URL(url_string) |
Parses URL into components (scheme, host, path, etc.) |
UserAgent |
UserAgent(value) |
Parses user-agent string into map (name, version, OS) |