Transformations
Uptrace allows to transform the ingested data using a YAML-based language. You can use it to rename/delete attribute keys, parse/change attribute values, sample/drop spans, etc.
Example
For example, to rename an attribute:
name: Rename service to service_name
scope: [spans, logs, events, datapoints]
type: rename_attr
old_key: service
new_key: service_name
You can use the YAML above to create an operation by following these steps:
- In the navigation menu on the left, click "Project" -> "Transformations".
- Click "New Operation" -> "New operation from YAML".
- Paste the YAML and click "Create".
Scope
Each operation must have a scope field that limits the operation to a particular data type (aka signal). The scope is an array and must contain one of the following values:
- spans
- logs
- events
- datapoints
Conditions
You can further narrow down the telemetry data to be transformed using if expression:
name: Rename service to service_name
scope: [spans, logs, events, datapoints]
type: rename_attr
old_key: service
new_key: service_name
if: attr("deployment_environment") == "prod"
Uptrace uses Expr language for writing expressions.
Execution
Uptrace executes operations one by one after normalising attribute names, i.e. service.name is normalised to service_name so you should use the normalised name.
Execution will stop if the signal is dropped by a drop operation, but otherwise Uptrace will execute all operations regardless of errors.
To change the execution order, use the priority field.
name: Rename service to service_name
scope: [spans, logs, events, datapoints]
priority: 100
type: rename_attr
old_key: service
new_key: service_name
Uptrace executes transformations before it processes the data so you can't use certain attributes set by Uptrace, for example, the _display_name column is not available and instead you should use spanName(), eventName(), logName(), and log_message.
For the same reason, you also can't use attributes extracted from structured logs and SQL queries, such as db_arg_* attributes.
| Uptrace Column | Replacement |
|---|---|
_display_name | spanName(), eventName(), and attr("log_message") |
db_arg_* | attr("db_statement") contains "something" |
http_response_status_class | http_response_status_code |
user_agent_* | user_agent_original |
Operations
Rename attribute
To rename attr from service to service_name:
name: Rename service to service_name
scope: [spans, logs, events]
type: rename_attr
old_key: service
new_key: service_name
Set overwrite: true to overwrite the target attribute if it already exists:
name: Rename service to service_name
scope: [spans, logs, events]
type: rename_attr
old_key: service
new_key: service_name
overwrite: true
Delete attributes
To delete attrs:
name: Delete foo and bar
scope: [spans, logs, events, datapoints]
type: delete_attrs
keys:
- foo
- bar
To delete attrs using a regular expression:
name: Delete foo and bar
scope: [spans, logs, events, datapoints]
type: delete_attrs
regexp: ^(foo|bar)$
To delete attrs in the specific metric:
name: Delete foo and bar
scope: [datapoints]
type: delete_attrs
keys:
- foo
- bar
if: metricName() == "my_metric_name"
Keep attributes
To keep some attributes and delete others:
name: Keep foo and bar
scope: [spans, logs, events, datapoints]
type: keep_attrs
keys:
- foo
- bar
To keep attributes matching a regular expression:
name: Keep foo and bar
scope: [spans, logs, events, datapoints]
type: keep_attrs
regexp: ^(foo|bar)$
Drop
To drop logs that match if expression:
name: Drop hello logs
scope: [logs]
type: drop
if: attr("log_message") contains "hello"
To drop spans that match if expression:
name: Drop Redis evalsha
scope: [spans]
type: drop
if: spanName() == "evalsha" && spanStatusCode() != "error"
The if condition is required for the drop operation.
Sample
To sample 50% of logs that match if expression:
name: Sample 50% hello logs
scope: [logs]
type: sample
fraction: 0.5
if: attr("log_message") startsWith "hello"
The fraction must be between 0 and 1. For spans, sampling is based on the trace ID so all spans in the same trace are either kept or dropped together. The if condition is optional — when omitted, the sampling applies to all signals in scope.
Change attribute type
To convert an attribute value to a different type:
name: Convert elapsed_ms to float
scope: [spans]
type: change_attr_type
key: elapsed_ms
type: float
Supported types: string, int, float, bool.
change_attr_type is not supported for datapoints. Use the script operation with type parsing functions instead.
Flatten Map
By default, Uptrace does not index attributes that contain maps. For example, the following attribute cannot be queried using the object.nested.foo path:
{
"object": {
"nested": {
"foo": "bar",
"hello": "world"
}
}
}
To flatten such attributes into dot-separated top-level attributes, use the flatten_map operation:
name: Flatten the object attribute
scope: [spans]
type: flatten_map
# Map attributes to flatten.
maps: ['object']
# Map paths to include.
include_paths: ['object.nested.foo']
include_regexp: '^object.nested\.'
# Map paths to exclude.
exclude_paths: ['object.nested.hello']
exclude_regexp: 'secret'
If you don't specify any paths, the operation will index the entire map but will stop after 32 paths. This limit exists to keep the number of indexed attributes low and improve performance.
IP Geo Attributes
The ip_geo_attrs operation enriches IP address attributes with GeoIP data. For each specified key, it looks up the IP address and adds {key}_country_code, {key}_country_name, and {key}_city attributes.
name: Enrich custom IP with geo data
scope: [spans, logs, events]
type: ip_geo_attrs
keys:
- custom_ip_address
client_address and client_socket_address are automatically processed by Uptrace and should not be added manually.
ip_geo_attrs is not supported for datapoints.
Text Index
The text_index operation collects selected attribute values into a full-text index column, enabling fast text search across attribute values using the *:value search syntax.
name: Index request attributes
scope: [spans, logs, events]
type: text_index
include:
- log_message
- exception_message
- http.request.**
exclude:
- http.request.headers.**
The include field accepts glob patterns with . as the separator:
| Pattern | Description |
|---|---|
foo | Indexes the foo attribute value directly |
foo.** | Indexes all nested keys in the foo map |
foo.* | Indexes one level of nested keys in the foo map |
foo?.bar | Matches attribute keys like foo1, foo2, etc. |
The exclude field uses the same glob patterns to remove specific paths from indexing.
For attributes of type map or array, the value is serialized as JSON. The search is case-insensitive and token-based, so substring matches are not supported. The maximum indexed text size per span/log is 4096 bytes.
Text Index is billed separately based on the number of indexed bytes. See the pricing page for details.
Once configured, you can search indexed content using *:value in the search bar:
*:hello
*:error|warning
-*:debug
Script
Uptrace allows to write simple scripts that can modify telemetry data, for example:
name: Update status code if span has exception* attributes
scope: [spans]
type: script
if: hasAttr("exception_type") && hasAttr("exception_message")
then:
- setSpanStatusCode("error")
- setSpanStatusMessage(attr("exception_message"))
else:
- setSpanStatusCode("ok")
The if condition is required for the script operation. The then and else lists contain expressions that are executed when the condition is true or false respectively.
To parse a string attribute value as a float:
name: Parse elapsed_ms
scope: [spans]
type: script
if: hasAttr("elapsed_ms")
then:
- setAttr("elapsed_ms", parseFloat(attr("elapsed_ms")))
To reduce cardinality of an attribute:
name: Reduce http_target cardinality
scope: [spans]
type: script
if: metricName() startsWith "http_server_" && hasAttr("http_target")
then:
- setAttr("http_target", replaceGlob(attr("http_target"), "/user/*/list/*", "/user/{userId}/list/{listId}"))
To replace all numbers in a string attribute using a regular expression:
name: Replace numbers in foo
scope: [spans]
type: script
if: hasAttr("foo")
then:
- setAttr("foo", replaceAllRegexp(attr("foo"), "(\\d+)", "<number>"))
To extract all numbers from a string attribute using a regular expression:
name: Extract numbers from foo
scope: [spans]
type: script
if: hasAttr("foo")
then:
- setAttr("foo", extractAllRegexp(attr("foo"), "(\\d+)", "$1"))
To convert a log into a span:
name: Convert logs with elapsed_ms to spans
scope: [logs]
type: script
if: hasAttr("elapsed_ms")
then:
- setSpanName("operation-name")
- setLogName("") # must zero the log name
- setSpanDuration(parseFloat(attr("elapsed_ms")) * 1e6)
- setSpanStatusCode("ok")
Functions reference
In addition to built-in functions provided by Expr, Uptrace supports the following functions. All functions can be used in if conditions and script expressions.
Span, log, and event functions
These functions are available when the scope includes spans, logs, or events.
Getter functions
| Function | Scope | Description |
|---|---|---|
spanName() | spans, logs, events | Returns the span name or an empty string. |
eventName() | events | Returns the event name or an empty string. |
logName() | logs | Returns the log name (alias for eventName). |
spanKind() | spans | Returns the span kind (internal, server, client, producer, consumer). |
spanDuration() | spans | Returns the span duration as a time.Duration (nanosecond precision). |
spanStatusCode() | spans, logs, events | Returns the span status code (unset, ok, error). |
spanStatusMessage() | spans, logs, events | Returns the span status message. |
Note that spanName() and eventName() return the original value as reported by OpenTelemetry, e.g. eventName() usually contains only a single word like message, log, etc.
Setter functions
| Function | Scope | Description |
|---|---|---|
setSpanName(string) | spans | Sets the span name. |
setEventName(string) | events | Sets the event name. |
setLogName(string) | logs | Sets the log name. |
setSpanKind(string) | spans | Sets the span kind. Must be one of: internal, server, client, producer, consumer. |
setSpanDuration(nanoseconds) | spans | Sets the span duration in nanoseconds. Accepts int, int64, or time.Duration. |
setSpanStatusCode(string) | spans | Sets the span status code. |
setSpanStatusMessage(string) | spans | Sets the span status message. |
Attribute functions
| Function | Scope | Description |
|---|---|---|
attr(key) | spans, logs, events | Returns the attribute value by key. |
attrType(key) | spans, logs, events | Returns the attribute type: nil, string, int, float, bool, array, or map. |
hasAttr(key) | spans, logs, events | Returns true if the attribute exists. |
setAttr(key, value) | spans, logs, events | Sets the attribute value. |
deleteAttr(key) | spans, logs, events | Deletes the attribute. |
renameAttr(oldKey, newKey) | spans, logs, events | Renames the attribute. |
renameAttr(oldKey, newKey, overwrite) | spans, logs, events | Renames the attribute, optionally overwriting if the target exists. |
Type parsing functions
| Function | Scope | Description |
|---|---|---|
parseInt(value) | spans, logs, events | Parses the value as int64. |
parseUint(value) | spans, logs, events | Parses the value as uint64. |
parseFloat(value) | spans, logs, events | Parses the value as float64. |
parseBool(value) | spans, logs, events | Parses the value as bool. Accepts strings like "true", "false", "1", "0". |
Datapoint functions
These functions are available when the scope includes datapoints.
Getter functions
| Function | Description |
|---|---|
metricName() | Returns the metric name. |
metricUnit() | Returns the metric unit. |
metricInstrument() | Returns the metric instrument type. |
libraryName() | Returns the instrumentation library name. |
libraryVersion() | Returns the instrumentation library version. |
Setter functions
| Function | Description |
|---|---|
setMetricName(string) | Sets the metric name. |
setMetricUnit(string) | Sets the metric unit. |
setMetricInstrument(string) | Sets the metric instrument type. |
Attribute functions
| Function | Description |
|---|---|
attr(key) | Returns the attribute value as a string. |
hasAttr(key) | Returns true if the attribute exists. |
setAttr(key, value) | Sets the attribute value. |
deleteAttr(key) | Deletes the attribute. |
renameAttr(oldKey, newKey) | Renames the attribute. |
renameAttr(oldKey, newKey, overwrite) | Renames the attribute, optionally overwriting. |
Common functions
These functions are available for all signal types.
| Function | Description |
|---|---|
replaceGlob(src, pattern, repl) | Returns repl if src matches glob pattern, otherwise returns src. |
replaceAllRegexp(src, pattern, repl) | Replaces all regexp matches in src with repl. |
extractAllRegexp(src, pattern, repl) | Extracts and transforms all regexp matches from src using repl template. |
parseDuration(string) | Parses a duration string (e.g. "1h30m", "500ms") as time.Duration. |
parseByteSize(string) | Parses a byte size string (e.g. "1GB", "512MB") as int64 bytes. |