Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[exporter/datadog]: Add datadog span operation name remapping config option #3444

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 18 additions & 0 deletions exporter/datadogexporter/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
9 changes: 9 additions & 0 deletions exporter/datadogexporter/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
14 changes: 7 additions & 7 deletions exporter/datadogexporter/denylister.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down
8 changes: 4 additions & 4 deletions exporter/datadogexporter/denylister_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}
13 changes: 12 additions & 1 deletion exporter/datadogexporter/example/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
18 changes: 9 additions & 9 deletions exporter/datadogexporter/trace_connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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))
Expand Down
6 changes: 3 additions & 3 deletions exporter/datadogexporter/traces_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ type traceExporter struct {
params component.ExporterCreateParams
cfg *config.Config
ctx context.Context
edgeConnection TraceEdgeConnection
edgeConnection traceEdgeConnection
obfuscator *obfuscate.Obfuscator
client *datadog.Client
denylister *Denylister
denylister *denylister
}

var (
Expand Down Expand Up @@ -69,7 +69,7 @@ func newTracesExporter(ctx context.Context, params component.ExporterCreateParam
obfuscator := obfuscate.NewObfuscator(obfuscatorConfig)

// a denylist for dropping ignored resources
denylister := NewDenylister(cfg.Traces.IgnoreResources)
denylister := newDenylister(cfg.Traces.IgnoreResources)

exporter := &traceExporter{
params: params,
Expand Down
28 changes: 22 additions & 6 deletions exporter/datadogexporter/translate_traces.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
}
Expand All @@ -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)
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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
}
Loading