Skip to content

Commit

Permalink
[exporter/datadog]: Add datadog span operation name remapping config …
Browse files Browse the repository at this point in the history
…option (open-telemetry#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 <[email protected]>

* [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 <[email protected]>
  • Loading branch information
ericmustin and mx-psi committed Jun 3, 2021
1 parent c9e0e7e commit d152adf
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 61 deletions.
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.ExporterCreateSettings
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.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,
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

0 comments on commit d152adf

Please sign in to comment.