From d152adfb523d93243022ecd52307e09d2b391fb0 Mon Sep 17 00:00:00 2001 From: Eric Mustin Date: Thu, 3 Jun 2021 11:04:13 -0400 Subject: [PATCH] [exporter/datadog]: Add datadog span operation name remapping config option (#3444) * [exporter/datadog]: add span remap option * [exporter/datadog]: add documentation around span_name_remappings config option * [exporter/datadog]: spelling and linting and other assorted small joys * Update exporter/datadogexporter/translate_traces.go Co-authored-by: Pablo Baeyens * [exporter/datadog]: update span remapping to use a map * [exporter/datadog]: linting span remapper * [exporter/datadog]: add config formatting * [exporter/datadog]: update span name remappings, make some details private * [exporter/datadog]: make more details of denylister private * [expoter/datadog]: also make traceconnection private Co-authored-by: Pablo Baeyens --- exporter/datadogexporter/config/config.go | 18 +++ .../datadogexporter/config/config_test.go | 9 ++ exporter/datadogexporter/denylister.go | 14 +-- exporter/datadogexporter/denylister_test.go | 8 +- exporter/datadogexporter/example/config.yaml | 13 +- exporter/datadogexporter/trace_connection.go | 18 +-- exporter/datadogexporter/traces_exporter.go | 6 +- exporter/datadogexporter/translate_traces.go | 28 ++++- .../datadogexporter/translate_traces_test.go | 119 +++++++++++++----- 9 files changed, 172 insertions(+), 61 deletions(-) diff --git a/exporter/datadogexporter/config/config.go b/exporter/datadogexporter/config/config.go index bc91c36c4d2fe..224cb21f0ce59 100644 --- a/exporter/datadogexporter/config/config.go +++ b/exporter/datadogexporter/config/config.go @@ -108,6 +108,13 @@ type TracesConfig struct { // all entries must be surrounded by double quotes and separated by commas. // ignore_resources: ["(GET|POST) /healthcheck"] IgnoreResources []string `mapstructure:"ignore_resources"` + + // SpanNameRemappings is the map of datadog span names and preferred name to map to. This can be used to + // automatically map Datadog Span Operation Names to an updated value. All entries should be key/value pairs. + // span_name_remappings: + // io.opentelemetry.javaagent.spring.client: spring.client + // instrumentation::express.server: express + SpanNameRemappings map[string]string `mapstructure:"span_name_remappings"` } // TagsConfig defines the tag-related configuration @@ -247,5 +254,16 @@ func (c *Config) Validate() error { } } } + + if c.Traces.SpanNameRemappings != nil { + for key, value := range c.Traces.SpanNameRemappings { + if value == "" { + return fmt.Errorf("'%s' is not valid value for span name remapping", value) + } + if key == "" { + return fmt.Errorf("'%s' is not valid key for span name remapping", key) + } + } + } return nil } diff --git a/exporter/datadogexporter/config/config_test.go b/exporter/datadogexporter/config/config_test.go index 0cb62845e7fde..029ad72d3e218 100644 --- a/exporter/datadogexporter/config/config_test.go +++ b/exporter/datadogexporter/config/config_test.go @@ -155,3 +155,12 @@ func TestIgnoreResourcesValidation(t *testing.T) { require.NoError(t, noErr) require.Error(t, err) } + +func TestSpanNameRemappingsValidation(t *testing.T) { + validCfg := Config{Traces: TracesConfig{SpanNameRemappings: map[string]string{"old.opentelemetryspan.name": "updated.name"}}} + invalidCfg := Config{Traces: TracesConfig{SpanNameRemappings: map[string]string{"oldname": ""}}} + noErr := validCfg.Validate() + err := invalidCfg.Validate() + require.NoError(t, noErr) + require.Error(t, err) +} diff --git a/exporter/datadogexporter/denylister.go b/exporter/datadogexporter/denylister.go index 7584f6bfae8b8..6b0a1a7689ebe 100644 --- a/exporter/datadogexporter/denylister.go +++ b/exporter/datadogexporter/denylister.go @@ -20,16 +20,16 @@ import ( "github.com/DataDog/datadog-agent/pkg/trace/exportable/pb" ) -// Denylister holds a list of regular expressions which will match resources +// denylister holds a list of regular expressions which will match resources // on spans that should be dropped. // From: https://github.com/DataDog/datadog-agent/blob/a6872e436681ea2136cf8a67465e99fdb4450519/pkg/trace/filters/blacklister.go#L15-L19 -type Denylister struct { +type denylister struct { list []*regexp.Regexp } -// Allows returns true if the Denylister permits this span. +// allows returns true if the Denylister permits this span. // From: https://github.com/DataDog/datadog-agent/blob/a6872e436681ea2136cf8a67465e99fdb4450519/pkg/trace/filters/blacklister.go#L21-L29 -func (f *Denylister) Allows(span *pb.Span) bool { +func (f *denylister) allows(span *pb.Span) bool { for _, entry := range f.list { if entry.MatchString(span.Resource) { return false @@ -38,11 +38,11 @@ func (f *Denylister) Allows(span *pb.Span) bool { return true } -// NewDenylister creates a new Denylister based on the given list of +// newDenylister creates a new Denylister based on the given list of // regular expressions. // From: https://github.com/DataDog/datadog-agent/blob/a6872e436681ea2136cf8a67465e99fdb4450519/pkg/trace/filters/blacklister.go#L41-L45 -func NewDenylister(exprs []string) *Denylister { - return &Denylister{list: compileRules(exprs)} +func newDenylister(exprs []string) *denylister { + return &denylister{list: compileRules(exprs)} } // compileRules compiles as many rules as possible from the list of expressions. diff --git a/exporter/datadogexporter/denylister_test.go b/exporter/datadogexporter/denylister_test.go index f6bcc42dcc71c..13776ac9fa302 100644 --- a/exporter/datadogexporter/denylister_test.go +++ b/exporter/datadogexporter/denylister_test.go @@ -68,16 +68,16 @@ func TestDenylister(t *testing.T) { for _, test := range tests { span := testSpan() span.Resource = test.resource - filter := NewDenylister(test.filter) + filter := newDenylister(test.filter) - assert.Equal(t, test.expectation, filter.Allows(span)) + assert.Equal(t, test.expectation, filter.allows(span)) } } func TestCompileRules(t *testing.T) { - filter := NewDenylister([]string{"\n{6}"}) + filter := newDenylister([]string{"\n{6}"}) for i := 0; i < 100; i++ { span := testSpan() - assert.True(t, filter.Allows(span)) + assert.True(t, filter.allows(span)) } } diff --git a/exporter/datadogexporter/example/config.yaml b/exporter/datadogexporter/example/config.yaml index e4865b713be3b..04c72dc52afe1 100644 --- a/exporter/datadogexporter/example/config.yaml +++ b/exporter/datadogexporter/example/config.yaml @@ -120,7 +120,18 @@ exporters: ## A blacklist of regular expressions can be provided to disable certain traces based on their resource name ## all entries must be surrounded by double quotes and separated by commas. # - # ignore_resources: ["(GET|POST) /healthcheck"] + # ignore_resources: ["(GET|POST) /healthcheck"] + + ## @param span_name_remappings - map of key/value pairs - optional + ## A map of Datadog span operation name keys and preferred name valuues to update those names to. This can be used to + ## automatically map Datadog Span Operation Names to an updated value, and is useful when a user wants to + ## shorten or modify span names to something more user friendly in the case of instrumentation libraries with + ## particularly verbose names. + # + # span_name_remappings: + # io.opentelemetry.javaagent.spring.client: spring.client + # instrumentation::express.server: express + service: pipelines: diff --git a/exporter/datadogexporter/trace_connection.go b/exporter/datadogexporter/trace_connection.go index 4ca28bacd3dc9..12c215338fc38 100644 --- a/exporter/datadogexporter/trace_connection.go +++ b/exporter/datadogexporter/trace_connection.go @@ -29,13 +29,13 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/utils" ) -// TraceEdgeConnection is used to send data to trace edge -type TraceEdgeConnection interface { +// traceEdgeConnection is used to send data to trace edge +type traceEdgeConnection interface { SendTraces(ctx context.Context, trace *pb.TracePayload, maxRetries int) error SendStats(ctx context.Context, stats *stats.Payload, maxRetries int) error } -type traceEdgeConnection struct { +type traceEdgeConnectionImpl struct { traceURL string statsURL string apiKey string @@ -49,10 +49,10 @@ const ( traceEdgeRetryInterval time.Duration = 10 * time.Second ) -// createTraceEdgeConnection returns a new TraceEdgeConnection -func createTraceEdgeConnection(rootURL, apiKey string, buildInfo component.BuildInfo) TraceEdgeConnection { +// createTraceEdgeConnection returns a new traceEdgeConnection +func createTraceEdgeConnection(rootURL, apiKey string, buildInfo component.BuildInfo) traceEdgeConnection { - return &traceEdgeConnection{ + return &traceEdgeConnectionImpl{ traceURL: rootURL + "/api/v0.2/traces", statsURL: rootURL + "/api/v0.2/stats", buildInfo: buildInfo, @@ -69,7 +69,7 @@ type Payload struct { } // SendTraces serializes a trace payload to protobuf and sends it to Trace Edge -func (con *traceEdgeConnection) SendTraces(ctx context.Context, trace *pb.TracePayload, maxRetries int) error { +func (con *traceEdgeConnectionImpl) SendTraces(ctx context.Context, trace *pb.TracePayload, maxRetries int) error { binary, marshallErr := proto.Marshal(trace) if marshallErr != nil { return fmt.Errorf("failed to serialize trace payload to protobuf: %w", marshallErr) @@ -108,7 +108,7 @@ func (con *traceEdgeConnection) SendTraces(ctx context.Context, trace *pb.TraceP } // SendStats serializes a stats payload to json and sends it to Trace Edge -func (con *traceEdgeConnection) SendStats(ctx context.Context, sts *stats.Payload, maxRetries int) error { +func (con *traceEdgeConnectionImpl) SendStats(ctx context.Context, sts *stats.Payload, maxRetries int) error { var b bytes.Buffer err := stats.EncodePayload(&b, sts) if err != nil { @@ -145,7 +145,7 @@ func (con *traceEdgeConnection) SendStats(ctx context.Context, sts *stats.Payloa } // sendPayloadToTraceEdge sends a payload to Trace Edge -func (con *traceEdgeConnection) sendPayloadToTraceEdge(ctx context.Context, apiKey string, payload *Payload, url string) (bool, error) { +func (con *traceEdgeConnectionImpl) sendPayloadToTraceEdge(ctx context.Context, apiKey string, payload *Payload, url string) (bool, error) { // Create the request to be sent to the API req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(payload.Bytes)) diff --git a/exporter/datadogexporter/traces_exporter.go b/exporter/datadogexporter/traces_exporter.go index 3a939d2d6534a..f7c74c47958fb 100644 --- a/exporter/datadogexporter/traces_exporter.go +++ b/exporter/datadogexporter/traces_exporter.go @@ -35,10 +35,10 @@ type traceExporter struct { params component.ExporterCreateSettings cfg *config.Config ctx context.Context - edgeConnection TraceEdgeConnection + edgeConnection traceEdgeConnection obfuscator *obfuscate.Obfuscator client *datadog.Client - denylister *Denylister + denylister *denylister } var ( @@ -69,7 +69,7 @@ func newTracesExporter(ctx context.Context, params component.ExporterCreateSetti obfuscator := obfuscate.NewObfuscator(obfuscatorConfig) // a denylist for dropping ignored resources - denylister := NewDenylister(cfg.Traces.IgnoreResources) + denylister := newDenylister(cfg.Traces.IgnoreResources) exporter := &traceExporter{ params: params, diff --git a/exporter/datadogexporter/translate_traces.go b/exporter/datadogexporter/translate_traces.go index 3e85e4bb12621..971a07323e8d7 100644 --- a/exporter/datadogexporter/translate_traces.go +++ b/exporter/datadogexporter/translate_traces.go @@ -64,7 +64,7 @@ const ( ) // converts Traces into an array of datadog trace payloads grouped by env -func convertToDatadogTd(td pdata.Traces, fallbackHost string, cfg *config.Config, blk *Denylister, buildInfo component.BuildInfo) ([]*pb.TracePayload, []datadog.Metric) { +func convertToDatadogTd(td pdata.Traces, fallbackHost string, cfg *config.Config, blk *denylister, buildInfo component.BuildInfo) ([]*pb.TracePayload, []datadog.Metric) { // TODO: // do we apply other global tags, like version+service, to every span or only root spans of a service // should globalTags['service'] take precedence over a trace's resource.service.name? I don't believe so, need to confirm @@ -76,14 +76,19 @@ func convertToDatadogTd(td pdata.Traces, fallbackHost string, cfg *config.Config seenHosts := make(map[string]struct{}) var series []datadog.Metric pushTime := pdata.TimestampFromTime(time.Now()) + + spanNameMap := cfg.Traces.SpanNameRemappings + for i := 0; i < resourceSpans.Len(); i++ { rs := resourceSpans.At(i) host, ok := metadata.HostnameFromAttributes(rs.Resource().Attributes()) if !ok { host = fallbackHost } + seenHosts[host] = struct{}{} - payload := resourceSpansToDatadogSpans(rs, host, cfg, blk) + payload := resourceSpansToDatadogSpans(rs, host, cfg, blk, spanNameMap) + traces = append(traces, &payload) } @@ -123,7 +128,7 @@ func aggregateTracePayloadsByEnv(tracePayloads []*pb.TracePayload) []*pb.TracePa } // converts a Trace's resource spans into a trace payload -func resourceSpansToDatadogSpans(rs pdata.ResourceSpans, hostname string, cfg *config.Config, blk *Denylister) pb.TracePayload { +func resourceSpansToDatadogSpans(rs pdata.ResourceSpans, hostname string, cfg *config.Config, blk *denylister, spanNameMap map[string]string) pb.TracePayload { // get env tag env := utils.NormalizeTag(cfg.Env) @@ -156,7 +161,7 @@ func resourceSpansToDatadogSpans(rs pdata.ResourceSpans, hostname string, cfg *c extractInstrumentationLibraryTags(ils.InstrumentationLibrary(), datadogTags) spans := ils.Spans() for j := 0; j < spans.Len(); j++ { - span := spanToDatadogSpan(spans.At(j), resourceServiceName, datadogTags, cfg) + span := spanToDatadogSpan(spans.At(j), resourceServiceName, datadogTags, cfg, spanNameMap) var apiTrace *pb.APITrace var ok bool @@ -193,7 +198,7 @@ func resourceSpansToDatadogSpans(rs pdata.ResourceSpans, hostname string, cfg *c // Root span is used to carry some trace-level metadata, such as sampling rate and priority. rootSpan := utils.GetRoot(apiTrace) - if !blk.Allows(rootSpan) { + if !blk.allows(rootSpan) { // drop trace by not adding to payload if it's root span matches denylist continue } @@ -215,6 +220,7 @@ func spanToDatadogSpan(s pdata.Span, serviceName string, datadogTags map[string]string, cfg *config.Config, + spanNameMap map[string]string, ) *pb.Span { tags := aggregateSpanTags(s, datadogTags) @@ -276,7 +282,7 @@ func spanToDatadogSpan(s pdata.Span, span := &pb.Span{ TraceID: decodeAPMTraceID(s.TraceID().Bytes()), SpanID: decodeAPMSpanID(s.SpanID().Bytes()), - Name: getDatadogSpanName(s, tags), + Name: remapDatadogSpanName(getDatadogSpanName(s, tags), spanNameMap), Resource: resourceName, Service: normalizedServiceName, Start: int64(startTime), @@ -631,3 +637,13 @@ func eventsToString(evts pdata.SpanEventSlice) string { eventArrayBytes, _ := json.Marshal(&eventArray) return string(eventArrayBytes) } + +// remapDatadogSpanName allows users to map their datadog span operation names to +// another string as they see fit. +func remapDatadogSpanName(name string, spanNameMap map[string]string) string { + if updatedSpanName := spanNameMap[name]; updatedSpanName != "" { + return updatedSpanName + } + + return name +} diff --git a/exporter/datadogexporter/translate_traces_test.go b/exporter/datadogexporter/translate_traces_test.go index be2108ae479c5..3392632ce83f8 100644 --- a/exporter/datadogexporter/translate_traces_test.go +++ b/exporter/datadogexporter/translate_traces_test.go @@ -133,7 +133,7 @@ func NewResourceSpansData(mockTraceID [16]byte, mockSpanID [8]byte, mockParentSp func TestConvertToDatadogTd(t *testing.T) { traces := pdata.NewTraces() traces.ResourceSpans().AppendEmpty() - denylister := NewDenylister([]string{}) + denylister := newDenylister([]string{}) buildInfo := component.BuildInfo{ Version: "1.0", } @@ -146,7 +146,7 @@ func TestConvertToDatadogTd(t *testing.T) { func TestConvertToDatadogTdNoResourceSpans(t *testing.T) { traces := pdata.NewTraces() - denylister := NewDenylister([]string{}) + denylister := newDenylister([]string{}) buildInfo := component.BuildInfo{ Version: "1.0", } @@ -180,7 +180,7 @@ func TestRunningTraces(t *testing.T) { Version: "1.0", } - _, runningMetrics := convertToDatadogTd(td, "fallbackHost", &config.Config{}, NewDenylister([]string{}), buildInfo) + _, runningMetrics := convertToDatadogTd(td, "fallbackHost", &config.Config{}, newDenylister([]string{}), buildInfo) runningHostnames := []string{} for _, metric := range runningMetrics { @@ -196,7 +196,8 @@ func TestRunningTraces(t *testing.T) { } func TestObfuscation(t *testing.T) { - denylister := NewDenylister([]string{}) + + denylister := newDenylister([]string{}) buildInfo := component.BuildInfo{ Version: "1.0", } @@ -231,7 +232,7 @@ func TestObfuscation(t *testing.T) { func TestBasicTracesTranslation(t *testing.T) { hostname := "testhostname" - denylister := NewDenylister([]string{}) + denylister := newDenylister([]string{}) // 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} @@ -244,7 +245,8 @@ func TestBasicTracesTranslation(t *testing.T) { rs := NewResourceSpansData(mockTraceID, mockSpanID, mockParentSpanID, pdata.StatusCodeUnset, false, mockEndTime) // translate mocks to datadog traces - datadogPayload := resourceSpansToDatadogSpans(rs, hostname, &config.Config{}, denylister) + datadogPayload := resourceSpansToDatadogSpans(rs, hostname, &config.Config{}, denylister, map[string]string{}) + // ensure we return the correct type assert.IsType(t, pb.TracePayload{}, datadogPayload) @@ -303,7 +305,7 @@ func TestBasicTracesDenylist(t *testing.T) { hostname := "testhostname" // adding some regex bits to the resource name, but this should drop the trace - denylister := NewDenylister([]string{".nd-To-E.d H.re"}) + denylister := newDenylister([]string{".nd-To-E.d H.re"}) // 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} @@ -316,7 +318,8 @@ func TestBasicTracesDenylist(t *testing.T) { rs := NewResourceSpansData(mockTraceID, mockSpanID, mockParentSpanID, pdata.StatusCodeUnset, false, mockEndTime) // translate mocks to datadog traces - datadogPayload := resourceSpansToDatadogSpans(rs, hostname, &config.Config{}, denylister) + datadogPayload := resourceSpansToDatadogSpans(rs, hostname, &config.Config{}, denylister, map[string]string{}) + // ensure we return the correct type assert.IsType(t, pb.TracePayload{}, datadogPayload) @@ -327,7 +330,7 @@ func TestBasicTracesDenylist(t *testing.T) { func TestTracesTranslationErrorsAndResource(t *testing.T) { hostname := "testhostname" - denylister := NewDenylister([]string{}) + denylister := newDenylister([]string{}) // 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} @@ -347,7 +350,8 @@ func TestTracesTranslationErrorsAndResource(t *testing.T) { }, } - datadogPayload := resourceSpansToDatadogSpans(rs, hostname, &cfg, denylister) + datadogPayload := resourceSpansToDatadogSpans(rs, hostname, &cfg, denylister, map[string]string{}) + // ensure we return the correct type assert.IsType(t, pb.TracePayload{}, datadogPayload) @@ -380,7 +384,7 @@ func TestTracesTranslationErrorsAndResource(t *testing.T) { // Ensures that if more than one error event occurs in a span, the last one is used for translation func TestTracesTranslationErrorsFromEventsUsesLast(t *testing.T) { hostname := "testhostname" - denylister := NewDenylister([]string{}) + denylister := newDenylister([]string{}) // 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} @@ -428,7 +432,7 @@ func TestTracesTranslationErrorsFromEventsUsesLast(t *testing.T) { }, } - datadogPayload := resourceSpansToDatadogSpans(rs, hostname, &cfg, denylister) + datadogPayload := resourceSpansToDatadogSpans(rs, hostname, &cfg, denylister, map[string]string{}) // Ensure the error type is copied over from the last error event logged assert.Equal(t, attribs[conventions.AttributeExceptionType].StringVal(), datadogPayload.Traces[0].Spans[0].Meta[ext.ErrorType]) @@ -443,7 +447,7 @@ func TestTracesTranslationErrorsFromEventsUsesLast(t *testing.T) { // Ensures that if the first or last event in the list is the error, that translation still behaves properly func TestTracesTranslationErrorsFromEventsBounds(t *testing.T) { hostname := "testhostname" - denylister := NewDenylister([]string{}) + denylister := newDenylister([]string{}) // 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} @@ -482,7 +486,7 @@ func TestTracesTranslationErrorsFromEventsBounds(t *testing.T) { }, } - datadogPayload := resourceSpansToDatadogSpans(rs, hostname, &cfg, denylister) + datadogPayload := resourceSpansToDatadogSpans(rs, hostname, &cfg, denylister, map[string]string{}) // Ensure the error type is copied over assert.Equal(t, attribs[conventions.AttributeExceptionType].StringVal(), datadogPayload.Traces[0].Spans[0].Meta[ext.ErrorType]) @@ -518,7 +522,7 @@ func TestTracesTranslationErrorsFromEventsBounds(t *testing.T) { func TestTracesTranslationOkStatus(t *testing.T) { hostname := "testhostname" - denylister := NewDenylister([]string{}) + denylister := newDenylister([]string{}) // 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} @@ -538,7 +542,8 @@ func TestTracesTranslationOkStatus(t *testing.T) { }, } - datadogPayload := resourceSpansToDatadogSpans(rs, hostname, &cfg, denylister) + datadogPayload := resourceSpansToDatadogSpans(rs, hostname, &cfg, denylister, map[string]string{}) + // ensure we return the correct type assert.IsType(t, pb.TracePayload{}, datadogPayload) @@ -567,7 +572,7 @@ func TestTracesTranslationOkStatus(t *testing.T) { // ensure that the datadog span uses the configured unified service tags func TestTracesTranslationConfig(t *testing.T) { hostname := "testhostname" - denylister := NewDenylister([]string{}) + denylister := newDenylister([]string{}) // 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} @@ -588,7 +593,7 @@ func TestTracesTranslationConfig(t *testing.T) { } // translate mocks to datadog traces - datadogPayload := resourceSpansToDatadogSpans(rs, hostname, &cfg, denylister) + datadogPayload := resourceSpansToDatadogSpans(rs, hostname, &cfg, denylister, map[string]string{}) // ensure we return the correct type assert.IsType(t, pb.TracePayload{}, datadogPayload) @@ -614,7 +619,7 @@ func TestTracesTranslationConfig(t *testing.T) { // ensure that the translation returns early if no resource instrumentation library spans func TestTracesTranslationNoIls(t *testing.T) { hostname := "testhostname" - denylister := NewDenylister([]string{}) + denylister := newDenylister([]string{}) rs := pdata.NewResourceSpans() @@ -626,7 +631,7 @@ func TestTracesTranslationNoIls(t *testing.T) { } // translate mocks to datadog traces - datadogPayload := resourceSpansToDatadogSpans(rs, hostname, &cfg, denylister) + datadogPayload := resourceSpansToDatadogSpans(rs, hostname, &cfg, denylister, map[string]string{}) // ensure we return the correct type assert.IsType(t, pb.TracePayload{}, datadogPayload) @@ -638,7 +643,7 @@ func TestTracesTranslationNoIls(t *testing.T) { // ensure that the translation returns early if no resource instrumentation library spans func TestTracesTranslationInvalidService(t *testing.T) { hostname := "testhostname" - denylister := NewDenylister([]string{}) + denylister := newDenylister([]string{}) // 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} @@ -676,9 +681,9 @@ func TestTracesTranslationInvalidService(t *testing.T) { } // translate mocks to datadog traces - datadogPayloadInvalidService := resourceSpansToDatadogSpans(rs, hostname, &cfgInvalidService, denylister) - datadogPayloadEmptyService := resourceSpansToDatadogSpans(rs, hostname, &cfgEmptyService, denylister) - datadogPayloadStartWithInvalidService := resourceSpansToDatadogSpans(rs, hostname, &cfgStartWithInvalidService, denylister) + datadogPayloadInvalidService := resourceSpansToDatadogSpans(rs, hostname, &cfgInvalidService, denylister, map[string]string{}) + datadogPayloadEmptyService := resourceSpansToDatadogSpans(rs, hostname, &cfgEmptyService, denylister, map[string]string{}) + datadogPayloadStartWithInvalidService := resourceSpansToDatadogSpans(rs, hostname, &cfgStartWithInvalidService, denylister, map[string]string{}) // ensure we return the correct type assert.IsType(t, pb.TracePayload{}, datadogPayloadInvalidService) @@ -696,7 +701,7 @@ func TestTracesTranslationInvalidService(t *testing.T) { // ensure that the datadog span uses the peer.name instead service.name when provided func TestTracesTranslationServicePeerName(t *testing.T) { hostname := "testhostname" - denylister := NewDenylister([]string{}) + denylister := newDenylister([]string{}) // 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} @@ -712,7 +717,7 @@ func TestTracesTranslationServicePeerName(t *testing.T) { span.Attributes().InsertString(conventions.AttributePeerService, "my_peer_service_name") // translate mocks to datadog traces - datadogPayload := resourceSpansToDatadogSpans(rs, hostname, &config.Config{}, denylister) + datadogPayload := resourceSpansToDatadogSpans(rs, hostname, &config.Config{}, denylister, map[string]string{}) // ensure we return the correct type assert.IsType(t, pb.TracePayload{}, datadogPayload) @@ -770,7 +775,7 @@ func TestTracesTranslationServicePeerName(t *testing.T) { // ensure that the datadog span uses the truncated tags if length exceeds max func TestTracesTranslationTruncatetag(t *testing.T) { hostname := "testhostname" - denylister := NewDenylister([]string{}) + denylister := newDenylister([]string{}) // 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} @@ -787,7 +792,7 @@ func TestTracesTranslationTruncatetag(t *testing.T) { span.Attributes().InsertString(conventions.AttributeExceptionStacktrace, RandStringBytes(5500)) // translate mocks to datadog traces - datadogPayload := resourceSpansToDatadogSpans(rs, hostname, &config.Config{}, denylister) + datadogPayload := resourceSpansToDatadogSpans(rs, hostname, &config.Config{}, denylister, map[string]string{}) // ensure we return the correct type assert.IsType(t, pb.TracePayload{}, datadogPayload) @@ -1166,7 +1171,7 @@ func TestTracePayloadAggr(t *testing.T) { // ensure that stats payloads get tagged with version tag func TestStatsAggregations(t *testing.T) { hostname := "testhostname" - denylister := NewDenylister([]string{}) + denylister := newDenylister([]string{}) // 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} @@ -1182,7 +1187,7 @@ func TestStatsAggregations(t *testing.T) { // translate mocks to datadog traces cfg := config.Config{} - datadogPayload := resourceSpansToDatadogSpans(rs, hostname, &cfg, denylister) + datadogPayload := resourceSpansToDatadogSpans(rs, hostname, &cfg, denylister, map[string]string{}) statsOutput := computeAPMStats(&datadogPayload, time.Now().UTC().UnixNano()) @@ -1202,7 +1207,7 @@ func TestStatsAggregations(t *testing.T) { // ensure that sanitization of trace payloads occurs func TestSanitization(t *testing.T) { - denylister := NewDenylister([]string{}) + denylister := newDenylister([]string{}) buildInfo := component.BuildInfo{ Version: "1.0", } @@ -1291,3 +1296,55 @@ func TestNormalizeTag(t *testing.T) { }) } } + +// ensure that sanitization of trace payloads occurs +func TestSpanNameMapping(t *testing.T) { + 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} + endTime := time.Now().Round(time.Second) + pdataEndTime := pdata.TimestampFromTime(endTime) + startTime := endTime.Add(-90 * time.Second) + pdataStartTime := pdata.TimestampFromTime(startTime) + + denylister := newDenylister([]string{}) + buildInfo := component.BuildInfo{ + Version: "1.0", + } + + traces := pdata.NewTraces() + traces.ResourceSpans().Resize(1) + rs := traces.ResourceSpans().At(0) + resource := rs.Resource() + resource.Attributes().InitFromMap(map[string]pdata.AttributeValue{ + "deployment.environment": pdata.NewAttributeValueString("UpperCase"), + }) + rs.InstrumentationLibrarySpans().Resize(1) + ilss := rs.InstrumentationLibrarySpans().At(0) + instrumentationLibrary := ilss.InstrumentationLibrary() + instrumentationLibrary.SetName("flash") + instrumentationLibrary.SetVersion("v1") + span := ilss.Spans().AppendEmpty() + + traceID := pdata.NewTraceID(mockTraceID) + spanID := pdata.NewSpanID(mockSpanID) + parentSpanID := pdata.NewSpanID(mockParentSpanID) + span.SetTraceID(traceID) + span.SetSpanID(spanID) + span.SetParentSpanID(parentSpanID) + span.SetName("End-To-End Here") + span.SetKind(pdata.SpanKindServer) + span.SetStartTimestamp(pdataStartTime) + span.SetEndTimestamp(pdataEndTime) + + config := config.Config{Traces: config.TracesConfig{SpanNameRemappings: map[string]string{"flash.server": "bang.client"}}} + + outputTraces, _ := convertToDatadogTd(traces, "test-host", &config, denylister, buildInfo) + aggregatedTraces := aggregateTracePayloadsByEnv(outputTraces) + + obfuscator := obfuscate.NewObfuscator(obfuscatorConfig) + obfuscatePayload(obfuscator, aggregatedTraces) + assert.Equal(t, 1, len(aggregatedTraces)) + + assert.Equal(t, "bang.client", aggregatedTraces[0].Traces[0].Spans[0].Name) +}