As telemetry pipelines become more sophisticated, so do the transformations they need to perform: sanitizing sensitive data, normalizing inconsistent schemas, and enforcing attribute contracts. While OTTL provides a rich set of transformation functions, expressing collection operations has required dedicated functions with hardcoded behavior for each new use case.
OpenTelemetry Collector Contrib v0.157.0 changes that by introducing lambda
expressions to OTTL. Lambdas let users pass inline logic directly to generic
higher-order functions, making complex collection transformations both reusable
and concise. The release includes eight new functions that leverage this
capability: Filter, MapEach, MapKeys, Any, All, Find, Reduce, and
When.
A lambda expression is a small anonymous function defined inline, directly where it is used. It takes a list of parameters and a body, which can be any valid OTTL expression:
(key, value) => HasPrefix(key, "http.")
(key, value) => value * 2
The following examples show what these functions make possible.
Filtering and transforming collections
Before these functions, operating on every element in a slice or map meant relying on dedicated functions with hardcoded behavior. Now:
# Keep only attributes whose key starts with "http."
set(span.attributes, Filter(span.attributes, (k, _) => HasPrefix(k, "http.")))
# Stringify all attribute values
set(span.attributes, MapEach(span.attributes, (_, v) => String(v)))
# Normalize all attribute key names to snake_case
set(span.attributes, MapKeys(span.attributes, (k, _) => ToSnakeCase(k)))
# Add a prefix to all resource attribute keys
set(resource.attributes, MapKeys(resource.attributes, (k, _) => Format("app.%s", [k])))
Asking questions about a collection
Any and All let you use the contents of a slice or map as a condition, which
opens up filter rules and where clauses that were not possible before:
# Drop spans originating from internal networks
filter:
trace_conditions:
- Any(span.attributes["http.request.header.x-forwarded-for"],
(_, v) => IsInCIDR(v, ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]))
# Only process spans where all db.* values are non-empty
transform:
trace_statements:
- set(span.attributes["db.complete"], true)
where All(span.attributes, (k, v) => not HasPrefix(k, "db.") or not IsEmpty(v))
Find returns the first element that matches a predicate. An optional second
lambda transforms the result before it is returned:
# Find the value of the first x- header
set(span.attributes["custom_header"], Find(span.attributes, (k, _) => HasPrefix(k, "x-")))
# Find the key of the first attribute whose value is "error", ignoring the value
set(log.attributes["error_key"], Find(log.attributes, (_, v) => v == "error", (k, _) => k))
Aggregating a collection
Reduce folds a slice or map into a single value:
# Total bytes across a list of response sizes
- set(span.attributes["total_bytes"],
Reduce(span.attributes["response.sizes"], 0, (acc, _, v) => acc + v))
# Concatenate all error messages into one string
- set(log.attributes["errors"],
Reduce(log.attributes["error.messages"], "", (acc, _, v) => Format("%s; %s", acc, v)))
Inline conditionals
When is not collection-based, but follows the same spirit.
# Replace two `set` statements with one
- set(span.attributes["speed_class"], When(() => (span.end_time_unix_nano -
span.start_time_unix_nano) > 1000000000, "slow", "fast"))
Combining functions
Because these functions are composable, you can pass the output of one directly
as the input of another. Here, Filter narrows a map down to attributes that
look like PII, and MapEach hashes their values before merging them back:
transform:
trace_statements:
- merge_maps(span.attributes,
MapEach(
Filter(span.attributes, (k, v) => IsMatch(k, "(?i)(email|phone|ssn|credit_card)")),
(_, v) => Format("%s (redacted)", [SHA1(String(v))])
), "upsert")
Combined, these functions cover a wide range of transformations.
Trying it out
All eight functions are experimental and available in OpenTelemetry
Collector Contrib v0.157.0 behind a feature gate:
--feature-gates=ottl.functions.enableLambda
As we wrap up, we encourage users to explore this new functionality and take advantage of its benefits in their telemetry pipelines!
If you have any questions or suggestions, we’d love to hear from you! Join the
conversation in the #otel-collector channel on the
CNCF Slack workspace.
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.