diff --git a/.chloggen/transformprocessor-set_semconv_span_name-v1.40.0.yaml b/.chloggen/transformprocessor-set_semconv_span_name-v1.40.0.yaml new file mode 100644 index 0000000000000..1346e38068a57 --- /dev/null +++ b/.chloggen/transformprocessor-set_semconv_span_name-v1.40.0.yaml @@ -0,0 +1,31 @@ +# 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. receiver/filelog) +component: processor/transform + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Add support for semantic conventions 1.38.0, 1.39.0, and 1.40.0 in the `set_semconv_span_name` function. + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [45911] + +# (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: > + The `set_semconv_span_name` function now recognizes semantic conventions 1.38.0, 1.39.0, and 1.40.0, allowing span + names to be determined using the latest rules. Support for the `rpc.system.name` attribute (introduced in 1.39.0) + has been added so span names can reflect the new RPC system conventions. Backward compatibility is preserved: the + `rpc.system` attribute remains supported. + +# 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] diff --git a/internal/datadog/e2e/go.mod b/internal/datadog/e2e/go.mod index 2f383e7939569..64a9ae88f86ce 100644 --- a/internal/datadog/e2e/go.mod +++ b/internal/datadog/e2e/go.mod @@ -150,6 +150,7 @@ require ( github.com/DataDog/zstd v1.5.7 // indirect github.com/DataDog/zstd_0 v0.0.0-20210310093942-586c1286621f // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 // indirect + github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Showmax/go-fqdn v1.0.0 // indirect github.com/alecthomas/participle/v2 v2.1.4 // indirect diff --git a/processor/transformprocessor/README.md b/processor/transformprocessor/README.md index 47d9b9aab7917..5cecadccbd8fc 100644 --- a/processor/transformprocessor/README.md +++ b/processor/transformprocessor/README.md @@ -671,7 +671,7 @@ The primary use case of the `set_semconv_span_name()` function is to address hig Parameters: -* `semconvVersion` is the version of the Semantic Conventions used to generate the `span.name`, older semconv attributes are supported. `1.37.0` is currently the only supported version. +* `semconvVersion` is the version of the Semantic Conventions used to generate the `span.name`, older semconv attributes are supported. Versions `1.37.0` to `1.40.0` are supported. * `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: @@ -686,7 +686,7 @@ Sanitization examples: 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}` + * Span name after applying `set_semconv_span_name("1.40.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: @@ -697,7 +697,7 @@ Sanitization examples: http.request.method: GET url.path: /api/v1/users/123 ``` - * Span name after applying `set_semconv_span_name("1.37.0")`: `GET` + * Span name after applying `set_semconv_span_name("1.40.0")`: `GET` * Loss of information on `span.name` occurs because the recommended attribute `http.route` is missing. Note that this loss of information is mitigated if the instrumentation produced attributes that contain the URL path like `url.path` or `url.full`. * Compliant span name is unchanged @@ -710,25 +710,26 @@ Sanitization examples: 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}` + * Span name after applying `set_semconv_span_name("1.40.0")`: `GET /api/v1/users/{id}` -Backward compatibility: `set_semconv_span_name` will map the following attributes to their equivalents per the v1.37.0 semantic conventions: +Backward compatibility: `set_semconv_span_name` will map the following attributes to their equivalents per the v1.39.0 semantic conventions: -| v1.37.0 Attribute | Older attribute | -|-----------------------|------------------------| -| `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` | +| v1.40.0 Attribute | Older attribute | +|-----------------------|--------------------| +| `http.request.method` | `http.method` | +| `rpc.method` | `rpc.grpc.method` | +| `rpc.service` | `rpc.grpc.service` | +| `rpc.system.name` | `rpc.system` | +| `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.40.0")` -- `set_semconv_span_name("1.37.0", "original_span_name")` +- `set_semconv_span_name("1.40.0", "original_span_name")` ## Examples diff --git a/processor/transformprocessor/go.mod b/processor/transformprocessor/go.mod index 3578ac8b94358..1105f3478faf9 100644 --- a/processor/transformprocessor/go.mod +++ b/processor/transformprocessor/go.mod @@ -24,6 +24,7 @@ require ( ) require ( + github.com/Masterminds/semver/v3 v3.4.0 go.opentelemetry.io/collector/component/componenttest v0.149.0 go.opentelemetry.io/collector/confmap/xconfmap v0.149.0 go.opentelemetry.io/collector/consumer/consumertest v0.149.0 diff --git a/processor/transformprocessor/go.sum b/processor/transformprocessor/go.sum index 5a5e40074a504..bd46e0f6d52e7 100644 --- a/processor/transformprocessor/go.sum +++ b/processor/transformprocessor/go.sum @@ -1,3 +1,5 @@ +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/participle/v2 v2.1.4 h1:W/H79S8Sat/krZ3el6sQMvMaahJ+XcM9WSI2naI7w2U= diff --git a/processor/transformprocessor/internal/traces/func_set_semconv_span_name.go b/processor/transformprocessor/internal/traces/func_set_semconv_span_name.go index bbe8e92ed73ea..ca4613b600ed4 100644 --- a/processor/transformprocessor/internal/traces/func_set_semconv_span_name.go +++ b/processor/transformprocessor/internal/traces/func_set_semconv_span_name.go @@ -8,17 +8,20 @@ import ( "errors" "fmt" + "github.com/Masterminds/semver/v3" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/ptrace" "go.opentelemetry.io/otel/attribute" - conventions "go.opentelemetry.io/otel/semconv/v1.38.0" + conventions "go.opentelemetry.io/otel/semconv/v1.40.0" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlspan" ) -// Currently only v1.37.0 is supported -const supportedSemconvVersion = "1.37.0" +var ( + minKnownSemConvVersion = semver.MustParse("1.37.0") + maxKnownSemConvVersion = semver.MustParse("1.40.0") +) type setSemconvSpanNameArguments struct { SemconvVersion string @@ -30,19 +33,10 @@ func NewSetSemconvSpanNameFactoryLegacy() ottl.Factory[ottlspan.TransformContext } func createSetSemconvSpanNameFunctionLegacy(_ 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") - } - if args.SemconvVersion != supportedSemconvVersion { - return nil, fmt.Errorf("unsupported semconv version: %s, supported version: %s", args.SemconvVersion, supportedSemconvVersion) + args, err := parseSemconvSpanNameArguments(oArgs) + if err != nil { + return nil, err } - - if !args.OriginalSpanNameAttribute.IsEmpty() && args.OriginalSpanNameAttribute.Get() == "" { - return nil, errors.New("originalSpanNameAttribute cannot be an empty string") - } - return func(_ context.Context, tCtx ottlspan.TransformContext) (any, error) { setSemconvSpanName(args.OriginalSpanNameAttribute, tCtx.GetSpan()) return nil, nil @@ -54,23 +48,32 @@ func NewSetSemconvSpanNameFactory() ottl.Factory[*ottlspan.TransformContext] { } func createSetSemconvSpanNameFunction(_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[*ottlspan.TransformContext], error) { - args, ok := oArgs.(*setSemconvSpanNameArguments) + args, err := parseSemconvSpanNameArguments(oArgs) + if err != nil { + return nil, err + } + return func(_ context.Context, tCtx *ottlspan.TransformContext) (any, error) { + setSemconvSpanName(args.OriginalSpanNameAttribute, tCtx.GetSpan()) + return nil, nil + }, nil +} +func parseSemconvSpanNameArguments(oArgs ottl.Arguments) (*setSemconvSpanNameArguments, error) { + args, ok := oArgs.(*setSemconvSpanNameArguments) if !ok { return nil, errors.New("NewSetSemconvSpanNameFactory args must be of type *setSemconvSpanNameArguments") } - if args.SemconvVersion != supportedSemconvVersion { - return nil, fmt.Errorf("unsupported semconv version: %s, supported version: %s", args.SemconvVersion, supportedSemconvVersion) + semconvVersion, err := semver.NewVersion(args.SemconvVersion) + if err != nil { + return nil, fmt.Errorf("failed to parse semconv version %q: %w", args.SemconvVersion, err) + } + if semconvVersion.LessThan(minKnownSemConvVersion) || semconvVersion.GreaterThan(maxKnownSemConvVersion) { + return nil, fmt.Errorf("unsupported semconv version %q: must be between %s and %s", args.SemconvVersion, minKnownSemConvVersion, maxKnownSemConvVersion) } - if !args.OriginalSpanNameAttribute.IsEmpty() && args.OriginalSpanNameAttribute.Get() == "" { return nil, errors.New("originalSpanNameAttribute cannot be an empty string") } - - return func(_ context.Context, tCtx *ottlspan.TransformContext) (any, error) { - setSemconvSpanName(args.OriginalSpanNameAttribute, tCtx.GetSpan()) - return nil, nil - }, nil + return args, nil } func setSemconvSpanName(originalSpanNameAttribute ottl.Optional[string], span ptrace.Span) { @@ -142,9 +145,9 @@ func httpSpanName(span ptrace.Span, subject attribute.Key) string { // https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/ func rpcSpanName(span ptrace.Span) string { - if system, ok := span.Attributes().Get(string(conventions.RPCSystemKey)); ok { + if system, ok := attributeValue(span, conventions.RPCSystemNameKey, "rpc.system"); ok { method, okMethod := attributeValue(span, conventions.RPCMethodKey, "rpc.grpc.method") - service, okService := attributeValue(span, conventions.RPCServiceKey, "rpc.grpc.service") + service, okService := attributeValue(span, "rpc.service", "rpc.grpc.service") if okMethod && okService { return service.AsString() + "/" + method.AsString() diff --git a/processor/transformprocessor/internal/traces/func_set_semconv_span_name_test.go b/processor/transformprocessor/internal/traces/func_set_semconv_span_name_test.go index 70b60b5fa6587..b93f7d065553d 100644 --- a/processor/transformprocessor/internal/traces/func_set_semconv_span_name_test.go +++ b/processor/transformprocessor/internal/traces/func_set_semconv_span_name_test.go @@ -24,7 +24,25 @@ func Test_createSetSemconvSpanNameFunction_parameterChecks(t *testing.T) { wantError bool }{ { - name: "valid semconv version and original span name attribute", + name: "valid semconv version 1.40.0 and original span name attribute", + semconvVersion: "1.40.0", + originalSpanNameAttribute: ottl.NewTestingOptional("original_span_name"), + wantError: false, + }, + { + name: "valid semconv version 1.39.0 and original span name attribute", + semconvVersion: "1.39.0", + originalSpanNameAttribute: ottl.NewTestingOptional("original_span_name"), + wantError: false, + }, + { + name: "valid semconv version 1.38.0 and original span name attribute", + semconvVersion: "1.38.0", + originalSpanNameAttribute: ottl.NewTestingOptional("original_span_name"), + wantError: false, + }, + { + name: "valid semconv version 1.37.0 and original span name attribute", semconvVersion: "1.37.0", originalSpanNameAttribute: ottl.NewTestingOptional("original_span_name"), wantError: false, @@ -42,8 +60,8 @@ func Test_createSetSemconvSpanNameFunction_parameterChecks(t *testing.T) { wantError: true, }, { - name: "invalid semconv version and valid original span name attribute", - semconvVersion: "1.38.0", + name: "unsupported semconv version and valid original span name attribute", + semconvVersion: "1.36.0", originalSpanNameAttribute: ottl.NewTestingOptional("original_span_name"), wantError: true, }, @@ -282,7 +300,6 @@ VALUES (@p7, @p8, @p9, @p10, @p11, @p12, @p13, @p14, @p15, @p16); }, want: "oteldemo.AdService/GetAds", }, - // MESSAGING - KAFKA { name: "Messaging OTel Demo - accounting - ", @@ -481,7 +498,7 @@ VALUES (@p7, @p8, @p9, @p10, @p11, @p12, @p13, @p14, @p15, @p16); tt.addAttributes(span.Attributes()) setSemconvNameFunction, err := createSetSemconvSpanNameFunction(ottl.FunctionContext{}, &setSemconvSpanNameArguments{ - SemconvVersion: supportedSemconvVersion, + SemconvVersion: maxKnownSemConvVersion.String(), OriginalSpanNameAttribute: ottl.NewTestingOptional("original_span_name"), }) @@ -680,6 +697,38 @@ func Test_rpcSpanName(t *testing.T) { }, want: "grpc", }, + { + name: "'rpc.system.name' and 'rpc.method', no 'rpc.service' - semconv 1.39+", + spanName: "a span name", + instrumentationLibrary: "hand crafted", + kind: ptrace.SpanKindServer, + addAttributes: func(attrs pcommon.Map) { + attrs.PutStr("rpc.system.name", "grpc") + attrs.PutStr("rpc.method", "oteldemo.AdService/a_method") + }, + want: "oteldemo.AdService/a_method", + }, + // Version-based priority: when both rpc.system.name and rpc.system are present + { + name: "semconv 1.40.0: both 'rpc.system.name' and 'rpc.system' present - prioritizes 'rpc.system.name'", + spanName: "a span name", + kind: ptrace.SpanKindServer, + addAttributes: func(attrs pcommon.Map) { + attrs.PutStr("rpc.system.name", "grpc") + attrs.PutStr("rpc.system", "other_rpc") + }, + want: "grpc", + }, + { + name: "semconv 1.39.0: both 'rpc.system.name' and 'rpc.system' present - prioritizes 'rpc.system.name'", + spanName: "a span name", + kind: ptrace.SpanKindServer, + addAttributes: func(attrs pcommon.Map) { + attrs.PutStr("rpc.system.name", "grpc") + attrs.PutStr("rpc.system", "other_rpc") + }, + want: "grpc", + }, } for _, tt := range tests {