Skip to content

Commit 8e332fc

Browse files
Add AlwaysRecordSampler (#7877)
1 parent a8a69be commit 8e332fc

File tree

3 files changed

+208
-0
lines changed

3 files changed

+208
-0
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## Unreleased
44

5+
* Add AlwaysRecordSampler
6+
([#7877](https://github.com/open-telemetry/opentelemetry-java/pull/7877))
7+
58
## Version 1.58.0 (2026-01-09)
69

710
### API
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
// Includes work from:
7+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
8+
// SPDX-License-Identifier: Apache-2.0
9+
10+
package io.opentelemetry.sdk.extension.incubator.trace.samplers;
11+
12+
import io.opentelemetry.api.common.Attributes;
13+
import io.opentelemetry.api.trace.SpanKind;
14+
import io.opentelemetry.api.trace.TraceState;
15+
import io.opentelemetry.context.Context;
16+
import io.opentelemetry.sdk.trace.data.LinkData;
17+
import io.opentelemetry.sdk.trace.samplers.Sampler;
18+
import io.opentelemetry.sdk.trace.samplers.SamplingDecision;
19+
import io.opentelemetry.sdk.trace.samplers.SamplingResult;
20+
import java.util.List;
21+
import javax.annotation.concurrent.Immutable;
22+
23+
/**
24+
* This sampler will return the sampling result of the provided {@link #rootSampler}, unless the
25+
* sampling result contains the sampling decision {@link SamplingDecision#DROP}, in which case, a
26+
* new sampling result will be returned that is functionally equivalent to the original, except that
27+
* it contains the sampling decision {@link SamplingDecision#RECORD_ONLY}. This ensures that all
28+
* spans are recorded, with no change to sampling.
29+
*
30+
* <p>An intended use case of this sampler is to provide a means of sending all spans to a processor
31+
* without having an impact on the sampling rate. This may be desirable if a user wishes to count or
32+
* otherwise measure all spans produced in a service, without incurring the cost of 100% sampling.
33+
*
34+
* <p>This class is internal and experimental. Its APIs are unstable and can change at any time. Its
35+
* APIs (or a version of them) may be promoted to the public stable API in the future, but no
36+
* guarantees are made.
37+
*/
38+
@Immutable
39+
public final class AlwaysRecordSampler implements Sampler {
40+
41+
private final Sampler rootSampler;
42+
43+
public static AlwaysRecordSampler create(Sampler rootSampler) {
44+
return new AlwaysRecordSampler(rootSampler);
45+
}
46+
47+
private AlwaysRecordSampler(Sampler rootSampler) {
48+
this.rootSampler = rootSampler;
49+
}
50+
51+
@Override
52+
public SamplingResult shouldSample(
53+
Context parentContext,
54+
String traceId,
55+
String name,
56+
SpanKind spanKind,
57+
Attributes attributes,
58+
List<LinkData> parentLinks) {
59+
SamplingResult result =
60+
rootSampler.shouldSample(parentContext, traceId, name, spanKind, attributes, parentLinks);
61+
if (result.getDecision() != SamplingDecision.DROP) {
62+
return result;
63+
}
64+
65+
return new RecordOnlyDelegateSamplingResult(result);
66+
}
67+
68+
@Override
69+
public String getDescription() {
70+
return "AlwaysRecordSampler{" + rootSampler.getDescription() + "}";
71+
}
72+
73+
private static class RecordOnlyDelegateSamplingResult implements SamplingResult {
74+
private final SamplingResult delegate;
75+
76+
private RecordOnlyDelegateSamplingResult(SamplingResult delegate) {
77+
this.delegate = delegate;
78+
}
79+
80+
@Override
81+
public SamplingDecision getDecision() {
82+
return SamplingDecision.RECORD_ONLY;
83+
}
84+
85+
@Override
86+
public Attributes getAttributes() {
87+
return delegate.getAttributes();
88+
}
89+
90+
@Override
91+
public TraceState getUpdatedTraceState(TraceState parentTraceState) {
92+
return delegate.getUpdatedTraceState(parentTraceState);
93+
}
94+
}
95+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
// Includes work from:
7+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
8+
// SPDX-License-Identifier: Apache-2.0
9+
10+
package io.opentelemetry.sdk.extension.incubator.trace.samplers;
11+
12+
import static org.assertj.core.api.Assertions.assertThat;
13+
import static org.mockito.ArgumentMatchers.any;
14+
import static org.mockito.ArgumentMatchers.anyString;
15+
import static org.mockito.Mockito.mock;
16+
import static org.mockito.Mockito.when;
17+
18+
import io.opentelemetry.api.common.AttributeKey;
19+
import io.opentelemetry.api.common.Attributes;
20+
import io.opentelemetry.api.trace.SpanKind;
21+
import io.opentelemetry.api.trace.TraceId;
22+
import io.opentelemetry.api.trace.TraceState;
23+
import io.opentelemetry.context.Context;
24+
import io.opentelemetry.sdk.trace.samplers.Sampler;
25+
import io.opentelemetry.sdk.trace.samplers.SamplingDecision;
26+
import io.opentelemetry.sdk.trace.samplers.SamplingResult;
27+
import java.util.Collections;
28+
import java.util.stream.Stream;
29+
import org.junit.jupiter.api.BeforeEach;
30+
import org.junit.jupiter.api.Test;
31+
import org.junit.jupiter.params.ParameterizedTest;
32+
import org.junit.jupiter.params.provider.Arguments;
33+
import org.junit.jupiter.params.provider.MethodSource;
34+
35+
/** Unit tests for {@link AlwaysRecordSampler}. */
36+
class AlwaysRecordSamplerTest {
37+
38+
// Mocks
39+
private Sampler mockSampler;
40+
41+
private AlwaysRecordSampler sampler;
42+
43+
@BeforeEach
44+
void setUpSamplers() {
45+
mockSampler = mock(Sampler.class);
46+
sampler = AlwaysRecordSampler.create(mockSampler);
47+
}
48+
49+
@Test
50+
void getDescription() {
51+
when(mockSampler.getDescription()).thenReturn("mockDescription");
52+
assertThat(sampler.getDescription()).isEqualTo("AlwaysRecordSampler{mockDescription}");
53+
}
54+
55+
private static Stream<Arguments> expectedSamplingDecisionArgs() {
56+
return Stream.of(
57+
Arguments.of(SamplingDecision.RECORD_AND_SAMPLE, SamplingDecision.RECORD_AND_SAMPLE),
58+
Arguments.of(SamplingDecision.RECORD_ONLY, SamplingDecision.RECORD_ONLY),
59+
Arguments.of(SamplingDecision.DROP, SamplingDecision.RECORD_ONLY));
60+
}
61+
62+
@ParameterizedTest
63+
@MethodSource("expectedSamplingDecisionArgs")
64+
void shouldSampleReturnsExpectedDecision(
65+
SamplingDecision rootDecision, SamplingDecision expectedDecision) {
66+
SamplingResult rootResult = buildRootSamplingResult(rootDecision);
67+
when(mockSampler.shouldSample(any(), anyString(), anyString(), any(), any(), any()))
68+
.thenReturn(rootResult);
69+
SamplingResult actualResult =
70+
sampler.shouldSample(
71+
Context.current(),
72+
TraceId.fromLongs(1, 2),
73+
"name",
74+
SpanKind.CLIENT,
75+
Attributes.empty(),
76+
Collections.emptyList());
77+
78+
if (rootDecision.equals(expectedDecision)) {
79+
assertThat(actualResult).isEqualTo(rootResult);
80+
assertThat(actualResult.getDecision()).isEqualTo(rootDecision);
81+
} else {
82+
assertThat(actualResult).isNotEqualTo(rootResult);
83+
assertThat(actualResult.getDecision()).isEqualTo(expectedDecision);
84+
}
85+
86+
assertThat(actualResult.getAttributes()).isEqualTo(rootResult.getAttributes());
87+
TraceState traceState = TraceState.builder().build();
88+
assertThat(actualResult.getUpdatedTraceState(traceState))
89+
.isEqualTo(rootResult.getUpdatedTraceState(traceState));
90+
}
91+
92+
private static SamplingResult buildRootSamplingResult(SamplingDecision samplingDecision) {
93+
return new SamplingResult() {
94+
@Override
95+
public SamplingDecision getDecision() {
96+
return samplingDecision;
97+
}
98+
99+
@Override
100+
public Attributes getAttributes() {
101+
return Attributes.of(AttributeKey.stringKey("key"), samplingDecision.name());
102+
}
103+
104+
@Override
105+
public TraceState getUpdatedTraceState(TraceState parentTraceState) {
106+
return TraceState.builder().put("key", samplingDecision.name()).build();
107+
}
108+
};
109+
}
110+
}

0 commit comments

Comments
 (0)