diff --git a/exporter/datadogexporter/translate_traces.go b/exporter/datadogexporter/translate_traces.go index 5d7ab99f66b7..bd09b1480d62 100644 --- a/exporter/datadogexporter/translate_traces.go +++ b/exporter/datadogexporter/translate_traces.go @@ -18,7 +18,6 @@ import ( "encoding/hex" "fmt" "strconv" - "strings" "github.com/DataDog/datadog-agent/pkg/trace/exportable/pb" "go.opentelemetry.io/collector/consumer/pdata" @@ -382,18 +381,18 @@ func getDatadogSpanName(s pdata.Span, datadogTags map[string]string) string { // The spec has changed over time and, depending on the original exporter, IL Name could represented a few different ways // so we try to account for all permutations if ilnOtlp, okOtlp := datadogTags[tracetranslator.TagInstrumentationName]; okOtlp { - return utils.NormalizeSpanName(fmt.Sprintf("%s.%s", ilnOtlp, strings.TrimPrefix(s.Kind().String(), "SPAN_KIND_"))) + return utils.NormalizeSpanName(fmt.Sprintf("%s.%s", ilnOtlp, utils.NormalizeSpanKind(s.Kind()))) } if ilnOtelCur, okOtelCur := datadogTags[currentILNameTag]; okOtelCur { - return utils.NormalizeSpanName(fmt.Sprintf("%s.%s", ilnOtelCur, strings.TrimPrefix(s.Kind().String(), "SPAN_KIND_"))) + return utils.NormalizeSpanName(fmt.Sprintf("%s.%s", ilnOtelCur, utils.NormalizeSpanKind(s.Kind()))) } if ilnOtelOld, okOtelOld := datadogTags[oldILNameTag]; okOtelOld { - return utils.NormalizeSpanName(fmt.Sprintf("%s.%s", ilnOtelOld, strings.TrimPrefix(s.Kind().String(), "SPAN_KIND_"))) + return utils.NormalizeSpanName(fmt.Sprintf("%s.%s", ilnOtelOld, utils.NormalizeSpanKind(s.Kind()))) } - return utils.NormalizeSpanName(fmt.Sprintf("%s.%s", "opentelemetry", strings.TrimPrefix(s.Kind().String(), "SPAN_KIND_"))) + return utils.NormalizeSpanName(fmt.Sprintf("%s.%s", "opentelemetry", utils.NormalizeSpanKind(s.Kind()))) } func getDatadogResourceName(s pdata.Span, datadogTags map[string]string) string { @@ -423,6 +422,15 @@ func getDatadogResourceName(s pdata.Span, datadogTags map[string]string) string return msgOperation } + // add resource convention for rpc services , method+service, fallback to just method if no service attribute + if rpcMethod, rpcMethodOk := datadogTags[conventions.AttributeRPCMethod]; rpcMethodOk { + if rpcService, rpcServiceOk := datadogTags[conventions.AttributeRPCService]; rpcServiceOk { + return fmt.Sprintf("%s %s", rpcMethod, rpcService) + } + + return rpcMethod + } + return s.Name() } diff --git a/exporter/datadogexporter/translate_traces_test.go b/exporter/datadogexporter/translate_traces_test.go index c09509975799..f02fb4a78d8d 100644 --- a/exporter/datadogexporter/translate_traces_test.go +++ b/exporter/datadogexporter/translate_traces_test.go @@ -491,7 +491,74 @@ func TestSpanResourceTranslationMessaging(t *testing.T) { assert.Equal(t, "Default Name", resourceNameDefault) } -// ensure that the datadog span name uses IL name +kind whenn available and falls back to opetelemetry + kind +// ensure that datadog span resource naming uses messaging operation even when destination is not available +func TestSpanResourceTranslationMessagingFallback(t *testing.T) { + span := pdata.NewSpan() + span.SetKind(pdata.SpanKindSERVER) + span.SetName("Default Name") + + ddHTTPTags := map[string]string{ + "messaging.operation": "receive", + } + + ddNotHTTPTags := map[string]string{ + "other": "GET", + } + + resourceNameHTTP := getDatadogResourceName(span, ddHTTPTags) + + resourceNameDefault := getDatadogResourceName(span, ddNotHTTPTags) + + assert.Equal(t, "receive", resourceNameHTTP) + assert.Equal(t, "Default Name", resourceNameDefault) +} + +// ensure that datadog span resource naming uses rpc method + rpc service when available +func TestSpanResourceTranslationRpc(t *testing.T) { + span := pdata.NewSpan() + span.SetKind(pdata.SpanKindSERVER) + span.SetName("Default Name") + + ddHTTPTags := map[string]string{ + "rpc.method": "example_method", + "rpc.service": "example_service", + } + + ddNotHTTPTags := map[string]string{ + "other": "GET", + } + + resourceNameHTTP := getDatadogResourceName(span, ddHTTPTags) + + resourceNameDefault := getDatadogResourceName(span, ddNotHTTPTags) + + assert.Equal(t, "example_method example_service", resourceNameHTTP) + assert.Equal(t, "Default Name", resourceNameDefault) +} + +// ensure that datadog span resource naming uses rpc method even when rpc service is not available +func TestSpanResourceTranslationRpcFallback(t *testing.T) { + span := pdata.NewSpan() + span.SetKind(pdata.SpanKindSERVER) + span.SetName("Default Name") + + ddHTTPTags := map[string]string{ + "rpc.method": "example_method", + } + + ddNotHTTPTags := map[string]string{ + "other": "GET", + } + + resourceNameHTTP := getDatadogResourceName(span, ddHTTPTags) + + resourceNameDefault := getDatadogResourceName(span, ddNotHTTPTags) + + assert.Equal(t, "example_method", resourceNameHTTP) + assert.Equal(t, "Default Name", resourceNameDefault) +} + +// ensure that the datadog span name uses IL name +kind when available and falls back to opetelemetry + kind func TestSpanNameTranslation(t *testing.T) { span := pdata.NewSpan() span.SetName("Default Name") @@ -517,17 +584,23 @@ func TestSpanNameTranslation(t *testing.T) { "otel.library.name": "@unusual/\\::value", } + ddIlTagsHyphen := map[string]string{ + "otel.library.name": "hyphenated-value", + } + spanNameIl := getDatadogSpanName(span, ddIlTags) spanNameDefault := getDatadogSpanName(span, ddNoIlTags) spanNameOld := getDatadogSpanName(span, ddIlTagsOld) spanNameCur := getDatadogSpanName(span, ddIlTagsCur) spanNameUnusual := getDatadogSpanName(span, ddIlTagsUnusual) + spanNameHyphen := getDatadogSpanName(span, ddIlTagsHyphen) assert.Equal(t, strings.ToLower(fmt.Sprintf("%s.%s", "il_name", strings.TrimPrefix(pdata.SpanKindSERVER.String(), "SPAN_KIND_"))), spanNameIl) assert.Equal(t, strings.ToLower(fmt.Sprintf("%s.%s", "opentelemetry", strings.TrimPrefix(pdata.SpanKindSERVER.String(), "SPAN_KIND_"))), spanNameDefault) assert.Equal(t, strings.ToLower(fmt.Sprintf("%s.%s", "old_value", strings.TrimPrefix(pdata.SpanKindSERVER.String(), "SPAN_KIND_"))), spanNameOld) assert.Equal(t, strings.ToLower(fmt.Sprintf("%s.%s", "current_value", strings.TrimPrefix(pdata.SpanKindSERVER.String(), "SPAN_KIND_"))), spanNameCur) assert.Equal(t, strings.ToLower(fmt.Sprintf("%s.%s", "unusual_value", strings.TrimPrefix(pdata.SpanKindSERVER.String(), "SPAN_KIND_"))), spanNameUnusual) + assert.Equal(t, strings.ToLower(fmt.Sprintf("%s.%s", "hyphenated_value", strings.TrimPrefix(pdata.SpanKindSERVER.String(), "SPAN_KIND_"))), spanNameHyphen) } // ensure that the datadog span type gets mapped from span kind diff --git a/exporter/datadogexporter/utils/trace_helpers.go b/exporter/datadogexporter/utils/trace_helpers.go index 0a7017e6c96d..ba1adf848d09 100644 --- a/exporter/datadogexporter/utils/trace_helpers.go +++ b/exporter/datadogexporter/utils/trace_helpers.go @@ -17,6 +17,8 @@ package utils import ( "strings" "unicode" + + "go.opentelemetry.io/collector/consumer/pdata" ) // constants for tags @@ -100,3 +102,8 @@ func NormalizeSpanName(tag string) string { return s } + +// NormalizeSpanKind returns a span kind with the SPAN_KIND prefix trimmed off +func NormalizeSpanKind(kind pdata.SpanKind) string { + return strings.TrimPrefix(kind.String(), "SPAN_KIND_") +}