Skip to content

Commit 76cafe5

Browse files
authored
[sdk-traces] Add support for OTEL_TRACES_SAMPLER and OTEL_TRACES_SAMPLER_ARG (#5448)
1 parent 8f9eb90 commit 76cafe5

File tree

5 files changed

+177
-1
lines changed

5 files changed

+177
-1
lines changed

docs/trace/customizing-the-sdk/README.md

+20
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,26 @@ var tracerProvider = Sdk.CreateTracerProviderBuilder()
363363
.Build();
364364
```
365365

366+
It is also possible to configure the sampler by using the following
367+
environmental variables:
368+
369+
| Environment variable | Description |
370+
| -------------------------- | -------------------------------------------------- |
371+
| `OTEL_TRACES_SAMPLER` | Sampler to be used for traces. See the [General SDK Configuration specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/sdk-environment-variables.md#general-sdk-configuration) for more details. |
372+
| `OTEL_TRACES_SAMPLER_ARG` | String value to be used as the sampler argument. |
373+
374+
The supported values for `OTEL_TRACES_SAMPLER` are:
375+
376+
* `always_off`
377+
* `always_on`
378+
* `traceidratio`
379+
* `parentbased_always_on`,
380+
* `parentbased_always_off`
381+
* `parentbased_traceidratio`
382+
383+
The options `traceidratio` and `parentbased_traceidratio` may have the sampler
384+
probability configured via the `OTEL_TRACES_SAMPLER_ARG` environment variable.
385+
366386
Follow [this](../extending-the-sdk/README.md#sampler) document
367387
to learn about writing custom samplers.
368388

src/OpenTelemetry/CHANGELOG.md

+10
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,16 @@ Released 2024-Mar-14
8181
Specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/sdk-environment-variables.md#exemplar).
8282
([#5412](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5412))
8383

84+
* `TracerProvider`s can now have a sampler configured via the
85+
`OTEL_TRACES_SAMPLER` environment variable. The supported values are:
86+
`always_off`, `always_on`, `traceidratio`, `parentbased_always_on`,
87+
`parentbased_always_off`, and `parentbased_traceidratio`. The options
88+
`traceidratio` and `parentbased_traceidratio` may have the sampler probability
89+
configured via the `OTEL_TRACES_SAMPLER_ARG` environment variable.
90+
For details see: [OpenTelemetry Environment Variable
91+
Specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/sdk-environment-variables.md#general-sdk-configuration).
92+
([#5448](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5448))
93+
8494
## 1.7.0
8595

8696
Released 2023-Dec-08

src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs

+12
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,18 @@ public void MetricInstrumentRemoved(string instrumentName, string meterName)
346346
this.WriteEvent(53, instrumentName, meterName);
347347
}
348348

349+
[Event(54, Message = "OTEL_TRACES_SAMPLER configuration was found but the value '{0}' is invalid and will be ignored.", Level = EventLevel.Warning)]
350+
public void TracesSamplerConfigInvalid(string configValue)
351+
{
352+
this.WriteEvent(54, configValue);
353+
}
354+
355+
[Event(55, Message = "OTEL_TRACES_SAMPLER_ARG configuration was found but the value '{0}' is invalid and will be ignored, default of value of '1.0' will be used.", Level = EventLevel.Warning)]
356+
public void TracesSamplerArgConfigInvalid(string configValue)
357+
{
358+
this.WriteEvent(55, configValue);
359+
}
360+
349361
#if DEBUG
350362
public class OpenTelemetryEventListener : EventListener
351363
{

src/OpenTelemetry/Trace/TracerProviderSdk.cs

+80-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Runtime.CompilerServices;
66
using System.Text;
77
using System.Text.RegularExpressions;
8+
using Microsoft.Extensions.Configuration;
89
using Microsoft.Extensions.DependencyInjection;
910
using OpenTelemetry.Internal;
1011
using OpenTelemetry.Resources;
@@ -13,6 +14,9 @@ namespace OpenTelemetry.Trace;
1314

1415
internal sealed class TracerProviderSdk : TracerProvider
1516
{
17+
internal const string TracesSamplerConfigKey = "OTEL_TRACES_SAMPLER";
18+
internal const string TracesSamplerArgConfigKey = "OTEL_TRACES_SAMPLER_ARG";
19+
1620
internal readonly IServiceProvider ServiceProvider;
1721
internal readonly IDisposable? OwnedServiceProvider;
1822
internal int ShutdownCount;
@@ -57,7 +61,7 @@ internal TracerProviderSdk(
5761
resourceBuilder.ServiceProvider = serviceProvider;
5862
this.Resource = resourceBuilder.Build();
5963

60-
this.sampler = state.Sampler ?? new ParentBasedSampler(new AlwaysOnSampler());
64+
this.sampler = GetSampler(serviceProvider!.GetRequiredService<IConfiguration>(), state.Sampler);
6165
OpenTelemetrySdkEventSource.Log.TracerProviderSdkEvent($"Sampler added = \"{this.sampler.GetType()}\".");
6266

6367
this.supportLegacyActivity = state.LegacyActivityOperationNames.Count > 0;
@@ -401,6 +405,81 @@ protected override void Dispose(bool disposing)
401405
base.Dispose(disposing);
402406
}
403407

408+
private static Sampler GetSampler(IConfiguration configuration, Sampler? stateSampler)
409+
{
410+
Sampler? sampler = null;
411+
412+
if (stateSampler != null)
413+
{
414+
sampler = stateSampler;
415+
}
416+
417+
if (configuration.TryGetStringValue(TracesSamplerConfigKey, out var configValue))
418+
{
419+
if (sampler != null)
420+
{
421+
OpenTelemetrySdkEventSource.Log.TracerProviderSdkEvent(
422+
$"Trace sampler configuration value '{configValue}' has been ignored because a value '{sampler.GetType().FullName}' was set programmatically.");
423+
return sampler;
424+
}
425+
426+
switch (configValue)
427+
{
428+
case var _ when string.Equals(configValue, "always_on", StringComparison.OrdinalIgnoreCase):
429+
sampler = new AlwaysOnSampler();
430+
break;
431+
case var _ when string.Equals(configValue, "always_off", StringComparison.OrdinalIgnoreCase):
432+
sampler = new AlwaysOffSampler();
433+
break;
434+
case var _ when string.Equals(configValue, "traceidratio", StringComparison.OrdinalIgnoreCase):
435+
{
436+
var traceIdRatio = ReadTraceIdRatio(configuration);
437+
sampler = new TraceIdRatioBasedSampler(traceIdRatio);
438+
break;
439+
}
440+
441+
case var _ when string.Equals(configValue, "parentbased_always_on", StringComparison.OrdinalIgnoreCase):
442+
sampler = new ParentBasedSampler(new AlwaysOnSampler());
443+
break;
444+
case var _ when string.Equals(configValue, "parentbased_always_off", StringComparison.OrdinalIgnoreCase):
445+
sampler = new ParentBasedSampler(new AlwaysOffSampler());
446+
break;
447+
case var _ when string.Equals(configValue, "parentbased_traceidratio", StringComparison.OrdinalIgnoreCase):
448+
{
449+
var traceIdRatio = ReadTraceIdRatio(configuration);
450+
sampler = new ParentBasedSampler(new TraceIdRatioBasedSampler(traceIdRatio));
451+
break;
452+
}
453+
454+
default:
455+
OpenTelemetrySdkEventSource.Log.TracesSamplerConfigInvalid(configValue ?? string.Empty);
456+
break;
457+
}
458+
459+
if (sampler != null)
460+
{
461+
OpenTelemetrySdkEventSource.Log.TracerProviderSdkEvent($"Trace sampler set to '{sampler.GetType().FullName}' from configuration.");
462+
}
463+
}
464+
465+
return sampler ?? new ParentBasedSampler(new AlwaysOnSampler());
466+
}
467+
468+
private static double ReadTraceIdRatio(IConfiguration configuration)
469+
{
470+
if (configuration.TryGetStringValue(TracesSamplerArgConfigKey, out var configValue) &&
471+
double.TryParse(configValue, out var traceIdRatio))
472+
{
473+
return traceIdRatio;
474+
}
475+
else
476+
{
477+
OpenTelemetrySdkEventSource.Log.TracesSamplerArgConfigInvalid(configValue ?? string.Empty);
478+
}
479+
480+
return 1.0;
481+
}
482+
404483
private static ActivitySamplingResult ComputeActivitySamplingResult(
405484
ref ActivityCreationOptions<ActivityContext> options,
406485
Sampler sampler)

test/OpenTelemetry.Tests/Trace/TracerProviderSdkTest.cs

+55
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
using System.Diagnostics;
5+
using Microsoft.Extensions.Configuration;
6+
using Microsoft.Extensions.DependencyInjection;
57
using OpenTelemetry.Instrumentation;
68
using OpenTelemetry.Resources;
79
using OpenTelemetry.Resources.Tests;
@@ -1039,6 +1041,59 @@ public void SdkPopulatesSamplingParamsCorrectlyForLegacyActivityWithInProcParent
10391041
activity.Stop();
10401042
}
10411043

1044+
[Theory]
1045+
[InlineData(null, null, "ParentBased{AlwaysOnSampler}")]
1046+
[InlineData("always_on", null, "AlwaysOnSampler")]
1047+
[InlineData("always_off", null, "AlwaysOffSampler")]
1048+
[InlineData("always_OFF", null, "AlwaysOffSampler")]
1049+
[InlineData("traceidratio", "0.5", "TraceIdRatioBasedSampler{0.500000}")]
1050+
[InlineData("traceidratio", "not_a_double", "TraceIdRatioBasedSampler{1.000000}")]
1051+
[InlineData("parentbased_always_on", null, "ParentBased{AlwaysOnSampler}")]
1052+
[InlineData("parentbased_always_off", null, "ParentBased{AlwaysOffSampler}")]
1053+
[InlineData("parentbased_traceidratio", "0.111", "ParentBased{TraceIdRatioBasedSampler{0.111000}}")]
1054+
[InlineData("parentbased_traceidratio", "not_a_double", "ParentBased{TraceIdRatioBasedSampler{1.000000}}")]
1055+
[InlineData("ParentBased_TraceIdRatio", "0.000001", "ParentBased{TraceIdRatioBasedSampler{0.000001}}")]
1056+
public void TestSamplerSetFromConfiguration(string configValue, string argValue, string samplerDescription)
1057+
{
1058+
var configBuilder = new ConfigurationBuilder();
1059+
1060+
configBuilder.AddInMemoryCollection(new Dictionary<string, string>
1061+
{
1062+
[TracerProviderSdk.TracesSamplerConfigKey] = configValue,
1063+
[TracerProviderSdk.TracesSamplerArgConfigKey] = argValue,
1064+
});
1065+
1066+
var builder = Sdk.CreateTracerProviderBuilder();
1067+
builder.ConfigureServices(s => s.AddSingleton<IConfiguration>(configBuilder.Build()));
1068+
using var tracerProvider = builder.Build();
1069+
var tracerProviderSdk = tracerProvider as TracerProviderSdk;
1070+
1071+
Assert.NotNull(tracerProviderSdk);
1072+
Assert.NotNull(tracerProviderSdk.Sampler);
1073+
Assert.Equal(samplerDescription, tracerProviderSdk.Sampler.Description);
1074+
}
1075+
1076+
[Fact]
1077+
public void TestSamplerConfigurationIgnoredWhenSetProgrammatically()
1078+
{
1079+
var configBuilder = new ConfigurationBuilder();
1080+
configBuilder.AddInMemoryCollection(new Dictionary<string, string>
1081+
{
1082+
[TracerProviderSdk.TracesSamplerConfigKey] = "always_off",
1083+
});
1084+
1085+
var builder = Sdk.CreateTracerProviderBuilder();
1086+
builder.ConfigureServices(s => s.AddSingleton<IConfiguration>(configBuilder.Build()));
1087+
builder.SetSampler(new AlwaysOnSampler());
1088+
1089+
using var tracerProvider = builder.Build();
1090+
var tracerProviderSdk = tracerProvider as TracerProviderSdk;
1091+
1092+
Assert.NotNull(tracerProviderSdk);
1093+
Assert.NotNull(tracerProviderSdk.Sampler);
1094+
Assert.Equal("AlwaysOnSampler", tracerProviderSdk.Sampler.Description);
1095+
}
1096+
10421097
[Fact]
10431098
public void TracerProvideSdkCreatesAndDiposesInstrumentation()
10441099
{

0 commit comments

Comments
 (0)