From c043b5262b44691471e8bcf0d04542e14857fce3 Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Fri, 7 Nov 2025 18:06:54 +0000 Subject: [PATCH] Add fix for not re-reporting telemetry when there are "empty" dynamic config changes --- .../Configuration/MutableSettings.cs | 4 +- .../Configuration/SettingsManager.cs | 85 ++++++++++-- .../Configuration/TracerSettings.cs | 129 +++++++++--------- .../Transport/RemoteConfigurationApi.cs | 2 + .../EmptyDatadogTracer.cs | 3 +- .../Agent/AgentWriterTests.cs | 3 +- .../TracerSettingsSettingManagerTests.cs | 76 +++++++++++ .../Util/StubDatadogTracer.cs | 3 +- 8 files changed, 222 insertions(+), 83 deletions(-) diff --git a/tracer/src/Datadog.Trace/Configuration/MutableSettings.cs b/tracer/src/Datadog.Trace/Configuration/MutableSettings.cs index ca354b771dcd..86b5e076c796 100644 --- a/tracer/src/Datadog.Trace/Configuration/MutableSettings.cs +++ b/tracer/src/Datadog.Trace/Configuration/MutableSettings.cs @@ -1074,10 +1074,10 @@ public static MutableSettings CreateInitialMutableSettings( /// by excluding all the default sources. Effectively gives all the settings their default /// values. Should only be used with the manual instrumentation source /// - public static MutableSettings CreateWithoutDefaultSources(TracerSettings tracerSettings) + public static MutableSettings CreateWithoutDefaultSources(TracerSettings tracerSettings, ConfigurationTelemetry telemetry) => CreateInitialMutableSettings( NullConfigurationSource.Instance, - new ConfigurationTelemetry(), + telemetry, new OverrideErrorLog(), tracerSettings); diff --git a/tracer/src/Datadog.Trace/Configuration/SettingsManager.cs b/tracer/src/Datadog.Trace/Configuration/SettingsManager.cs index 6e8dcc6a8b26..74bcd3e5ece4 100644 --- a/tracer/src/Datadog.Trace/Configuration/SettingsManager.cs +++ b/tracer/src/Datadog.Trace/Configuration/SettingsManager.cs @@ -7,41 +7,56 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Threading; using Datadog.Trace.Configuration.ConfigurationSources; using Datadog.Trace.Configuration.ConfigurationSources.Telemetry; using Datadog.Trace.Configuration.Telemetry; -using Datadog.Trace.Logging; namespace Datadog.Trace.Configuration; public partial record TracerSettings { - internal class SettingsManager( - TracerSettings tracerSettings, - MutableSettings initialMutable, - ExporterSettings initialExporter) + internal class SettingsManager { - private readonly TracerSettings _tracerSettings = tracerSettings; + private readonly TracerSettings _tracerSettings; + private readonly ConfigurationTelemetry _initialTelemetry; private readonly List _subscribers = []; private IConfigurationSource _dynamicConfigurationSource = NullConfigurationSource.Instance; private ManualInstrumentationConfigurationSourceBase _manualConfigurationSource = new ManualInstrumentationConfigurationSource(new Dictionary(), useDefaultSources: true); + // We delay creating these, as we likely won't need them + private ConfigurationTelemetry? _noDefaultSettingsTelemetry; + private MutableSettings? _noDefaultSourcesSettings; + private SettingChanges? _latest; + public SettingsManager(IConfigurationSource source, TracerSettings tracerSettings, IConfigurationTelemetry telemetry, OverrideErrorLog errorLog) + { + // We record the telemetry for the initial settings in a dedicated ConfigurationTelemetry, + // because we need to be able to reapply this configuration on dynamic config updates + // We don't re-record error logs, so we just use the built-in for that + var initialTelemetry = new ConfigurationTelemetry(); + InitialMutableSettings = MutableSettings.CreateInitialMutableSettings(source, initialTelemetry, errorLog, tracerSettings); + InitialExporterSettings = new ExporterSettings(source, initialTelemetry); + _tracerSettings = tracerSettings; + _initialTelemetry = initialTelemetry; + initialTelemetry.CopyTo(telemetry); + } + /// /// Gets the initial . On app startup, these will be the values read from /// static sources. To subscribe to updates to these settings, from code or remote config, call . /// - public MutableSettings InitialMutableSettings { get; } = initialMutable; + public MutableSettings InitialMutableSettings { get; } /// /// Gets the initial . On app startup, these will be the values read from /// static sources. To subscribe to updates to these settings, from code or remote config, call . /// - public ExporterSettings InitialExporterSettings { get; } = initialExporter; + public ExporterSettings InitialExporterSettings { get; } /// /// Subscribe to changes in and/or . @@ -133,21 +148,45 @@ private bool UpdateSettings( ManualInstrumentationConfigurationSourceBase manualSource, IConfigurationTelemetry telemetry) { - var initialSettings = manualSource.UseDefaultSources - ? InitialMutableSettings - : MutableSettings.CreateWithoutDefaultSources(_tracerSettings); + // Set the correct default telemetry and initial settings depending + // on whether the manual config source explicitly disables using the default sources + ConfigurationTelemetry defaultTelemetry; + MutableSettings initialSettings; + if (manualSource.UseDefaultSources) + { + defaultTelemetry = _initialTelemetry; + initialSettings = InitialMutableSettings; + } + else + { + // We only need to initialize the "no default sources" settings once + // and we don't want to initialize them if we don't _need_ to + // so lazy-initialize here + if (_noDefaultSourcesSettings is null || _noDefaultSettingsTelemetry is null) + { + InitialiseNoDefaultSourceSettings(); + } + + defaultTelemetry = _noDefaultSettingsTelemetry; + initialSettings = _noDefaultSourcesSettings; + } var current = _latest; var currentMutable = current?.UpdatedMutable ?? current?.PreviousMutable ?? InitialMutableSettings; var currentExporter = current?.UpdatedExporter ?? current?.PreviousExporter ?? InitialExporterSettings; + // we create a temporary ConfigurationTelemetry object to hold the changes to settings + // if nothing is actually written, and nothing changes compared to the default, then we + // don't need to report it to the provided telemetry + var tempTelemetry = new ConfigurationTelemetry(); + var overrideErrorLog = new OverrideErrorLog(); var newMutableSettings = MutableSettings.CreateUpdatedMutableSettings( dynamicConfigSource, manualSource, initialSettings, _tracerSettings, - telemetry, + tempTelemetry, overrideErrorLog); // TODO: We'll later report these // The only exporter setting we currently _allow_ to change is the AgentUri, but if that does change, @@ -159,7 +198,7 @@ private bool UpdateSettings( var newRawExporterSettings = ExporterSettings.Raw.CreateUpdatedFromManualConfig( currentExporter.RawSettings, manualSource, - telemetry, + tempTelemetry, manualSource.UseDefaultSources); var isSameMutableSettings = currentMutable.Equals(newMutableSettings); @@ -171,6 +210,11 @@ private bool UpdateSettings( return null; } + // we have changes, so we need to report them + // First record the "default"/fallback values, then record the "new" values + defaultTelemetry.CopyTo(telemetry); + tempTelemetry.CopyTo(telemetry); + Log.Information("Notifying consumers of new settings"); var updatedMutableSettings = isSameMutableSettings ? null : newMutableSettings; var updatedExporterSettings = isSameExporterSettings ? null : new ExporterSettings(newRawExporterSettings, telemetry); @@ -178,6 +222,21 @@ private bool UpdateSettings( return new SettingChanges(updatedMutableSettings, updatedExporterSettings, currentMutable, currentExporter); } + [MemberNotNull(nameof(_noDefaultSettingsTelemetry))] + [MemberNotNull(nameof(_noDefaultSourcesSettings))] + private void InitialiseNoDefaultSourceSettings() + { + if (_noDefaultSourcesSettings is not null + && _noDefaultSettingsTelemetry is not null) + { + return; + } + + var telemetry = new ConfigurationTelemetry(); + _noDefaultSettingsTelemetry = telemetry; + _noDefaultSourcesSettings = MutableSettings.CreateWithoutDefaultSources(_tracerSettings, telemetry); + } + private void NotifySubscribers(SettingChanges settings) { _latest = settings; diff --git a/tracer/src/Datadog.Trace/Configuration/TracerSettings.cs b/tracer/src/Datadog.Trace/Configuration/TracerSettings.cs index 6fee833ca806..c16f8e1b1878 100644 --- a/tracer/src/Datadog.Trace/Configuration/TracerSettings.cs +++ b/tracer/src/Datadog.Trace/Configuration/TracerSettings.cs @@ -125,8 +125,6 @@ internal TracerSettings(IConfigurationSource? source, IConfigurationTelemetry te .AsBoolResult() .OverrideWith(in otelActivityListenerEnabled, ErrorLog, defaultValue: false); - var exporter = new ExporterSettings(source, _telemetry); - PeerServiceTagsEnabled = config .WithKeys(ConfigurationKeys.PeerServiceDefaultsEnabled) .AsBool(defaultValue: false); @@ -335,66 +333,6 @@ not null when string.Equals(value, "otlp", StringComparison.OrdinalIgnoreCase) = OpenTelemetryLogsEnabled = OpenTelemetryLogsEnabled && OtelLogsExporterEnabled; - DataPipelineEnabled = config - .WithKeys(ConfigurationKeys.TraceDataPipelineEnabled) - .AsBool(defaultValue: EnvironmentHelpers.IsUsingAzureAppServicesSiteExtension() && !EnvironmentHelpers.IsAzureFunctions()); - - if (DataPipelineEnabled) - { - // Due to missing quantization and obfuscation in native side, we can't enable the native trace exporter - // as it may lead to different stats results than the managed one. - if (StatsComputationEnabled) - { - DataPipelineEnabled = false; - Log.Warning( - $"{ConfigurationKeys.TraceDataPipelineEnabled} is enabled, but {ConfigurationKeys.StatsComputationEnabled} is enabled. Disabling data pipeline."); - _telemetry.Record(ConfigurationKeys.TraceDataPipelineEnabled, false, ConfigurationOrigins.Calculated); - } - - // Windows supports UnixDomainSocket https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/ - // but tokio hasn't added support for it yet https://github.com/tokio-rs/tokio/issues/2201 - // There's an issue here, in that technically a user can initially be configured to send over TCP/named pipes, - // and so we allow and enable the datapipeline. Later, they could configure the app in code to send over UDS. - // This is a problem, as we currently don't support toggling the data pipeline at runtime, so we explicitly block - // this scenario in the public API. - if (exporter.TracesTransport == TracesTransportType.UnixDomainSocket && FrameworkDescription.Instance.IsWindows()) - { - DataPipelineEnabled = false; - Log.Warning( - $"{ConfigurationKeys.TraceDataPipelineEnabled} is enabled, but TracesTransport is set to UnixDomainSocket which is not supported on Windows. Disabling data pipeline."); - _telemetry.Record(ConfigurationKeys.TraceDataPipelineEnabled, false, ConfigurationOrigins.Calculated); - } - - if (!isLibDatadogAvailable.IsAvailable) - { - DataPipelineEnabled = false; - if (isLibDatadogAvailable.Exception is not null) - { - Log.Warning( - isLibDatadogAvailable.Exception, - $"{ConfigurationKeys.TraceDataPipelineEnabled} is enabled, but libdatadog is not available. Disabling data pipeline."); - } - else - { - Log.Warning( - $"{ConfigurationKeys.TraceDataPipelineEnabled} is enabled, but libdatadog is not available. Disabling data pipeline."); - } - - _telemetry.Record(ConfigurationKeys.TraceDataPipelineEnabled, false, ConfigurationOrigins.Calculated); - } - - // SSI already utilizes libdatadog. To prevent unexpected behavior, - // we proactively disable the data pipeline when SSI is enabled. Theoretically, this should not cause any issues, - // but as a precaution, we are taking a conservative approach during the initial rollout phase. - if (!string.IsNullOrEmpty(EnvironmentHelpers.GetEnvironmentVariable("DD_INJECTION_ENABLED"))) - { - DataPipelineEnabled = false; - Log.Warning( - $"{ConfigurationKeys.TraceDataPipelineEnabled} is enabled, but SSI is enabled. Disabling data pipeline."); - _telemetry.Record(ConfigurationKeys.TraceDataPipelineEnabled, false, ConfigurationOrigins.Calculated); - } - } - // We should also be writing telemetry for OTEL_LOGS_EXPORTER similar to OTEL_METRICS_EXPORTER, but we don't have a corresponding Datadog config // When we do, we can insert that here CustomSamplingRulesFormat = config.WithKeys(ConfigurationKeys.CustomSamplingRulesFormat) @@ -731,9 +669,70 @@ not null when string.Equals(value, "otlp", StringComparison.OrdinalIgnoreCase) = // We create a lazy here because this is kind of expensive, and we want to avoid calling it if we can _fallbackApplicationName = new(() => ApplicationNameHelpers.GetFallbackApplicationName(this)); - // Move the creation of these settings inside SettingsManager? - var initialMutableSettings = MutableSettings.CreateInitialMutableSettings(source, telemetry, errorLog, this); - Manager = new(this, initialMutableSettings, exporter); + // There's a circular dependency here because DataPipeline depends on ExporterSettings, + // but the settings manager depends on TracerSettings. Basically this is all fine as long + // as nothing in the MutableSettings or ExporterSettings depends on the value of DataPipelineEnabled! + Manager = new(source, this, telemetry, errorLog); + + DataPipelineEnabled = config + .WithKeys(ConfigurationKeys.TraceDataPipelineEnabled) + .AsBool(defaultValue: EnvironmentHelpers.IsUsingAzureAppServicesSiteExtension() && !EnvironmentHelpers.IsAzureFunctions()); + + if (DataPipelineEnabled) + { + // Due to missing quantization and obfuscation in native side, we can't enable the native trace exporter + // as it may lead to different stats results than the managed one. + if (StatsComputationEnabled) + { + DataPipelineEnabled = false; + Log.Warning( + $"{ConfigurationKeys.TraceDataPipelineEnabled} is enabled, but {ConfigurationKeys.StatsComputationEnabled} is enabled. Disabling data pipeline."); + _telemetry.Record(ConfigurationKeys.TraceDataPipelineEnabled, false, ConfigurationOrigins.Calculated); + } + + // Windows supports UnixDomainSocket https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/ + // but tokio hasn't added support for it yet https://github.com/tokio-rs/tokio/issues/2201 + // There's an issue here, in that technically a user can initially be configured to send over TCP/named pipes, + // and so we allow and enable the datapipeline. Later, they could configure the app in code to send over UDS. + // This is a problem, as we currently don't support toggling the data pipeline at runtime, so we explicitly block + // this scenario in the public API. + if (Manager.InitialExporterSettings.TracesTransport == TracesTransportType.UnixDomainSocket && FrameworkDescription.Instance.IsWindows()) + { + DataPipelineEnabled = false; + Log.Warning( + $"{ConfigurationKeys.TraceDataPipelineEnabled} is enabled, but TracesTransport is set to UnixDomainSocket which is not supported on Windows. Disabling data pipeline."); + _telemetry.Record(ConfigurationKeys.TraceDataPipelineEnabled, false, ConfigurationOrigins.Calculated); + } + + if (!isLibDatadogAvailable.IsAvailable) + { + DataPipelineEnabled = false; + if (isLibDatadogAvailable.Exception is not null) + { + Log.Warning( + isLibDatadogAvailable.Exception, + $"{ConfigurationKeys.TraceDataPipelineEnabled} is enabled, but libdatadog is not available. Disabling data pipeline."); + } + else + { + Log.Warning( + $"{ConfigurationKeys.TraceDataPipelineEnabled} is enabled, but libdatadog is not available. Disabling data pipeline."); + } + + _telemetry.Record(ConfigurationKeys.TraceDataPipelineEnabled, false, ConfigurationOrigins.Calculated); + } + + // SSI already utilizes libdatadog. To prevent unexpected behavior, + // we proactively disable the data pipeline when SSI is enabled. Theoretically, this should not cause any issues, + // but as a precaution, we are taking a conservative approach during the initial rollout phase. + if (!string.IsNullOrEmpty(EnvironmentHelpers.GetEnvironmentVariable("DD_INJECTION_ENABLED"))) + { + DataPipelineEnabled = false; + Log.Warning( + $"{ConfigurationKeys.TraceDataPipelineEnabled} is enabled, but SSI is enabled. Disabling data pipeline."); + _telemetry.Record(ConfigurationKeys.TraceDataPipelineEnabled, false, ConfigurationOrigins.Calculated); + } + } } internal bool IsRunningInCiVisibility { get; } diff --git a/tracer/src/Datadog.Trace/RemoteConfigurationManagement/Transport/RemoteConfigurationApi.cs b/tracer/src/Datadog.Trace/RemoteConfigurationManagement/Transport/RemoteConfigurationApi.cs index ae46547bad31..ac0875bb5427 100644 --- a/tracer/src/Datadog.Trace/RemoteConfigurationManagement/Transport/RemoteConfigurationApi.cs +++ b/tracer/src/Datadog.Trace/RemoteConfigurationManagement/Transport/RemoteConfigurationApi.cs @@ -6,6 +6,7 @@ #nullable enable using System; +using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -15,6 +16,7 @@ using Datadog.Trace.Logging; using Datadog.Trace.PlatformHelpers; using Datadog.Trace.RemoteConfigurationManagement.Protocol; +using Datadog.Trace.Util.Streams; using Datadog.Trace.Vendors.Newtonsoft.Json; namespace Datadog.Trace.RemoteConfigurationManagement.Transport diff --git a/tracer/test/Datadog.Trace.Security.Unit.Tests/EmptyDatadogTracer.cs b/tracer/test/Datadog.Trace.Security.Unit.Tests/EmptyDatadogTracer.cs index c43533eeb3a8..30ba0ace8710 100644 --- a/tracer/test/Datadog.Trace.Security.Unit.Tests/EmptyDatadogTracer.cs +++ b/tracer/test/Datadog.Trace.Security.Unit.Tests/EmptyDatadogTracer.cs @@ -6,6 +6,7 @@ using System; using Datadog.Trace.Configuration; using Datadog.Trace.Configuration.Schema; +using Datadog.Trace.Configuration.Telemetry; using Datadog.Trace.Sampling; using Moq; @@ -23,7 +24,7 @@ public EmptyDatadogTracer() DefaultServiceName = "My Service Name"; Settings = new TracerSettings(NullConfigurationSource.Instance); var namingSchema = new NamingSchema(SchemaVersion.V0, false, false, DefaultServiceName, null, null); - PerTraceSettings = new PerTraceSettings(null, null, namingSchema, MutableSettings.CreateWithoutDefaultSources(Settings)); + PerTraceSettings = new PerTraceSettings(null, null, namingSchema, MutableSettings.CreateWithoutDefaultSources(Settings, new ConfigurationTelemetry())); } public string DefaultServiceName { get; } diff --git a/tracer/test/Datadog.Trace.Tests/Agent/AgentWriterTests.cs b/tracer/test/Datadog.Trace.Tests/Agent/AgentWriterTests.cs index 7eeaa76f8c52..8eb464810838 100644 --- a/tracer/test/Datadog.Trace.Tests/Agent/AgentWriterTests.cs +++ b/tracer/test/Datadog.Trace.Tests/Agent/AgentWriterTests.cs @@ -11,6 +11,7 @@ using Datadog.Trace.Agent; using Datadog.Trace.Agent.MessagePack; using Datadog.Trace.Configuration; +using Datadog.Trace.Configuration.Telemetry; using Datadog.Trace.DogStatsd; using Datadog.Trace.Sampling; using Datadog.Trace.TestHelpers; @@ -427,7 +428,7 @@ public async Task AddsTraceKeepRateMetricToRootSpan() var tracer = new Mock(); tracer.Setup(x => x.DefaultServiceName).Returns("Default"); - tracer.Setup(x => x.PerTraceSettings).Returns(new PerTraceSettings(null, null, null!, MutableSettings.CreateWithoutDefaultSources(new(NullConfigurationSource.Instance)))); + tracer.Setup(x => x.PerTraceSettings).Returns(new PerTraceSettings(null, null, null!, MutableSettings.CreateWithoutDefaultSources(new(NullConfigurationSource.Instance), new ConfigurationTelemetry()))); var traceContext = new TraceContext(tracer.Object); var rootSpanContext = new SpanContext(null, traceContext, null); var rootSpan = new Span(rootSpanContext, DateTimeOffset.UtcNow); diff --git a/tracer/test/Datadog.Trace.Tests/Configuration/TracerSettingsSettingManagerTests.cs b/tracer/test/Datadog.Trace.Tests/Configuration/TracerSettingsSettingManagerTests.cs index bdf52cd7bd0e..1cfb0f2377d3 100644 --- a/tracer/test/Datadog.Trace.Tests/Configuration/TracerSettingsSettingManagerTests.cs +++ b/tracer/test/Datadog.Trace.Tests/Configuration/TracerSettingsSettingManagerTests.cs @@ -16,4 +16,80 @@ namespace Datadog.Trace.Tests.Configuration; public class TracerSettingsSettingManagerTests { + [Fact] + public void UpdateSettings_HandlesDynamicConfigurationChanges() + { + var tracerSettings = new TracerSettings(); + SettingChanges settingChanges = null; + tracerSettings.Manager.SubscribeToChanges(changes => settingChanges = changes); + + // default is null, but it's recorded as "1.0" in telemetry + tracerSettings.Manager.InitialMutableSettings.GlobalSamplingRate.Should().Be(null); + var sampleRateConfig = GetLatestSampleRateTelemetry((ConfigurationTelemetry)tracerSettings.Telemetry); + + sampleRateConfig.Should().NotBeNull(); + sampleRateConfig.Origin.Should().Be(ConfigurationOrigins.Default); + sampleRateConfig.DoubleValue.Should().Be(1.0); + + var rawConfig1 = """{"action": "enable", "revision": 1698167126064, "service_target": {"service": "test_service", "env": "test_env"}, "lib_config": {"tracing_sampling_rate": 0.7, "log_injection_enabled": null, "tracing_header_tags": null, "runtime_metrics_enabled": null, "tracing_debug": null, "tracing_service_mapping": null, "tracing_sampling_rules": null, "data_streams_enabled": null, "dynamic_instrumentation_enabled": null, "exception_replay_enabled": null, "code_origin_enabled": null, "live_debugging_enabled": null}, "id": "-1796479631020605752"}"""; + var dynamicConfig = new DynamicConfigConfigurationSource(rawConfig1, ConfigurationOrigins.RemoteConfig); + var telemetry = new ConfigurationTelemetry(); + + var wasUpdated = tracerSettings.Manager.UpdateDynamicConfigurationSettings( + dynamicConfig, + centralTelemetry: telemetry); + + wasUpdated.Should().BeTrue(); + settingChanges.Should().NotBeNull(); + settingChanges!.UpdatedExporter.Should().BeNull(); + settingChanges!.UpdatedMutable.Should().NotBeNull(); + settingChanges!.UpdatedMutable.GlobalSamplingRate.Should().Be(0.7); + + sampleRateConfig = GetLatestSampleRateTelemetry(telemetry); + + sampleRateConfig.Should().NotBeNull(); + sampleRateConfig.Origin.Should().Be(ConfigurationOrigins.RemoteConfig); + sampleRateConfig.DoubleValue.Should().Be(0.7); + telemetry.Clear(); + + // reset to "default" + var rawConfig2 = """{"action": "enable", "revision": 1698167126064, "service_target": {"service": "test_service", "env": "test_env"}, "lib_config": {"tracing_sampling_rate": null, "log_injection_enabled": null, "tracing_header_tags": null, "runtime_metrics_enabled": null, "tracing_debug": null, "tracing_service_mapping": null, "tracing_sampling_rules": null, "data_streams_enabled": null, "dynamic_instrumentation_enabled": null, "exception_replay_enabled": null, "code_origin_enabled": null, "live_debugging_enabled": null}, "id": "5931732111467439992"}"""; + dynamicConfig = new DynamicConfigConfigurationSource(rawConfig2, ConfigurationOrigins.RemoteConfig); + + wasUpdated = tracerSettings.Manager.UpdateDynamicConfigurationSettings( + dynamicConfig, + centralTelemetry: telemetry); + + wasUpdated.Should().BeTrue(); + settingChanges.Should().NotBeNull(); + settingChanges!.UpdatedExporter.Should().BeNull(); + settingChanges!.UpdatedMutable.Should().NotBeNull(); + settingChanges!.UpdatedMutable.GlobalSamplingRate.Should().Be(null); + + sampleRateConfig = GetLatestSampleRateTelemetry(telemetry); + + sampleRateConfig.Should().NotBeNull(); + sampleRateConfig.Origin.Should().Be(ConfigurationOrigins.Default); + sampleRateConfig.DoubleValue.Should().Be(1.0); + telemetry.Clear(); + + // Send the same config again, and make sure we _don't_ record more telemetry, so what we already have is correct + var existingChanges = settingChanges; + wasUpdated = tracerSettings.Manager.UpdateDynamicConfigurationSettings( + dynamicConfig, + centralTelemetry: telemetry); + + wasUpdated.Should().BeFalse(); + existingChanges.Should().Be(settingChanges); + telemetry.GetQueueForTesting().Should().BeEmpty(); + } + + private static ConfigurationTelemetry.ConfigurationTelemetryEntry GetLatestSampleRateTelemetry(ConfigurationTelemetry telemetry) + { + return telemetry + .GetQueueForTesting() + .Where(x => x.Key == ConfigurationKeys.GlobalSamplingRate) + .OrderByDescending(x => x.SeqId) + .FirstOrDefault(); + } } diff --git a/tracer/test/Datadog.Trace.Tests/Util/StubDatadogTracer.cs b/tracer/test/Datadog.Trace.Tests/Util/StubDatadogTracer.cs index 595985f4c427..ed889f080d90 100644 --- a/tracer/test/Datadog.Trace.Tests/Util/StubDatadogTracer.cs +++ b/tracer/test/Datadog.Trace.Tests/Util/StubDatadogTracer.cs @@ -6,6 +6,7 @@ using System; using Datadog.Trace.Configuration; using Datadog.Trace.Configuration.Schema; +using Datadog.Trace.Configuration.Telemetry; namespace Datadog.Trace.Tests.Util; @@ -16,7 +17,7 @@ public StubDatadogTracer() DefaultServiceName = "stub-service"; Settings = new TracerSettings(NullConfigurationSource.Instance); var namingSchema = new NamingSchema(SchemaVersion.V0, false, false, DefaultServiceName, null, null); - PerTraceSettings = new PerTraceSettings(null, null, namingSchema, MutableSettings.CreateWithoutDefaultSources(Settings)); + PerTraceSettings = new PerTraceSettings(null, null, namingSchema, MutableSettings.CreateWithoutDefaultSources(Settings, new ConfigurationTelemetry())); } public string DefaultServiceName { get; }