Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
af3293a
OTTL function get_sem_conv_span_name
cyrille-leclerc Oct 6, 2025
3a8d2a6
OTTL function get_sem_conv_span_name
cyrille-leclerc Oct 6, 2025
5bc2e46
Merge branch 'main' into ottl-GetSemConvSpanName
cyrille-leclerc Oct 10, 2025
1d7f81c
Rename OTTL function get_sem_conv_span_name -> get_semconv_span_name
cyrille-leclerc Oct 10, 2025
d760120
make generate
cyrille-leclerc Oct 10, 2025
6e64951
OTTL function get_semconv_span_name
cyrille-leclerc Oct 10, 2025
b8b2926
OTTL function GetSemconvSpanName()
cyrille-leclerc Oct 10, 2025
999a423
Add docs README
cyrille-leclerc Oct 13, 2025
d0e568e
Changelog and better docs
cyrille-leclerc Oct 13, 2025
c89ca08
rename file
cyrille-leclerc Oct 13, 2025
8875bd6
Merge branch 'main' into ottl-GetSemConvSpanName
cyrille-leclerc Oct 13, 2025
03947d8
cleanup chlog
cyrille-leclerc Oct 13, 2025
e272862
Better docs
cyrille-leclerc Oct 13, 2025
2a3d7a0
OTTL function GetSemconvSpanName()
cyrille-leclerc Oct 15, 2025
2494f08
OTTL function GetSemconvSpanName()
cyrille-leclerc Oct 15, 2025
b2a7d5f
Update pkg/ottl/ottlfuncs/README.md
cyrille-leclerc Oct 16, 2025
88fb84e
Merge branch 'main' into ottl-GetSemConvSpanName
cyrille-leclerc Oct 16, 2025
11b1ef8
Fix semconv for DB span names
cyrille-leclerc Oct 17, 2025
fcb3cb2
Merge remote-tracking branch 'cyrille-leclerc/ottl-GetSemConvSpanName…
cyrille-leclerc Oct 17, 2025
7f37e56
Fix semconv for DB span names
cyrille-leclerc Oct 17, 2025
ccf7557
Merge branch 'main' into ottl-GetSemConvSpanName
cyrille-leclerc Oct 22, 2025
1575830
Convert the OTTL function into a Transform Processor function
cyrille-leclerc Oct 23, 2025
593608e
make gotidy
cyrille-leclerc Oct 23, 2025
917157a
convert function to set_semconv_span_name()
cyrille-leclerc Oct 28, 2025
8d032be
convert function to set_semconv_span_name()
cyrille-leclerc Oct 28, 2025
a065750
Fix docs
cyrille-leclerc Oct 29, 2025
497c915
Merge branch 'main' into ottl-GetSemConvSpanName
cyrille-leclerc Oct 29, 2025
dedc7cf
Merge branch 'main' into ottl-GetSemConvSpanName
cyrille-leclerc Oct 29, 2025
6fb2373
go tidy
cyrille-leclerc Oct 29, 2025
7f639aa
Refactor code
cyrille-leclerc Oct 29, 2025
5decfcf
Fix lint errors
cyrille-leclerc Oct 29, 2025
751f170
Refactor code
cyrille-leclerc Oct 29, 2025
90fa9ef
Cleanup code
cyrille-leclerc Oct 29, 2025
063dd29
Cleanup code
cyrille-leclerc Oct 29, 2025
efbbb97
Cleanup code
cyrille-leclerc Oct 29, 2025
533a766
Merge branch 'open-telemetry:main' into ottl-GetSemConvSpanName
cyrille-leclerc Oct 29, 2025
6e87e07
Better README.md
cyrille-leclerc Oct 29, 2025
9be059e
Merge branch 'main' into ottl-GetSemConvSpanName
cyrille-leclerc Oct 29, 2025
17274db
Better README.md
cyrille-leclerc Oct 29, 2025
7beeb65
Merge remote-tracking branch 'cyrille-leclerc/ottl-GetSemConvSpanName…
cyrille-leclerc Oct 29, 2025
293de9c
Merge branch 'main' into ottl-GetSemConvSpanName
cyrille-leclerc Oct 30, 2025
07a8604
clarify support for older semconv attributes.
cyrille-leclerc Oct 31, 2025
73a844a
Merge branch 'main' into ottl-GetSemConvSpanName
cyrille-leclerc Oct 31, 2025
7620d74
Merge remote-tracking branch 'origin/main' into ottl-GetSemConvSpanName
cyrille-leclerc Nov 2, 2025
9034da3
Fix changelog and improve docs
cyrille-leclerc Nov 2, 2025
ff2bf32
Merge remote-tracking branch 'origin/main' into ottl-GetSemConvSpanName
cyrille-leclerc Nov 4, 2025
51f02a3
fix deps bump
cyrille-leclerc Nov 4, 2025
bac7386
go mod tidy
cyrille-leclerc Nov 4, 2025
df56ef1
Merge branch 'main' into ottl-GetSemConvSpanName
cyrille-leclerc Nov 4, 2025
020aaa4
Merge branch 'main' into ottl-GetSemConvSpanName
cyrille-leclerc Nov 5, 2025
e8f38c0
Merge branch 'main' into ottl-GetSemConvSpanName
cyrille-leclerc Nov 7, 2025
9237424
go mod tidy
cyrille-leclerc Nov 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .chloggen/transform-func-set_semconv_span_name.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: "enhancement"

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: "processor/transform"

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: "New Transform Processor function `set_semconv_span_name()` to overwrite the span name with the semantic conventions for HTTP, RPC, messaging, and database spans."

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [43124]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext: |
In other cases, the original `span.name` is unchanged.
The primary use of `set_semconv_span_name()` is alongside the
[Span Metrics Connector](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/connector/spanmetricsconnector)
to address high-cardinality issues in span metrics when `span.name` does not comply with the OTel requirement
that span names be low cardinality.

# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: [user]
74 changes: 74 additions & 0 deletions processor/transformprocessor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,10 @@ In addition to the common OTTL functions, the processor defines its own function
- [aggregate_on_attribute_value](#aggregate_on_attribute_value)
- [merge_histogram_buckets](#merge_histogram_buckets)

**Traces only functions**

- [set_semconv_span_name](#set_semconv_span_name)

### convert_sum_to_gauge

`convert_sum_to_gauge()`
Expand Down Expand Up @@ -650,6 +654,76 @@ Examples:
# counts: [5, 11, 1]
```

### set_semconv_span_name

`set_semconv_span_name(semconvVersion, Optional[originalSpanNameAttribute])`

The `set_semconv_span_name()` function overwrites a span name using the OpenTelemetry semantic conventions for [HTTP](https://opentelemetry.io/docs/specs/semconv/http/http-spans/), [RPC](https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/), [messaging](https://opentelemetry.io/docs/specs/semconv/messaging/messaging-spans/), and [database](https://opentelemetry.io/docs/specs/semconv/database/) spans. In other cases, the original `span.name` remains unchanged.

The primary use case of the `set_semconv_span_name()` function is to address high-cardinality issues in span metrics when `span.name` doesn't comply with the OpenTelemetry requirement that span names be low cardinality such as `GET /product/12345`, `GET/product?id=12345`, or `SELECT * FROM product WHERE id=12345`.

Parameters:

* `semconvVersion` is the version of the Semantic Conventions used to generate the `span.name`, older semconv attributes are supported. `1.37.0` is the supported version.
* `originalSpanNameAttribute` is the optional name of the attribute used to copy the original `span.name` if different from the name derived from semantic conventions.

Sanitization examples:

* Span with high-cardinality name but recommended semantic convention attributes
* Incoming span:
```
span.name: GET /api/v1/users/123 # /!\ high cardinality
span.kind: server
span.attributes
http.request.method: GET
http.route: /api/v1/users/{id}
url.path: /api/v1/users/123
```
* Span name after applying `set_semconv_span_name("1.37.0")`: `GET /api/v1/users/{id}`
* No loss of information on `span.name` occurs because the recommended attribute `http.route` is present.
* Span with high-cardinality name lacking recommended semantic convention attribute `http.route`
* Incoming span:
```
span.name: GET /api/v1/users/123 # /!\ high cardinality
span.kind: server
span.attributes
http.request.method: GET
url.path: /api/v1/users/123
```
* Span name after applying `set_semconv_span_name("1.37.0")`: `GET`
* Loss of information on `span.name` occurs because the recommended attribute `http.route` is missing
and is mitigated by other span attributes like `url.path` or `url.full`.
* Compliant span name is unchanged
* Incoming span:
```
span.name: GET /api/v1/users/{id}
span.kind: server
span.attributes
http.request.method: GET
http.route: /api/v1/users/{id}
url.path: /api/v1/users/123
```
* Span name after applying `set_semconv_span_name("1.37.0")`: `GET /api/v1/users/{id}`


Backward compatibility: `set_semconv_span_name()` supports the version 1.37.0 of the semantic conventions and backward compatibility
for the following attributes:

| Attribute | Backward compatibility |
|-----------------------|------------------------|
| `http.request.method` | `http.method` |
| `rpc.method` | `rpc.grpc.method` |
| `rpc.service` | `rpc.grpc.service` |
| `db.system.name` | `db.system` |
| `db.operation.name` | `db.operation` |
| `db.collection.name` | `db.name` |

Examples:

- `set_semconv_span_name("1.37.0")`

- `set_semconv_span_name("1.37.0", "original_span_name")`

## Examples

### Perform transformation if field does not exist
Expand Down
2 changes: 1 addition & 1 deletion processor/transformprocessor/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ require (
go.opentelemetry.io/collector/processor/processorhelper/xprocessorhelper v0.139.1-0.20251105131800-09a271914bdc
go.opentelemetry.io/collector/processor/processortest v0.139.1-0.20251105131800-09a271914bdc
go.opentelemetry.io/collector/processor/xprocessor v0.139.1-0.20251105131800-09a271914bdc
go.opentelemetry.io/otel v1.38.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
)

Expand Down Expand Up @@ -75,7 +76,6 @@ require (
go.opentelemetry.io/collector/component/componentstatus v0.139.1-0.20251105131800-09a271914bdc // indirect
go.opentelemetry.io/collector/pdata/testdata v0.139.1-0.20251105131800-09a271914bdc // indirect
go.opentelemetry.io/collector/pipeline v1.45.1-0.20251105131800-09a271914bdc // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package traces // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor/internal/traces"

import (
"context"
"errors"
"fmt"

"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"

"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlspan"
)

type setSemconvSpanNameArguments struct {
SemconvVersion string
OriginalSpanNameAttribute ottl.Optional[string]
}

func NewSetSemconvSpanNameFactory() ottl.Factory[ottlspan.TransformContext] {
return ottl.NewFactory("set_semconv_span_name", &setSemconvSpanNameArguments{}, createSetSemconvSpanNameFunction)
}

func createSetSemconvSpanNameFunction(_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[ottlspan.TransformContext], error) {
args, ok := oArgs.(*setSemconvSpanNameArguments)

if !ok {
return nil, errors.New("NewSetSemconvSpanNameFactory args must be of type *setSemconvSpanNameArguments")
}
semconvVersion := args.SemconvVersion
originalSpanNameAttribute := args.OriginalSpanNameAttribute

// Currently only v1.37.0 is supported
supportedSemconvVersion := "1.37.0"
if semconvVersion != supportedSemconvVersion {
return nil, fmt.Errorf("unsupported semconv version: %s, supported version: %s", semconvVersion, supportedSemconvVersion)
}

return func(_ context.Context, tCtx ottlspan.TransformContext) (any, error) {
setSemconvSpanName(originalSpanNameAttribute, tCtx.GetSpan())
return nil, nil
}, nil
}

func setSemconvSpanName(originalSpanNameAttribute ottl.Optional[string], span ptrace.Span) {
originalSpanName := span.Name()
semConvSpanName := SemconvSpanName(span)
span.SetName(semConvSpanName)
if originalSpanName != semConvSpanName && originalSpanNameAttribute.GetOr("") != "" {
span.Attributes().PutStr(originalSpanNameAttribute.Get(), originalSpanName)
}
}

func SemconvSpanName(span ptrace.Span) string {
switch span.Kind() {
case ptrace.SpanKindServer:
spanName := httpSpanName(span, semconv.HTTPRouteKey)
if spanName != "" {
return spanName
}
spanName = rpcSpanName(span)
if spanName != "" {
return spanName
}
spanName = messagingSpanName(span)
if spanName != "" {
return spanName
}
case ptrace.SpanKindConsumer:
spanName := messagingSpanName(span)
if spanName != "" {
return spanName
}
case ptrace.SpanKindClient:
spanName := httpSpanName(span, semconv.URLTemplateKey)
if spanName != "" {
return spanName
}
spanName = rpcSpanName(span)
if spanName != "" {
return spanName
}
spanName = dbSpanName(span)
if spanName != "" {
return spanName
}
spanName = messagingSpanName(span)
if spanName != "" {
return spanName
}
case ptrace.SpanKindProducer:
spanName := messagingSpanName(span)
if spanName != "" {
return spanName
}
}
// If no semantic convention defines the span name, default to the original span name
return span.Name()
}

// https://opentelemetry.io/docs/specs/semconv/http/http-spans/
func httpSpanName(span ptrace.Span, subject attribute.Key) string {
if method, ok := attributeValue(span, semconv.HTTPRequestMethodKey, "http.method"); ok {
if subjectVal, ok := span.Attributes().Get(string(subject)); ok {
return method.AsString() + " " + subjectVal.AsString()
}
return method.AsString()
}
return ""
}

// https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/
func rpcSpanName(span ptrace.Span) string {
if system, ok := span.Attributes().Get(string(semconv.RPCSystemKey)); ok {
method, okMethod := attributeValue(span, semconv.RPCMethodKey, "rpc.grpc.method")
service, okService := attributeValue(span, semconv.RPCServiceKey, "rpc.grpc.service")

if okMethod && okService {
return service.AsString() + "/" + method.AsString()
}
if okMethod {
return method.AsString()
}
if okService {
return service.AsString() + "/*"
}
return system.AsString()
}
return ""
}

// https://opentelemetry.io/docs/specs/semconv/database/database-spans/
func dbSpanName(span ptrace.Span) string {
if system, ok := attributeValue(span, semconv.DBSystemNameKey, "db.system"); ok {
if summary, ok := span.Attributes().Get(string(semconv.DBQuerySummaryKey)); ok {
return summary.AsString()
}
operationName := ""
if operation, ok := attributeValue(span, semconv.DBOperationNameKey, "db.operation"); ok {
operationName = operation.AsString()
}

target := databaseTarget(span)

if operationName != "" && target != "" {
return operationName + " " + target
}
if operationName != "" {
return operationName
}
if target != "" {
return target
}
return system.AsString()
}
return ""
}

func databaseTarget(span ptrace.Span) string {
dbNamespace := ""
if namespace, ok := attributeValue(span, semconv.DBNamespaceKey, "db.name"); ok {
dbNamespace = namespace.AsString()
}

if collection, ok := span.Attributes().Get(string(semconv.DBCollectionNameKey)); ok {
if dbNamespace != "" {
return dbNamespace + "." + collection.AsString()
}
return collection.AsString()
}

if storedProcedure, ok := span.Attributes().Get(string(semconv.DBStoredProcedureNameKey)); ok {
if dbNamespace != "" {
return dbNamespace + "." + storedProcedure.AsString()
}
return storedProcedure.AsString()
}

if dbNamespace != "" {
return dbNamespace
}

if serverAddress, ok := span.Attributes().Get(string(semconv.ServerAddressKey)); ok {
if serverPort, ok := span.Attributes().Get(string(semconv.ServerPortKey)); ok {
return serverAddress.AsString() + ":" + serverPort.AsString()
}
return serverAddress.AsString()
}
return ""
}

// https://opentelemetry.io/docs/specs/semconv/messaging/messaging-spans/#span-name
func messagingSpanName(span ptrace.Span) string {
if system, ok := span.Attributes().Get(string(semconv.MessagingSystemKey)); ok {
operation, okOperation := attributeValue(span, semconv.MessagingOperationNameKey, "messaging.operation")
destination := messagingDestination(span)

if okOperation && destination != "" {
return operation.AsString() + " " + destination
}
if destination != "" {
return destination
}
if okOperation {
return operation.AsString()
}
return system.AsString()
}
return ""
}

// https://opentelemetry.io/docs/specs/semconv/messaging/messaging-spans/#span-name
func messagingDestination(span ptrace.Span) string {
if temporaryDestination, ok := span.Attributes().Get(string(semconv.MessagingDestinationTemporaryKey)); ok {
if temporaryDestination.Bool() {
return "(temporary)"
}
}
if anonymousDestination, ok := span.Attributes().Get(string(semconv.MessagingDestinationAnonymousKey)); ok {
// anonymous destinations should also be marked as temporary by the messaging instrumentation
// double check in case the instrumentation forgot to mark the anonymous destination as temporary
if anonymousDestination.Bool() {
return "(anonymous)"
}
}
if destinationTemplate, ok := span.Attributes().Get(string(semconv.MessagingDestinationTemplateKey)); ok {
return destinationTemplate.AsString()
}
if destinationName, ok := attributeValue(span, semconv.MessagingDestinationNameKey, "messaging.destination"); ok {
return destinationName.AsString()
}
if serverAddress, ok := span.Attributes().Get(string(semconv.ServerAddressKey)); ok {
if serverPort, ok := span.Attributes().Get(string(semconv.ServerPortKey)); ok {
return serverAddress.AsString() + ":" + serverPort.AsString()
}
return serverAddress.AsString()
}
return ""
}

func attributeValue(span ptrace.Span, name attribute.Key, alias string) (pcommon.Value, bool) {
if val, ok := span.Attributes().Get(string(name)); ok {
return val, true
}
if alias != "" {
if val, ok := span.Attributes().Get(alias); ok {
return val, true
}
}
return pcommon.Value{}, false
}
Loading