diff --git a/exporter/datadogexporter/translate_traces.go b/exporter/datadogexporter/translate_traces.go index 2ca687049334..5b0e76940d05 100644 --- a/exporter/datadogexporter/translate_traces.go +++ b/exporter/datadogexporter/translate_traces.go @@ -203,6 +203,11 @@ func spanToDatadogSpan(s pdata.Span, } } + // peer.service should always be prioritized for service names when set because it is what the user decided. + if peerService, ok := tags[conventions.AttributePeerService]; ok { + serviceName = peerService + } + normalizedServiceName := utils.NormalizeServiceName(serviceName) // canonical resource attribute version should override others if it exists diff --git a/exporter/datadogexporter/translate_traces_test.go b/exporter/datadogexporter/translate_traces_test.go index 3f2de77794ff..8432221357d7 100644 --- a/exporter/datadogexporter/translate_traces_test.go +++ b/exporter/datadogexporter/translate_traces_test.go @@ -645,6 +645,80 @@ func TestTracesTranslationInvalidService(t *testing.T) { assert.Equal(t, "alt-service", datadogPayloadStartWithInvalidService.Traces[0].Spans[0].Service) } +// ensure that the datadog span uses the peer.name instead service.name when provided +func TestTracesTranslationServicePeerName(t *testing.T) { + hostname := "testhostname" + calculator := newSublayerCalculator() + + // generate mock trace, span and parent span ids + mockTraceID := [16]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F} + mockSpanID := [8]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8} + mockParentSpanID := [8]byte{0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8} + mockEndTime := time.Now().Round(time.Second) + + // create mock resource span data + // set shouldError and resourceServiceandEnv to false to test defaut behavior + rs := NewResourceSpansData(mockTraceID, mockSpanID, mockParentSpanID, pdata.StatusCodeUnset, false, mockEndTime) + + span := rs.InstrumentationLibrarySpans().At(0).Spans().At(0) + span.Attributes().InsertString(conventions.AttributePeerService, "my_peer_service_name") + + // translate mocks to datadog traces + datadogPayload := resourceSpansToDatadogSpans(rs, calculator, hostname, &config.Config{}) + // ensure we return the correct type + assert.IsType(t, pb.TracePayload{}, datadogPayload) + + // ensure hostname arg is respected + assert.Equal(t, hostname, datadogPayload.HostName) + assert.Equal(t, 1, len(datadogPayload.Traces)) + + // ensure trace id gets translated to uint64 correctly + assert.Equal(t, decodeAPMTraceID(mockTraceID), datadogPayload.Traces[0].TraceID) + + // ensure the correct number of spans are expected + assert.Equal(t, 1, len(datadogPayload.Traces[0].Spans)) + + // ensure span's trace id matches payload trace id + assert.Equal(t, datadogPayload.Traces[0].TraceID, datadogPayload.Traces[0].Spans[0].TraceID) + + // ensure span's spanId and parentSpanId are set correctly + assert.Equal(t, decodeAPMSpanID(mockSpanID), datadogPayload.Traces[0].Spans[0].SpanID) + assert.Equal(t, decodeAPMSpanID(mockParentSpanID), datadogPayload.Traces[0].Spans[0].ParentID) + + // ensure that span.resource defaults to otlp span.name + assert.Equal(t, "End-To-End Here", datadogPayload.Traces[0].Spans[0].Resource) + + // ensure that span.name defaults to string representing instrumentation library if present + assert.Equal(t, strings.ToLower(fmt.Sprintf("%s.%s", datadogPayload.Traces[0].Spans[0].Meta[conventions.InstrumentationLibraryName], strings.TrimPrefix(pdata.SpanKindSERVER.String(), "SPAN_KIND_"))), datadogPayload.Traces[0].Spans[0].Name) + + // ensure that span.type is based on otlp span.kind + assert.Equal(t, "web", datadogPayload.Traces[0].Spans[0].Type) + + // ensure that span.meta and span.metrics pick up attibutes, instrumentation ibrary and resource attribs + assert.Equal(t, 11, len(datadogPayload.Traces[0].Spans[0].Meta)) + assert.Equal(t, 1, len(datadogPayload.Traces[0].Spans[0].Metrics)) + + // ensure that span error is based on otlp span status + assert.Equal(t, int32(0), datadogPayload.Traces[0].Spans[0].Error) + + // ensure that span meta also inccludes correctly sets resource attributes + assert.Equal(t, "kube-system", datadogPayload.Traces[0].Spans[0].Meta["namespace"]) + + // ensure that span service name gives resource service.name priority + assert.Equal(t, "my_peer_service_name", datadogPayload.Traces[0].Spans[0].Service) + + // ensure a duration and start time are calculated + assert.NotNil(t, datadogPayload.Traces[0].Spans[0].Start) + assert.NotNil(t, datadogPayload.Traces[0].Spans[0].Duration) + + pdataMockEndTime := pdata.TimestampFromTime(mockEndTime) + pdataMockStartTime := pdata.TimestampFromTime(mockEndTime.Add(-90 * time.Second)) + mockEventsString := fmt.Sprintf("[{\"attributes\":{},\"name\":\"start\",\"time\":%d},{\"attributes\":{\"flag\":false},\"name\":\"end\",\"time\":%d}]", pdataMockStartTime, pdataMockEndTime) + + // ensure that events tag is set if span events exist and contains structured json fields + assert.Equal(t, mockEventsString, datadogPayload.Traces[0].Spans[0].Meta["events"]) +} + // ensure that datadog span resource naming uses http method+route when available func TestSpanResourceTranslation(t *testing.T) { span := pdata.NewSpan()