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: <cel-string> # default: "'CONTINUE'"
scope:
condition: <cel-boolean> # default: true
action: <cel-string> # default: "'CONTINUE'"
span: # TRACES only
condition: <cel-boolean> # default: true
action: <cel-string> # default: "'CONTINUE'"
log: # LOGS only
condition: <cel-boolean> # default: true
action: <cel-string> # default: "'CONTINUE'"
metric: # METRICS only
condition: <cel-boolean> # default: true
action: <cel-string> # default: "'CONTINUE'"
datapoint: # METRICS only
condition: <cel-boolean> # default: true
action: <cel-string> # default: "'CONTINUE'"
vars: # Optional
- name: string
value: <cel-expression>
output:
identifier: <cel-string>
name: <cel-string>
typeName: <cel-string>
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
rank:
specificity: float # Merge priority; see specificity ranges below
OtelRelationMapping
Each relation mapping:
-
Resolves a
sourceIdandtargetId. -
Assigns a dependency 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: <cel-string> # default: "'CONTINUE'"
scope:
condition: <cel-boolean> # default: true
action: <cel-string> # default: "'CONTINUE'"
span: # TRACES only
condition: <cel-boolean> # default: true
action: <cel-string> # default: "'CONTINUE'"
log: # LOGS only
condition: <cel-boolean> # default: true
action: <cel-string> # default: "'CONTINUE'"
metric: # METRICS only
condition: <cel-boolean> # default: true
action: <cel-string> # default: "'CONTINUE'"
datapoint: # METRICS only
condition: <cel-boolean> # default: true
action: <cel-string> # default: "'CONTINUE'"
vars: # Optional
- name: string
value: <cel-expression>
output:
sourceId: <cel-string>
targetId: <cel-string>
typeName: <cel-string>
dependencyType: <cel-string> # Optional: 'HIERARCHICAL', 'CONNECTION' or 'UNCLASSIFIED' (default)
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 |
|
The component identifier also links spans back to this component in the Traces perspective. When a span is selected, its resource attributes, span attributes, span name, kind, status, and scope name/version are all used to resolve matching mappings — so identifiers that reference span-level fields (for example, |
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.
Component merging
When multiple sources contribute to the same component (matched by identifier), SUSE® Observability merges them into a single component. The merger applies per-field rules to pick the winning name and typeName:
-
If one side is an external-topology sync (ExtTopo) and the other is an OTel mapping, the ExtTopo side wins for
nameandtypeNameregardless of specificity. -
Otherwise the side with the higher
specificitywins.
specificity is declared under the required rank block in each OtelComponentMapping (as rank.specificity). Use a higher value to specialise a more specific mapping over a more general one for the same component identifier.
Specificity ranges
The following ranges are recommended for rank.specificity to ensure consistent precedence across StackPacks:
| Range | Use |
|---|---|
0 |
Universal base (common / catch-all) |
1–99 |
Environment & infrastructure (cloud provider, account, cluster) |
100–199 |
Platform & orchestration (Kubernetes, OTel base mappings) |
200–299 |
Application / service |
300–399 |
Runtime / SDK / language |
400+ |
User / customer overrides |
Higher specificity wins when multiple mappings contribute to the same component identifier.
For each contributing source, the merged component additionally carries two preserved labels so the original per-source values remain queryable:
-
source-type:<value>— thetypeNameproduced by that source. -
source-name:<value>— thenameproduced by that source.
These labels are injected at synthesis time, so they appear on every component (single-source as well as merged). Query them with STQL (for example, label = 'source-type:pod') or, in component-presentation CEL contexts, with the multi-value tags map described in Multi-value tag access in CEL.
|
Different values for the same key (for example, one source contributing |
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
The action field is a CEL string expression that must evaluate to one of the supported action values. Action values must be specified as CEL string literals using single quotes (e.g., 'CREATE').
Supported action values:
-
'CONTINUE'– continue evaluation to the next level. -
'CREATE'– create a component or relation at this level. -
'DELETE'– delete a component at this level (only valid at theloglevel).
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.
Because the action field is a CEL expression, conditional actions using the ternary operator are supported. This allows a single mapping to take different actions based on telemetry data:
log:
condition: "log.attributes['k8s.resource.api_group'] == 'apps'"
action: |
(log.body['type'] == 'ADDED' || log.body['type'] == 'MODIFIED') ?
'CREATE' :
(log.body['type'] == 'DELETED' ?
'DELETE' :
'CONTINUE')
|
When a ternary action expression can produce a terminal action ( |
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).
Multi-value tag access in CEL
Component-presentation CEL contexts expose component tags as a multi-value map:
tags: map<string, list<string>>
A tag key can have zero, one, or many values, for example:
tags['source-type'] == ['pod', 'virt-launcher']
Use standard CEL map/list membership for existence checks and value matching:
# Standard CEL: check whether a tag key exists
${'source-type' in tags}
# Standard CEL: check whether a key exists and one of its values is present
${'source-type' in tags && 'pod' in tags['source-type']}
For tag keys that are valid CEL identifiers, field selection is also supported as standard CEL shorthand for map access. It returns the list of values for the key:
# Standard CEL: equivalent to tags['namespace']; returns list<string>
${tags.namespace}
# Standard CEL: equivalent to 'namespace' in tags
${has(tags.namespace)}
Use bracket access for keys that are not valid CEL identifiers, such as keys containing - or .:
${tags['source-type']}
${tags['service.name']}
Use a guard before indexing when the key may be absent. For keys that are valid CEL identifiers, has(tags.namespace) is a standard CEL field-presence check. For all keys, including keys that contain - or ., use the standard in operator for map-key and list-value membership.
The following StackState-specific helper functions are also available in component-presentation CEL contexts:
-
tags.contains('key:value')— returnstruewhentagscontains keykeyand one of its values isvalue. This is a convenience helper equivalent to the standard CEL expression'key' in tags && 'value' in tags['key']. It also works onsource.tags, for examplesource.tags.contains('source-type:pod'). -
tags.singleValue('key')— returns the value forkeywhen the key has exactly one value. Returns an empty string if the key is missing or has multiple values. This is useful in places where a plain string is required, such as PromQL or STQL template interpolation. -
selectPrefix(tags, 'prefix_')— returns amap<string, string>for tag keys that start withprefix_, with the prefix stripped from the returned keys. Entries with missing or multiple values are omitted because the result map contains single string values.
Examples:
# StackState helper: same result as 'source-type' in tags && 'pod' in tags['source-type']
${tags.contains('source-type:pod')}
# StackState helper: single-value extraction
${tags.singleValue('namespace')}
# StackState helper: extract single-value tags with a common prefix
${selectPrefix(tags, 'label_')}
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.