diff --git a/ddtrace/tracer/remote_config.go b/ddtrace/tracer/remote_config.go index 05ddba8345..224d88d1dc 100644 --- a/ddtrace/tracer/remote_config.go +++ b/ddtrace/tracer/remote_config.go @@ -77,10 +77,45 @@ func (t *tags) toMap() *map[string]interface{} { // onRemoteConfigUpdate is a remote config callaback responsible for processing APM_TRACING RC-product updates. func (t *tracer) onRemoteConfigUpdate(u remoteconfig.ProductUpdate) map[string]state.ApplyStatus { statuses := map[string]state.ApplyStatus{} - if u == nil { + if len(u) == 0 { return statuses } + removed := func() bool { + // Returns true if all the values in the update are nil. + for _, raw := range u { + if raw != nil { + return false + } + } + return true + } var telemConfigs []telemetry.Configuration + if removed() { + // The remote-config client is signaling that the configuration has been deleted for this product. + // We re-apply the startup configuration values. + for path := range u { + log.Debug("Nil payload from RC. Path: %s.", path) + statuses[path] = state.ApplyStatus{State: state.ApplyStateAcknowledged} + } + log.Debug("Resetting configurations") + updated := t.config.traceSampleRate.reset() + if updated { + telemConfigs = append(telemConfigs, t.config.traceSampleRate.toTelemetry()) + } + updated = t.config.headerAsTags.reset() + if updated { + telemConfigs = append(telemConfigs, t.config.headerAsTags.toTelemetry()) + } + updated = t.config.globalTags.reset() + if updated { + telemConfigs = append(telemConfigs, t.config.globalTags.toTelemetry()) + } + if len(telemConfigs) > 0 { + log.Debug("Reporting %d configuration changes to telemetry", len(telemConfigs)) + telemetry.GlobalClient.ConfigChange(telemConfigs) + } + return statuses + } for path, raw := range u { if raw == nil { continue diff --git a/ddtrace/tracer/remote_config_test.go b/ddtrace/tracer/remote_config_test.go index 5fc238ad5c..0af62dfa86 100644 --- a/ddtrace/tracer/remote_config_test.go +++ b/ddtrace/tracer/remote_config_test.go @@ -288,6 +288,56 @@ func TestOnRemoteConfigUpdate(t *testing.T) { telemetryClient.AssertCalled(t, "ConfigChange", []telemetry.Configuration{{Name: "trace_tags", Value: "key0:val0,key1:val1,key2:val2," + runtimeIDTag, Origin: ""}}) }) + t.Run("Deleted config", func(t *testing.T) { + defer globalconfig.ClearHeaderTags() + telemetryClient := new(telemetrytest.MockClient) + defer telemetry.MockGlobalClient(telemetryClient)() + + t.Setenv("DD_TRACE_SAMPLE_RATE", "0.1") + t.Setenv("DD_TRACE_HEADER_TAGS", "X-Test-Header:my-tag-from-env") + t.Setenv("DD_TAGS", "ddtag:from-env") + tracer, _, _, stop := startTestTracer(t, WithService("my-service"), WithEnv("my-env")) + defer stop() + + // Apply RC. Assert configuration is updated to the RC values. + input := remoteconfig.ProductUpdate{ + "path": []byte(`{"lib_config": {"tracing_sampling_rate": 0.2,"tracing_header_tags": [{"header": "X-Test-Header", "tag_name": "my-tag-from-rc"}],"tracing_tags": ["ddtag:from-rc"]}, "service_target": {"service": "my-service", "env": "my-env"}}`), + } + applyStatus := tracer.onRemoteConfigUpdate(input) + require.Equal(t, state.ApplyStateAcknowledged, applyStatus["path"].State) + s := tracer.StartSpan("web.request").(*span) + s.Finish() + require.Equal(t, 0.2, s.Metrics[keyRulesSamplerAppliedRate]) + require.Equal(t, "my-tag-from-rc", globalconfig.HeaderTag("X-Test-Header")) + require.Equal(t, "from-rc", s.Meta["ddtag"]) + + // 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_tags", Value: "ddtag:from-rc," + ext.RuntimeID + ":" + globalconfig.RuntimeID(), Origin: "remote_config"}, + }) + + // Remove RC. Assert configuration is reset to the original values. + input = remoteconfig.ProductUpdate{"path": nil} + applyStatus = tracer.onRemoteConfigUpdate(input) + require.Equal(t, state.ApplyStateAcknowledged, applyStatus["path"].State) + s = tracer.StartSpan("web.request").(*span) + s.Finish() + require.Equal(t, 0.1, s.Metrics[keyRulesSamplerAppliedRate]) + require.Equal(t, "my-tag-from-env", globalconfig.HeaderTag("X-Test-Header")) + require.Equal(t, "from-env", s.Meta["ddtag"]) + + // 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: ""}, + }) + }) + assert.Equal(t, 0, globalconfig.HeaderTagsLen()) }