From c2e12d45f3da6ae470583a585d338ed12ff43ad5 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Tue, 2 Dec 2025 11:09:23 -0500 Subject: [PATCH 01/12] move shared logic to 'get' generic --- internal/config/configprovider.go | 135 +++++++++--------------------- 1 file changed, 40 insertions(+), 95 deletions(-) diff --git a/internal/config/configprovider.go b/internal/config/configprovider.go index 3200915847..01f043e3f3 100644 --- a/internal/config/configprovider.go +++ b/internal/config/configprovider.go @@ -41,130 +41,75 @@ func defaultconfigProvider() *configProvider { } } -func (p *configProvider) getString(key string, def string) string { - // TODO: Eventually, iterate over all sources and report telemetry +// get is a generic helper that iterates through config sources and parses values. +// The parse function should return the parsed value and true if parsing succeeded, or false otherwise. +func get[T any](p *configProvider, key string, def T, parse func(string) (T, bool)) T { for _, source := range p.sources { if v := source.get(key); v != "" { var id string if s, ok := source.(idAwareConfigSource); ok { id = s.getID() } - telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.origin(), ID: id}) - return v + if parsed, ok := parse(v); ok { + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.origin(), ID: id}) + return parsed + } } } telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: def, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}) return def } +func (p *configProvider) getString(key string, def string) string { + return get(p, key, def, func(v string) (string, bool) { + return v, true + }) +} + func (p *configProvider) getBool(key string, def bool) bool { - for _, source := range p.sources { - if v := source.get(key); v != "" { - var id string - if s, ok := source.(idAwareConfigSource); ok { - id = s.getID() - } - if v == "true" { - telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.origin(), ID: id}) - return true - } else if v == "false" { - telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.origin(), ID: id}) - return false - } + return get(p, key, def, func(v string) (bool, bool) { + if v == "true" { + return true, true + } else if v == "false" { + return false, true } - } - telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: def, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}) - return def + return false, false + }) } func (p *configProvider) getInt(key string, def int) int { - for _, source := range p.sources { - if v := source.get(key); v != "" { - var id string - if s, ok := source.(idAwareConfigSource); ok { - id = s.getID() - } - intVal, err := strconv.Atoi(v) - if err == nil { - telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.origin(), ID: id}) - return intVal - } - } - } - telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: def, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}) - return def + return get(p, key, def, func(v string) (int, bool) { + intVal, err := strconv.Atoi(v) + return intVal, err == nil + }) } func (p *configProvider) getMap(key string, def map[string]string) map[string]string { - for _, source := range p.sources { - if v := source.get(key); v != "" { - var id string - if s, ok := source.(idAwareConfigSource); ok { - id = s.getID() - } - m := parseMapString(v) - if len(m) > 0 { - telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.origin(), ID: id}) - return m - } - } - } - telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: def, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}) - return def + return get(p, key, def, func(v string) (map[string]string, bool) { + m := parseMapString(v) + return m, len(m) > 0 + }) } func (p *configProvider) getDuration(key string, def time.Duration) time.Duration { - for _, source := range p.sources { - if v := source.get(key); v != "" { - var id string - if s, ok := source.(idAwareConfigSource); ok { - id = s.getID() - } - d, err := time.ParseDuration(v) - if err == nil { - telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.origin(), ID: id}) - return d - } - } - } - telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: def, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}) - return def + return get(p, key, def, func(v string) (time.Duration, bool) { + d, err := time.ParseDuration(v) + return d, err == nil + }) } func (p *configProvider) getFloat(key string, def float64) float64 { - for _, source := range p.sources { - if v := source.get(key); v != "" { - var id string - if s, ok := source.(idAwareConfigSource); ok { - id = s.getID() - } - floatVal, err := strconv.ParseFloat(v, 64) - if err == nil { - telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.origin(), ID: id}) - return floatVal - } - } - } - telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: def, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}) - return def + return get(p, key, def, func(v string) (float64, bool) { + floatVal, err := strconv.ParseFloat(v, 64) + return floatVal, err == nil + }) } func (p *configProvider) getURL(key string, def *url.URL) *url.URL { - for _, source := range p.sources { - if v := source.get(key); v != "" { - var id string - if s, ok := source.(idAwareConfigSource); ok { - id = s.getID() - } - u, err := url.Parse(v) - if err == nil { - telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.origin(), ID: id}) - return u - } - } - } - telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: def, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}) - return def + return get(p, key, def, func(v string) (*url.URL, bool) { + u, err := url.Parse(v) + return u, err == nil + }) } // normalizeKey is a helper function for configSource implementations to normalize the key to a valid environment variable name. From 319265458b84d93b5dbca84fbf65a3f1be73a43b Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Tue, 2 Dec 2025 11:41:06 -0500 Subject: [PATCH 02/12] Make Config mutable: Modify debug field from tracer options --- ddtrace/tracer/log.go | 2 +- ddtrace/tracer/option.go | 12 +++++------- ddtrace/tracer/option_test.go | 18 +++++++++--------- internal/config/config.go | 26 ++++++++++++++++++++------ 4 files changed, 35 insertions(+), 23 deletions(-) diff --git a/ddtrace/tracer/log.go b/ddtrace/tracer/log.go index 9e4148b7f3..71dc2e9f74 100644 --- a/ddtrace/tracer/log.go +++ b/ddtrace/tracer/log.go @@ -130,7 +130,7 @@ func logStartup(t *tracer) { Env: t.config.env, Service: t.config.serviceName, AgentURL: agentURL, - Debug: t.config.debug, + Debug: t.config.internalConfig.Debug(), AnalyticsEnabled: !math.IsNaN(globalconfig.AnalyticsRate()), SampleRate: fmt.Sprintf("%f", t.rulesSampling.traces.globalRate), SampleRateLimit: "disabled", diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index d86bcbee79..7b50e48cb9 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -141,8 +141,8 @@ const ( // config holds the tracer configuration. type config struct { - // debug, when true, writes details to logs. - debug bool + // internalConfig holds a reference to the global configuration singleton. + internalConfig *internalconfig.Config // appsecStartOptions controls the options used when starting appsec features. appsecStartOptions []appsecconfig.StartOption @@ -379,7 +379,7 @@ const partialFlushMinSpansDefault = 1000 // and passed user opts. func newConfig(opts ...StartOption) (*config, error) { c := new(config) - internalConfig := internalconfig.Get() + c.internalConfig = internalconfig.Get() // If this was built with a recent-enough version of Orchestrion, force the orchestrion config to // the baked-in values. We do this early so that opts can be used to override the baked-in values, @@ -482,7 +482,6 @@ func newConfig(opts ...StartOption) (*config, error) { c.logStartup = internal.BoolEnv("DD_TRACE_STARTUP_LOGS", true) c.runtimeMetrics = internal.BoolVal(getDDorOtelConfig("metrics"), false) c.runtimeMetricsV2 = internal.BoolEnv("DD_RUNTIME_METRICS_V2_ENABLED", true) - c.debug = internalConfig.Debug() c.logDirectory = env.Get("DD_TRACE_LOG_DIRECTORY") c.enabled = newDynamicConfig("tracing_enabled", internal.BoolVal(getDDorOtelConfig("enabled"), true), func(_ bool) bool { return true }, equal[bool]) if _, ok := env.Lookup("DD_TRACE_ENABLED"); ok { @@ -613,7 +612,7 @@ func newConfig(opts ...StartOption) (*config, error) { if c.logger != nil { log.UseLogger(c.logger) } - if c.debug { + if c.internalConfig.Debug() { log.SetLevel(log.LevelDebug) } // Check if CI Visibility mode is enabled @@ -1009,8 +1008,7 @@ func WithDebugStack(enabled bool) StartOption { // WithDebugMode enables debug mode on the tracer, resulting in more verbose logging. func WithDebugMode(enabled bool) StartOption { return func(c *config) { - telemetry.RegisterAppConfig("trace_debug_enabled", enabled, telemetry.OriginCode) - c.debug = enabled + c.internalConfig.SetDebug(enabled) } } diff --git a/ddtrace/tracer/option_test.go b/ddtrace/tracer/option_test.go index 2c74ed2f12..76ce2e3c0d 100644 --- a/ddtrace/tracer/option_test.go +++ b/ddtrace/tracer/option_test.go @@ -375,7 +375,7 @@ func TestTracerOptionsDefaults(t *testing.T) { assert.Equal(x.Timeout, y.Timeout) compareHTTPClients(t, x, y) assert.True(getFuncName(x.Transport.(*http.Transport).DialContext) == getFuncName(internal.DefaultDialer(30*time.Second).DialContext)) - assert.False(c.debug) + assert.False(c.internalConfig.Debug()) }) t.Run("http-client", func(t *testing.T) { @@ -430,21 +430,21 @@ func TestTracerOptionsDefaults(t *testing.T) { assert.NoError(t, err) defer tracer.Stop() c := tracer.config - assert.True(t, c.debug) + assert.True(t, c.internalConfig.Debug()) }) t.Run("env", func(t *testing.T) { t.Setenv("DD_TRACE_DEBUG", "true") internalconfig.ResetForTesting() c, err := newTestConfig() assert.NoError(t, err) - assert.True(t, c.debug) + assert.True(t, c.internalConfig.Debug()) }) t.Run("otel-env-debug", func(t *testing.T) { t.Setenv("OTEL_LOG_LEVEL", "debug") internalconfig.ResetForTesting() c, err := newTestConfig() assert.NoError(t, err) - assert.True(t, c.debug) + assert.True(t, c.internalConfig.Debug()) }) t.Run("otel-env-notdebug", func(t *testing.T) { // any value other than debug, does nothing @@ -452,7 +452,7 @@ func TestTracerOptionsDefaults(t *testing.T) { internalconfig.ResetForTesting() c, err := newTestConfig() assert.NoError(t, err) - assert.False(t, c.debug) + assert.False(t, c.internalConfig.Debug()) }) t.Run("override-chain", func(t *testing.T) { assert := assert.New(t) @@ -461,17 +461,17 @@ func TestTracerOptionsDefaults(t *testing.T) { internalconfig.ResetForTesting() c, err := newTestConfig(WithDebugMode(false)) assert.NoError(err) - assert.False(c.debug) + assert.False(c.internalConfig.Debug()) // env override otel t.Setenv("DD_TRACE_DEBUG", "false") internalconfig.ResetForTesting() c, err = newTestConfig() assert.NoError(err) - assert.False(c.debug) + assert.False(c.internalConfig.Debug()) // option override env c, err = newTestConfig(WithDebugMode(true)) assert.NoError(err) - assert.True(c.debug) + assert.True(c.internalConfig.Debug()) }) }) @@ -745,7 +745,7 @@ func TestTracerOptionsDefaults(t *testing.T) { assert.NotNil(c.globalTags.get()) assert.Equal("v", c.globalTags.get()["k"]) assert.Equal("testEnv", c.env) - assert.True(c.debug) + assert.True(c.internalConfig.Debug()) }) t.Run("env-tags", func(t *testing.T) { diff --git a/internal/config/config.go b/internal/config/config.go index 01d7450a02..23b5992c9f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -9,15 +9,19 @@ import ( "net/url" "sync" "time" + + "github.com/DataDog/dd-trace-go/v2/internal/telemetry" ) var ( - instance *config + instance *Config configOnce sync.Once ) // Config represents global configuration properties. -type config struct { +type Config struct { + mu sync.RWMutex + // Config fields are protected by the mutex. agentURL *url.URL debug bool logStartup bool @@ -49,8 +53,8 @@ type config struct { // loadConfig initializes and returns a new config by reading from all configured sources. // This function is NOT thread-safe and should only be called once through Get's sync.Once. -func loadConfig() *config { - cfg := new(config) +func loadConfig() *Config { + cfg := new(Config) // TODO: Use defaults from config json instead of hardcoding them here cfg.agentURL = provider.getURL("DD_TRACE_AGENT_URL", &url.URL{Scheme: "http", Host: "localhost:8126"}) @@ -88,13 +92,23 @@ func loadConfig() *config { // This function is thread-safe and can be called from multiple goroutines concurrently. // The configuration is lazily initialized on first access using sync.Once, ensuring // loadConfig() is called exactly once even under concurrent access. -func Get() *config { +func Get() *Config { configOnce.Do(func() { instance = loadConfig() }) return instance } -func (c *config) Debug() bool { +func (c *Config) Debug() bool { + c.mu.RLock() + defer c.mu.RUnlock() return c.debug } + +// SetDebug sets the debug flag and reports telemetry. +func (c *Config) SetDebug(enabled bool) { + c.mu.Lock() + defer c.mu.Unlock() + c.debug = enabled + telemetry.RegisterAppConfig("trace_debug_enabled", enabled, telemetry.OriginCode) +} From d8f7d4bf01a6626dbb39f4d15f5b35de7503a851 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Tue, 2 Dec 2025 12:17:59 -0500 Subject: [PATCH 03/12] rename trace_debug_enabled to DD_TRACE_DEBUG --- internal/config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/config/config.go b/internal/config/config.go index 23b5992c9f..74c3a54e91 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -110,5 +110,5 @@ func (c *Config) SetDebug(enabled bool) { c.mu.Lock() defer c.mu.Unlock() c.debug = enabled - telemetry.RegisterAppConfig("trace_debug_enabled", enabled, telemetry.OriginCode) + telemetry.RegisterAppConfig("DD_TRACE_DEBUG", enabled, telemetry.OriginCode) } From 63a09e64970e4572c61bde1f45119f9a7b3df635 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Tue, 2 Dec 2025 14:59:48 -0500 Subject: [PATCH 04/12] introduce usefreshconfig --- ddtrace/tracer/option_test.go | 6 ------ ddtrace/tracer/telemetry_test.go | 2 +- ddtrace/tracer/tracer_test.go | 3 +++ internal/config/config.go | 21 ++++++++++++++------- internal/config/config_testing.go | 5 +---- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/ddtrace/tracer/option_test.go b/ddtrace/tracer/option_test.go index 76ce2e3c0d..b08360eca4 100644 --- a/ddtrace/tracer/option_test.go +++ b/ddtrace/tracer/option_test.go @@ -26,7 +26,6 @@ import ( "github.com/DataDog/dd-trace-go/v2/ddtrace/ext" "github.com/DataDog/dd-trace-go/v2/internal" - internalconfig "github.com/DataDog/dd-trace-go/v2/internal/config" "github.com/DataDog/dd-trace-go/v2/internal/globalconfig" "github.com/DataDog/dd-trace-go/v2/internal/log" "github.com/DataDog/dd-trace-go/v2/internal/telemetry" @@ -434,14 +433,12 @@ func TestTracerOptionsDefaults(t *testing.T) { }) t.Run("env", func(t *testing.T) { t.Setenv("DD_TRACE_DEBUG", "true") - internalconfig.ResetForTesting() c, err := newTestConfig() assert.NoError(t, err) assert.True(t, c.internalConfig.Debug()) }) t.Run("otel-env-debug", func(t *testing.T) { t.Setenv("OTEL_LOG_LEVEL", "debug") - internalconfig.ResetForTesting() c, err := newTestConfig() assert.NoError(t, err) assert.True(t, c.internalConfig.Debug()) @@ -449,7 +446,6 @@ func TestTracerOptionsDefaults(t *testing.T) { t.Run("otel-env-notdebug", func(t *testing.T) { // any value other than debug, does nothing t.Setenv("OTEL_LOG_LEVEL", "notdebug") - internalconfig.ResetForTesting() c, err := newTestConfig() assert.NoError(t, err) assert.False(t, c.internalConfig.Debug()) @@ -458,13 +454,11 @@ func TestTracerOptionsDefaults(t *testing.T) { assert := assert.New(t) // option override otel t.Setenv("OTEL_LOG_LEVEL", "debug") - internalconfig.ResetForTesting() c, err := newTestConfig(WithDebugMode(false)) assert.NoError(err) assert.False(c.internalConfig.Debug()) // env override otel t.Setenv("DD_TRACE_DEBUG", "false") - internalconfig.ResetForTesting() c, err = newTestConfig() assert.NoError(err) assert.False(c.internalConfig.Debug()) diff --git a/ddtrace/tracer/telemetry_test.go b/ddtrace/tracer/telemetry_test.go index 6857ca08ce..457aec78c7 100644 --- a/ddtrace/tracer/telemetry_test.go +++ b/ddtrace/tracer/telemetry_test.go @@ -67,7 +67,7 @@ func TestTelemetryEnabled(t *testing.T) { defer globalconfig.SetServiceName("") defer Stop() - telemetrytest.CheckConfig(t, telemetryClient.Configuration, "trace_debug_enabled", true) + telemetrytest.CheckConfig(t, telemetryClient.Configuration, "DD_TRACE_DEBUG", true) telemetrytest.CheckConfig(t, telemetryClient.Configuration, "service", "test-serv") telemetrytest.CheckConfig(t, telemetryClient.Configuration, "env", "test-env") telemetrytest.CheckConfig(t, telemetryClient.Configuration, "runtime_metrics_enabled", true) diff --git a/ddtrace/tracer/tracer_test.go b/ddtrace/tracer/tracer_test.go index 907af06558..5c1d81cfb0 100644 --- a/ddtrace/tracer/tracer_test.go +++ b/ddtrace/tracer/tracer_test.go @@ -31,6 +31,7 @@ import ( "github.com/DataDog/dd-trace-go/v2/ddtrace/ext" "github.com/DataDog/dd-trace-go/v2/ddtrace/internal/tracerstats" "github.com/DataDog/dd-trace-go/v2/internal" + internalconfig "github.com/DataDog/dd-trace-go/v2/internal/config" "github.com/DataDog/dd-trace-go/v2/internal/globalconfig" "github.com/DataDog/dd-trace-go/v2/internal/log" "github.com/DataDog/dd-trace-go/v2/internal/remoteconfig" @@ -74,6 +75,8 @@ var ( ) func TestMain(m *testing.M) { + internalconfig.SetUseFreshConfig(true) + // defer internalconfig.SetUseFreshConfig(false) if internal.BoolEnv("DD_APPSEC_ENABLED", false) { // things are slower with AppSec; double wait times timeMultiplicator = time.Duration(2) diff --git a/internal/config/config.go b/internal/config/config.go index 74c3a54e91..2a945acf04 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -8,14 +8,15 @@ package config import ( "net/url" "sync" + "sync/atomic" "time" "github.com/DataDog/dd-trace-go/v2/internal/telemetry" ) var ( - instance *Config - configOnce sync.Once + useFreshConfig atomic.Bool + instance atomic.Value ) // Config represents global configuration properties. @@ -93,10 +94,17 @@ func loadConfig() *Config { // The configuration is lazily initialized on first access using sync.Once, ensuring // loadConfig() is called exactly once even under concurrent access. func Get() *Config { - configOnce.Do(func() { - instance = loadConfig() - }) - return instance + v := instance.Load() + if v == nil || useFreshConfig.Load() { + cfg := loadConfig() + instance.Store(cfg) + return cfg + } + return v.(*Config) +} + +func SetUseFreshConfig(use bool) { + useFreshConfig.Store(use) } func (c *Config) Debug() bool { @@ -105,7 +113,6 @@ func (c *Config) Debug() bool { return c.debug } -// SetDebug sets the debug flag and reports telemetry. func (c *Config) SetDebug(enabled bool) { c.mu.Lock() defer c.mu.Unlock() diff --git a/internal/config/config_testing.go b/internal/config/config_testing.go index a4018aa593..2c3c5af7a7 100644 --- a/internal/config/config_testing.go +++ b/internal/config/config_testing.go @@ -5,8 +5,6 @@ package config -import "sync" - // ResetForTesting resets the global configuration state for testing. // // WARNING: This function is intended for use in tests only to reset state between @@ -14,6 +12,5 @@ import "sync" // accesses the global config, as it can cause race conditions and violate the // singleton initialization guarantee. func ResetForTesting() { - instance = nil - configOnce = sync.Once{} + instance.Store(nil) } From ef4772aba7698a035c3337f7a90ec79f7ff3e930 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Wed, 3 Dec 2025 13:10:06 -0500 Subject: [PATCH 05/12] nits --- internal/config/config.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/config/config.go b/internal/config/config.go index 2a945acf04..2bee6f8be2 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -20,6 +20,8 @@ var ( ) // Config represents global configuration properties. +// Config instances should be obtained via Get() which always returns a non-nil value. +// Methods on Config assume a non-nil receiver and will panic if called on nil. type Config struct { mu sync.RWMutex // Config fields are protected by the mutex. From b0b138b7e81834c7c974163e329ccfbd5c67cf46 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Wed, 3 Dec 2025 13:30:34 -0500 Subject: [PATCH 06/12] delete ResetForTesting --- internal/config/config_testing.go | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 internal/config/config_testing.go diff --git a/internal/config/config_testing.go b/internal/config/config_testing.go deleted file mode 100644 index 2c3c5af7a7..0000000000 --- a/internal/config/config_testing.go +++ /dev/null @@ -1,16 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed -// under the Apache License Version 2.0. -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2025 Datadog, Inc. - -package config - -// ResetForTesting resets the global configuration state for testing. -// -// WARNING: This function is intended for use in tests only to reset state between -// test cases. It must not be called concurrently with Get() or other code that -// accesses the global config, as it can cause race conditions and violate the -// singleton initialization guarantee. -func ResetForTesting() { - instance.Store(nil) -} From e12fcf6af99ace7717a645b9f34da584b834f5a2 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Wed, 3 Dec 2025 14:00:29 -0500 Subject: [PATCH 07/12] Modify SetDebug to take in an origin --- ddtrace/tracer/option.go | 2 +- internal/config/config.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index 7b50e48cb9..0ce39ee3a7 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -1008,7 +1008,7 @@ func WithDebugStack(enabled bool) StartOption { // WithDebugMode enables debug mode on the tracer, resulting in more verbose logging. func WithDebugMode(enabled bool) StartOption { return func(c *config) { - c.internalConfig.SetDebug(enabled) + c.internalConfig.SetDebug(enabled, telemetry.OriginCode) } } diff --git a/internal/config/config.go b/internal/config/config.go index 2bee6f8be2..a67cbbb0ec 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -115,9 +115,9 @@ func (c *Config) Debug() bool { return c.debug } -func (c *Config) SetDebug(enabled bool) { +func (c *Config) SetDebug(enabled bool, origin telemetry.Origin) { c.mu.Lock() defer c.mu.Unlock() c.debug = enabled - telemetry.RegisterAppConfig("DD_TRACE_DEBUG", enabled, telemetry.OriginCode) + telemetry.RegisterAppConfig("DD_TRACE_DEBUG", enabled, origin) } From 19d7eb4cc1f29570ee71ec4f7f89f65536b9222b Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Thu, 4 Dec 2025 09:44:06 -0500 Subject: [PATCH 08/12] restore configprovider.go to main --- internal/config/configprovider.go | 135 +++++++++++++++++++++--------- 1 file changed, 95 insertions(+), 40 deletions(-) diff --git a/internal/config/configprovider.go b/internal/config/configprovider.go index 01f043e3f3..3200915847 100644 --- a/internal/config/configprovider.go +++ b/internal/config/configprovider.go @@ -41,75 +41,130 @@ func defaultconfigProvider() *configProvider { } } -// get is a generic helper that iterates through config sources and parses values. -// The parse function should return the parsed value and true if parsing succeeded, or false otherwise. -func get[T any](p *configProvider, key string, def T, parse func(string) (T, bool)) T { +func (p *configProvider) getString(key string, def string) string { + // TODO: Eventually, iterate over all sources and report telemetry for _, source := range p.sources { if v := source.get(key); v != "" { var id string if s, ok := source.(idAwareConfigSource); ok { id = s.getID() } - if parsed, ok := parse(v); ok { - telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.origin(), ID: id}) - return parsed - } + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.origin(), ID: id}) + return v } } telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: def, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}) return def } -func (p *configProvider) getString(key string, def string) string { - return get(p, key, def, func(v string) (string, bool) { - return v, true - }) -} - func (p *configProvider) getBool(key string, def bool) bool { - return get(p, key, def, func(v string) (bool, bool) { - if v == "true" { - return true, true - } else if v == "false" { - return false, true + for _, source := range p.sources { + if v := source.get(key); v != "" { + var id string + if s, ok := source.(idAwareConfigSource); ok { + id = s.getID() + } + if v == "true" { + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.origin(), ID: id}) + return true + } else if v == "false" { + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.origin(), ID: id}) + return false + } } - return false, false - }) + } + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: def, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}) + return def } func (p *configProvider) getInt(key string, def int) int { - return get(p, key, def, func(v string) (int, bool) { - intVal, err := strconv.Atoi(v) - return intVal, err == nil - }) + for _, source := range p.sources { + if v := source.get(key); v != "" { + var id string + if s, ok := source.(idAwareConfigSource); ok { + id = s.getID() + } + intVal, err := strconv.Atoi(v) + if err == nil { + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.origin(), ID: id}) + return intVal + } + } + } + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: def, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}) + return def } func (p *configProvider) getMap(key string, def map[string]string) map[string]string { - return get(p, key, def, func(v string) (map[string]string, bool) { - m := parseMapString(v) - return m, len(m) > 0 - }) + for _, source := range p.sources { + if v := source.get(key); v != "" { + var id string + if s, ok := source.(idAwareConfigSource); ok { + id = s.getID() + } + m := parseMapString(v) + if len(m) > 0 { + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.origin(), ID: id}) + return m + } + } + } + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: def, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}) + return def } func (p *configProvider) getDuration(key string, def time.Duration) time.Duration { - return get(p, key, def, func(v string) (time.Duration, bool) { - d, err := time.ParseDuration(v) - return d, err == nil - }) + for _, source := range p.sources { + if v := source.get(key); v != "" { + var id string + if s, ok := source.(idAwareConfigSource); ok { + id = s.getID() + } + d, err := time.ParseDuration(v) + if err == nil { + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.origin(), ID: id}) + return d + } + } + } + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: def, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}) + return def } func (p *configProvider) getFloat(key string, def float64) float64 { - return get(p, key, def, func(v string) (float64, bool) { - floatVal, err := strconv.ParseFloat(v, 64) - return floatVal, err == nil - }) + for _, source := range p.sources { + if v := source.get(key); v != "" { + var id string + if s, ok := source.(idAwareConfigSource); ok { + id = s.getID() + } + floatVal, err := strconv.ParseFloat(v, 64) + if err == nil { + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.origin(), ID: id}) + return floatVal + } + } + } + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: def, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}) + return def } func (p *configProvider) getURL(key string, def *url.URL) *url.URL { - return get(p, key, def, func(v string) (*url.URL, bool) { - u, err := url.Parse(v) - return u, err == nil - }) + for _, source := range p.sources { + if v := source.get(key); v != "" { + var id string + if s, ok := source.(idAwareConfigSource); ok { + id = s.getID() + } + u, err := url.Parse(v) + if err == nil { + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.origin(), ID: id}) + return u + } + } + } + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: def, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}) + return def } // normalizeKey is a helper function for configSource implementations to normalize the key to a valid environment variable name. From 5ade4e8f00369575a79f38fe0f54b27b204855e1 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Thu, 4 Dec 2025 10:16:30 -0500 Subject: [PATCH 09/12] Add unit tests for config --- internal/config/config_test.go | 218 +++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 internal/config/config_test.go diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 0000000000..975525f578 --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,218 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2025 Datadog, Inc. + +package config + +import ( + "sync" + "sync/atomic" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGet(t *testing.T) { + t.Run("returns non-nil config", func(t *testing.T) { + resetGlobalState() + defer resetGlobalState() + + cfg := Get() + assert.NotNil(t, cfg, "Get() should never return nil") + }) + + t.Run("singleton behavior - returns same instance", func(t *testing.T) { + resetGlobalState() + defer resetGlobalState() + + cfg1 := Get() + cfg2 := Get() + cfg3 := Get() + + // All calls should return the same instance + assert.Same(t, cfg1, cfg2, "First and second Get() calls should return the same instance") + assert.Same(t, cfg1, cfg3, "First and third Get() calls should return the same instance") + }) + + t.Run("fresh config flag forces new instance", func(t *testing.T) { + resetGlobalState() + defer resetGlobalState() + + // Get the first instance + cfg1 := Get() + require.NotNil(t, cfg1) + + // Enable fresh config + SetUseFreshConfig(true) + + // Get should now return a new instance + cfg2 := Get() + require.NotNil(t, cfg2) + assert.NotSame(t, cfg1, cfg2, "With useFreshConfig=true, Get() should return a new instance") + + // Another call with useFreshConfig still true should return another new instance + cfg3 := Get() + require.NotNil(t, cfg3) + assert.NotSame(t, cfg2, cfg3, "With useFreshConfig=true, each Get() call should return a new instance") + + // Disable fresh config + SetUseFreshConfig(false) + + // Now it should cache again + cfg4 := Get() + cfg5 := Get() + assert.Same(t, cfg4, cfg5, "With useFreshConfig=false, Get() should cache the same instance") + }) + + t.Run("concurrent access is safe", func(t *testing.T) { + resetGlobalState() + defer resetGlobalState() + + const numGoroutines = 100 + var wg sync.WaitGroup + wg.Add(numGoroutines) + + // All goroutines should get a non-nil config + configs := make([]*Config, numGoroutines) + + for i := range numGoroutines { + go func(j int) { + defer wg.Done() + configs[j] = Get() + }(i) + } + + wg.Wait() + + // All configs should be non-nil + for i, cfg := range configs { + assert.NotNil(t, cfg, "Config at index %d should not be nil", i) + } + + // All configs should be the same instance (singleton) + firstConfig := configs[0] + for i, cfg := range configs[1:] { + assert.Same(t, firstConfig, cfg, "Config at index %d should be the same instance", i+1) + } + }) + + t.Run("concurrent access with fresh config", func(t *testing.T) { + resetGlobalState() + defer resetGlobalState() + + SetUseFreshConfig(true) + defer SetUseFreshConfig(false) + + const numGoroutines = 50 + var wg sync.WaitGroup + wg.Add(numGoroutines) + + // Track if we get different instances (which is expected with useFreshConfig=true) + var uniqueInstances sync.Map + var configCount atomic.Int32 + + for range numGoroutines { + go func() { + defer wg.Done() + cfg := Get() + require.NotNil(t, cfg, "Get() should not return nil even under concurrent access") + + // Track unique instances + if _, loaded := uniqueInstances.LoadOrStore(cfg, true); !loaded { + configCount.Add(1) + } + }() + } + + wg.Wait() + + // With useFreshConfig=true, we should get multiple different instances + count := configCount.Load() + assert.Greater(t, count, int32(1), "With useFreshConfig=true, should get multiple different instances") + }) + + t.Run("config is properly initialized with values", func(t *testing.T) { + resetGlobalState() + defer resetGlobalState() + + // Set an environment variable to ensure it's loaded + t.Setenv("DD_TRACE_DEBUG", "true") + + cfg := Get() + require.NotNil(t, cfg) + + // Verify that config values are accessible (using the Debug() method) + debug := cfg.Debug() + assert.True(t, debug, "Config should have loaded DD_TRACE_DEBUG=true") + }) + + t.Run("Setter methods update config and maintain thread-safety", func(t *testing.T) { + resetGlobalState() + defer resetGlobalState() + + cfg := Get() + require.NotNil(t, cfg) + + initialDebug := cfg.Debug() + cfg.SetDebug(!initialDebug, "test") + assert.Equal(t, !initialDebug, cfg.Debug(), "Debug setting should have changed") + + // Verify concurrent reads don't panic + const numReaders = 100 + var wg sync.WaitGroup + wg.Add(numReaders) + + for range numReaders { + go func() { + defer wg.Done() + _ = cfg.Debug() + }() + } + + wg.Wait() + }) + + t.Run("SetDebug concurrent with reads is safe", func(t *testing.T) { + resetGlobalState() + defer resetGlobalState() + + cfg := Get() + require.NotNil(t, cfg) + + var wg sync.WaitGroup + const numOperations = 100 + + // Start readers + wg.Add(numOperations) + for range numOperations { + go func() { + defer wg.Done() + _ = cfg.Debug() + }() + } + + // Start writers + wg.Add(numOperations) + for i := range numOperations { + go func(val bool) { + defer wg.Done() + cfg.SetDebug(val, "test") + }(i%2 == 0) + } + + wg.Wait() + + // Should not panic and should have a valid boolean value + finalDebug := cfg.Debug() + assert.IsType(t, true, finalDebug) + }) +} + +// resetGlobalState resets the global singleton state for testing +func resetGlobalState() { + // Clear any stored value + instance = atomic.Value{} + useFreshConfig.Store(false) +} From 8eb1f4115a51fe498547cba0c9e943af15e65d48 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Fri, 5 Dec 2025 10:47:44 -0500 Subject: [PATCH 10/12] Fix config tests --- internal/config/config.go | 11 ++++++++--- internal/config/config_test.go | 12 ++++++------ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index a67cbbb0ec..e811241a12 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -17,6 +17,7 @@ import ( var ( useFreshConfig atomic.Bool instance atomic.Value + once sync.Once ) // Config represents global configuration properties. @@ -96,13 +97,17 @@ func loadConfig() *Config { // The configuration is lazily initialized on first access using sync.Once, ensuring // loadConfig() is called exactly once even under concurrent access. func Get() *Config { - v := instance.Load() - if v == nil || useFreshConfig.Load() { + if useFreshConfig.Load() { cfg := loadConfig() instance.Store(cfg) return cfg } - return v.(*Config) + + once.Do(func() { + cfg := loadConfig() + instance.Store(cfg) + }) + return instance.Load().(*Config) } func SetUseFreshConfig(use bool) { diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 975525f578..59e3f861f8 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -44,7 +44,7 @@ func TestGet(t *testing.T) { cfg1 := Get() require.NotNil(t, cfg1) - // Enable fresh config + // Enable fresh config to allow us to create new instances SetUseFreshConfig(true) // Get should now return a new instance @@ -57,10 +57,10 @@ func TestGet(t *testing.T) { require.NotNil(t, cfg3) assert.NotSame(t, cfg2, cfg3, "With useFreshConfig=true, each Get() call should return a new instance") - // Disable fresh config + // Disable fresh config to allow us to cache the same instance SetUseFreshConfig(false) - // Now it should cache again + // Now it should cache the same instance cfg4 := Get() cfg5 := Get() assert.Same(t, cfg4, cfg5, "With useFreshConfig=false, Get() should cache the same instance") @@ -102,8 +102,8 @@ func TestGet(t *testing.T) { resetGlobalState() defer resetGlobalState() + // Enable fresh config to allow us to create new instances SetUseFreshConfig(true) - defer SetUseFreshConfig(false) const numGoroutines = 50 var wg sync.WaitGroup @@ -210,9 +210,9 @@ func TestGet(t *testing.T) { }) } -// resetGlobalState resets the global singleton state for testing +// resetGlobalState resets all global singleton state for testing func resetGlobalState() { - // Clear any stored value + once = sync.Once{} instance = atomic.Value{} useFreshConfig.Store(false) } From 4ca04248e42a4a701c719097dad172fd953f514f Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Fri, 5 Dec 2025 11:47:57 -0500 Subject: [PATCH 11/12] migrate profilerHotspots --- ddtrace/tracer/log.go | 2 +- ddtrace/tracer/option.go | 6 +----- ddtrace/tracer/option_test.go | 4 ++-- ddtrace/tracer/telemetry.go | 2 +- ddtrace/tracer/tracer.go | 6 +++--- internal/config/config.go | 15 ++++++++++++++- 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/ddtrace/tracer/log.go b/ddtrace/tracer/log.go index 71dc2e9f74..0e6fb13dc5 100644 --- a/ddtrace/tracer/log.go +++ b/ddtrace/tracer/log.go @@ -141,7 +141,7 @@ func logStartup(t *tracer) { RuntimeMetricsEnabled: t.config.runtimeMetrics, RuntimeMetricsV2Enabled: t.config.runtimeMetricsV2, ApplicationVersion: t.config.version, - ProfilerCodeHotspotsEnabled: t.config.profilerHotspots, + ProfilerCodeHotspotsEnabled: t.config.internalConfig.ProfilerHotspotsEnabled(), ProfilerEndpointsEnabled: t.config.profilerEndpoints, Architecture: runtime.GOARCH, GlobalService: globalconfig.ServiceName(), diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index 0ce39ee3a7..f73dc8c60a 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -253,9 +253,6 @@ type config struct { // errors will record a stack trace when this option is set. noDebugStack bool - // profilerHotspots specifies whether profiler Code Hotspots is enabled. - profilerHotspots bool - // profilerEndpoints specifies whether profiler endpoint filtering is enabled. profilerEndpoints bool @@ -488,7 +485,6 @@ func newConfig(opts ...StartOption) (*config, error) { c.enabled.cfgOrigin = telemetry.OriginEnvVar } c.profilerEndpoints = internal.BoolEnv(traceprof.EndpointEnvVar, true) - c.profilerHotspots = internal.BoolEnv(traceprof.CodeHotspotsEnvVar, true) if compatMode := env.Get("DD_TRACE_CLIENT_HOSTNAME_COMPAT"); compatMode != "" { if semver.IsValid(compatMode) { c.enableHostnameDetection = semver.Compare(semver.MajorMinor(compatMode), "v1.66") <= 0 @@ -1316,7 +1312,7 @@ func WithLogStartup(enabled bool) StartOption { // DD_PROFILING_CODE_HOTSPOTS_COLLECTION_ENABLED env variable or true. func WithProfilerCodeHotspots(enabled bool) StartOption { return func(c *config) { - c.profilerHotspots = enabled + c.internalConfig.SetProfilerHotspotsEnabled(enabled, telemetry.OriginCode) } } diff --git a/ddtrace/tracer/option_test.go b/ddtrace/tracer/option_test.go index b08360eca4..eb753603cb 100644 --- a/ddtrace/tracer/option_test.go +++ b/ddtrace/tracer/option_test.go @@ -778,14 +778,14 @@ func TestTracerOptionsDefaults(t *testing.T) { t.Run("default", func(t *testing.T) { c, err := newTestConfig(WithAgentTimeout(2)) assert.NoError(t, err) - assert.True(t, c.profilerHotspots) + assert.True(t, c.internalConfig.ProfilerHotspotsEnabled()) }) t.Run("override", func(t *testing.T) { t.Setenv(traceprof.CodeHotspotsEnvVar, "false") c, err := newTestConfig(WithAgentTimeout(2)) assert.NoError(t, err) - assert.False(t, c.profilerHotspots) + assert.False(t, c.internalConfig.ProfilerHotspotsEnabled()) }) }) diff --git a/ddtrace/tracer/telemetry.go b/ddtrace/tracer/telemetry.go index 4704efd773..47316e0c22 100644 --- a/ddtrace/tracer/telemetry.go +++ b/ddtrace/tracer/telemetry.go @@ -52,7 +52,7 @@ func startTelemetry(c *config) telemetry.Client { {Name: "runtime_metrics_v2_enabled", Value: c.runtimeMetricsV2}, {Name: "dogstatsd_addr", Value: c.dogstatsdAddr}, {Name: "debug_stack_enabled", Value: !c.noDebugStack}, - {Name: "profiling_hotspots_enabled", Value: c.profilerHotspots}, + {Name: "profiling_hotspots_enabled", Value: c.internalConfig.ProfilerHotspotsEnabled()}, {Name: "profiling_endpoints_enabled", Value: c.profilerEndpoints}, {Name: "trace_span_attribute_schema", Value: c.spanAttributeSchemaVersion}, {Name: "trace_peer_service_defaults_enabled", Value: c.peerServiceDefaultsEnabled}, diff --git a/ddtrace/tracer/tracer.go b/ddtrace/tracer/tracer.go index 33b0541830..9003e9e694 100644 --- a/ddtrace/tracer/tracer.go +++ b/ddtrace/tracer/tracer.go @@ -801,7 +801,7 @@ func (t *tracer) StartSpan(operationName string, options ...StartSpanOption) *Sp log.Debug("Started Span: %v, Operation: %s, Resource: %s, Tags: %v, %v", //nolint:gocritic // Debug logging needs full span representation span, span.name, span.resource, span.meta, span.metrics) } - if t.config.profilerHotspots || t.config.profilerEndpoints { + if t.config.internalConfig.ProfilerHotspotsEnabled() || t.config.profilerEndpoints { t.applyPPROFLabels(span.pprofCtxRestore, span) } else { span.pprofCtxRestore = nil @@ -835,12 +835,12 @@ func (t *tracer) applyPPROFLabels(ctx gocontext.Context, span *Span) { // https://go-review.googlesource.com/c/go/+/574516 for more information. labels := make([]string, 0, 3*2 /* 3 key value pairs */) localRootSpan := span.Root() - if t.config.profilerHotspots && localRootSpan != nil { + if t.config.internalConfig.ProfilerHotspotsEnabled() && localRootSpan != nil { localRootSpan.mu.RLock() labels = append(labels, traceprof.LocalRootSpanID, strconv.FormatUint(localRootSpan.spanID, 10)) localRootSpan.mu.RUnlock() } - if t.config.profilerHotspots { + if t.config.internalConfig.ProfilerHotspotsEnabled() { labels = append(labels, traceprof.SpanID, strconv.FormatUint(span.spanID, 10)) } if t.config.profilerEndpoints && localRootSpan != nil { diff --git a/internal/config/config.go b/internal/config/config.go index e811241a12..7386aed2d2 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -71,7 +71,7 @@ func loadConfig() *Config { cfg.hostname = provider.getString("DD_TRACE_SOURCE_HOSTNAME", "") cfg.runtimeMetrics = provider.getBool("DD_RUNTIME_METRICS_ENABLED", false) cfg.runtimeMetricsV2 = provider.getBool("DD_RUNTIME_METRICS_V2_ENABLED", false) - cfg.profilerHotspots = provider.getBool("DD_PROFILING_CODE_HOTSPOTS_COLLECTION_ENABLED", false) + cfg.profilerHotspots = provider.getBool("DD_PROFILING_CODE_HOTSPOTS_COLLECTION_ENABLED", true) cfg.profilerEndpoints = provider.getBool("DD_PROFILING_ENDPOINT_COLLECTION_ENABLED", false) cfg.spanAttributeSchemaVersion = provider.getInt("DD_TRACE_SPAN_ATTRIBUTE_SCHEMA", 0) cfg.peerServiceDefaultsEnabled = provider.getBool("DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED", false) @@ -126,3 +126,16 @@ func (c *Config) SetDebug(enabled bool, origin telemetry.Origin) { c.debug = enabled telemetry.RegisterAppConfig("DD_TRACE_DEBUG", enabled, origin) } + +func (c *Config) ProfilerHotspotsEnabled() bool { + c.mu.RLock() + defer c.mu.RUnlock() + return c.profilerHotspots +} + +func (c *Config) SetProfilerHotspotsEnabled(enabled bool, origin telemetry.Origin) { + c.mu.Lock() + defer c.mu.Unlock() + c.profilerHotspots = enabled + telemetry.RegisterAppConfig("DD_PROFILING_CODE_HOTSPOTS_COLLECTION_ENABLED", enabled, origin) +} From d79558ed2c604f4c7a6cb97cdf2442fc490d88b6 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Wed, 10 Dec 2025 11:24:35 -0500 Subject: [PATCH 12/12] use constant for profilerhotspots env var name --- internal/config/config.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 1189b44f45..8dfbb375d2 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -11,6 +11,7 @@ import ( "time" "github.com/DataDog/dd-trace-go/v2/internal/telemetry" + "github.com/DataDog/dd-trace-go/v2/internal/traceprof" ) var ( @@ -81,7 +82,7 @@ func loadConfig() *Config { cfg.hostname = provider.getString("DD_TRACE_SOURCE_HOSTNAME", "") cfg.runtimeMetrics = provider.getBool("DD_RUNTIME_METRICS_ENABLED", false) cfg.runtimeMetricsV2 = provider.getBool("DD_RUNTIME_METRICS_V2_ENABLED", false) - cfg.profilerHotspots = provider.getBool("DD_PROFILING_CODE_HOTSPOTS_COLLECTION_ENABLED", true) + cfg.profilerHotspots = provider.getBool(traceprof.CodeHotspotsEnvVar, true) cfg.profilerEndpoints = provider.getBool("DD_PROFILING_ENDPOINT_COLLECTION_ENABLED", false) cfg.spanAttributeSchemaVersion = provider.getInt("DD_TRACE_SPAN_ATTRIBUTE_SCHEMA", 0) cfg.peerServiceDefaultsEnabled = provider.getBool("DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED", false) @@ -145,5 +146,5 @@ func (c *Config) SetProfilerHotspotsEnabled(enabled bool, origin telemetry.Origi c.mu.Lock() defer c.mu.Unlock() c.profilerHotspots = enabled - telemetry.RegisterAppConfig("DD_PROFILING_CODE_HOTSPOTS_COLLECTION_ENABLED", enabled, origin) + telemetry.RegisterAppConfig(traceprof.CodeHotspotsEnvVar, enabled, origin) }