diff --git a/OpenTelemetry.slnx b/OpenTelemetry.slnx
index 5e0d7941f0d..5c8f5df8d68 100644
--- a/OpenTelemetry.slnx
+++ b/OpenTelemetry.slnx
@@ -87,6 +87,7 @@
+
diff --git a/build/Common.props b/build/Common.props
index 44c697e393c..ff61b5f5bd0 100644
--- a/build/Common.props
+++ b/build/Common.props
@@ -12,7 +12,7 @@
all
low
- $(NoWarn);OTEL1000;OTEL1001;OTEL1002;OTEL1004
+ $(NoWarn);OTEL1000;OTEL1001;OTEL1002;OTEL1004;OTEL1005
latest-All
diff --git a/docs/diagnostics/experimental-apis/OTEL1005.md b/docs/diagnostics/experimental-apis/OTEL1005.md
new file mode 100644
index 00000000000..b46b7200687
--- /dev/null
+++ b/docs/diagnostics/experimental-apis/OTEL1005.md
@@ -0,0 +1,18 @@
+# OpenTelemetry .NET Diagnostic: OTEL1005
+
+## Overview
+
+This is an experimental API for allowing spans to always be recorded.
+
+### Details
+
+#### AlwaysRecordSampler
+
+TODO: Explanation.
+
+**Parameters:**
+
+* TODO: Details
+* `span` - a read/write span object for the span which is about to be ended.
+
+**Returns:** `TODO`
diff --git a/docs/diagnostics/experimental-apis/README.md b/docs/diagnostics/experimental-apis/README.md
index daa80d34b38..a87f927b6ba 100644
--- a/docs/diagnostics/experimental-apis/README.md
+++ b/docs/diagnostics/experimental-apis/README.md
@@ -33,6 +33,12 @@ Description: ExemplarReservoir Support
Details: [OTEL1004](./OTEL1004.md)
+### OTEL1005
+
+Description: AlwaysRecordSampler implementation for recording all spans
+
+Details: [OTEL1005](./OTEL1005.md)
+
## Inactive
Experimental APIs which have been released stable or removed:
diff --git a/src/OpenTelemetry/.publicApi/Experimental/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/Experimental/PublicAPI.Unshipped.txt
index b9507b58b38..00d40c1e9a1 100644
--- a/src/OpenTelemetry/.publicApi/Experimental/PublicAPI.Unshipped.txt
+++ b/src/OpenTelemetry/.publicApi/Experimental/PublicAPI.Unshipped.txt
@@ -25,3 +25,6 @@ OpenTelemetry.Metrics.MetricStreamConfiguration.ExemplarReservoirFactory.set ->
[OTEL1000]static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.UseOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action? configureBuilder, System.Action? configureOptions) -> Microsoft.Extensions.Logging.ILoggingBuilder!
[OTEL1001]static OpenTelemetry.Sdk.CreateLoggerProviderBuilder() -> OpenTelemetry.Logs.LoggerProviderBuilder!
[OTEL1004]virtual OpenTelemetry.Metrics.FixedSizeExemplarReservoir.OnCollected() -> void
+[OTEL1005]OpenTelemetry.Trace.AlwaysRecordSampler
+[OTEL1005]static OpenTelemetry.Trace.AlwaysRecordSampler.Create(OpenTelemetry.Trace.Sampler! rootSampler) -> OpenTelemetry.Trace.AlwaysRecordSampler!
+[OTEL1005]override OpenTelemetry.Trace.AlwaysRecordSampler.ShouldSample(in OpenTelemetry.Trace.SamplingParameters samplingParameters) -> OpenTelemetry.Trace.SamplingResult
diff --git a/src/OpenTelemetry/CHANGELOG.md b/src/OpenTelemetry/CHANGELOG.md
index c46a345cd2f..f7b4d4ea2e5 100644
--- a/src/OpenTelemetry/CHANGELOG.md
+++ b/src/OpenTelemetry/CHANGELOG.md
@@ -12,6 +12,9 @@ Notes](../../RELEASENOTES.md).
* Added support for `Meter.TelemetrySchemaUrl` property.
([#6714](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6714))
+* Added `AlwaysRecordSampler`.
+ ([#6732](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6732))
+
## 1.14.0
Released 2025-Nov-12
diff --git a/src/OpenTelemetry/Trace/Sampler/AlwaysRecordSampler.cs b/src/OpenTelemetry/Trace/Sampler/AlwaysRecordSampler.cs
new file mode 100644
index 00000000000..38192c1813a
--- /dev/null
+++ b/src/OpenTelemetry/Trace/Sampler/AlwaysRecordSampler.cs
@@ -0,0 +1,70 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+// Includes work from:
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+#if EXPOSE_EXPERIMENTAL_FEATURES
+using System.Diagnostics.CodeAnalysis;
+#endif
+using OpenTelemetry.Internal;
+
+namespace OpenTelemetry.Trace;
+
+#if EXPOSE_EXPERIMENTAL_FEATURES
+///
+/// This sampler will return the sampling result of the provided rootSampler, unless the
+/// sampling result contains the sampling decision , in which case, a
+/// new sampling result will be returned that is functionally equivalent to the original, except that
+/// it contains the sampling decision . This ensures that all
+/// spans are recorded, with no change to sampling.
+///
+/// The intended use case of this sampler is to provide a means of sending all spans to a
+/// processor without having an impact on the sampling rate. This may be desirable if a user wishes
+/// to count or otherwise measure all spans produced in a service, without incurring the cost of 100%
+/// sampling.
+///
+[Experimental(DiagnosticDefinitions.AlwaysRecordSamplerExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)]
+public
+#else
+internal
+#endif
+ sealed class AlwaysRecordSampler : Sampler
+{
+ private readonly Sampler rootSampler;
+
+ private AlwaysRecordSampler(Sampler rootSampler)
+ {
+ this.rootSampler = rootSampler;
+ this.Description = "AlwaysRecordSampler{" + rootSampler.Description + "}";
+ }
+
+ ///
+ /// Method to create an AlwaysRecordSampler.
+ ///
+ /// rootSampler to create AlwaysRecordSampler from.
+ /// Created AlwaysRecordSampler.
+ public static AlwaysRecordSampler Create(Sampler rootSampler)
+ {
+ Guard.ThrowIfNull(rootSampler);
+ return new AlwaysRecordSampler(rootSampler);
+ }
+
+ ///
+ public override SamplingResult ShouldSample(in SamplingParameters samplingParameters)
+ {
+ SamplingResult result = this.rootSampler.ShouldSample(samplingParameters);
+ if (result.Decision == SamplingDecision.Drop)
+ {
+ result = WrapResultWithRecordOnlyResult(result);
+ }
+
+ return result;
+ }
+
+ private static SamplingResult WrapResultWithRecordOnlyResult(SamplingResult result)
+ {
+ return new SamplingResult(SamplingDecision.RecordOnly, result.Attributes, result.TraceStateString);
+ }
+}
\ No newline at end of file
diff --git a/src/Shared/DiagnosticDefinitions.cs b/src/Shared/DiagnosticDefinitions.cs
index f6b449352ff..84afbcea909 100644
--- a/src/Shared/DiagnosticDefinitions.cs
+++ b/src/Shared/DiagnosticDefinitions.cs
@@ -10,6 +10,7 @@ internal static class DiagnosticDefinitions
public const string LoggerProviderExperimentalApi = "OTEL1000";
public const string LogsBridgeExperimentalApi = "OTEL1001";
public const string ExemplarReservoirExperimentalApi = "OTEL1004";
+ public const string AlwaysRecordSamplerExperimentalApi = "OTEL1005";
/* Definitions which have been released stable:
public const string ExemplarExperimentalApi = "OTEL1002";
diff --git a/test/OpenTelemetry.Tests/Trace/AlwaysRecordSamplerTests.cs b/test/OpenTelemetry.Tests/Trace/AlwaysRecordSamplerTests.cs
new file mode 100644
index 00000000000..cda19a84bdf
--- /dev/null
+++ b/test/OpenTelemetry.Tests/Trace/AlwaysRecordSamplerTests.cs
@@ -0,0 +1,95 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+// Includes work from:
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using System.Diagnostics;
+using OpenTelemetry.Tests;
+using Xunit;
+
+namespace OpenTelemetry.Trace.Tests;
+
+///
+/// AlwaysRecordSamplerTest test class.
+///
+public class AlwaysRecordSamplerTests
+{
+ ///
+ /// Tests Description is set properly with AlwaysRecordSampler keyword.
+ ///
+ [Fact]
+ public void TestGetDescription()
+ {
+ var testSampler = new TestSampler();
+ var sampler = AlwaysRecordSampler.Create(testSampler);
+ Assert.Equal("AlwaysRecordSampler{TestSampler}", sampler.Description);
+ }
+
+ ///
+ /// Test RECORD_AND_SAMPLE sampling decision.
+ ///
+ [Fact]
+ public void TestRecordAndSampleSamplingDecision()
+ {
+ ValidateShouldSample(SamplingDecision.RecordAndSample, SamplingDecision.RecordAndSample);
+ }
+
+ ///
+ /// Test RECORD_ONLY sampling decision.
+ ///
+ [Fact]
+ public void TestRecordOnlySamplingDecision()
+ {
+ ValidateShouldSample(SamplingDecision.RecordOnly, SamplingDecision.RecordOnly);
+ }
+
+ ///
+ /// Test DROP sampling decision.
+ ///
+ [Fact]
+ public void TestDropSamplingDecision()
+ {
+ ValidateShouldSample(SamplingDecision.Drop, SamplingDecision.RecordOnly);
+ }
+
+ private static SamplingResult BuildRootSamplingResult(SamplingDecision samplingDecision)
+ {
+ ActivityTagsCollection? attributes = new ActivityTagsCollection
+ {
+ { "key", samplingDecision.GetType().Name },
+ };
+ string traceState = samplingDecision.GetType().Name;
+#pragma warning disable CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types.
+ return new SamplingResult(samplingDecision, attributes, traceState);
+#pragma warning restore CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types.
+ }
+
+ private static void ValidateShouldSample(
+ SamplingDecision rootDecision, SamplingDecision expectedDecision)
+ {
+ SamplingResult rootResult = BuildRootSamplingResult(rootDecision);
+ var testSampler = new TestSampler { SamplingAction = _ => rootResult };
+ var sampler = AlwaysRecordSampler.Create(testSampler);
+
+ SamplingParameters samplingParameters = new SamplingParameters(
+ default, default, "name", ActivityKind.Client, new ActivityTagsCollection(), new List());
+
+ SamplingResult actualResult = sampler.ShouldSample(samplingParameters);
+
+ if (rootDecision.Equals(expectedDecision))
+ {
+ Assert.True(actualResult.Equals(rootResult));
+ Assert.True(actualResult.Decision.Equals(rootDecision));
+ }
+ else
+ {
+ Assert.False(actualResult.Equals(rootResult));
+ Assert.True(actualResult.Decision.Equals(expectedDecision));
+ }
+
+ Assert.Equal(rootResult.Attributes, actualResult.Attributes);
+ Assert.Equal(rootDecision.GetType().Name, actualResult.TraceStateString);
+ }
+}