otel-ottl

Installation
SKILL.md

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 attributes
  • scope - Instrumentation scope
  • span - Span data (traces)
  • spanevent - Span events
  • metric - Metric metadata
  • datapoint - Metric data points
  • log - 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)

References

Weekly Installs
155
GitHub Stars
50
First Seen
Mar 13, 2026