diff --git a/ddtrace/tracer/dynamic_config.go b/ddtrace/tracer/dynamic_config.go index 919b4f7229..db48be5a73 100644 --- a/ddtrace/tracer/dynamic_config.go +++ b/ddtrace/tracer/dynamic_config.go @@ -19,7 +19,7 @@ type dynamicConfig[T any] struct { current T // holds the current configuration value startup T // holds the startup configuration value cfgName string // holds the name of the configuration, has to be compatible with telemetry.Configuration.Name - cfgOrigin string // holds the origin of the current configuration value (currently only supports remote_config, empty otherwise) + cfgOrigin telemetry.Origin // holds the origin of the current configuration value (currently only supports remote_config, empty otherwise) apply func(T) bool // executes any config-specific operations to propagate the update properly, returns whether the update was applied equal func(x, y T) bool // compares two configuration values, this is used to avoid unnecessary config and telemetry updates } @@ -42,7 +42,7 @@ func (dc *dynamicConfig[T]) get() T { } // update applies a new configuration value -func (dc *dynamicConfig[T]) update(val T, origin string) bool { +func (dc *dynamicConfig[T]) update(val T, origin telemetry.Origin) bool { dc.Lock() defer dc.Unlock() if dc.equal(dc.current, val) { @@ -61,7 +61,8 @@ func (dc *dynamicConfig[T]) reset() bool { return false } dc.current = dc.startup - dc.cfgOrigin = "" + // TODO: set the origin to the startup value's origin + dc.cfgOrigin = telemetry.OriginDefault return dc.apply(dc.startup) } @@ -69,7 +70,7 @@ func (dc *dynamicConfig[T]) reset() bool { // Returns whether the configuration value has been updated or not func (dc *dynamicConfig[T]) handleRC(val *T) bool { if val != nil { - return dc.update(*val, "remote_config") + return dc.update(*val, telemetry.OriginRemoteConfig) } return dc.reset() } diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index d73308f038..30352a9ba0 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -29,6 +29,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "gopkg.in/DataDog/dd-trace-go.v1/internal/namingschema" "gopkg.in/DataDog/dd-trace-go.v1/internal/normalizer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" "gopkg.in/DataDog/dd-trace-go.v1/internal/traceprof" "gopkg.in/DataDog/dd-trace-go.v1/internal/version" @@ -336,7 +337,9 @@ func newConfig(opts ...StartOption) *config { } c.headerAsTags = newDynamicConfig("trace_header_tags", nil, setHeaderTags, equalSlice[string]) if v := os.Getenv("DD_TRACE_HEADER_TAGS"); v != "" { - WithHeaderTags(strings.Split(v, ","))(c) + c.headerAsTags.update(strings.Split(v, ","), telemetry.OriginEnvVar) + // Required to ensure that the startup header tags are set on reset. + c.headerAsTags.startup = c.headerAsTags.current } if v := os.Getenv("DD_TAGS"); v != "" { tags := internal.ParseTagString(v) @@ -344,6 +347,8 @@ func newConfig(opts ...StartOption) *config { for key, val := range tags { WithGlobalTag(key, val)(c) } + // TODO: should we track the origin of these tags individually? + c.globalTags.cfgOrigin = telemetry.OriginEnvVar } if _, ok := os.LookupEnv("AWS_LAMBDA_FUNCTION_NAME"); ok { // AWS_LAMBDA_FUNCTION_NAME being set indicates that we're running in an AWS Lambda environment. @@ -354,6 +359,9 @@ func newConfig(opts ...StartOption) *config { c.runtimeMetrics = internal.BoolEnv("DD_RUNTIME_METRICS_ENABLED", false) c.debug = internal.BoolEnv("DD_TRACE_DEBUG", false) c.enabled = newDynamicConfig("tracing_enabled", internal.BoolEnv("DD_TRACE_ENABLED", true), func(b bool) bool { return true }, equal[bool]) + if _, ok := os.LookupEnv("DD_TRACE_ENABLED"); ok { + c.enabled.cfgOrigin = telemetry.OriginEnvVar + } c.profilerEndpoints = internal.BoolEnv(traceprof.EndpointEnvVar, true) c.profilerHotspots = internal.BoolEnv(traceprof.CodeHotspotsEnvVar, true) c.enableHostnameDetection = internal.BoolEnv("DD_CLIENT_HOSTNAME_ENABLED", true) @@ -509,7 +517,8 @@ func newConfig(opts ...StartOption) *config { } // Re-initialize the globalTags config with the value constructed from the environment and start options // This allows persisting the initial value of globalTags for future resets and updates. - c.initGlobalTags(c.globalTags.get()) + globalTagsOrigin := c.globalTags.cfgOrigin + c.initGlobalTags(c.globalTags.get(), globalTagsOrigin) return c } @@ -898,7 +907,7 @@ func WithPeerServiceMapping(from, to string) StartOption { func WithGlobalTag(k string, v interface{}) StartOption { return func(c *config) { if c.globalTags.get() == nil { - c.initGlobalTags(map[string]interface{}{}) + c.initGlobalTags(map[string]interface{}{}, telemetry.OriginDefault) } c.globalTags.Lock() defer c.globalTags.Unlock() @@ -907,13 +916,14 @@ func WithGlobalTag(k string, v interface{}) StartOption { } // initGlobalTags initializes the globalTags config with the provided init value -func (c *config) initGlobalTags(init map[string]interface{}) { +func (c *config) initGlobalTags(init map[string]interface{}, origin telemetry.Origin) { apply := func(map[string]interface{}) bool { // always set the runtime ID on updates c.globalTags.current[ext.RuntimeID] = globalconfig.RuntimeID() return true } - c.globalTags = newDynamicConfig[map[string]interface{}]("trace_tags", init, apply, equalMap[string]) + c.globalTags = newDynamicConfig("trace_tags", init, apply, equalMap[string]) + c.globalTags.cfgOrigin = origin } // WithSampler sets the given sampler to be used with the tracer. By default diff --git a/ddtrace/tracer/option_test.go b/ddtrace/tracer/option_test.go index 71eaac7587..55d7a65487 100644 --- a/ddtrace/tracer/option_test.go +++ b/ddtrace/tracer/option_test.go @@ -29,6 +29,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "gopkg.in/DataDog/dd-trace-go.v1/internal/namingschema" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" "gopkg.in/DataDog/dd-trace-go.v1/internal/traceprof" "gopkg.in/DataDog/dd-trace-go.v1/internal/version" @@ -548,6 +549,7 @@ func TestTracerOptionsDefaults(t *testing.T) { defer tracer.Stop() c := tracer.config assert.True(t, c.enabled.current) + assert.Equal(t, c.enabled.cfgOrigin, telemetry.OriginDefault) }) t.Run("override", func(t *testing.T) { @@ -556,6 +558,7 @@ func TestTracerOptionsDefaults(t *testing.T) { defer tracer.Stop() c := tracer.config assert.False(t, c.enabled.current) + assert.Equal(t, c.enabled.cfgOrigin, telemetry.OriginEnvVar) }) }) diff --git a/ddtrace/tracer/remote_config_test.go b/ddtrace/tracer/remote_config_test.go index e779daa856..3e5e4d881e 100644 --- a/ddtrace/tracer/remote_config_test.go +++ b/ddtrace/tracer/remote_config_test.go @@ -30,6 +30,8 @@ func TestOnRemoteConfigUpdate(t *testing.T) { tracer, _, _, stop := startTestTracer(t, WithService("my-service"), WithEnv("my-env")) defer stop() + require.Equal(t, telemetry.OriginDefault, tracer.config.traceSampleRate.cfgOrigin) + // Apply RC. Assert _dd.rule_psr shows the RC sampling rate (0.5) is applied input := remoteconfig.ProductUpdate{ "path": []byte( @@ -47,7 +49,7 @@ func TestOnRemoteConfigUpdate(t *testing.T) { telemetryClient.AssertCalled( t, "ConfigChange", - []telemetry.Configuration{{Name: "trace_sample_rate", Value: 0.5, Origin: "remote_config"}}, + []telemetry.Configuration{{Name: "trace_sample_rate", Value: 0.5, Origin: telemetry.OriginRemoteConfig}}, ) //Apply RC with sampling rules. Assert _dd.rule_psr shows the corresponding rule matched rate. @@ -100,6 +102,8 @@ func TestOnRemoteConfigUpdate(t *testing.T) { tracer, _, _, stop := startTestTracer(t, WithService("my-service"), WithEnv("my-env")) defer stop() + require.Equal(t, telemetry.OriginEnvVar, tracer.config.traceSampleRate.cfgOrigin) + // Apply RC. Assert _dd.rule_psr shows the RC sampling rate (0.2) is applied input := remoteconfig.ProductUpdate{ "path": []byte( @@ -117,7 +121,7 @@ func TestOnRemoteConfigUpdate(t *testing.T) { telemetryClient.AssertCalled( t, "ConfigChange", - []telemetry.Configuration{{Name: "trace_sample_rate", Value: 0.2, Origin: "remote_config"}}, + []telemetry.Configuration{{Name: "trace_sample_rate", Value: 0.2, Origin: telemetry.OriginRemoteConfig}}, ) // Unset RC. Assert _dd.rule_psr shows the previous sampling rate (0.1) is applied @@ -135,7 +139,7 @@ func TestOnRemoteConfigUpdate(t *testing.T) { telemetryClient.AssertCalled( t, "ConfigChange", - []telemetry.Configuration{{Name: "trace_sample_rate", Value: 0.1, Origin: ""}}, + []telemetry.Configuration{{Name: "trace_sample_rate", Value: 0.1, Origin: telemetry.OriginDefault}}, ) }) @@ -152,6 +156,8 @@ func TestOnRemoteConfigUpdate(t *testing.T) { tracer, _, _, stop := startTestTracer(t, WithService("my-service"), WithEnv("my-env")) defer stop() + require.Equal(t, telemetry.OriginDefault, tracer.config.traceSampleRate.cfgOrigin) + s := tracer.StartSpan("web.request").(*span) s.Finish() require.Equal(t, 0.1, s.Metrics[keyRulesSamplerAppliedRate]) @@ -188,11 +194,11 @@ func TestOnRemoteConfigUpdate(t *testing.T) { telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 1) telemetryClient.AssertCalled(t, "ConfigChange", []telemetry.Configuration{ - {Name: "trace_sample_rate", Value: 0.5, Origin: "remote_config"}, + {Name: "trace_sample_rate", Value: 0.5, Origin: telemetry.OriginRemoteConfig}, { Name: "trace_sample_rules", Value: `[{"service":"my-service","name":"web.request","resource":"abc","sample_rate":1,"provenance":"customer"}]`, - Origin: "remote_config", + Origin: telemetry.OriginRemoteConfig, }}) }) @@ -265,16 +271,16 @@ func TestOnRemoteConfigUpdate(t *testing.T) { } telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 2) telemetryClient.AssertCalled(t, "ConfigChange", []telemetry.Configuration{ - {Name: "trace_sample_rate", Value: 0.5, Origin: "remote_config"}, + {Name: "trace_sample_rate", Value: 0.5, Origin: telemetry.OriginRemoteConfig}, {Name: "trace_sample_rules", - Value: `[{"service":"my-service","name":"web.request","resource":"abc","sample_rate":1,"provenance":"customer"} {"service":"my-service","name":"web.request","resource":"*","sample_rate":0.3,"provenance":"dynamic"}]`, Origin: "remote_config"}, + Value: `[{"service":"my-service","name":"web.request","resource":"abc","sample_rate":1,"provenance":"customer"} {"service":"my-service","name":"web.request","resource":"*","sample_rate":0.3,"provenance":"dynamic"}]`, Origin: telemetry.OriginRemoteConfig}, }) telemetryClient.AssertCalled(t, "ConfigChange", []telemetry.Configuration{ - {Name: "trace_sample_rate", Value: nil, Origin: ""}, + {Name: "trace_sample_rate", Value: nil, Origin: telemetry.OriginDefault}, { Name: "trace_sample_rules", Value: `[{"service":"my-service","name":"web.request","resource":"*","sample_rate":0.1,"type":"1"}]`, - Origin: "", + Origin: telemetry.OriginDefault, }, }) }) @@ -315,11 +321,11 @@ func TestOnRemoteConfigUpdate(t *testing.T) { telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 1) telemetryClient.AssertCalled(t, "ConfigChange", []telemetry.Configuration{ - {Name: "trace_sample_rate", Value: 0.5, Origin: "remote_config"}, + {Name: "trace_sample_rate", Value: 0.5, Origin: telemetry.OriginRemoteConfig}, { Name: "trace_sample_rules", Value: `[{"service":"my-service","name":"web.request","resource":"*","sample_rate":1,"tags":{"tag-a":"tv-a??"},"provenance":"customer"}]`, - Origin: "remote_config", + Origin: telemetry.OriginRemoteConfig, }, }) }) @@ -332,6 +338,8 @@ func TestOnRemoteConfigUpdate(t *testing.T) { tracer, _, _, stop := startTestTracer(t, WithService("my-service"), WithEnv("my-env")) defer stop() + require.Equal(t, telemetry.OriginDefault, tracer.config.traceSampleRate.cfgOrigin) + // Apply RC. Assert global config shows the RC header tag is applied input := remoteconfig.ProductUpdate{ "path": []byte( @@ -348,7 +356,7 @@ func TestOnRemoteConfigUpdate(t *testing.T) { t, "ConfigChange", []telemetry.Configuration{ - {Name: "trace_header_tags", Value: "X-Test-Header:my-tag-name", Origin: "remote_config"}, + {Name: "trace_header_tags", Value: "X-Test-Header:my-tag-name", Origin: telemetry.OriginRemoteConfig}, }, ) @@ -365,7 +373,7 @@ func TestOnRemoteConfigUpdate(t *testing.T) { telemetryClient.AssertCalled( t, "ConfigChange", - []telemetry.Configuration{{Name: "trace_header_tags", Value: "", Origin: ""}}, + []telemetry.Configuration{{Name: "trace_header_tags", Value: "", Origin: telemetry.OriginDefault}}, ) }) @@ -373,8 +381,10 @@ func TestOnRemoteConfigUpdate(t *testing.T) { telemetryClient := new(telemetrytest.MockClient) defer telemetry.MockGlobalClient(telemetryClient)() - Start(WithService("my-service"), WithEnv("my-env")) - defer Stop() + tr, _, _, stop := startTestTracer(t, WithService("my-service"), WithEnv("my-env")) + defer stop() + + require.Equal(t, telemetry.OriginDefault, tr.config.traceSampleRate.cfgOrigin) input := remoteconfig.ProductUpdate{ "path": []byte( @@ -382,8 +392,6 @@ func TestOnRemoteConfigUpdate(t *testing.T) { ), } - tr, ok := internal.GetGlobalTracer().(*tracer) - require.Equal(t, true, ok) applyStatus := tr.onRemoteConfigUpdate(input) require.Equal(t, state.ApplyStateAcknowledged, applyStatus["path"].State) require.Equal(t, false, tr.config.enabled.current) @@ -451,7 +459,7 @@ func TestOnRemoteConfigUpdate(t *testing.T) { t, "ConfigChange", []telemetry.Configuration{ - {Name: "trace_header_tags", Value: "X-Test-Header:my-tag-name-from-rc", Origin: "remote_config"}, + {Name: "trace_header_tags", Value: "X-Test-Header:my-tag-name-from-rc", Origin: telemetry.OriginRemoteConfig}, }, ) @@ -470,7 +478,7 @@ func TestOnRemoteConfigUpdate(t *testing.T) { t, "ConfigChange", []telemetry.Configuration{ - {Name: "trace_header_tags", Value: "X-Test-Header:my-tag-name-from-env", Origin: ""}, + {Name: "trace_header_tags", Value: "X-Test-Header:my-tag-name-from-env", Origin: telemetry.OriginDefault}, }, ) }, @@ -508,7 +516,7 @@ func TestOnRemoteConfigUpdate(t *testing.T) { t, "ConfigChange", []telemetry.Configuration{ - {Name: "trace_header_tags", Value: "X-Test-Header:my-tag-name-from-rc", Origin: "remote_config"}, + {Name: "trace_header_tags", Value: "X-Test-Header:my-tag-name-from-rc", Origin: telemetry.OriginRemoteConfig}, }, ) @@ -527,7 +535,7 @@ func TestOnRemoteConfigUpdate(t *testing.T) { t, "ConfigChange", []telemetry.Configuration{ - {Name: "trace_header_tags", Value: "X-Test-Header:my-tag-name-in-code", Origin: ""}, + {Name: "trace_header_tags", Value: "X-Test-Header:my-tag-name-in-code", Origin: telemetry.OriginDefault}, }, ) }, @@ -540,6 +548,8 @@ func TestOnRemoteConfigUpdate(t *testing.T) { tracer, _, _, stop := startTestTracer(t, WithService("my-service"), WithEnv("my-env")) defer stop() + require.Equal(t, telemetry.OriginDefault, tracer.config.traceSampleRate.cfgOrigin) + input := remoteconfig.ProductUpdate{ "path": []byte( `{"lib_config": {"tracing_sampling_rate": "string value", "service_target": {"service": "my-service", "env": "my-env"}}}`, @@ -560,6 +570,8 @@ func TestOnRemoteConfigUpdate(t *testing.T) { tracer, _, _, stop := startTestTracer(t, WithServiceName("my-service"), WithEnv("my-env")) defer stop() + require.Equal(t, telemetry.OriginDefault, tracer.config.traceSampleRate.cfgOrigin) + input := remoteconfig.ProductUpdate{ "path": []byte(`{"lib_config": {}, "service_target": {"service": "other-service", "env": "my-env"}}`), } @@ -578,6 +590,8 @@ func TestOnRemoteConfigUpdate(t *testing.T) { tracer, _, _, stop := startTestTracer(t, WithServiceName("my-service"), WithEnv("my-env")) defer stop() + require.Equal(t, telemetry.OriginDefault, tracer.config.traceSampleRate.cfgOrigin) + input := remoteconfig.ProductUpdate{ "path": []byte(`{"lib_config": {}, "service_target": {"service": "my-service", "env": "other-env"}}`), } @@ -602,6 +616,9 @@ func TestOnRemoteConfigUpdate(t *testing.T) { ) defer stop() + require.Equal(t, telemetry.OriginDefault, tracer.config.traceSampleRate.cfgOrigin) + require.Equal(t, telemetry.OriginEnvVar, tracer.config.globalTags.cfgOrigin) + // Apply RC. Assert global tags have the RC tags key3:val3,key4:val4 applied + runtime ID input := remoteconfig.ProductUpdate{ "path": []byte( @@ -626,7 +643,7 @@ func TestOnRemoteConfigUpdate(t *testing.T) { t, "ConfigChange", []telemetry.Configuration{ - {Name: "trace_tags", Value: "key3:val3,key4:val4," + runtimeIDTag, Origin: "remote_config"}, + {Name: "trace_tags", Value: "key3:val3,key4:val4," + runtimeIDTag, Origin: telemetry.OriginRemoteConfig}, }, ) @@ -651,7 +668,7 @@ func TestOnRemoteConfigUpdate(t *testing.T) { t, "ConfigChange", []telemetry.Configuration{ - {Name: "trace_tags", Value: "key0:val0,key1:val1,key2:val2," + runtimeIDTag, Origin: ""}, + {Name: "trace_tags", Value: "key0:val0,key1:val1,key2:val2," + runtimeIDTag, Origin: telemetry.OriginDefault}, }, ) }) @@ -667,6 +684,8 @@ func TestOnRemoteConfigUpdate(t *testing.T) { tracer, _, _, stop := startTestTracer(t, WithService("my-service"), WithEnv("my-env")) defer stop() + require.Equal(t, telemetry.OriginEnvVar, tracer.config.traceSampleRate.cfgOrigin) + // Apply RC. Assert configuration is updated to the RC values. input := remoteconfig.ProductUpdate{ "path": []byte( @@ -684,12 +703,12 @@ func TestOnRemoteConfigUpdate(t *testing.T) { // Telemetry telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 1) telemetryClient.AssertCalled(t, "ConfigChange", []telemetry.Configuration{ - {Name: "trace_sample_rate", Value: 0.2, Origin: "remote_config"}, - {Name: "trace_header_tags", Value: "X-Test-Header:my-tag-from-rc", Origin: "remote_config"}, + {Name: "trace_sample_rate", Value: 0.2, Origin: telemetry.OriginRemoteConfig}, + {Name: "trace_header_tags", Value: "X-Test-Header:my-tag-from-rc", Origin: telemetry.OriginRemoteConfig}, { Name: "trace_tags", Value: "ddtag:from-rc," + ext.RuntimeID + ":" + globalconfig.RuntimeID(), - Origin: "remote_config", + Origin: telemetry.OriginRemoteConfig, }, }) @@ -706,9 +725,9 @@ func TestOnRemoteConfigUpdate(t *testing.T) { // Telemetry telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 2) telemetryClient.AssertCalled(t, "ConfigChange", []telemetry.Configuration{ - {Name: "trace_sample_rate", Value: 0.1, Origin: ""}, - {Name: "trace_header_tags", Value: "X-Test-Header:my-tag-from-env", Origin: ""}, - {Name: "trace_tags", Value: "ddtag:from-env," + ext.RuntimeID + ":" + globalconfig.RuntimeID(), Origin: ""}, + {Name: "trace_sample_rate", Value: 0.1, Origin: telemetry.OriginDefault}, + {Name: "trace_header_tags", Value: "X-Test-Header:my-tag-from-env", Origin: telemetry.OriginDefault}, + {Name: "trace_tags", Value: "ddtag:from-env," + ext.RuntimeID + ":" + globalconfig.RuntimeID(), Origin: telemetry.OriginDefault}, }) }) diff --git a/ddtrace/tracer/telemetry.go b/ddtrace/tracer/telemetry.go index 3c9b635fb7..1f112ff830 100644 --- a/ddtrace/tracer/telemetry.go +++ b/ddtrace/tracer/telemetry.go @@ -54,7 +54,7 @@ func startTelemetry(c *config) { {Name: "trace_span_attribute_schema", Value: c.spanAttributeSchemaVersion}, {Name: "trace_peer_service_defaults_enabled", Value: c.peerServiceDefaultsEnabled}, {Name: "orchestrion_enabled", Value: c.orchestrionCfg.Enabled}, - {Name: "trace_enabled", Value: c.enabled.current}, + {Name: "trace_enabled", Value: c.enabled.current, Origin: c.enabled.cfgOrigin}, c.traceSampleRate.toTelemetry(), c.headerAsTags.toTelemetry(), c.globalTags.toTelemetry(), diff --git a/ddtrace/tracer/telemetry_test.go b/ddtrace/tracer/telemetry_test.go index f64c5e4f80..09e10cf638 100644 --- a/ddtrace/tracer/telemetry_test.go +++ b/ddtrace/tracer/telemetry_test.go @@ -47,6 +47,7 @@ func TestTelemetryEnabled(t *testing.T) { telemetry.Check(t, telemetryClient.Configuration, "env", "test-env") telemetry.Check(t, telemetryClient.Configuration, "runtime_metrics_enabled", true) telemetry.Check(t, telemetryClient.Configuration, "stats_computation_enabled", false) + telemetry.Check(t, telemetryClient.Configuration, "trace_enabled", true) telemetry.Check(t, telemetryClient.Configuration, "trace_span_attribute_schema", 0) telemetry.Check(t, telemetryClient.Configuration, "trace_peer_service_defaults_enabled", true) telemetry.Check(t, telemetryClient.Configuration, "trace_peer_service_mapping", "key:val") diff --git a/ddtrace/tracer/tracer.go b/ddtrace/tracer/tracer.go index c40a8293bf..93e07ec407 100644 --- a/ddtrace/tracer/tracer.go +++ b/ddtrace/tracer/tracer.go @@ -8,6 +8,7 @@ package tracer import ( gocontext "context" "encoding/binary" + "math" "os" "runtime/pprof" rt "runtime/trace" @@ -252,6 +253,13 @@ func newUnstartedTracer(opts ...StartOption) *tracer { globalRate := globalSampleRate() rulesSampler := newRulesSampler(c.traceRules, c.spanRules, globalRate) c.traceSampleRate = newDynamicConfig("trace_sample_rate", globalRate, rulesSampler.traces.setGlobalSampleRate, equal[float64]) + // If globalSampleRate returns NaN, it means the environment variable was not set or valid. + // We could always set the origin to "env_var" inconditionally, but then it wouldn't be possible + // to distinguish between the case where the environment variable was not set and the case where + // it default to NaN. + if !math.IsNaN(globalRate) { + c.traceSampleRate.cfgOrigin = telemetry.OriginEnvVar + } c.traceSampleRules = newDynamicConfig("trace_sample_rules", c.traceRules, rulesSampler.traces.setTraceSampleRules, EqualsFalseNegative) var dataStreamsProcessor *datastreams.Processor diff --git a/internal/appsec/config/config.go b/internal/appsec/config/config.go index d7cf538881..33ca45e56b 100644 --- a/internal/appsec/config/config.go +++ b/internal/appsec/config/config.go @@ -37,7 +37,7 @@ func registerSCAAppConfigTelemetry(client telemetry.Client) { return } if defined { - client.RegisterAppConfig(EnvSCAEnabled, val, "env_var") + client.RegisterAppConfig(EnvSCAEnabled, val, telemetry.OriginEnvVar) } } diff --git a/internal/appsec/config/config_test.go b/internal/appsec/config/config_test.go index 19dec18669..77d29e36df 100644 --- a/internal/appsec/config/config_test.go +++ b/internal/appsec/config/config_test.go @@ -8,6 +8,7 @@ package config import ( "testing" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/telemetrytest" ) @@ -49,12 +50,12 @@ func TestSCAEnabled(t *testing.T) { } telemetryClient := new(telemetrytest.MockClient) - telemetryClient.On("RegisterAppConfig", EnvSCAEnabled, tc.expectedValue, "env_var").Return() + telemetryClient.On("RegisterAppConfig", EnvSCAEnabled, tc.expectedValue, telemetry.OriginEnvVar).Return() registerSCAAppConfigTelemetry(telemetryClient) if tc.telemetryExpected { - telemetryClient.AssertCalled(t, "RegisterAppConfig", EnvSCAEnabled, tc.expectedValue, "env_var") + telemetryClient.AssertCalled(t, "RegisterAppConfig", EnvSCAEnabled, tc.expectedValue, telemetry.OriginEnvVar) telemetryClient.AssertNumberOfCalls(t, "RegisterAppConfig", 1) } else { telemetryClient.AssertNumberOfCalls(t, "RegisterAppConfig", 0) diff --git a/internal/appsec/telemetry.go b/internal/appsec/telemetry.go index aeac5827d2..2b07117bd8 100644 --- a/internal/appsec/telemetry.go +++ b/internal/appsec/telemetry.go @@ -26,10 +26,10 @@ var ( wafSupported, _ = waf.SupportsTarget() wafHealthy, _ = waf.Health() staticConfigs = []telemetry.Configuration{ - {Name: "goos", Value: runtime.GOOS, Origin: "code"}, - {Name: "goarch", Value: runtime.GOARCH, Origin: "code"}, - {Name: "waf_supports_target", Value: wafSupported, Origin: "code"}, - {Name: "waf_healthy", Value: wafHealthy, Origin: "code"}, + {Name: "goos", Value: runtime.GOOS, Origin: telemetry.OriginCode}, + {Name: "goarch", Value: runtime.GOARCH, Origin: telemetry.OriginCode}, + {Name: "waf_supports_target", Value: wafSupported, Origin: telemetry.OriginCode}, + {Name: "waf_healthy", Value: wafHealthy, Origin: telemetry.OriginCode}, } ) @@ -62,7 +62,7 @@ func (a *appsecTelemetry) addEnvConfig(name string, value any) { if a == nil { return } - a.configs = append(a.configs, telemetry.Configuration{Name: name, Value: value, Origin: "env_var"}) + a.configs = append(a.configs, telemetry.Configuration{Name: name, Value: value, Origin: telemetry.OriginEnvVar}) } // setEnabled makes AppSec as having effectively been enabled. diff --git a/internal/telemetry/client.go b/internal/telemetry/client.go index 1613c5f911..1b01ced31f 100644 --- a/internal/telemetry/client.go +++ b/internal/telemetry/client.go @@ -30,7 +30,7 @@ import ( // Client buffers and sends telemetry messages to Datadog (possibly through an // agent). type Client interface { - RegisterAppConfig(name string, val interface{}, origin string) + RegisterAppConfig(name string, val interface{}, origin Origin) ProductChange(namespace Namespace, enabled bool, configuration []Configuration) ConfigChange(configuration []Configuration) Record(namespace Namespace, metric MetricKind, name string, value float64, tags []string, common bool) @@ -158,7 +158,7 @@ func log(msg string, args ...interface{}) { // RegisterAppConfig allows to register a globally-defined application configuration. // This configuration will be sent when the telemetry client is started and over related configuration updates. -func (c *client) RegisterAppConfig(name string, value interface{}, origin string) { +func (c *client) RegisterAppConfig(name string, value interface{}, origin Origin) { c.globalAppConfig = append(c.globalAppConfig, Configuration{ Name: name, Value: value, diff --git a/internal/telemetry/message.go b/internal/telemetry/message.go index 3348187596..27a72768d9 100644 --- a/internal/telemetry/message.go +++ b/internal/telemetry/message.go @@ -5,7 +5,11 @@ package telemetry -import "net/http" +import ( + "bytes" + "fmt" + "net/http" +) // Request captures all necessary information for a telemetry event submission type Request struct { @@ -132,13 +136,48 @@ type ConfigurationChange struct { RemoteConfig *RemoteConfig `json:"remote_config,omitempty"` } +type Origin int + +const ( + OriginDefault Origin = iota + OriginCode + OriginDDConfig + OriginEnvVar + OriginRemoteConfig +) + +func (o Origin) String() string { + switch o { + case OriginDefault: + return "default" + case OriginCode: + return "code" + case OriginDDConfig: + return "dd_config" + case OriginEnvVar: + return "env_var" + case OriginRemoteConfig: + return "remote_config" + default: + return fmt.Sprintf("unknown origin %d", o) + } +} + +func (o Origin) MarshalJSON() ([]byte, error) { + var b bytes.Buffer + b.WriteString(`"`) + b.WriteString(o.String()) + b.WriteString(`"`) + return b.Bytes(), nil +} + // Configuration is a library-specific configuration value // that should be initialized through StringConfig, IntConfig, FloatConfig, or BoolConfig type Configuration struct { Name string `json:"name"` Value interface{} `json:"value"` - // origin is the source of the config. It is one of {env_var, code, dd_config, remote_config} - Origin string `json:"origin"` + // origin is the source of the config. It is one of {default, env_var, code, dd_config, remote_config}. + Origin Origin `json:"origin"` Error Error `json:"error"` IsOverriden bool `json:"is_overridden"` } diff --git a/internal/telemetry/telemetry_test.go b/internal/telemetry/telemetry_test.go index e7c917100b..2666a91c44 100644 --- a/internal/telemetry/telemetry_test.go +++ b/internal/telemetry/telemetry_test.go @@ -142,27 +142,27 @@ func TestProductChange(t *testing.T) { // Test that globally registered app config is sent in telemetry requests including the configuration state. func TestRegisterAppConfig(t *testing.T) { client := new(client) - client.RegisterAppConfig("key1", "val1", "origin1") + client.RegisterAppConfig("key1", "val1", OriginDefault) // Test that globally registered app config is sent in app-started payloads - client.start([]Configuration{{Name: "key2", Value: "val2", Origin: "origin2"}}, NamespaceTracers, false) + client.start([]Configuration{{Name: "key2", Value: "val2", Origin: OriginDDConfig}}, NamespaceTracers, false) req := client.requests[0].Body require.Equal(t, RequestTypeAppStarted, req.RequestType) appStarted := req.Payload.(*AppStarted) cfg := appStarted.Configuration require.Len(t, cfg, 2) - require.Contains(t, cfg, Configuration{Name: "key1", Value: "val1", Origin: "origin1"}) - require.Contains(t, cfg, Configuration{Name: "key2", Value: "val2", Origin: "origin2"}) + require.Contains(t, cfg, Configuration{Name: "key1", Value: "val1", Origin: OriginDefault}) + require.Contains(t, cfg, Configuration{Name: "key2", Value: "val2", Origin: OriginDDConfig}) // Test that globally registered app config is sent in app-client-configuration-change payloads - client.ProductChange(NamespaceTracers, true, []Configuration{{Name: "key3", Value: "val3", Origin: "origin3"}}) + client.ProductChange(NamespaceTracers, true, []Configuration{{Name: "key3", Value: "val3", Origin: OriginCode}}) req = client.requests[2].Body require.Equal(t, RequestTypeAppClientConfigurationChange, req.RequestType) appConfigChange := req.Payload.(*ConfigurationChange) cfg = appConfigChange.Configuration require.Len(t, cfg, 2) - require.Contains(t, cfg, Configuration{Name: "key1", Value: "val1", Origin: "origin1"}) - require.Contains(t, cfg, Configuration{Name: "key3", Value: "val3", Origin: "origin3"}) + require.Contains(t, cfg, Configuration{Name: "key1", Value: "val1", Origin: OriginDefault}) + require.Contains(t, cfg, Configuration{Name: "key3", Value: "val3", Origin: OriginCode}) } diff --git a/internal/telemetry/telemetrytest/telemetrytest.go b/internal/telemetry/telemetrytest/telemetrytest.go index 9a0df1b78c..0c8b22bd32 100644 --- a/internal/telemetry/telemetrytest/telemetrytest.go +++ b/internal/telemetry/telemetrytest/telemetrytest.go @@ -27,7 +27,7 @@ type MockClient struct { Metrics map[telemetry.Namespace]map[string]float64 } -func (c *MockClient) RegisterAppConfig(name string, val interface{}, origin string) { +func (c *MockClient) RegisterAppConfig(name string, val interface{}, origin telemetry.Origin) { _ = c.Called(name, val, origin) }