From 33e232d5cbbbedf006296c49f9d47895d5fdcde0 Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Thu, 18 Sep 2025 18:09:06 -0700 Subject: [PATCH 1/8] Initial support for env variable --- .../src/AzureMonitorExporterExtensions.cs | 26 +-- .../src/DefaultAzureMonitorExporterOptions.cs | 45 +++++ .../Platform/EnvironmentVariableConstants.cs | 15 ++ ...ddAzureMonitorTraceExporterSamplerTests.cs | 99 +++++++++++ ...DefaultAzureMonitorExporterOptionsTests.cs | 156 ++++++++++++++++++ 5 files changed, 329 insertions(+), 12 deletions(-) create mode 100644 sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/AddAzureMonitorTraceExporterSamplerTests.cs create mode 100644 sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/DefaultAzureMonitorExporterOptionsTests.cs diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/AzureMonitorExporterExtensions.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/AzureMonitorExporterExtensions.cs index 90a19f000ca7..b2cdebe7ba02 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/AzureMonitorExporterExtensions.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/AzureMonitorExporterExtensions.cs @@ -9,6 +9,7 @@ using Azure.Monitor.OpenTelemetry.Exporter.Internals; using Azure.Monitor.OpenTelemetry.Exporter.Internals.Diagnostics; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using OpenTelemetry; using OpenTelemetry.Logs; @@ -47,12 +48,19 @@ public static TracerProviderBuilder AddAzureMonitorTraceExporter( var finalOptionsName = name ?? Options.DefaultName; - if (name != null && configure != null) + // Ensure our default options configurator (which reads IConfiguration + environment variables) + // is registered exactly once so that OTEL_TRACES_SAMPLER / OTEL_TRACES_SAMPLER_ARG work for this path. + builder.ConfigureServices(services => { - // If we are using named options we register the - // configuration delegate into options pipeline. - builder.ConfigureServices(services => services.Configure(finalOptionsName, configure)); - } + services.TryAddEnumerable(ServiceDescriptor.Singleton, DefaultAzureMonitorExporterOptions>()); + + if (name != null && configure != null) + { + // If we are using named options we register the configuration delegate into the options pipeline + // After the DefaultAzureMonitorExporterOptions so explicit code configuration can override env/config values. + services.Configure(finalOptionsName, configure); + } + }); var deferredBuilder = builder as IDeferredTracerProviderBuilder; if (deferredBuilder == null) @@ -66,11 +74,7 @@ public static TracerProviderBuilder AddAzureMonitorTraceExporter( var exporterOptions = sp.GetRequiredService>().Get(finalOptionsName); if (name == null && configure != null) { - // If we are NOT using named options, we execute the - // configuration delegate inline. The reason for this is - // AzureMonitorExporterOptions is shared by all signals. Without a - // name, delegates for all signals will mix together. See: - // https://github.com/open-telemetry/opentelemetry-dotnet/issues/4043 + // For unnamed options execute configuration delegate inline so it overrides env/config values. configure(exporterOptions); } @@ -85,8 +89,6 @@ public static TracerProviderBuilder AddAzureMonitorTraceExporter( if (credential != null) { - // Credential can be set by either AzureMonitorExporterOptions or Extension Method Parameter. - // Options should take precedence. exporterOptions.Credential ??= credential; } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/DefaultAzureMonitorExporterOptions.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/DefaultAzureMonitorExporterOptions.cs index 587d796a4107..abe2374f0b7c 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/DefaultAzureMonitorExporterOptions.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/DefaultAzureMonitorExporterOptions.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using Azure.Monitor.OpenTelemetry.Exporter.Internals.Diagnostics; using Azure.Monitor.OpenTelemetry.Exporter.Internals.Platform; using Microsoft.Extensions.Configuration; @@ -38,6 +39,11 @@ public void Configure(AzureMonitorExporterOptions options) { options.ConnectionString = connectionStringFromIConfig; } + + // Sampler configuration via IConfiguration + var samplerFromConfig = _configuration[EnvironmentVariableConstants.OTEL_TRACES_SAMPLER]; + var samplerArgFromConfig = _configuration[EnvironmentVariableConstants.OTEL_TRACES_SAMPLER_ARG]; + ConfigureSamplingOptions(samplerFromConfig, samplerArgFromConfig, options); } // Environment Variable should take precedence. @@ -46,6 +52,45 @@ public void Configure(AzureMonitorExporterOptions options) { options.ConnectionString = connectionStringFromEnvVar; } + + // Explicit environment variables for sampler should override IConfiguration. + var samplerTypeEnv = Environment.GetEnvironmentVariable(EnvironmentVariableConstants.OTEL_TRACES_SAMPLER); + var samplerArgEnv = Environment.GetEnvironmentVariable(EnvironmentVariableConstants.OTEL_TRACES_SAMPLER_ARG); + ConfigureSamplingOptions(samplerTypeEnv, samplerArgEnv, options); + } + catch (Exception ex) + { + AzureMonitorExporterEventSource.Log.ConfigureFailed(ex); + } + } + + private static void ConfigureSamplingOptions(string? samplerType, string? samplerArg, AzureMonitorExporterOptions options) + { + if (string.IsNullOrEmpty(samplerType) || string.IsNullOrEmpty(samplerArg)) + { + return; + } + + try + { + var samplerKey = samplerType!.Trim().ToLowerInvariant(); + switch (samplerKey) + { + case "microsoft.rate_limited": + if (double.TryParse(samplerArg, NumberStyles.Float, CultureInfo.InvariantCulture, out var tracesPerSecond) && tracesPerSecond >= 0) + { + options.TracesPerSecond = tracesPerSecond; + } + break; + case "microsoft.fixed_percentage": + if (double.TryParse(samplerArg, NumberStyles.Float, CultureInfo.InvariantCulture, out var ratio) && ratio >= 0.0 && ratio <= 1.0) + { + options.SamplingRatio = (float)ratio; + } + break; + default: + break; + } } catch (Exception ex) { diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/Platform/EnvironmentVariableConstants.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/Platform/EnvironmentVariableConstants.cs index 7d7fa1245885..409a372aab74 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/Platform/EnvironmentVariableConstants.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/Platform/EnvironmentVariableConstants.cs @@ -26,6 +26,8 @@ internal static class EnvironmentVariableConstants EXPORT_RESOURCE_METRIC, ASPNETCORE_DISABLE_URL_QUERY_REDACTION, HTTPCLIENT_DISABLE_URL_QUERY_REDACTION, + OTEL_TRACES_SAMPLER, + OTEL_TRACES_SAMPLER_ARG, }; /// @@ -106,5 +108,18 @@ internal static class EnvironmentVariableConstants /// . /// public const string HTTPCLIENT_DISABLE_URL_QUERY_REDACTION = "OTEL_DOTNET_EXPERIMENTAL_HTTPCLIENT_DISABLE_URL_QUERY_REDACTION"; + + /// + /// OpenTelemetry environment variable to specify the sampler to use for traces. + /// Supported values: microsoft.rate_limited, microsoft.fixed_percentage + /// + public const string OTEL_TRACES_SAMPLER = "OTEL_TRACES_SAMPLER"; + + /// + /// OpenTelemetry environment variable to specify the sampler argument. + /// For microsoft.rate_limited sampler: traces per second (double). + /// For microsoft.fixed_percentage sampler: sampling ratio (double from 0 to 1). + /// + public const string OTEL_TRACES_SAMPLER_ARG = "OTEL_TRACES_SAMPLER_ARG"; } } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/AddAzureMonitorTraceExporterSamplerTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/AddAzureMonitorTraceExporterSamplerTests.cs new file mode 100644 index 000000000000..9ddbe0f5f8f9 --- /dev/null +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/AddAzureMonitorTraceExporterSamplerTests.cs @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Reflection; +using OpenTelemetry; +using OpenTelemetry.Trace; +using Xunit; + +namespace Azure.Monitor.OpenTelemetry.Exporter.Tests +{ + [CollectionDefinition("TraceExporterEnvVarTests", DisableParallelization = true)] + public class AddAzureMonitorTraceExporterSamplerTests + { + private const string ConnStr = "InstrumentationKey=00000000-0000-0000-0000-000000000000"; + + [Fact] + public void EnvVar_RateLimitedSampler_Applied() + { + string? prevSampler = Environment.GetEnvironmentVariable("OTEL_TRACES_SAMPLER"); + string? prevArg = Environment.GetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG"); + try + { + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER", "microsoft.rate_limited"); + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG", "7"); + + using var provider = Sdk.CreateTracerProviderBuilder() + .AddAzureMonitorTraceExporter(o => o.ConnectionString = ConnStr) + .Build(); + + var sampler = GetSampler(provider); + Assert.Equal("Azure.Monitor.OpenTelemetry.Exporter.Internals.RateLimitedSampler", sampler.GetType().FullName); + } + finally + { + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER", prevSampler); + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG", prevArg); + } + } + + [Fact] + public void EnvVar_FixedPercentageSampler_Applied() + { + string? prevSampler = Environment.GetEnvironmentVariable("OTEL_TRACES_SAMPLER"); + string? prevArg = Environment.GetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG"); + try + { + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER", "microsoft.fixed_percentage"); + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG", "0.25"); + + using var provider = Sdk.CreateTracerProviderBuilder() + .AddAzureMonitorTraceExporter(o => o.ConnectionString = ConnStr) + .Build(); + + var sampler = GetSampler(provider); + Assert.Equal("Azure.Monitor.OpenTelemetry.Exporter.Internals.ApplicationInsightsSampler", sampler.GetType().FullName); + } + finally + { + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER", prevSampler); + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG", prevArg); + } + } + + [Fact] + public void ConfigureDelegate_OverridesEnvVar() + { + string? prevSampler = Environment.GetEnvironmentVariable("OTEL_TRACES_SAMPLER"); + string? prevArg = Environment.GetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG"); + try + { + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER", "microsoft.fixed_percentage"); + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG", "0.10"); + + using var provider = Sdk.CreateTracerProviderBuilder() + .AddAzureMonitorTraceExporter(o => { o.ConnectionString = ConnStr; o.TracesPerSecond = 5; }) + .Build(); + + var sampler = GetSampler(provider); + Assert.Equal("Azure.Monitor.OpenTelemetry.Exporter.Internals.RateLimitedSampler", sampler.GetType().FullName); + } + finally + { + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER", prevSampler); + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG", prevArg); + } + } + + private static Sampler GetSampler(TracerProvider provider) + { + // The concrete TracerProvider implementation is internal to OTel SDK; retrieve via reflection. + var samplerProperty = provider.GetType().GetProperty("Sampler", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + Assert.NotNull(samplerProperty); + var sampler = samplerProperty!.GetValue(provider) as Sampler; + Assert.NotNull(sampler); + return sampler!; + } + } +} diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/DefaultAzureMonitorExporterOptionsTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/DefaultAzureMonitorExporterOptionsTests.cs new file mode 100644 index 000000000000..255c44fc729b --- /dev/null +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/DefaultAzureMonitorExporterOptionsTests.cs @@ -0,0 +1,156 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Configuration; +using Xunit; + +namespace Azure.Monitor.OpenTelemetry.Exporter.Tests +{ + [CollectionDefinition("ExporterEnvVarTests", DisableParallelization = true)] + public class DefaultAzureMonitorExporterOptionsTests + { + [Fact] + public void Configure_Sampler_From_IConfiguration_FixedPercentage() + { + // Arrange + var configValues = new List> + { + new("OTEL_TRACES_SAMPLER", "microsoft.fixed_percentage"), + new("OTEL_TRACES_SAMPLER_ARG", "0.25"), + }; + var configuration = new ConfigurationBuilder().AddInMemoryCollection(configValues).Build(); + var defaultConfigurator = new DefaultAzureMonitorExporterOptions(configuration); + var options = new AzureMonitorExporterOptions(); + + // Act + defaultConfigurator.Configure(options); + + // Assert + Assert.Equal(0.25f, options.SamplingRatio); + Assert.Null(options.TracesPerSecond); // Not set for fixed percentage + } + + [Fact] + public void Configure_Sampler_From_IConfiguration_RateLimited() + { + // Arrange + var configValues = new List> + { + new("OTEL_TRACES_SAMPLER", "microsoft.rate_limited"), + new("OTEL_TRACES_SAMPLER_ARG", "5"), + }; + var configuration = new ConfigurationBuilder().AddInMemoryCollection(configValues).Build(); + var defaultConfigurator = new DefaultAzureMonitorExporterOptions(configuration); + var options = new AzureMonitorExporterOptions(); + + // Act + defaultConfigurator.Configure(options); + + // Assert + Assert.Equal(5d, options.TracesPerSecond); + Assert.Equal(1.0f, options.SamplingRatio); // untouched + } + + [Fact] + public void Configure_Sampler_InvalidArgs_Ignored() + { + // Arrange invalid fixed percentage (>1) + var configValues = new List> + { + new("OTEL_TRACES_SAMPLER", "microsoft.fixed_percentage"), + new("OTEL_TRACES_SAMPLER_ARG", "1.5"), + }; + var configuration = new ConfigurationBuilder().AddInMemoryCollection(configValues).Build(); + var defaultConfigurator = new DefaultAzureMonitorExporterOptions(configuration); + var options = new AzureMonitorExporterOptions(); + + // Act + defaultConfigurator.Configure(options); + + // Assert - defaults unchanged + Assert.Equal(1.0f, options.SamplingRatio); + Assert.Null(options.TracesPerSecond); + + // Now test invalid negative rate_limited + configValues = new List> + { + new("OTEL_TRACES_SAMPLER", "microsoft.rate_limited"), + new("OTEL_TRACES_SAMPLER_ARG", "-2"), + }; + configuration = new ConfigurationBuilder().AddInMemoryCollection(configValues).Build(); + defaultConfigurator = new DefaultAzureMonitorExporterOptions(configuration); + options = new AzureMonitorExporterOptions(); + + // Act + defaultConfigurator.Configure(options); + + // Assert + Assert.Null(options.TracesPerSecond); + Assert.Equal(1.0f, options.SamplingRatio); + } + + [Fact] + public void Configure_Sampler_EnvironmentVariable_Only_FixedPercentage() + { + string? prevSampler = Environment.GetEnvironmentVariable("OTEL_TRACES_SAMPLER"); + string? prevSamplerArg = Environment.GetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG"); + try + { + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER", "microsoft.fixed_percentage"); + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG", "0.30"); + + var defaultConfigurator = new DefaultAzureMonitorExporterOptions(); + var options = new AzureMonitorExporterOptions(); + + // Act + defaultConfigurator.Configure(options); + + // Assert + Assert.Equal(0.30f, options.SamplingRatio); + Assert.Null(options.TracesPerSecond); + } + finally + { + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER", prevSampler); + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG", prevSamplerArg); + } + } + + [Fact] + public void Configure_Sampler_EnvironmentVariable_Overrides_IConfiguration() + { + // Arrange - configuration provides fixed percentage + var configValues = new List> + { + new("OTEL_TRACES_SAMPLER", "microsoft.fixed_percentage"), + new("OTEL_TRACES_SAMPLER_ARG", "0.50"), + }; + var configuration = new ConfigurationBuilder().AddInMemoryCollection(configValues).Build(); + var defaultConfigurator = new DefaultAzureMonitorExporterOptions(configuration); + var options = new AzureMonitorExporterOptions(); + + string? prevSampler = Environment.GetEnvironmentVariable("OTEL_TRACES_SAMPLER"); + string? prevSamplerArg = Environment.GetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG"); + try + { + // Environment overrides to rate_limited 10 + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER", "microsoft.rate_limited"); + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG", "10"); + + // Act + defaultConfigurator.Configure(options); + + // Assert + Assert.Equal(0.50f, options.SamplingRatio); // value from config still present + Assert.Equal(10d, options.TracesPerSecond); // overridden by env var + } + finally + { + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER", prevSampler); + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG", prevSamplerArg); + } + } + } +} From 7b13288242f5e55ead5baa5781219a0c2c5c8422 Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Thu, 18 Sep 2025 18:15:26 -0700 Subject: [PATCH 2/8] Add logs to event source. --- .../src/DefaultAzureMonitorExporterOptions.cs | 32 ++++++++++++++++--- .../AzureMonitorExporterEventSource.cs | 6 ++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/DefaultAzureMonitorExporterOptions.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/DefaultAzureMonitorExporterOptions.cs index abe2374f0b7c..30eb44020c56 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/DefaultAzureMonitorExporterOptions.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/DefaultAzureMonitorExporterOptions.cs @@ -74,21 +74,45 @@ private static void ConfigureSamplingOptions(string? samplerType, string? sample try { var samplerKey = samplerType!.Trim().ToLowerInvariant(); + string samplerArgValue = samplerArg ?? string.Empty; // ensure non-null for logging switch (samplerKey) { case "microsoft.rate_limited": - if (double.TryParse(samplerArg, NumberStyles.Float, CultureInfo.InvariantCulture, out var tracesPerSecond) && tracesPerSecond >= 0) + if (double.TryParse(samplerArg, NumberStyles.Float, CultureInfo.InvariantCulture, out var tracesPerSecond)) { - options.TracesPerSecond = tracesPerSecond; + if (tracesPerSecond >= 0) + { + options.TracesPerSecond = tracesPerSecond; + } + else + { + AzureMonitorExporterEventSource.Log.InvalidSamplerArgument(samplerKey, samplerArgValue); + } + } + else + { + AzureMonitorExporterEventSource.Log.InvalidSamplerArgument(samplerKey, samplerArgValue); } break; case "microsoft.fixed_percentage": - if (double.TryParse(samplerArg, NumberStyles.Float, CultureInfo.InvariantCulture, out var ratio) && ratio >= 0.0 && ratio <= 1.0) + if (double.TryParse(samplerArg, NumberStyles.Float, CultureInfo.InvariantCulture, out var ratio)) + { + if (ratio >= 0.0 && ratio <= 1.0) + { + options.SamplingRatio = (float)ratio; + } + else + { + AzureMonitorExporterEventSource.Log.InvalidSamplerArgument(samplerKey, samplerArgValue); + } + } + else { - options.SamplingRatio = (float)ratio; + AzureMonitorExporterEventSource.Log.InvalidSamplerArgument(samplerKey, samplerArgValue); } break; default: + AzureMonitorExporterEventSource.Log.InvalidSamplerType(samplerType); break; } } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/Diagnostics/AzureMonitorExporterEventSource.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/Diagnostics/AzureMonitorExporterEventSource.cs index 626148cd129b..8d2ee09e826a 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/Diagnostics/AzureMonitorExporterEventSource.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/Diagnostics/AzureMonitorExporterEventSource.cs @@ -458,5 +458,11 @@ public void ConfigureFailed(System.Exception ex) [Event(45, Message = "The {0} method received an AzureMonitorExporterOptions with EnableLiveMetrics set to true, which isn't supported. Note that LiveMetrics is only available via the UseAzureMonitorExporter API.", Level = EventLevel.Warning)] public void LiveMetricsNotSupported(string methodName) => WriteEvent(45, methodName); + + [Event(46, Message = "Invalid sampler type '{0}'. Supported values: microsoft.rate_limited, microsoft.fixed_percentage", Level = EventLevel.Warning)] + public void InvalidSamplerType(string samplerType) => WriteEvent(46, samplerType); + + [Event(47, Message = "Invalid sampler argument '{1}' for sampler '{0}'. Ignoring.", Level = EventLevel.Warning)] + public void InvalidSamplerArgument(string samplerType, string samplerArg) => WriteEvent(47, samplerType, samplerArg); } } From 3687f3da476d78be7aeb6c88df622f0a1eab37ac Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Thu, 18 Sep 2025 18:42:19 -0700 Subject: [PATCH 3/8] Distro changes. --- .../CHANGELOG.md | 5 +- .../src/AzureMonitorAspNetCoreEventSource.cs | 6 + .../src/DefaultAzureMonitorOptions.cs | 69 +++++++++ .../DefaultAzureMonitorOptionsSamplerTests.cs | 135 ++++++++++++++++++ .../CHANGELOG.md | 5 + 5 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Tests/DefaultAzureMonitorOptionsSamplerTests.cs diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/CHANGELOG.md b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/CHANGELOG.md index f92253e4f597..753b7c85af72 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/CHANGELOG.md +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/CHANGELOG.md @@ -4,6 +4,10 @@ ### Features Added +* Added support for configuring sampling via OpenTelemetry environment variables: + * `OTEL_TRACES_SAMPLER` (supported values: `microsoft.rate_limited`, `microsoft.fixed_percentage`). + * `OTEL_TRACES_SAMPLER_ARG` (rate limit in traces/sec for `microsoft.rate_limited`, sampling ratio 0.0–1.0 for `microsoft.fixed_percentage`). + ### Breaking Changes ### Bugs Fixed @@ -260,7 +264,6 @@ - Update OpenTelemetry dependencies ([41398](https://github.com/Azure/azure-sdk-for-net/pull/41398)) - OpenTelemetry 1.7.0 - - OpenTelemetry.Extensions.Hosting 1.7.0 - NEW: OpenTelemetry.Instrumentation.AspNetCore 1.7.0 - NEW: OpenTelemetry.Instrumentation.Http 1.7.0 diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/AzureMonitorAspNetCoreEventSource.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/AzureMonitorAspNetCoreEventSource.cs index 9f3c3d251aef..68f881dddb39 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/AzureMonitorAspNetCoreEventSource.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/AzureMonitorAspNetCoreEventSource.cs @@ -127,5 +127,11 @@ public void ErrorInitializingPartOfSdkVersion(string typeName, System.Exception [Event(13, Message = "Failed to get Type version while initialize SDK version due to an exception. Not user actionable. Type: {0}. {1}", Level = EventLevel.Warning)] public void ErrorInitializingPartOfSdkVersion(string typeName, string exceptionMessage) => WriteEvent(13, typeName, exceptionMessage); + + [Event(14, Message = "Invalid sampler type '{0}'. Supported values: microsoft.rate_limited, microsoft.fixed_percentage", Level = EventLevel.Warning)] + public void InvalidSamplerType(string samplerType) => WriteEvent(14, samplerType); + + [Event(15, Message = "Invalid sampler argument '{1}' for sampler '{0}'. Ignoring.", Level = EventLevel.Warning)] + public void InvalidSamplerArgument(string samplerType, string samplerArg) => WriteEvent(15, samplerType, samplerArg); } } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/DefaultAzureMonitorOptions.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/DefaultAzureMonitorOptions.cs index 8d3e6220af01..76b4df8dbec3 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/DefaultAzureMonitorOptions.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/DefaultAzureMonitorOptions.cs @@ -4,6 +4,7 @@ using Azure.Monitor.OpenTelemetry.Exporter.Internals.Platform; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; +using System.Globalization; namespace Azure.Monitor.OpenTelemetry.AspNetCore { @@ -35,6 +36,11 @@ public void Configure(AzureMonitorOptions options) { options.ConnectionString = connectionStringFromIConfig; } + + // Sampler configuration via IConfiguration + var samplerFromConfig = _configuration[EnvironmentVariableConstants.OTEL_TRACES_SAMPLER]; + var samplerArgFromConfig = _configuration[EnvironmentVariableConstants.OTEL_TRACES_SAMPLER_ARG]; + ConfigureSamplingOptions(samplerFromConfig, samplerArgFromConfig, options); } // Environment Variable should take precedence. @@ -43,6 +49,69 @@ public void Configure(AzureMonitorOptions options) { options.ConnectionString = connectionStringFromEnvVar; } + + // Explicit environment variables for sampler should override IConfiguration. + var samplerTypeEnv = Environment.GetEnvironmentVariable(EnvironmentVariableConstants.OTEL_TRACES_SAMPLER); + var samplerArgEnv = Environment.GetEnvironmentVariable(EnvironmentVariableConstants.OTEL_TRACES_SAMPLER_ARG); + ConfigureSamplingOptions(samplerTypeEnv, samplerArgEnv, options); + } + catch (Exception ex) + { + AzureMonitorAspNetCoreEventSource.Log.ConfigureFailed(ex); + } + } + + private static void ConfigureSamplingOptions(string? samplerType, string? samplerArg, AzureMonitorOptions options) + { + if (string.IsNullOrEmpty(samplerType) || string.IsNullOrEmpty(samplerArg)) + { + return; + } + + try + { + var samplerKey = samplerType!.Trim().ToLowerInvariant(); + string samplerArgValue = samplerArg ?? string.Empty; + switch (samplerKey) + { + case "microsoft.rate_limited": + if (double.TryParse(samplerArg, NumberStyles.Float, CultureInfo.InvariantCulture, out var tracesPerSecond)) + { + if (tracesPerSecond >= 0) + { + options.TracesPerSecond = tracesPerSecond; + } + else + { + AzureMonitorAspNetCoreEventSource.Log.InvalidSamplerArgument(samplerKey, samplerArgValue); + } + } + else + { + AzureMonitorAspNetCoreEventSource.Log.InvalidSamplerArgument(samplerKey, samplerArgValue); + } + break; + case "microsoft.fixed_percentage": + if (double.TryParse(samplerArg, NumberStyles.Float, CultureInfo.InvariantCulture, out var ratio)) + { + if (ratio >= 0.0 && ratio <= 1.0) + { + options.SamplingRatio = (float)ratio; + } + else + { + AzureMonitorAspNetCoreEventSource.Log.InvalidSamplerArgument(samplerKey, samplerArgValue); + } + } + else + { + AzureMonitorAspNetCoreEventSource.Log.InvalidSamplerArgument(samplerKey, samplerArgValue); + } + break; + default: + AzureMonitorAspNetCoreEventSource.Log.InvalidSamplerType(samplerType); + break; + } } catch (Exception ex) { diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Tests/DefaultAzureMonitorOptionsSamplerTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Tests/DefaultAzureMonitorOptionsSamplerTests.cs new file mode 100644 index 000000000000..77e862ca7806 --- /dev/null +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Tests/DefaultAzureMonitorOptionsSamplerTests.cs @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Configuration; +using Xunit; + +namespace Azure.Monitor.OpenTelemetry.AspNetCore.Tests +{ + [CollectionDefinition("AspNetCoreSamplerEnvVarTests", DisableParallelization = true)] + public class DefaultAzureMonitorOptionsSamplerTests + { + [Fact] + public void Configure_Sampler_From_IConfiguration_FixedPercentage() + { + var configValues = new List> + { + new("OTEL_TRACES_SAMPLER", "microsoft.fixed_percentage"), + new("OTEL_TRACES_SAMPLER_ARG", "0.40"), + }; + var configuration = new ConfigurationBuilder().AddInMemoryCollection(configValues).Build(); + var configurator = new DefaultAzureMonitorOptions(configuration); + var options = new AzureMonitorOptions(); + + configurator.Configure(options); + + Assert.Equal(0.40f, options.SamplingRatio); + Assert.Null(options.TracesPerSecond); + } + + [Fact] + public void Configure_Sampler_From_IConfiguration_RateLimited() + { + var configValues = new List> + { + new("OTEL_TRACES_SAMPLER", "microsoft.rate_limited"), + new("OTEL_TRACES_SAMPLER_ARG", "15"), + }; + var configuration = new ConfigurationBuilder().AddInMemoryCollection(configValues).Build(); + var configurator = new DefaultAzureMonitorOptions(configuration); + var options = new AzureMonitorOptions(); + + configurator.Configure(options); + + Assert.Equal(15d, options.TracesPerSecond); + Assert.Equal(1.0f, options.SamplingRatio); // default unchanged + } + + [Fact] + public void Configure_Sampler_InvalidArgs_Ignored() + { + // invalid percentage > 1 + var configValues = new List> + { + new("OTEL_TRACES_SAMPLER", "microsoft.fixed_percentage"), + new("OTEL_TRACES_SAMPLER_ARG", "1.5"), + }; + var configuration = new ConfigurationBuilder().AddInMemoryCollection(configValues).Build(); + var configurator = new DefaultAzureMonitorOptions(configuration); + var options = new AzureMonitorOptions(); + configurator.Configure(options); + Assert.Equal(1.0f, options.SamplingRatio); // default + Assert.Null(options.TracesPerSecond); + + // invalid negative rate + configValues = new List> + { + new("OTEL_TRACES_SAMPLER", "microsoft.rate_limited"), + new("OTEL_TRACES_SAMPLER_ARG", "-2"), + }; + configuration = new ConfigurationBuilder().AddInMemoryCollection(configValues).Build(); + configurator = new DefaultAzureMonitorOptions(configuration); + options = new AzureMonitorOptions(); + configurator.Configure(options); + Assert.Null(options.TracesPerSecond); + Assert.Equal(1.0f, options.SamplingRatio); + } + + [Fact] + public void Configure_Sampler_EnvironmentVariable_Overrides_IConfiguration() + { + var configValues = new List> + { + new("OTEL_TRACES_SAMPLER", "microsoft.fixed_percentage"), + new("OTEL_TRACES_SAMPLER_ARG", "0.20"), + }; + var configuration = new ConfigurationBuilder().AddInMemoryCollection(configValues).Build(); + var configurator = new DefaultAzureMonitorOptions(configuration); + var options = new AzureMonitorOptions(); + + string? prevSampler = Environment.GetEnvironmentVariable("OTEL_TRACES_SAMPLER"); + string? prevSamplerArg = Environment.GetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG"); + try + { + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER", "microsoft.rate_limited"); + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG", "11"); + + configurator.Configure(options); + + Assert.Equal(0.20f, options.SamplingRatio); // from config + Assert.Equal(11d, options.TracesPerSecond); // from env + } + finally + { + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER", prevSampler); + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG", prevSamplerArg); + } + } + + [Fact] + public void Configure_Sampler_EnvironmentVariable_Only_FixedPercentage() + { + string? prevSampler = Environment.GetEnvironmentVariable("OTEL_TRACES_SAMPLER"); + string? prevSamplerArg = Environment.GetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG"); + try + { + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER", "microsoft.fixed_percentage"); + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG", "0.55"); + + var configurator = new DefaultAzureMonitorOptions(); + var options = new AzureMonitorOptions(); + configurator.Configure(options); + + Assert.Equal(0.55f, options.SamplingRatio); + Assert.Null(options.TracesPerSecond); + } + finally + { + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER", prevSampler); + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG", prevSamplerArg); + } + } + } +} diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/CHANGELOG.md b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/CHANGELOG.md index f6d72208d601..8deda8f0838e 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/CHANGELOG.md +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/CHANGELOG.md @@ -4,6 +4,11 @@ ### Features Added +* Added support for configuring sampling via OpenTelemetry environment variables: + * `OTEL_TRACES_SAMPLER` (supported values: `microsoft.rate_limited`, `microsoft.fixed_percentage`). + * `OTEL_TRACES_SAMPLER_ARG` (rate limit in traces/sec for `microsoft.rate_limited`, sampling ratio 0.0–1.0 for `microsoft.fixed_percentage`). + This now applies to both `UseAzureMonitorExporter` and the direct `Sdk.CreateTracerProviderBuilder().AddAzureMonitorTraceExporter(...)` path. + ### Breaking Changes ### Bugs Fixed From a3ecf5456319538a8bcfd5a7dcdb286c9ed9c0b3 Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Thu, 18 Sep 2025 18:49:13 -0700 Subject: [PATCH 4/8] changelog patch --- sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/CHANGELOG.md | 1 + sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/CHANGELOG.md b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/CHANGELOG.md index 753b7c85af72..b54811f6c481 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/CHANGELOG.md +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/CHANGELOG.md @@ -7,6 +7,7 @@ * Added support for configuring sampling via OpenTelemetry environment variables: * `OTEL_TRACES_SAMPLER` (supported values: `microsoft.rate_limited`, `microsoft.fixed_percentage`). * `OTEL_TRACES_SAMPLER_ARG` (rate limit in traces/sec for `microsoft.rate_limited`, sampling ratio 0.0–1.0 for `microsoft.fixed_percentage`). + ([#52720](https://github.com/Azure/azure-sdk-for-net/pull/52720)) ### Breaking Changes diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/CHANGELOG.md b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/CHANGELOG.md index 8deda8f0838e..99a467af25a3 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/CHANGELOG.md +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/CHANGELOG.md @@ -8,6 +8,7 @@ * `OTEL_TRACES_SAMPLER` (supported values: `microsoft.rate_limited`, `microsoft.fixed_percentage`). * `OTEL_TRACES_SAMPLER_ARG` (rate limit in traces/sec for `microsoft.rate_limited`, sampling ratio 0.0–1.0 for `microsoft.fixed_percentage`). This now applies to both `UseAzureMonitorExporter` and the direct `Sdk.CreateTracerProviderBuilder().AddAzureMonitorTraceExporter(...)` path. + ([#52720](https://github.com/Azure/azure-sdk-for-net/pull/52720)) ### Breaking Changes From 5136530672477cb17c3b47501eb090b2f39a884b Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Thu, 18 Sep 2025 21:27:18 -0700 Subject: [PATCH 5/8] copilot feedback --- .../CHANGELOG.md | 10 +++++++--- .../src/DefaultAzureMonitorOptions.cs | 2 +- .../CHANGELOG.md | 13 +++++++++---- .../src/DefaultAzureMonitorExporterOptions.cs | 2 +- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/CHANGELOG.md b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/CHANGELOG.md index b54811f6c481..a8d1d292f74a 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/CHANGELOG.md +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/CHANGELOG.md @@ -4,9 +4,13 @@ ### Features Added -* Added support for configuring sampling via OpenTelemetry environment variables: - * `OTEL_TRACES_SAMPLER` (supported values: `microsoft.rate_limited`, `microsoft.fixed_percentage`). - * `OTEL_TRACES_SAMPLER_ARG` (rate limit in traces/sec for `microsoft.rate_limited`, sampling ratio 0.0–1.0 for `microsoft.fixed_percentage`). +* Added support for configuring sampling via OpenTelemetry environment + variables: + * `OTEL_TRACES_SAMPLER` (supported values: `microsoft.rate_limited`, + `microsoft.fixed_percentage`). + * `OTEL_TRACES_SAMPLER_ARG` (rate limit in traces/sec for + `microsoft.rate_limited`, sampling ratio 0.0 - 1.0 for + `microsoft.fixed_percentage`). ([#52720](https://github.com/Azure/azure-sdk-for-net/pull/52720)) ### Breaking Changes diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/DefaultAzureMonitorOptions.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/DefaultAzureMonitorOptions.cs index 76b4df8dbec3..9a54cf8ae102 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/DefaultAzureMonitorOptions.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/DefaultAzureMonitorOptions.cs @@ -109,7 +109,7 @@ private static void ConfigureSamplingOptions(string? samplerType, string? sample } break; default: - AzureMonitorAspNetCoreEventSource.Log.InvalidSamplerType(samplerType); + AzureMonitorAspNetCoreEventSource.Log.InvalidSamplerType(samplerType ?? string.Empty); break; } } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/CHANGELOG.md b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/CHANGELOG.md index 99a467af25a3..8413c307918d 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/CHANGELOG.md +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/CHANGELOG.md @@ -4,10 +4,15 @@ ### Features Added -* Added support for configuring sampling via OpenTelemetry environment variables: - * `OTEL_TRACES_SAMPLER` (supported values: `microsoft.rate_limited`, `microsoft.fixed_percentage`). - * `OTEL_TRACES_SAMPLER_ARG` (rate limit in traces/sec for `microsoft.rate_limited`, sampling ratio 0.0–1.0 for `microsoft.fixed_percentage`). - This now applies to both `UseAzureMonitorExporter` and the direct `Sdk.CreateTracerProviderBuilder().AddAzureMonitorTraceExporter(...)` path. +* Added support for configuring sampling via OpenTelemetry environment + variables: + * `OTEL_TRACES_SAMPLER` (supported values: `microsoft.rate_limited`, + `microsoft.fixed_percentage`). + * `OTEL_TRACES_SAMPLER_ARG` (rate limit in traces/sec for + `microsoft.rate_limited`, sampling ratio 0.0 - 1.0 for + `microsoft.fixed_percentage`). This now applies to both + `UseAzureMonitorExporter` and the direct + `Sdk.CreateTracerProviderBuilder().AddAzureMonitorTraceExporter(...)` path. ([#52720](https://github.com/Azure/azure-sdk-for-net/pull/52720)) ### Breaking Changes diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/DefaultAzureMonitorExporterOptions.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/DefaultAzureMonitorExporterOptions.cs index 30eb44020c56..8d2090868430 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/DefaultAzureMonitorExporterOptions.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/DefaultAzureMonitorExporterOptions.cs @@ -112,7 +112,7 @@ private static void ConfigureSamplingOptions(string? samplerType, string? sample } break; default: - AzureMonitorExporterEventSource.Log.InvalidSamplerType(samplerType); + AzureMonitorExporterEventSource.Log.InvalidSamplerType(samplerType ?? string.Empty); break; } } From 0aff2aec2dd98f134997bb58c0bc8fa085aaa063 Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Thu, 2 Oct 2025 14:29:05 -0700 Subject: [PATCH 6/8] Event source fix --- .../Internals/Diagnostics/AzureMonitorExporterEventSource.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/Diagnostics/AzureMonitorExporterEventSource.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/Diagnostics/AzureMonitorExporterEventSource.cs index 194418c1a593..8affcf29db13 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/Diagnostics/AzureMonitorExporterEventSource.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/Diagnostics/AzureMonitorExporterEventSource.cs @@ -487,9 +487,9 @@ public void CustomerSdkStatsInitializationFailed(Exception ex) public void CustomerSdkStatsInitializationFailed(string exceptionMessage) => WriteEvent(48, exceptionMessage); [Event(49, Message = "Invalid sampler type '{0}'. Supported values: microsoft.rate_limited, microsoft.fixed_percentage", Level = EventLevel.Warning)] - public void InvalidSamplerType(string samplerType) => WriteEvent(46, samplerType); + public void InvalidSamplerType(string samplerType) => WriteEvent(49, samplerType); [Event(50, Message = "Invalid sampler argument '{1}' for sampler '{0}'. Ignoring.", Level = EventLevel.Warning)] - public void InvalidSamplerArgument(string samplerType, string samplerArg) => WriteEvent(47, samplerType, samplerArg); + public void InvalidSamplerArgument(string samplerType, string samplerArg) => WriteEvent(50, samplerType, samplerArg); } } From 5410c3d2fd7b27b513e41bc9d8d38f8c5096c01f Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Thu, 2 Oct 2025 16:16:51 -0700 Subject: [PATCH 7/8] Fix intermittent test failures --- ...DefaultAzureMonitorExporterOptionsTests.cs | 153 +++++++++++------- 1 file changed, 99 insertions(+), 54 deletions(-) diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/DefaultAzureMonitorExporterOptionsTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/DefaultAzureMonitorExporterOptionsTests.cs index 255c44fc729b..d03ee06eb569 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/DefaultAzureMonitorExporterOptionsTests.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/DefaultAzureMonitorExporterOptionsTests.cs @@ -8,87 +8,132 @@ namespace Azure.Monitor.OpenTelemetry.Exporter.Tests { - [CollectionDefinition("ExporterEnvVarTests", DisableParallelization = true)] + [Collection("ExporterEnvVarTests")] public class DefaultAzureMonitorExporterOptionsTests { [Fact] public void Configure_Sampler_From_IConfiguration_FixedPercentage() { - // Arrange - var configValues = new List> + string? prevSampler = Environment.GetEnvironmentVariable("OTEL_TRACES_SAMPLER"); + string? prevSamplerArg = Environment.GetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG"); + + try { - new("OTEL_TRACES_SAMPLER", "microsoft.fixed_percentage"), - new("OTEL_TRACES_SAMPLER_ARG", "0.25"), - }; - var configuration = new ConfigurationBuilder().AddInMemoryCollection(configValues).Build(); - var defaultConfigurator = new DefaultAzureMonitorExporterOptions(configuration); - var options = new AzureMonitorExporterOptions(); + // Clear environment variables to ensure they don't interfere with IConfiguration + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER", null); + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG", null); + + // Arrange + var configValues = new List> + { + new("OTEL_TRACES_SAMPLER", "microsoft.fixed_percentage"), + new("OTEL_TRACES_SAMPLER_ARG", "0.25"), + }; + var configuration = new ConfigurationBuilder().AddInMemoryCollection(configValues).Build(); + var defaultConfigurator = new DefaultAzureMonitorExporterOptions(configuration); + var options = new AzureMonitorExporterOptions(); - // Act - defaultConfigurator.Configure(options); + // Act + defaultConfigurator.Configure(options); - // Assert - Assert.Equal(0.25f, options.SamplingRatio); - Assert.Null(options.TracesPerSecond); // Not set for fixed percentage + // Assert + Assert.Equal(0.25f, options.SamplingRatio); + Assert.Null(options.TracesPerSecond); // Not set for fixed percentage + } + finally + { + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER", prevSampler); + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG", prevSamplerArg); + } } [Fact] public void Configure_Sampler_From_IConfiguration_RateLimited() { - // Arrange - var configValues = new List> + string? prevSampler = Environment.GetEnvironmentVariable("OTEL_TRACES_SAMPLER"); + string? prevSamplerArg = Environment.GetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG"); + + try { - new("OTEL_TRACES_SAMPLER", "microsoft.rate_limited"), - new("OTEL_TRACES_SAMPLER_ARG", "5"), - }; - var configuration = new ConfigurationBuilder().AddInMemoryCollection(configValues).Build(); - var defaultConfigurator = new DefaultAzureMonitorExporterOptions(configuration); - var options = new AzureMonitorExporterOptions(); + // Clear environment variables to ensure they don't interfere with IConfiguration + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER", null); + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG", null); + + // Arrange + var configValues = new List> + { + new("OTEL_TRACES_SAMPLER", "microsoft.rate_limited"), + new("OTEL_TRACES_SAMPLER_ARG", "5"), + }; + var configuration = new ConfigurationBuilder().AddInMemoryCollection(configValues).Build(); + var defaultConfigurator = new DefaultAzureMonitorExporterOptions(configuration); + var options = new AzureMonitorExporterOptions(); - // Act - defaultConfigurator.Configure(options); + // Act + defaultConfigurator.Configure(options); - // Assert - Assert.Equal(5d, options.TracesPerSecond); - Assert.Equal(1.0f, options.SamplingRatio); // untouched + // Assert + Assert.Equal(5d, options.TracesPerSecond); + Assert.Equal(1.0f, options.SamplingRatio); // untouched + } + finally + { + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER", prevSampler); + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG", prevSamplerArg); + } } [Fact] public void Configure_Sampler_InvalidArgs_Ignored() { - // Arrange invalid fixed percentage (>1) - var configValues = new List> + string? prevSampler = Environment.GetEnvironmentVariable("OTEL_TRACES_SAMPLER"); + string? prevSamplerArg = Environment.GetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG"); + + try { - new("OTEL_TRACES_SAMPLER", "microsoft.fixed_percentage"), - new("OTEL_TRACES_SAMPLER_ARG", "1.5"), - }; - var configuration = new ConfigurationBuilder().AddInMemoryCollection(configValues).Build(); - var defaultConfigurator = new DefaultAzureMonitorExporterOptions(configuration); - var options = new AzureMonitorExporterOptions(); + // Clear environment variables to ensure they don't interfere with IConfiguration + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER", null); + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG", null); + + // Arrange invalid fixed percentage (>1) + var configValues = new List> + { + new("OTEL_TRACES_SAMPLER", "microsoft.fixed_percentage"), + new("OTEL_TRACES_SAMPLER_ARG", "1.5"), + }; + var configuration = new ConfigurationBuilder().AddInMemoryCollection(configValues).Build(); + var defaultConfigurator = new DefaultAzureMonitorExporterOptions(configuration); + var options = new AzureMonitorExporterOptions(); - // Act - defaultConfigurator.Configure(options); + // Act + defaultConfigurator.Configure(options); - // Assert - defaults unchanged - Assert.Equal(1.0f, options.SamplingRatio); - Assert.Null(options.TracesPerSecond); + // Assert - defaults unchanged + Assert.Equal(1.0f, options.SamplingRatio); + Assert.Null(options.TracesPerSecond); - // Now test invalid negative rate_limited - configValues = new List> - { - new("OTEL_TRACES_SAMPLER", "microsoft.rate_limited"), - new("OTEL_TRACES_SAMPLER_ARG", "-2"), - }; - configuration = new ConfigurationBuilder().AddInMemoryCollection(configValues).Build(); - defaultConfigurator = new DefaultAzureMonitorExporterOptions(configuration); - options = new AzureMonitorExporterOptions(); + // Now test invalid negative rate_limited + configValues = new List> + { + new("OTEL_TRACES_SAMPLER", "microsoft.rate_limited"), + new("OTEL_TRACES_SAMPLER_ARG", "-2"), + }; + configuration = new ConfigurationBuilder().AddInMemoryCollection(configValues).Build(); + defaultConfigurator = new DefaultAzureMonitorExporterOptions(configuration); + options = new AzureMonitorExporterOptions(); - // Act - defaultConfigurator.Configure(options); + // Act + defaultConfigurator.Configure(options); - // Assert - Assert.Null(options.TracesPerSecond); - Assert.Equal(1.0f, options.SamplingRatio); + // Assert + Assert.Null(options.TracesPerSecond); + Assert.Equal(1.0f, options.SamplingRatio); + } + finally + { + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER", prevSampler); + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG", prevSamplerArg); + } } [Fact] From 764c3e4784c8159358ac3c5d80002539a905018c Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Thu, 2 Oct 2025 16:20:08 -0700 Subject: [PATCH 8/8] left out test fix --- ...DefaultAzureMonitorExporterOptionsTests.cs | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/DefaultAzureMonitorExporterOptionsTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/DefaultAzureMonitorExporterOptionsTests.cs index d03ee06eb569..4ccc2bd238db 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/DefaultAzureMonitorExporterOptionsTests.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/DefaultAzureMonitorExporterOptionsTests.cs @@ -141,6 +141,7 @@ public void Configure_Sampler_EnvironmentVariable_Only_FixedPercentage() { string? prevSampler = Environment.GetEnvironmentVariable("OTEL_TRACES_SAMPLER"); string? prevSamplerArg = Environment.GetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG"); + try { Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER", "microsoft.fixed_percentage"); @@ -166,20 +167,25 @@ public void Configure_Sampler_EnvironmentVariable_Only_FixedPercentage() [Fact] public void Configure_Sampler_EnvironmentVariable_Overrides_IConfiguration() { - // Arrange - configuration provides fixed percentage - var configValues = new List> - { - new("OTEL_TRACES_SAMPLER", "microsoft.fixed_percentage"), - new("OTEL_TRACES_SAMPLER_ARG", "0.50"), - }; - var configuration = new ConfigurationBuilder().AddInMemoryCollection(configValues).Build(); - var defaultConfigurator = new DefaultAzureMonitorExporterOptions(configuration); - var options = new AzureMonitorExporterOptions(); - string? prevSampler = Environment.GetEnvironmentVariable("OTEL_TRACES_SAMPLER"); string? prevSamplerArg = Environment.GetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG"); + try { + // Clear environment variables first to ensure clean state + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER", null); + Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG", null); + + // Arrange - configuration provides fixed percentage + var configValues = new List> + { + new("OTEL_TRACES_SAMPLER", "microsoft.fixed_percentage"), + new("OTEL_TRACES_SAMPLER_ARG", "0.50"), + }; + var configuration = new ConfigurationBuilder().AddInMemoryCollection(configValues).Build(); + var defaultConfigurator = new DefaultAzureMonitorExporterOptions(configuration); + var options = new AzureMonitorExporterOptions(); + // Environment overrides to rate_limited 10 Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER", "microsoft.rate_limited"); Environment.SetEnvironmentVariable("OTEL_TRACES_SAMPLER_ARG", "10");