Skip to content
Open
2 changes: 1 addition & 1 deletion ddtrace/tracer/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ type TracerConf struct {
PartialFlush bool
PartialFlushMinSpans int
PeerServiceDefaults bool
PeerServiceMappings map[string]string
PeerServiceMapping func(string)(string, bool)
ServiceTag string
TracingAsTransport bool
VersionTag string
Expand Down
13 changes: 12 additions & 1 deletion ddtrace/tracer/civisibility_nooptracer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,18 @@ func TestCiVisibilityNoopTracer_TracerConf(t *testing.T) {
wrappedConf := wrapped.TracerConf()
tracerConf := tr.TracerConf()

assert.Equal(t, tracerConf, wrappedConf)
// Compare all fields except PeerServiceMapping (functions can't be compared with reflect.DeepEqual).
assert.Equal(t, tracerConf.CanComputeStats, wrappedConf.CanComputeStats)
assert.Equal(t, tracerConf.CanDropP0s, wrappedConf.CanDropP0s)
assert.Equal(t, tracerConf.DebugAbandonedSpans, wrappedConf.DebugAbandonedSpans)
assert.Equal(t, tracerConf.Disabled, wrappedConf.Disabled)
assert.Equal(t, tracerConf.PartialFlush, wrappedConf.PartialFlush)
assert.Equal(t, tracerConf.PartialFlushMinSpans, wrappedConf.PartialFlushMinSpans)
assert.Equal(t, tracerConf.PeerServiceDefaults, wrappedConf.PeerServiceDefaults)
assert.Equal(t, tracerConf.EnvTag, wrappedConf.EnvTag)
assert.Equal(t, tracerConf.VersionTag, wrappedConf.VersionTag)
assert.Equal(t, tracerConf.ServiceTag, wrappedConf.ServiceTag)
assert.Equal(t, tracerConf.TracingAsTransport, wrappedConf.TracingAsTransport)
}

func TestCiVisibilityNoopTracer_Flush(t *testing.T) {
Expand Down
23 changes: 2 additions & 21 deletions ddtrace/tracer/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,12 +223,6 @@ type config struct {
// spanAttributeSchemaVersion holds the selected DD_TRACE_SPAN_ATTRIBUTE_SCHEMA version.
spanAttributeSchemaVersion int

// peerServiceDefaultsEnabled indicates whether the peer.service tag calculation is enabled or not.
peerServiceDefaultsEnabled bool

// peerServiceMappings holds a set of service mappings to dynamically rename peer.service values.
peerServiceMappings map[string]string

// orchestrionCfg holds Orchestrion (aka auto-instrumentation) configuration.
// Only used for telemetry currently.
orchestrionCfg orchestrionConfig
Expand Down Expand Up @@ -359,16 +353,6 @@ func newConfig(opts ...StartOption) (*config, error) {
namingschema.LoadFromEnv()
c.spanAttributeSchemaVersion = int(namingschema.GetVersion())

// peer.service tag default calculation is enabled by default if using attribute schema >= 1
c.peerServiceDefaultsEnabled = true
if c.spanAttributeSchemaVersion == int(namingschema.SchemaV0) {
c.peerServiceDefaultsEnabled = internal.BoolEnv("DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED", false)
}
c.peerServiceMappings = make(map[string]string)
if v := env.Get("DD_TRACE_PEER_SERVICE_MAPPING"); v != "" {
internal.ForEachStringTag(v, internal.DDTagsDelimiter, func(key, val string) { c.peerServiceMappings[key] = val })
}

// LLM Observability config
c.llmobs = llmobsconfig.Config{
Enabled: internal.BoolEnv(envLLMObsEnabled, false),
Expand Down Expand Up @@ -987,17 +971,14 @@ func WithServiceMapping(from, to string) StartOption {
// Related documentation: https://docs.datadoghq.com/tracing/guide/inferred-service-opt-in/?tab=go#apm-tracer-configuration
func WithPeerServiceDefaults(enabled bool) StartOption {
return func(c *config) {
c.peerServiceDefaultsEnabled = enabled
c.internalConfig.SetPeerServiceDefaultsEnabled(enabled, telemetry.OriginCode)
}
}

// WithPeerServiceMapping determines the value of the peer.service tag "from" to be renamed to service "to".
func WithPeerServiceMapping(from, to string) StartOption {
return func(c *config) {
if c.peerServiceMappings == nil {
c.peerServiceMappings = make(map[string]string)
}
c.peerServiceMappings[from] = to
c.internalConfig.SetPeerServiceMapping(from, to, telemetry.OriginCode)
}
}

Expand Down
16 changes: 8 additions & 8 deletions ddtrace/tracer/option_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -861,25 +861,25 @@ func TestTracerOptionsDefaults(t *testing.T) {
t.Run("defaults", func(t *testing.T) {
c, err := newTestConfig(WithAgentTimeout(2))
assert.NoError(t, err)
assert.Equal(t, c.peerServiceDefaultsEnabled, false)
assert.Empty(t, c.peerServiceMappings)
assert.Equal(t, c.internalConfig.PeerServiceDefaultsEnabled(), false)
assert.Empty(t, c.internalConfig.PeerServiceMappings())
})

t.Run("defaults-with-schema-v1", func(t *testing.T) {
t.Setenv("DD_TRACE_SPAN_ATTRIBUTE_SCHEMA", "v1")
c, err := newTestConfig(WithAgentTimeout(2))
assert.NoError(t, err)
assert.Equal(t, c.peerServiceDefaultsEnabled, true)
assert.Empty(t, c.peerServiceMappings)
assert.Equal(t, c.internalConfig.PeerServiceDefaultsEnabled(), true)
assert.Empty(t, c.internalConfig.PeerServiceMappings())
})

t.Run("env-vars", func(t *testing.T) {
t.Setenv("DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED", "true")
t.Setenv("DD_TRACE_PEER_SERVICE_MAPPING", "old:new,old2:new2")
c, err := newTestConfig(WithAgentTimeout(2))
assert.NoError(t, err)
assert.Equal(t, c.peerServiceDefaultsEnabled, true)
assert.Equal(t, c.peerServiceMappings, map[string]string{"old": "new", "old2": "new2"})
assert.Equal(t, c.internalConfig.PeerServiceDefaultsEnabled(), true)
assert.Equal(t, c.internalConfig.PeerServiceMappings(), map[string]string{"old": "new", "old2": "new2"})
})

t.Run("options", func(t *testing.T) {
Expand All @@ -888,8 +888,8 @@ func TestTracerOptionsDefaults(t *testing.T) {
WithPeerServiceDefaults(true)(c)
WithPeerServiceMapping("old", "new")(c)
WithPeerServiceMapping("old2", "new2")(c)
assert.Equal(t, c.peerServiceDefaultsEnabled, true)
assert.Equal(t, c.peerServiceMappings, map[string]string{"old": "new", "old2": "new2"})
assert.Equal(t, c.internalConfig.PeerServiceDefaultsEnabled(), true)
assert.Equal(t, c.internalConfig.PeerServiceMappings(), map[string]string{"old": "new", "old2": "new2"})
})
})

Expand Down
10 changes: 6 additions & 4 deletions ddtrace/tracer/spancontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@
// reasonable as span is actually way bigger, and avoids re-allocating
// over and over. Could be fine-tuned at runtime.
traceStartSize = 10
traceMaxSize = internalconfig.TraceMaxSize

Check failure on line 467 in ddtrace/tracer/spancontext.go

View workflow job for this annotation

GitHub Actions / checklocks

may require checklocks annotation for mu, used with lock held 100% of the time
)

// newTrace creates a new trace using the given callback which will be called
Expand Down Expand Up @@ -817,10 +817,12 @@
s.setMetaLocked(keyPeerServiceSource, source)
}
// Overwrite existing peer.service value if remapped by the user
ps := s.meta[ext.PeerService]
if to, ok := tc.PeerServiceMappings[ps]; ok {
s.setMetaLocked(keyPeerServiceRemappedFrom, ps)
s.setMetaLocked(ext.PeerService, to)
if tc.PeerServiceMapping != nil {
ps := s.meta[ext.PeerService]
if to, ok := tc.PeerServiceMapping(ps); ok {
s.setMetaLocked(keyPeerServiceRemappedFrom, ps)
s.setMetaLocked(ext.PeerService, to)
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions ddtrace/tracer/spancontext_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -811,8 +811,8 @@ func TestSpanPeerService(t *testing.T) {
assert.Nil(t, err)
defer stop()

tracer.config.peerServiceDefaultsEnabled = tc.peerServiceDefaultsEnabled
tracer.config.peerServiceMappings = tc.peerServiceMappings
tracer.config.internalConfig.SetPeerServiceDefaultsEnabled(tc.peerServiceDefaultsEnabled, telemetry.OriginCode)
tracer.config.internalConfig.SetPeerServiceMappings(tc.peerServiceMappings, telemetry.OriginCode)

p := tracer.StartSpan("parent-span", tc.spanOpts...)
opts := append([]StartSpanOption{ChildOf(p.Context())}, tc.spanOpts...)
Expand Down
4 changes: 2 additions & 2 deletions ddtrace/tracer/telemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func startTelemetry(c *config) telemetry.Client {
{Name: "debug_stack_enabled", Value: c.internalConfig.DebugStack()},
{Name: "profiling_hotspots_enabled", Value: c.internalConfig.ProfilerHotspotsEnabled()},
{Name: "trace_span_attribute_schema", Value: c.spanAttributeSchemaVersion},
{Name: "trace_peer_service_defaults_enabled", Value: c.peerServiceDefaultsEnabled},
{Name: "trace_peer_service_defaults_enabled", Value: c.internalConfig.PeerServiceDefaultsEnabled()},
{Name: "orchestrion_enabled", Value: c.orchestrionCfg.Enabled, Origin: telemetry.OriginCode},
{Name: "trace_enabled", Value: traceEnabled, Origin: traceEnabledOrigin},
{Name: "trace_log_directory", Value: c.internalConfig.LogDirectory()},
Expand All @@ -69,7 +69,7 @@ func startTelemetry(c *config) telemetry.Client {
{Name: "span_sample_rules", Value: c.spanRules},
}
var peerServiceMapping []string
for key, value := range c.peerServiceMappings {
for key, value := range c.internalConfig.PeerServiceMappings() {
peerServiceMapping = append(peerServiceMapping, fmt.Sprintf("%s:%s", key, value))
}
telemetryConfigs = append(telemetryConfigs,
Expand Down
18 changes: 10 additions & 8 deletions ddtrace/tracer/tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,14 @@ type TracerConf struct { //nolint:revive
PartialFlush bool
PartialFlushMinSpans int
PeerServiceDefaults bool
PeerServiceMappings map[string]string
EnvTag string
VersionTag string
ServiceTag string
TracingAsTransport bool
isLambdaFunction bool
// PeerServiceMapping performs a single-key lookup without copying the underlying map.
// This avoids lock contention and per-call allocations on the hot path.
PeerServiceMapping func(string) (string, bool)
EnvTag string
VersionTag string
ServiceTag string
TracingAsTransport bool
isLambdaFunction bool
}

// Tracer specifies an implementation of the Datadog tracer which allows starting
Expand Down Expand Up @@ -982,8 +984,8 @@ func (t *tracer) TracerConf() TracerConf {
Disabled: !t.config.enabled.get(),
PartialFlush: pfEnabled,
PartialFlushMinSpans: pfMin,
PeerServiceDefaults: t.config.peerServiceDefaultsEnabled,
PeerServiceMappings: t.config.peerServiceMappings,
PeerServiceDefaults: t.config.internalConfig.PeerServiceDefaultsEnabled(),
PeerServiceMapping: t.config.internalConfig.PeerServiceMapping,
EnvTag: t.config.internalConfig.Env(),
VersionTag: t.config.internalConfig.Version(),
ServiceTag: t.config.serviceName,
Expand Down
80 changes: 79 additions & 1 deletion internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,6 @@ func loadConfig() *Config {
cfg.runtimeMetricsV2 = provider.getBool("DD_RUNTIME_METRICS_V2_ENABLED", true)
cfg.profilerHotspots = provider.getBool("DD_PROFILING_CODE_HOTSPOTS_COLLECTION_ENABLED", true)
cfg.profilerEndpoints = provider.getBool("DD_PROFILING_ENDPOINT_COLLECTION_ENABLED", true)
cfg.spanAttributeSchemaVersion = provider.getInt("DD_TRACE_SPAN_ATTRIBUTE_SCHEMA", 0)
cfg.peerServiceDefaultsEnabled = provider.getBool("DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED", false)
cfg.peerServiceMappings = provider.getMap("DD_TRACE_PEER_SERVICE_MAPPING", nil)
cfg.debugAbandonedSpans = provider.getBool("DD_TRACE_DEBUG_ABANDONED_SPANS", false)
Expand Down Expand Up @@ -159,6 +158,17 @@ func loadConfig() *Config {
}
}

// Parse span attribute schema version from "v0"/"v1" string format.
if schemaStr := provider.getString("DD_TRACE_SPAN_ATTRIBUTE_SCHEMA", ""); schemaStr != "" {
if v, ok := parseSpanAttributeSchema(schemaStr); ok {
cfg.spanAttributeSchemaVersion = v
}
}
// peer.service defaults are enabled when using span attribute schema v1 or later.
if cfg.spanAttributeSchemaVersion >= 1 {
cfg.peerServiceDefaultsEnabled = true
}

// AWS_LAMBDA_FUNCTION_NAME being set indicates that we're running in an AWS Lambda environment.
// See: https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html
// TODO: Is it possible that we can just use `v != ""` to configure one setting, `lambdaMode` instead
Expand Down Expand Up @@ -597,6 +607,74 @@ func (c *Config) SetServiceMapping(from, to string, origin telemetry.Origin) {
reportTelemetry("DD_SERVICE_MAPPING", strings.Join(all, ","), origin)
}

func (c *Config) PeerServiceDefaultsEnabled() bool {
c.mu.RLock()
defer c.mu.RUnlock()
return c.peerServiceDefaultsEnabled
}

func (c *Config) SetPeerServiceDefaultsEnabled(enabled bool, origin telemetry.Origin) {
c.mu.Lock()
defer c.mu.Unlock()
c.peerServiceDefaultsEnabled = enabled
reportTelemetry("DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED", enabled, origin)
}

// PeerServiceMappings returns a copy of the peer service mappings map. If no mappings are set, returns nil.
// Not intended for hot paths — use PeerServiceMapping for single-key lookups to avoid per-call allocations.
func (c *Config) PeerServiceMappings() map[string]string {
c.mu.RLock()
defer c.mu.RUnlock()
if c.peerServiceMappings == nil {
return nil
}
result := make(map[string]string, len(c.peerServiceMappings))
maps.Copy(result, c.peerServiceMappings)
return result
}

// PeerServiceMapping performs a single mapping lookup without copying the underlying map.
// This is better than PeerServiceMappings() for hot paths to avoid per-call allocations.
func (c *Config) PeerServiceMapping(from string) (to string, ok bool) {
c.mu.RLock()
m := c.peerServiceMappings
if m == nil {
c.mu.RUnlock()
return "", false
}
to, ok = m[from]
c.mu.RUnlock()
return to, ok
}
Comment on lines +636 to +648
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to introduce APIs that are as of yet unused. So, depending on the outcome of this, you can potentially delete this, as well as SetPeerServiceMapping.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorted


func (c *Config) SetPeerServiceMappings(mappings map[string]string, origin telemetry.Origin) {
c.mu.Lock()
c.peerServiceMappings = make(map[string]string, len(mappings))
maps.Copy(c.peerServiceMappings, mappings)
all := make([]string, 0, len(c.peerServiceMappings))
for k, v := range c.peerServiceMappings {
all = append(all, fmt.Sprintf("%s:%s", k, v))
}
c.mu.Unlock()

reportTelemetry("DD_TRACE_PEER_SERVICE_MAPPING", strings.Join(all, ","), origin)
}

func (c *Config) SetPeerServiceMapping(from, to string, origin telemetry.Origin) {
c.mu.Lock()
if c.peerServiceMappings == nil {
c.peerServiceMappings = make(map[string]string)
}
c.peerServiceMappings[from] = to
all := make([]string, 0, len(c.peerServiceMappings))
for k, v := range c.peerServiceMappings {
all = append(all, fmt.Sprintf("%s:%s", k, v))
}
c.mu.Unlock()

reportTelemetry("DD_TRACE_PEER_SERVICE_MAPPING", strings.Join(all, ","), origin)
}

func (c *Config) RetryInterval() time.Duration {
c.mu.RLock()
defer c.mu.RUnlock()
Expand Down
21 changes: 20 additions & 1 deletion internal/config/config_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@

package config

import "github.com/DataDog/dd-trace-go/v2/internal/log"
import (
"strings"

"github.com/DataDog/dd-trace-go/v2/internal/log"
)

const (
// DefaultRateLimit specifies the default rate limit per second for traces.
Expand Down Expand Up @@ -35,6 +39,21 @@ func validateRateLimit(rate float64) bool {
return true
}

// parseSpanAttributeSchema parses the DD_TRACE_SPAN_ATTRIBUTE_SCHEMA value.
// It accepts "v0", "v1" (case-insensitive) and returns the corresponding integer version.
// An empty string defaults to 0 (v0). Invalid values are rejected.
func parseSpanAttributeSchema(v string) (int, bool) {
switch strings.ToLower(v) {
case "", "v0":
return 0, true
case "v1":
return 1, true
default:
log.Warn("DD_TRACE_SPAN_ATTRIBUTE_SCHEMA=%s is not a valid value, ignoring", v)
return 0, false
}
}

func validatePartialFlushMinSpans(minSpans int) bool {
if minSpans <= 0 {
log.Warn("ignoring DD_TRACE_PARTIAL_FLUSH_MIN_SPANS: negative value %d", minSpans)
Expand Down
6 changes: 6 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,12 @@ var specialCaseSetters = map[string]func(*Config, telemetry.Origin){
"SetServiceMapping": func(c *Config, origin telemetry.Origin) {
c.SetServiceMapping("from-service", "to-service", origin)
},
"SetPeerServiceMappings": func(c *Config, origin telemetry.Origin) {
c.SetPeerServiceMappings(map[string]string{"old": "new"}, origin)
},
"SetPeerServiceMapping": func(c *Config, origin telemetry.Origin) {
c.SetPeerServiceMapping("old-peer", "new-peer", origin)
},
}

// TestAllSettersReportTelemetry verifies Set* methods report telemetry with seqID > defaultSeqID.
Expand Down
Loading