OpenTelemetry Collector deployment, instrumentation (Java/Python/Node.js/.NET/Go), and OTTL pipeline transforms for Coralogix — coralogix exporter config, Helm chart selection, Kubernetes topology, ECS/EKS/GKE deployments, SDK setup, APM transactions, and OTTL cardinality/PII/routing.
98
97%
Does it follow best practices?
Impact
99%
1.13xAverage score across 81 eval scenarios
Advisory
Suggest reviewing before use
Three Collector components accept OTTL expressions: the transform processor (mutate),
the filter processor (drop), and the routing connector (split pipelines).
The transform processor mutates telemetry in-place. Statements are grouped by signal type
(log_statements, trace_statements, metric_statements) and context.
Always set error_mode: ignore or error_mode: silent. The default propagate halts
the pipeline on any runtime error, dropping all subsequent records.
ignore — logs the error and continuessilent — suppresses the error and continues (use when attribute-missing errors are expected)processors:
transform:
error_mode: ignore
log_statements:
- context: resource
statements:
- set(attributes["environment"], attributes["deployment.environment"])
- context: log
conditions:
- IsMap(body)
statements:
- keep_keys(body, ["message", "level", "timestamp", "trace_id", "span_id"])
trace_statements:
- context: span
conditions:
- attributes["http.route"] != nil
statements:
- set(name, Concat([attributes["http.request.method"], attributes["http.route"]], " "))
metric_statements:
- context: metric
statements:
- replace_pattern(name, "_total$", "")
- context: resource
statements:
- keep_keys(attributes, ["service.name", "k8s.namespace.name", "k8s.deployment.name"])
- context: datapoint
statements:
- delete_key(attributes, "process.command_args")
- delete_key(attributes, "process.command")OTTL functions split into two categories with different usage rules:
set(), delete_key(), keep_keys(), replace_pattern() — modify telemetry in place, used as statementsIsMatch(), SHA256(), ParseJSON(), Concat(), Split(), ExtractPatterns() — return a value, used as expressions inside statements or conditionsThis means ParseJSON(body) alone on a statement line is invalid — it returns a map but does nothing with it. Wrap it in an editor: merge_maps(attributes, ParseJSON(body), "insert").
For the full, always-current list see ottlfuncs README:
| Function | What it does |
|---|---|
set(target, value) | Set a field to a value |
delete_key(map, key) | Remove a single key from a map |
delete_matching_keys(map, pattern) | Remove all keys matching a regex |
keep_keys(map, [keys]) | Remove all keys not in the list (allowlist) |
replace_pattern(target, regex, replacement) | Replace regex matches in a string field |
replace_all_patterns(map, "value", regex, replacement) | Replace pattern in all map values |
merge_maps(target, source, strategy) | Merge two attribute maps |
truncate_all(map, limit) | Truncate all string values to a max length |
limit(map, limit, [keys]) | Cap the number of keys in a map |
IsMatch(value, regex) | Boolean — does value match regex |
IsMap(value) | Boolean — is value a map (use to guard body access) |
IsString(value) | Boolean — is value a string |
ParseJSON(value) | Parse a JSON string into a map (Converter — use inside merge_maps or indexing) |
Concat(values, separator) | Concatenate a list of strings |
Split(value, delimiter) | Split string, returns list (index with [0], [1], ...) |
Substring(value, start, length) | Extract a substring by position |
ExtractPatterns(value, regex) | Extract named capture groups, returns a map |
SHA256(value) | SHA-256 hash of a string (useful for pseudonymizing PII) |
Int(value) | Convert to integer |
Double(value) | Convert to floating-point number |
String(value) | Convert to string |
Time(value, format, [location], [locale]) | Parse a string into a timestamp |
IsInt(value) / IsDouble(value) / IsString(value) | Guard type-specific comparisons and conversions |
The filter processor drops records that match the given OTTL condition. A match means drop.
processors:
filter:
error_mode: ignore
logs:
log_record:
- severity_number < SEVERITY_NUMBER_INFO # drop DEBUG and TRACE
- IsMatch(body, "^.*GET /healthz.*200.*$") # drop health check hits
- resource.attributes["service.name"] == "noisy-exporter" # drop from a specific service
- 'IsMatch(resource.attributes["service.name"], ".(ccrecognition|monitoring).") and severity_number > 9'
metrics:
metric:
- name == "go.goroutines" # drop by exact metric name
- IsMatch(name, "^go\\..*") # drop all Go runtime metrics
traces:
span:
- attributes["http.target"] == "/healthz" # drop health check spans
- attributes["db.system"] != nil # drop all DB spans
- duration < 1000000 # drop spans under 1ms (nanoseconds)Severity number constants (use these instead of integers):
| Constant | Severity |
|---|---|
SEVERITY_NUMBER_TRACE | 1 |
SEVERITY_NUMBER_DEBUG | 5 |
SEVERITY_NUMBER_INFO | 9 |
SEVERITY_NUMBER_WARN | 13 |
SEVERITY_NUMBER_ERROR | 17 |
SEVERITY_NUMBER_FATAL | 21 |
severity_number < SEVERITY_NUMBER_INFO drops both TRACE and DEBUG levels.
For current constants and the full field list, see the filter processor documentation.
The routing connector splits a pipeline into multiple downstream pipelines based on OTTL
conditions. Use this when different data needs different processors or exporters.
connectors:
routing:
error_mode: ignore
default_pipelines: [traces/sampled]
table:
- statement: route() where attributes["force_sample"] == "true"
pipelines: [traces/full]
- statement: route() where resource.attributes["k8s.namespace.name"] == "prod"
pipelines: [traces/prod]
service:
pipelines:
traces/ingress:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [routing]
traces/full:
receivers: [routing]
exporters: [otlp/backend]
traces/prod:
receivers: [routing]
processors: [tail_sampling]
exporters: [otlp/backend]
traces/sampled:
receivers: [routing]
processors: [tail_sampling]
exporters: [otlp/backend]service:
pipelines:
logs:
processors: [filter, transform, batch]k8sattributes before a filter that uses k8s.namespace.name), put enrichment processors before filterFor simple attribute operations (add, update, delete) without conditions, the attributes
processor is simpler than OTTL:
processors:
attributes:
actions:
- key: sensitive_field
action: delete
- key: env
value: production
action: insertUse transform when you need: where conditions, cross-context access (resource.attributes
from a span context), functions like replace_pattern/keep_keys, or conditions: blocks.
OTTL statements embedded in YAML have to be valid on two parsing layers: the
YAML parser strips its own quoting and escapes first, then the OTTL parser reads
what is left as a string literal. A replace_pattern regex like
"cycle-(manager|rpa-manager)\\.[0-9a-f]{8}-..." that works in a standalone regex
tester can fail at Collector startup with:
statement has invalid syntax: 1:28: invalid quoted stringThis is a syntax error, not an OTTL bug or a bad regex — the backslashes and quotes were consumed twice. Three concrete fixes, in order of readability:
processors:
transform:
error_mode: ignore
trace_statements:
- context: span
statements:
# 1. YAML single quotes outside, OTTL double quotes inside — cleanest
- 'replace_pattern(name, "cycle-(manager|rpa-manager)\\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", "cleaned-span-name")'
# 2. YAML block scalar — backslashes survive untouched, good for very long expressions
- >-
replace_pattern(name,
"cycle-(manager|rpa-manager)\\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
"cleaned-span-name")
# 3. YAML double quotes outside — must double every backslash to survive YAML
- "replace_pattern(name, \"cycle-(manager|rpa-manager)\\\\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\", \"cleaned-span-name\")"Rule of thumb: YAML single quotes outside, OTTL double quotes inside. The OTTL string literal keeps its normal double quotes and you only escape for OTTL, not for YAML.
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10
scenario-11
scenario-12
scenario-13
scenario-14
scenario-15
scenario-16
scenario-17
scenario-18
scenario-19
scenario-20
scenario-21
scenario-22
scenario-23
scenario-24
scenario-25
scenario-26
scenario-27
scenario-28
scenario-29
scenario-30
scenario-31
scenario-32
scenario-33
scenario-34
scenario-35
scenario-36
scenario-37
scenario-38
scenario-39
scenario-40
scenario-41
scenario-42
scenario-43
scenario-44
scenario-45
scenario-46
scenario-47
scenario-48
scenario-49
scenario-50
scenario-51
scenario-52
scenario-53
scenario-54
scenario-55
scenario-56
scenario-57
scenario-58
scenario-59
scenario-60
scenario-61
scenario-62
scenario-63
scenario-64
scenario-65
scenario-66
scenario-67
scenario-68
scenario-69
scenario-70
scenario-71
scenario-72
scenario-73
scenario-74
scenario-75
scenario-76
scenario-77
scenario-78
scenario-79
scenario-80
scenario-81
skills
opentelemetry
opentelemetry-collector
references
opentelemetry-instrumentation
opentelemetry-ottl