$npx -y skills add ollygarden/opentelemetry-agent-skills --skill otel-ottlOpenTelemetry Transformation Language (OTTL) expert for writing and debugging telemetry transformations in the OpenTelemetry Collector. Use when authoring or reviewing transform, filter, tail_sampling processor configs or routing connector configs, debugging OTTL syntax o
| 1 | # OpenTelemetry Transformation Language (OTTL) |
| 2 | |
| 3 | OTTL is a domain-specific language for transforming telemetry inside the OpenTelemetry Collector. It is consumed by the `transform`, `filter`, and `tail_sampling` processors, the `routing` connector, and a few other components in [opentelemetry-collector-contrib](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/pkg/ottl). |
| 4 | |
| 5 | This skill targets `pkg/ottl` as of collector-contrib **v0.156.0**. Function and path availability differs across Collector releases; check the upstream `pkg/ottl/ottlfuncs/README.md` and `pkg/ottl/contexts/*/README.md` for the exact set in older or newer releases. |
| 6 | |
| 7 | ## Statement syntax |
| 8 | |
| 9 | ```ottl |
| 10 | function(arguments) [where condition] |
| 11 | ``` |
| 12 | |
| 13 | Every statement has exactly one **editor** (lowercase: `set`, `delete_key`, `append`, …) optionally guarded by a `where` clause whose body is a boolean expression. Conditions can call **converters** (uppercase: `Concat`, `IsMatch`, `ParseJSON`, …) which return values but do not mutate telemetry. |
| 14 | |
| 15 | ```ottl |
| 16 | set(span.attributes["env"], "prod") where resource.attributes["env"] == nil |
| 17 | ``` |
| 18 | |
| 19 | ## Workflow |
| 20 | |
| 21 | 1. **Pick the component.** `transform` rewrites; `filter` drops; the `routing` connector fans out by pipeline; `tail_sampling` keeps/drops traces. The component decides which contexts and function set are usable. |
| 22 | 2. **Pick the context.** `resource`, `scope`, `span`, `spanevent`, `metric`, `datapoint`, `exemplar`, `log`, `profile`, `profilesample`, or `otelcol` for Collector request/client metadata. Operate at the lowest level that gives you the data — using `datapoint` to set attributes is much cheaper than walking through `metric.data_points` from the metric context. |
| 23 | 3. **Write statements.** Reach for `references/quick-reference.md` for common recipes; `references/contexts.md` for paths/enums; `references/functions.md` for the editor and converter catalog. |
| 24 | 4. **Set `error_mode`.** `ignore` keeps the pipeline running and logs errors; `silent` does the same but quietly; `propagate` aborts on first failure. In v0.156, `transform` and `filter` default to `ignore` through enabled beta feature gates; `routing` defaults to `propagate` unless `connector.routing.defaultErrorModeIgnore` is enabled; lower-level OTTL sequences default to `propagate`. |
| 25 | 5. **Verify.** OTTL gotchas are the kind that pass the eye test (see [Common gotchas](#common-gotchas)). Use the [telemetrygen verification recipe](../otel-telemetrygen/SKILL.md#verifying-a-collector-config) — `otelcol-contrib` + file exporter + telemetrygen — to confirm the snippet does what the prose claims before shipping. |
| 26 | |
| 27 | ## Contexts at a glance |
| 28 | |
| 29 | OTTL paths are scoped by signal. Higher levels are reachable from lower ones (e.g., `resource.attributes` from a span statement); the reverse is not true. |
| 30 | |
| 31 | | Context | Common paths | |
| 32 | |---------|--------------| |
| 33 | | Resource | `resource.attributes["service.name"]`, `resource.schema_url` | |
| 34 | | Scope | `scope.name`, `scope.version`, `scope.attributes["…"]` | |
| 35 | | Span | `span.name`, `span.kind`, `span.status.code`, `span.attributes["…"]`, `span.flags` | |
| 36 | | Span Event | `spanevent.name`, `spanevent.attributes["…"]`, `spanevent.event_index` | |
| 37 | | Metric | `metric.name`, `metric.unit`, `metric.type`, `metric.aggregation_temporality` | |
| 38 | | DataPoint | `datapoint.value_double`, `datapoint.value_int`, `datapoint.attributes["…"]` | |
| 39 | | Exemplar | `exemplar.double_value`, `exemplar.int_value`, `exemplar.filtered_attributes["…"]` (transform `metric_statements` only, v0.156+) | |
| 40 | | Log | `log.body`, `log.body.string`, `log.severity_number`, `log.attributes["…"]` | |
| 41 | | Profile | `profile.profile_id`, `profile.attributes["…"]` (Development) | |
| 42 | | OTelCol | `otelcol.client.metadata["x-tenant"][0]`, `otelcol.grpc.metadata["x-tenant"][0]` (read-only, enabled by default feature gate) | |
| 43 | |
| 44 | Full path inventory plus enums in `references/contexts.md`. |
| 45 | |
| 46 | ## Essential functions |
| 47 | |
| 48 | ```ottl |
| 49 | # Editors (mutate telemetry) |
| 50 | set(target, value) |
| 51 | delete_key(target, key) # delete by exact key |
| 52 | delete_matching_keys(target, regex) # delete by regex |
| 53 | delete_index(target, start, end?) # remove one slice item or range (v0.145+) |
| 54 | keep_keys(target, [k1, k2]) # keep only these keys |
| 55 | merge_maps(target, source, "upsert") # "insert" | "update" | "upsert" |
| 56 | truncate_all(target, max_len) # UTF-8 safe by default in v0.148+ |
| 57 | replace_pattern(target, regex, replacement) |
| 58 | stringify_all(target) # map values -> strings (v0.155+) |
| 59 | |
| 60 | # Converters (return values) |
| 61 | Concat([a, b], "-") |
| 62 | Split(s, ",") |
| 63 | ToLowerCase(s) |