Skip to content

Commit f912bdf

Browse files
committed
Added AlwaysRecordSampler.
1 parent d855a7a commit f912bdf

File tree

9 files changed

+198
-1
lines changed

9 files changed

+198
-1
lines changed

OpenTelemetry.slnx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
<File Path="docs/diagnostics/experimental-apis/OTEL1000.md" />
8888
<File Path="docs/diagnostics/experimental-apis/OTEL1001.md" />
8989
<File Path="docs/diagnostics/experimental-apis/OTEL1004.md" />
90+
<File Path="docs/diagnostics/experimental-apis/OTEL1005.md" />
9091
<File Path="docs/diagnostics/experimental-apis/README.md" />
9192
</Folder>
9293
<Folder Name="/docs/logs/">

build/Common.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<NuGetAuditMode>all</NuGetAuditMode>
1313
<NuGetAuditLevel>low</NuGetAuditLevel>
1414
<!-- Suppress warnings for repo code using experimental features -->
15-
<NoWarn>$(NoWarn);OTEL1000;OTEL1001;OTEL1002;OTEL1004</NoWarn>
15+
<NoWarn>$(NoWarn);OTEL1000;OTEL1001;OTEL1002;OTEL1004;OTEL1005</NoWarn>
1616
<AnalysisLevel>latest-All</AnalysisLevel>
1717
</PropertyGroup>
1818

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# OpenTelemetry .NET Diagnostic: OTEL1005
2+
3+
## Overview
4+
5+
This is an experimental API for allowing spans to always be recorded.
6+
7+
### Details
8+
9+
#### AlwaysRecordSampler
10+
11+
TODO: Explanation.
12+
13+
**Parameters:**
14+
15+
* TODO: Details
16+
* `span` - a read/write span object for the span which is about to be ended.
17+
18+
**Returns:** `TODO`

docs/diagnostics/experimental-apis/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ Description: ExemplarReservoir Support
3333

3434
Details: [OTEL1004](./OTEL1004.md)
3535

36+
### OTEL1005
37+
38+
Description: AlwaysRecordSampler implementation for recording all spans
39+
40+
Details: [OTEL1005](./OTEL1005.md)
41+
3642
## Inactive
3743

3844
Experimental APIs which have been released stable or removed:

src/OpenTelemetry/.publicApi/Experimental/PublicAPI.Unshipped.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,6 @@ OpenTelemetry.Metrics.MetricStreamConfiguration.ExemplarReservoirFactory.set ->
2525
[OTEL1000]static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.UseOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action<OpenTelemetry.Logs.LoggerProviderBuilder!>? configureBuilder, System.Action<OpenTelemetry.Logs.OpenTelemetryLoggerOptions!>? configureOptions) -> Microsoft.Extensions.Logging.ILoggingBuilder!
2626
[OTEL1001]static OpenTelemetry.Sdk.CreateLoggerProviderBuilder() -> OpenTelemetry.Logs.LoggerProviderBuilder!
2727
[OTEL1004]virtual OpenTelemetry.Metrics.FixedSizeExemplarReservoir.OnCollected() -> void
28+
[OTEL1005]OpenTelemetry.Trace.AlwaysRecordSampler
29+
[OTEL1005]static OpenTelemetry.Trace.AlwaysRecordSampler.Create(OpenTelemetry.Trace.Sampler! rootSampler) -> OpenTelemetry.Trace.AlwaysRecordSampler!
30+
[OTEL1005]override OpenTelemetry.Trace.AlwaysRecordSampler.ShouldSample(in OpenTelemetry.Trace.SamplingParameters samplingParameters) -> OpenTelemetry.Trace.SamplingResult

src/OpenTelemetry/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ Notes](../../RELEASENOTES.md).
1212
* Added support for `Meter.TelemetrySchemaUrl` property.
1313
([#6714](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6714))
1414

15+
* Added `AlwaysRecordSampler`.
16+
([#6732](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6732))
17+
1518
## 1.14.0
1619

1720
Released 2025-Nov-12
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Includes work from:
5+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
6+
// SPDX-License-Identifier: Apache-2.0
7+
8+
#if EXPOSE_EXPERIMENTAL_FEATURES
9+
using System.Diagnostics.CodeAnalysis;
10+
#endif
11+
using OpenTelemetry.Internal;
12+
13+
namespace OpenTelemetry.Trace;
14+
15+
#if EXPOSE_EXPERIMENTAL_FEATURES
16+
/// <summary>
17+
/// This sampler will return the sampling result of the provided rootSampler, unless the
18+
/// sampling result contains the sampling decision <see cref="SamplingDecision.Drop"/>, in which case, a
19+
/// new sampling result will be returned that is functionally equivalent to the original, except that
20+
/// it contains the sampling decision <see cref="SamplingDecision.RecordOnly"/>. This ensures that all
21+
/// spans are recorded, with no change to sampling.
22+
///
23+
/// The intended use case of this sampler is to provide a means of sending all spans to a
24+
/// processor without having an impact on the sampling rate. This may be desirable if a user wishes
25+
/// to count or otherwise measure all spans produced in a service, without incurring the cost of 100%
26+
/// sampling.
27+
/// </summary>
28+
[Experimental(DiagnosticDefinitions.AlwaysRecordSamplerExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)]
29+
public
30+
#else
31+
internal
32+
#endif
33+
sealed class AlwaysRecordSampler : Sampler
34+
{
35+
private readonly Sampler rootSampler;
36+
37+
private AlwaysRecordSampler(Sampler rootSampler)
38+
{
39+
this.rootSampler = rootSampler;
40+
this.Description = "AlwaysRecordSampler{" + rootSampler.Description + "}";
41+
}
42+
43+
/// <summary>
44+
/// Method to create an AlwaysRecordSampler.
45+
/// </summary>
46+
/// <param name="rootSampler"><see cref="Sampler"/>rootSampler to create AlwaysRecordSampler from.</param>
47+
/// <returns>Created AlwaysRecordSampler.</returns>
48+
public static AlwaysRecordSampler Create(Sampler rootSampler)
49+
{
50+
Guard.ThrowIfNull(rootSampler);
51+
return new AlwaysRecordSampler(rootSampler);
52+
}
53+
54+
/// <inheritdoc/>
55+
public override SamplingResult ShouldSample(in SamplingParameters samplingParameters)
56+
{
57+
SamplingResult result = this.rootSampler.ShouldSample(samplingParameters);
58+
if (result.Decision == SamplingDecision.Drop)
59+
{
60+
result = WrapResultWithRecordOnlyResult(result);
61+
}
62+
63+
return result;
64+
}
65+
66+
private static SamplingResult WrapResultWithRecordOnlyResult(SamplingResult result)
67+
{
68+
return new SamplingResult(SamplingDecision.RecordOnly, result.Attributes, result.TraceStateString);
69+
}
70+
}

src/Shared/DiagnosticDefinitions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ internal static class DiagnosticDefinitions
1010
public const string LoggerProviderExperimentalApi = "OTEL1000";
1111
public const string LogsBridgeExperimentalApi = "OTEL1001";
1212
public const string ExemplarReservoirExperimentalApi = "OTEL1004";
13+
public const string AlwaysRecordSamplerExperimentalApi = "OTEL1005";
1314

1415
/* Definitions which have been released stable:
1516
public const string ExemplarExperimentalApi = "OTEL1002";
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Includes work from:
5+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
6+
// SPDX-License-Identifier: Apache-2.0
7+
8+
using System.Diagnostics;
9+
using OpenTelemetry.Tests;
10+
using Xunit;
11+
12+
namespace OpenTelemetry.Trace.Tests;
13+
14+
/// <summary>
15+
/// AlwaysRecordSamplerTest test class.
16+
/// </summary>
17+
public class AlwaysRecordSamplerTests
18+
{
19+
/// <summary>
20+
/// Tests Description is set properly with AlwaysRecordSampler keyword.
21+
/// </summary>
22+
[Fact]
23+
public void TestGetDescription()
24+
{
25+
var testSampler = new TestSampler();
26+
var sampler = AlwaysRecordSampler.Create(testSampler);
27+
Assert.Equal("AlwaysRecordSampler{TestSampler}", sampler.Description);
28+
}
29+
30+
/// <summary>
31+
/// Test RECORD_AND_SAMPLE sampling decision.
32+
/// </summary>
33+
[Fact]
34+
public void TestRecordAndSampleSamplingDecision()
35+
{
36+
ValidateShouldSample(SamplingDecision.RecordAndSample, SamplingDecision.RecordAndSample);
37+
}
38+
39+
/// <summary>
40+
/// Test RECORD_ONLY sampling decision.
41+
/// </summary>
42+
[Fact]
43+
public void TestRecordOnlySamplingDecision()
44+
{
45+
ValidateShouldSample(SamplingDecision.RecordOnly, SamplingDecision.RecordOnly);
46+
}
47+
48+
/// <summary>
49+
/// Test DROP sampling decision.
50+
/// </summary>
51+
[Fact]
52+
public void TestDropSamplingDecision()
53+
{
54+
ValidateShouldSample(SamplingDecision.Drop, SamplingDecision.RecordOnly);
55+
}
56+
57+
private static SamplingResult BuildRootSamplingResult(SamplingDecision samplingDecision)
58+
{
59+
ActivityTagsCollection? attributes = new ActivityTagsCollection
60+
{
61+
{ "key", samplingDecision.GetType().Name },
62+
};
63+
string traceState = samplingDecision.GetType().Name;
64+
#pragma warning disable CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types.
65+
return new SamplingResult(samplingDecision, attributes, traceState);
66+
#pragma warning restore CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types.
67+
}
68+
69+
private static void ValidateShouldSample(
70+
SamplingDecision rootDecision, SamplingDecision expectedDecision)
71+
{
72+
SamplingResult rootResult = BuildRootSamplingResult(rootDecision);
73+
var testSampler = new TestSampler { SamplingAction = _ => rootResult };
74+
var sampler = AlwaysRecordSampler.Create(testSampler);
75+
76+
SamplingParameters samplingParameters = new SamplingParameters(
77+
default, default, "name", ActivityKind.Client, new ActivityTagsCollection(), new List<ActivityLink>());
78+
79+
SamplingResult actualResult = sampler.ShouldSample(samplingParameters);
80+
81+
if (rootDecision.Equals(expectedDecision))
82+
{
83+
Assert.True(actualResult.Equals(rootResult));
84+
Assert.True(actualResult.Decision.Equals(rootDecision));
85+
}
86+
else
87+
{
88+
Assert.False(actualResult.Equals(rootResult));
89+
Assert.True(actualResult.Decision.Equals(expectedDecision));
90+
}
91+
92+
Assert.Equal(rootResult.Attributes, actualResult.Attributes);
93+
Assert.Equal(rootDecision.GetType().Name, actualResult.TraceStateString);
94+
}
95+
}

0 commit comments

Comments
 (0)