Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion docs/file-based-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ tracer_provider:
processors:
# Batch processor for OTLP HTTP
- batch:
# Configure delay interval (in milliseconds) between two consecutive exports.
# Configure delay interval (in milliseconds) between two consecutive exports.
# Value must be non-negative.
# If omitted or null, 5000 is used.
schedule_delay: 5000
Expand Down Expand Up @@ -110,6 +110,36 @@ tracer_provider:
- simple:
exporter:
console:

# Configure the sampler. If omitted, parent based sampler with a root of always_on is used.
sampler:
# Configure sampler to be parent_based.
parent_based:
# Configure root sampler.
# If omitted or null, always_on is used.
root:
# Configure sampler to be always_on.
always_on:
# Configure remote_parent_sampled sampler.
# If omitted or null, always_on is used.
remote_parent_sampled:
# Configure sampler to be always_on.
always_on:
# Configure remote_parent_not_sampled sampler.
# If omitted or null, always_off is used.
remote_parent_not_sampled:
# Configure sampler to be always_off.
always_off:
# Configure local_parent_sampled sampler.
# If omitted or null, always_on is used.
local_parent_sampled:
# Configure sampler to be always_on.
always_on:
# Configure local_parent_not_sampled sampler.
# If omitted or null, always_off is used.
local_parent_not_sampled:
# Configure sampler to be always_off.
always_off:
```

### Resource Configuration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,17 @@ public static TracerProviderBuilder UseEnvironmentVariables(
builder.AddOpenTracingShimSource();
}

builder
builder = builder
// Exporters can cause dependency loads.
// Should be called later if dependency listeners are already setup.
.SetExporter(settings, pluginManager)
.AddSource(settings.ActivitySources.ToArray());
.SetExporter(settings, pluginManager);

if (settings.Sampler != null)
{
builder = builder.SetSampler(settings.Sampler);
}

builder = builder.AddSource(settings.ActivitySources.ToArray());

foreach (var legacySource in settings.AdditionalLegacySources)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration.Parser;
using Vendors.YamlDotNet.Serialization;

namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration;

[EmptyObjectOnEmptyYaml]
internal class ParentBasedSamplerConfig
{
[YamlMember(Alias = "root")]
public SamplerConfig? Root { get; set; }

[YamlMember(Alias = "remote_parent_sampled")]
public SamplerConfig? RemoteParentSampled { get; set; }

[YamlMember(Alias = "remote_parent_not_sampled")]
public SamplerConfig? RemoteParentNotSampled { get; set; }

[YamlMember(Alias = "local_parent_sampled")]
public SamplerConfig? LocalParentSampled { get; set; }

[YamlMember(Alias = "local_parent_not_sampled")]
public SamplerConfig? LocalParentNotSampled { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration.Parser;
using Vendors.YamlDotNet.Serialization;

namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration;

internal class SamplerConfig
{
[YamlMember(Alias = "always_on")]
public object? AlwaysOn { get; set; }

[YamlMember(Alias = "always_off")]
public object? AlwaysOff { get; set; }

[YamlMember(Alias = "trace_id_ratio")]
public TraceIdRatioSamplerConfig? TraceIdRatio { get; set; }

[YamlMember(Alias = "parent_based")]
public ParentBasedSamplerConfig? ParentBased { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using OpenTelemetry.AutoInstrumentation.Logging;
using OpenTelemetry.Trace;

namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration;

internal static class SamplerFactory
{
private static readonly IOtelLogger Logger = OtelLogging.GetLogger();

public static Sampler? CreateSampler(SamplerConfig? samplerConfig, bool failFast)
{
try
{
return CreateSamplerInternal(samplerConfig, failFast, "tracer_provider.sampler");
}
catch (Exception ex) when (!failFast)
{
Logger.Error(ex, "Failed to create sampler from file-based configuration.");
return null;
}
}

private static Sampler? CreateSamplerInternal(SamplerConfig? samplerConfig, bool failFast, string path)
{
if (samplerConfig == null)
{
return null;
}

var configuredSamplers = new Dictionary<string, Sampler?>();

if (samplerConfig.AlwaysOn != null)
{
configuredSamplers.Add("always_on", new AlwaysOnSampler());
}

if (samplerConfig.AlwaysOff != null)
{
configuredSamplers.Add("always_off", new AlwaysOffSampler());
}

if (samplerConfig.TraceIdRatio != null)
{
configuredSamplers.Add("trace_id_ratio", CreateTraceIdRatioSampler(samplerConfig.TraceIdRatio, failFast, path + ".trace_id_ratio"));
}

if (samplerConfig.ParentBased != null)
{
configuredSamplers.Add("parent_based", CreateParentBasedSampler(samplerConfig.ParentBased, failFast, path + ".parent_based"));
}

if (configuredSamplers.Count == 0)
{
var message = $"Sampler configuration '{path}' does not specify a sampler type.";
Logger.Warning(message);

if (failFast)
{
throw new InvalidOperationException(message);
}

return null;
}

if (configuredSamplers.Count > 1)
{
var configuredNames = string.Join(", ", configuredSamplers.Keys);
var message = $"Sampler configuration '{path}' specifies multiple sampler types ({configuredNames}). Only one sampler can be configured.";
Logger.Error(message);

if (failFast)
{
throw new InvalidOperationException(message);
}

return null;
}

var configuredSampler = configuredSamplers.Values.First();
if (configuredSampler == null)
{
var message = $"Sampler configuration '{path}' is invalid.";
Logger.Error(message);

if (failFast)
{
throw new InvalidOperationException(message);
}
}

return configuredSampler;
}

private static Sampler? CreateTraceIdRatioSampler(TraceIdRatioSamplerConfig config, bool failFast, string path)
{
if (!config.Ratio.HasValue)
{
var message = $"Sampler configuration '{path}' must define the 'ratio' property.";
Logger.Error(message);

if (failFast)
{
throw new InvalidOperationException(message);
}

return null;
}

var ratio = config.Ratio.Value;
if (ratio is < 0 or > 1)
{
var message = $"Sampler configuration '{path}' ratio must be between 0 and 1 inclusive.";
Logger.Error(message);

if (failFast)
{
throw new InvalidOperationException(message);
}

return null;
}

return new TraceIdRatioBasedSampler(ratio);
}

private static Sampler CreateParentBasedSampler(ParentBasedSamplerConfig config, bool failFast, string path)
{
var rootSampler = GetSamplerOrDefault(config.Root, new AlwaysOnSampler(), failFast, path + ".root", "always_on");
var remoteParentSampled = GetSamplerOrDefault(config.RemoteParentSampled, new AlwaysOnSampler(), failFast, path + ".remote_parent_sampled", "always_on");
var remoteParentNotSampled = GetSamplerOrDefault(config.RemoteParentNotSampled, new AlwaysOffSampler(), failFast, path + ".remote_parent_not_sampled", "always_off");
var localParentSampled = GetSamplerOrDefault(config.LocalParentSampled, new AlwaysOnSampler(), failFast, path + ".local_parent_sampled", "always_on");
var localParentNotSampled = GetSamplerOrDefault(config.LocalParentNotSampled, new AlwaysOffSampler(), failFast, path + ".local_parent_not_sampled", "always_off");

return new ParentBasedSampler(rootSampler, remoteParentSampled, remoteParentNotSampled, localParentSampled, localParentNotSampled);
}

private static Sampler GetSamplerOrDefault(SamplerConfig? samplerConfig, Sampler defaultSampler, bool failFast, string path, string defaultSamplerName)
{
if (samplerConfig == null)
{
return defaultSampler;
}

var sampler = CreateSamplerInternal(samplerConfig, failFast, path);
if (sampler != null)
{
return sampler;
}

var message = $"Sampler configuration '{path}' is invalid. Falling back to default '{defaultSamplerName}' sampler.";
Logger.Warning(message);

if (failFast)
{
throw new InvalidOperationException(message);
}

return defaultSampler;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using Vendors.YamlDotNet.Serialization;

namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration;

internal class TraceIdRatioSamplerConfig
{
[YamlMember(Alias = "ratio")]
public double? Ratio { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ internal class TracerProviderConfiguration
{
[YamlMember(Alias = "processors")]
public List<ProcessorConfig> Processors { get; set; } = new();

[YamlMember(Alias = "sampler")]
public SamplerConfig? Sampler { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration;
using OpenTelemetry.AutoInstrumentation.Configurations.Otlp;
using OpenTelemetry.AutoInstrumentation.Logging;
using OpenTelemetry.Trace;

namespace OpenTelemetry.AutoInstrumentation.Configurations;

Expand Down Expand Up @@ -63,6 +64,11 @@ internal class TracerSettings : Settings
/// </summary>
public IReadOnlyList<ProcessorConfig>? Processors { get; private set; } = null;

/// <summary>
/// Gets the sampler configured via file-based configuration.
/// </summary>
public Sampler? Sampler { get; private set; }

protected override void OnLoadEnvVar(Configuration configuration)
{
TracesExporters = ParseTracesExporter(configuration);
Expand Down Expand Up @@ -112,6 +118,8 @@ protected override void OnLoadFile(YamlConfiguration configuration)
}

Processors = processors;

Sampler = SamplerFactory.CreateSampler(configuration.TracerProvider?.Sampler, configuration.FailFast);
}

private static List<TracesExporter> ParseTracesExporter(Configuration configuration)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using System.Diagnostics;
using OpenTelemetry.AutoInstrumentation.Configurations;
using OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration;
using OpenTelemetry.Trace;
using Xunit;

namespace OpenTelemetry.AutoInstrumentation.Tests.Configurations.FileBased;
Expand Down Expand Up @@ -349,6 +351,55 @@ public void LoadMethod_SkipWrongExporterConfiguration(SkipConfigurationTestCase
Assert.Empty(settings.TracesExporters);
}

[Fact]
public void LoadFile_ConfiguresParentBasedSampler()
{
var samplerConfig = new SamplerConfig
{
ParentBased = new ParentBasedSamplerConfig
{
Root = new SamplerConfig { AlwaysOn = new object() },
RemoteParentSampled = new SamplerConfig { AlwaysOn = new object() },
RemoteParentNotSampled = new SamplerConfig { AlwaysOff = new object() },
LocalParentSampled = new SamplerConfig { AlwaysOn = new object() },
LocalParentNotSampled = new SamplerConfig { AlwaysOff = new object() }
}
};

var conf = new YamlConfiguration
{
TracerProvider = new TracerProviderConfiguration
{
Sampler = samplerConfig
}
};

var settings = new TracerSettings();

settings.LoadFile(conf);

var sampler = Assert.IsType<ParentBasedSampler>(settings.Sampler);

Assert.Equal(SamplingDecision.RecordAndSample, sampler.ShouldSample(CreateSamplingParameters(default)).Decision);

var remoteSampledParent = new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded, traceState: null, isRemote: true);
Assert.Equal(SamplingDecision.RecordAndSample, sampler.ShouldSample(CreateSamplingParameters(remoteSampledParent)).Decision);

var remoteNotSampledParent = new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None, traceState: null, isRemote: true);
Assert.Equal(SamplingDecision.Drop, sampler.ShouldSample(CreateSamplingParameters(remoteNotSampledParent)).Decision);

var localSampledParent = new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded, traceState: null, isRemote: false);
Assert.Equal(SamplingDecision.RecordAndSample, sampler.ShouldSample(CreateSamplingParameters(localSampledParent)).Decision);

var localNotSampledParent = new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None, traceState: null, isRemote: false);
Assert.Equal(SamplingDecision.Drop, sampler.ShouldSample(CreateSamplingParameters(localNotSampledParent)).Decision);
}

private static SamplingParameters CreateSamplingParameters(ActivityContext parentContext)
{
return new SamplingParameters(parentContext, ActivityTraceId.CreateRandom(), "span", ActivityKind.Internal, default(TagList), new ActivityLink[] { });
}

public class SkipConfigurationTestCase
{
internal SkipConfigurationTestCase(YamlConfiguration configuration)
Expand Down
Loading
Loading