Schemas
Overview
This page describes the schemas for defining an OtelComponentMapping or OtelRelationMapping, along with detailed explanations of constructs, expression syntax and semantics.
Schemas for OTel Component & Relation Mappings
OtelComponentMapping
Each component mapping:
-
Selects telemetry using conditions.
-
Extracts values using expressions.
-
Produces a single logical component identified by a stable identifier.
Multiple telemetry records may resolve to the same component identifier; in that case, the component is merged and refreshed.
_type: "OtelComponentMapping"
name: string
input:
signal: ["TRACES" | "METRICS" | "LOGS"]
resource:
condition: <cel-boolean> # default: true
action: CONTINUE # default
scope:
condition: <cel-boolean> # default: true
action: CONTINUE # default
span: # TRACES only
condition: <cel-boolean> # default: true
action: CONTINUE # default
log: # LOGS only
condition: <cel-boolean> # default: true
action: CONTINUE # default
metric: # METRICS only
condition: <cel-boolean> # default: true
action: CONTINUE # default
datapoint: # METRICS only
condition: <cel-boolean> # default: true
action: CONTINUE # default
vars: # Optional
- name: string
value: <cel-expression>
output:
identifier: <cel-string>
name: <cel-string>
typeName: <cel-string>
typeIdentifier: <cel-string> # Optional
domainName: <cel-string>
domainIdentifier: <cel-string> # Optional
layerName: <cel-string>
layerIdentifier: <cel-string> # Optional
required: # Optional (required means that if any expression fails, the mapping fails)
version: <cel-string> # Optional
additionalIdentifiers: # Optional
- <cel-string>
tags: # Optional
- source: <cel-string>
target: string
- source: <cel-string>
pattern: regex
target: string
configuration: <cel-expression> # Optional
status: <cel-expression> # Optional
optional: # Optional (besides being optional on the schema, optional means that if any expression under `optional` fails, the mapping will continue, but without the failed expression/field)
version: <cel-string> # Optional
additionalIdentifiers: # Optional
- <cel-string>
tags: # Optional
- source: <cel-string>
target: string
- source: <cel-map>
pattern: regex
target: string
configuration: <cel-expression> # Optional
status: <cel-expression> # Optional
expireAfter: duration-ms
OtelRelationMapping
Each relation mapping:
-
Resolves a
sourceIdandtargetId. -
Assigns a relation type.
-
Produces a directed edge between existing or future components.
Relations are materialised when source and target components exist.
_type: "OtelRelationMapping"
name: string
input:
signal: ["TRACES" | "METRICS" | "LOGS"]
resource:
condition: <cel-boolean> # default: true
action: CONTINUE # default
scope:
condition: <cel-boolean> # default: true
action: CONTINUE # default
span: # TRACES only
condition: <cel-boolean> # default: true
action: CONTINUE # default
log: # LOGS only
condition: <cel-boolean> # default: true
action: CONTINUE # default
metric: # METRICS only
condition: <cel-boolean> # default: true
action: CONTINUE # default
datapoint: # METRICS only
condition: <cel-boolean> # default: true
action: CONTINUE # default
vars: # Optional
- name: string
value: <cel-expression>
output:
sourceId: <cel-string>
targetId: <cel-string>
typeName: <cel-string>
typeIdentifier: <cel-string> # Optional
expireAfter: duration-ms
Component & relation identity, merging, and lifecycle
Component identity
The output.identifier field defines the main identity of a component and must be "globally" unique within the entire topology. Via output.required.additionalIdentifiers you can specify more identifiers for the component. Components that have at least one overlapping identifier are considered the same logical entity and are merged by the platform. This makes identifier construction a critical design choice.
Identifiers should:
-
Follow the SUSE® Observability identifier (i.e.,
urn:…) format. Refer to identifiers documentation for more information. -
Be stable across time.
-
Reflect the intended cardinality (service, instance, database, and the like).
-
Avoid unbounded dimensions.
|
Using the |
Relation identity
The relation identity is a composite of the form output.sourceId-output.targetId, where sourceId and targetId are component identifiers (for example, from output.identifier).
Refresh and expiration
Each mapping defines an expireAfter duration. This value controls how long a component or relation remains in the topology without being refreshed by new matching telemetry. If no refresh happens the component or relation is removed from the topology.
Internally, the OTel collector continuously refreshes components as telemetry matches - this refresh window is based on the expireAfter field per mapping.
Input traversal model
Topology mappings traverse OpenTelemetry data hierarchically - resource → scope → metric/span → datapoint
Each level may define:
-
A condition: a CEL boolean expression.
-
An action: what to do if the condition matches.
Available fields per signal
The selected signal determines which data structures and attributes are available for expression evaluation.
TRACES
Provides access to:
-
resource.attributes
-
scope.name, scope.version, scope.attributes
-
span.name, span.kind, span.statusMessage, span.statusCode, span.attributes
Examples:
resource.attributes['service.name'] == 'checkout'
# OR
span.kind == 'SPAN_KIND_SERVER' && span.attributes['http.method'] == 'POST'
METRICS
Provides access to:
-
resource.attributes
-
scope.name, scope.version, scope.attributes
-
metric.name, metric.description, metric.unit
-
datapoint.attributes
Examples:
metric.name == 'traces_service_graph_request_total'
# OR
datapoint.attributes['connection_type'] == 'database'
LOGS
Provides access to:
-
resource.attributes
-
scope.name, scope.version, scope.attributes
-
log.eventName, log.attributes, log.body
The log.body field contains the complete log record payload and can be used with pick() and omit() functions for field selection.
Examples:
log.attributes['k8s.resource.api_group'] == 'some_resource.k8s.io'
# Filter log body to extract only status fields
${pick(log.body, ['status', 'conditions'])}
# Extract configuration, excluding status
${omit(log.body, ['status'])}
Conditions and actions
Conditions
Conditions determine whether processing continues at a given level. If a condition evaluates to false, the mapping is skipped for that telemetry element.
Defaults:
-
Conditions default to 'true'. This allows for omitting explicit conditions at each level to act as a fall-through.
Constraints:
-
When there are multiple input signals declared (e.g.,
TRACESandMETRICS), data from within an expression can only be accessed from the common-ancestor-level, which in all cases are scope-level. -
Data/field access from a lower/child-level is not allowed. For example, scope-level fields cannot be accessed at the resource-level.
-
Data/field access from another input-signal is not allowed. For example, metric-level fields cannot be accessed from span-level and vice versa.
Conditions may access data from a parent-level. For example, at the span-level, scope- and resource-level data may be accessed.
input:
signal:
- "TRACES"
resource:
condition: "'service.name' in resource.attributes"
# action omitted since the default is `CONTINUE`
scope:
action: "CREATE" # input block describes that it expects to filter until this level only
condition: "scope.name.contains('http') && resource.attributes['service.name'] == 'cart-svc'" # it's allowed to access resource-level fields at scope-level
Actions
Supported actions:
-
CONTINUE – continue evaluation to the next level.
-
CREATE – create a component or relation at this level.
Defaults:
-
Actions default to 'CONTINUE'.
This means an explicit CREATE action needs to be specified at an input-level for the mapping to be applied.
Here are a few more examples of valid input declarations:
TRACES
input:
signal:
- "TRACES"
resource:
condition: "'service.name' in resource.attributes"
# action omitted since the default is `CONTINUE`
scope:
# action and condition have been omitted since the defaults are `CONTINUE` and `true` respectively
span:
action: "CREATE"
condition: "span.attributes['http.method'] == 'POST'"
METRICS
input:
signal:
- "METRICS"
resource:
# action and condition have been omitted since the defaults are `CONTINUE` and `true` respectively
scope:
condition: "scope.name == 'traces_service_graph'"
metric:
# action omitted since the default is `CONTINUE`
condition: "metric.name == 'traces_service_graph_request_total'"
datapoint:
action: "CREATE"
condition: |
'client' in datapoint.attributes &&
'server' in datapoint.attributes
Variables (vars)
Mappings may define variables to avoid repetition and improve readability.
Variables:
-
Are evaluated using CEL expressions.
-
May reference any fields that are available at the deepest input-level matched by the input filters. For example, if the input selection has the
CREATEaction at the span-level, resource, scope and span fields can be used. -
Can be reused across output fields.
Example:
vars:
- name: "service"
value: "resource.attributes['service.name']"
Variables are resolved before evaluating output expressions.
Output
The output section defines how OpenTelemetry signals (traces/metrics) should be mapped to components or relations.
Output fields:
-
Use CEL expressions to dynamically generate values.
-
May reference any fields that are available at the deepest input-level matched by the input filters. For example, if the input selection has the
CREATEaction at the span-level, resource, scope and span fields can be used.
If the value for a field doesn’t need to be dynamically generated, a CEL string literal can be used where the schema specifies a <cel-string> expression. Since expression values are YAML strings containing CEL, use single quotes for CEL string literals inside double-quoted YAML values. For example: typeName: "'my-type'" (the outer double quotes are YAML, the inner single quotes make it a CEL string literal).
Required vs. Optional Subsections
The component mapping schema distinguishes between two error-handling behaviors:
optional (optional subsection)
If any expression under optional fails, the mapping continues but without the failed field.
Tag mappings
Component mappings may define tag mappings to enrich components with metadata.
Two forms are supported:
Direct mapping
- source: "'value'"
target: "key"
Result: key:value
- source: "resource.attributes['service.name']"
target: "service.name"
Given: resource.attributes { 'service.name': 'cart-svc' }
Result: service.name:cart-svc
The source for a direct mapping needs to be a:
-
string literal
-
string expression
Regex-based extraction
- source: "resource.attributes"
pattern: "telemetry.sdk\.(.*)"
target: "telemetry.sdk.${1}"
Given: resource.attributes: { 'telemetry.sdk.language': 'go', 'telemetry.sdk.version': '1.23.1' }
Result: telemetry.sdk.language:go;telemetry.sdk.version:1.23.1
Regex-based mappings support multiple capture groups, which can be referenced positionally in the target expression.
The source for a regex-based mapping needs to be:
-
map expression
Example with multiple capture groups:
- source: "resource.attributes"
pattern: "^(os|host|cloud|azure|gcp)\.(.*)"
target: "${1}.${2}"
In regex-based mappings, capture groups are substituted into the target.
CEL expression (<cel-*>) explanation:
All expression fields use standard CEL syntax. Expressions are not wrapped in ${…} — write plain CEL directly.
-
<cel-string>- needs to return a string; can be one of:-
string literal (e.g.,
'hello') -
string expression (e.g.,
resource.attributes['service.name']) -
string concatenation using the
+operator (e.g.,'urn:opentelemetry:namespace/' + resource.attributes['namespace'] + ':service/' + resource.attributes['service.name'])
-
-
<cel-boolean>- needs to return a boolean; can be one of:-
boolean literal (e.g.,
true) -
boolean expression (e.g.,
'namespace' in resource.attributes)
-
-
<cel-map>- needs to return a map; can be one of:-
map literal (e.g.,
{'a': 1, 'b': 'two'}) -
map expression (e.g.,
resource.attributes)
-
-
<cel-expression>- returns "any" type, can be one of:-
string expression
-
boolean expression
-
map expression (e.g.,
resource.attributes- returns the attribute map) -
list expression (e.g.,
resource.attributes['process.command_args']- returns a list)
-
String concatenation
Use the + operator to build strings from multiple parts. For example:
'urn:opentelemetry:namespace/' + vars.namespace + ':service/' + vars.service
For conditional string construction, use the ternary operator:
'service.instance.id' in resource.attributes ?
resource.attributes['service.name'] + ' - ' + resource.attributes['service.instance.id'] :
resource.attributes['service.name'] + ' - instance'
Type casting for non-string values
CEL is strongly typed and does not automatically convert types during concatenation. When concatenating values of different types, you must explicitly cast non-string types to strings.
For example, if process.pid is an integer (e.g., 1), this expression will fail:
resource.attributes['service.name'] + '/' + resource.attributes['process.pid']
The correct form requires casting the non-string value to a string using string():
resource.attributes['service.name'] + '/' + string(resource.attributes['process.pid'])
CEL language reference
CEL is a safe, side-effect-free expression language designed for configuration and policy use cases. In the context of OpenTelemetry topology mappings, CEL is used to:
-
Define conditions that decide whether a mapping applies
-
Compute variables from telemetry attributes
-
Build identifiers, names, and tags dynamically
Expressions are evaluated against a well-defined, typed context derived from the OpenTelemetry signal being processed (for example, resource, scope, span, metric, or datapoint).
Visit CEL’s langdef for a thorough reference.
An online CEL playground like https://playcel.undistro.io/ is a helpful tool to do quick expression validity checks.
Custom CEL functions for field selection
The following custom functions are available for selecting or filtering fields from maps:
pick(map, ['key1', 'key2', …])
Returns a new map containing only the specified keys from the input map.
Usage: Extract a subset of fields (e.g., for status information)
Example:
status: ${pick(log.body, ['status', 'conditions', 'lastUpdate'])}
Given:
log.body: {
'apiVersion': 'v1',
'kind': 'Pod',
'spec': {...},
'status': {...},
'conditions': [...],
'lastUpdate': '2026-03-30T10:00:00Z'
}
Result: { 'status': {…}, 'conditions': […], 'lastUpdate': '2026-03-30T10:00:00Z' }
omit(map, ['key1', 'key2', …])
Returns a new map excluding the specified keys from the input map.
Usage: Extract all fields except specific ones (e.g., for configuration, excluding status)
Example:
configuration: ${omit(log.body, ['status', 'metadata'])}
Given:
log.body: {
'apiVersion': 'v1',
'kind': 'Pod',
'metadata': {...},
'spec': {...},
'status': {...}
}
Result: { 'apiVersion': 'v1', 'kind': 'Pod', 'spec': {…} }
Use in output fields
These functions are particularly useful for populating component configuration and status fields:
output:
required:
configuration: ${omit(log.body, ['status'])} # Everything except status
status: ${pick(log.body, ['status'])} # Only status field
This pattern separates component configuration (spec) from operational state (status).
Common patterns and best practices
-
Guard mappings with conditions to avoid unintended cardinality
-
Always handle missing attributes defensively
-
Keep identifiers stable and predictable
-
Prefer variables for complex expressions and to promote readability
-
Use expireAfter values appropriate to signal frequency
See the troubleshooting page for guidance on troubleshooting OTel mappings.