Skip to content

Commit 673f89e

Browse files
PeterF778jack-berg
andauthored
Issue 7869 - Support the new W3C random flag (#8012)
Co-authored-by: Jack Berg <34418638+jack-berg@users.noreply.github.com>
1 parent 8a1d9e0 commit 673f89e

File tree

11 files changed

+238
-11
lines changed

11 files changed

+238
-11
lines changed

api/all/src/main/java/io/opentelemetry/api/trace/ImmutableTraceFlags.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@
1010
import javax.annotation.concurrent.Immutable;
1111

1212
@Immutable
13-
final class ImmutableTraceFlags implements TraceFlags {
13+
final class ImmutableTraceFlags implements TraceFlags, TraceFlagsBuilder {
1414
private static final ImmutableTraceFlags[] INSTANCES = buildInstances();
1515
// Bit to represent whether trace is sampled or not.
1616
private static final byte SAMPLED_BIT = 0x01;
17+
// Bit to indicate that the lower 56 bits of the trace id have been randomly generated with
18+
// uniform distribution
19+
private static final byte RANDOM_TRACE_ID_BIT = 0x02;
1720

1821
static final ImmutableTraceFlags DEFAULT = fromByte((byte) 0x00);
1922
static final ImmutableTraceFlags SAMPLED = fromByte(SAMPLED_BIT);
@@ -55,6 +58,11 @@ public boolean isSampled() {
5558
return (this.byteRep & SAMPLED_BIT) != 0;
5659
}
5760

61+
@Override
62+
public boolean isTraceIdRandom() {
63+
return (this.byteRep & RANDOM_TRACE_ID_BIT) != 0;
64+
}
65+
5866
@Override
5967
public String asHex() {
6068
return this.hexRep;
@@ -65,6 +73,26 @@ public byte asByte() {
6573
return this.byteRep;
6674
}
6775

76+
@Override
77+
public ImmutableTraceFlags setSampled(boolean isSampled) {
78+
byte newByte = isSampled ? (byte) (asByte() | SAMPLED_BIT) : (byte) (asByte() & ~SAMPLED_BIT);
79+
return fromByte(newByte);
80+
}
81+
82+
@Override
83+
public ImmutableTraceFlags setRandomTraceId(boolean isRandomTraceId) {
84+
byte newByte =
85+
isRandomTraceId
86+
? (byte) (asByte() | RANDOM_TRACE_ID_BIT)
87+
: (byte) (asByte() & ~RANDOM_TRACE_ID_BIT);
88+
return fromByte(newByte);
89+
}
90+
91+
@Override
92+
public TraceFlags build() {
93+
return this;
94+
}
95+
6896
@Override
6997
public String toString() {
7098
return asHex();

api/all/src/main/java/io/opentelemetry/api/trace/SpanContext.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@
1212
/**
1313
* A class that represents a span context. A span context contains the state that must propagate to
1414
* child {@link Span}s and across process boundaries. It contains the identifiers (a {@link TraceId
15-
* trace_id} and {@link SpanId span_id}) associated with the {@link Span} and a set of options
16-
* (currently only whether the context is sampled or not), as well as the {@link TraceState
17-
* traceState} and the {@link boolean remote} flag.
15+
* trace_id} and {@link SpanId span_id}) associated with the {@link Span}, {@link TraceFlags}, as
16+
* well as the {@link TraceState traceState} and the {@link boolean remote} flag.
1817
*
1918
* <p>Implementations of this interface *must* be immutable and have well-defined value-based
2019
* equals/hashCode implementations. If an implementation does not strictly conform to these

api/all/src/main/java/io/opentelemetry/api/trace/TraceFlags.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,18 @@ static TraceFlags fromByte(byte traceFlagsByte) {
7676
*/
7777
boolean isSampled();
7878

79+
/**
80+
* Returns {@code true} if the TraceId accompanying this {@link TraceFlags} is known to be
81+
* generated by a truly random Id generator, otherwise {@code false}. Providing default
82+
* implementation just to maintain compatibility.
83+
*
84+
* @return {@code true} if the randomTraceId bit is on for this {@link TraceFlags}, otherwise
85+
* {@code false}.
86+
*/
87+
default boolean isTraceIdRandom() {
88+
return false;
89+
}
90+
7991
/**
8092
* Returns the lowercase hex (base16) representation of this {@link TraceFlags}.
8193
*
@@ -89,4 +101,9 @@ static TraceFlags fromByte(byte traceFlagsByte) {
89101
* @return the byte representation of the {@link TraceFlags}.
90102
*/
91103
byte asByte();
104+
105+
/** Returns an instance of {@link TraceFlagsBuilder} for {@link TraceFlags}. */
106+
static TraceFlagsBuilder builder() {
107+
return ImmutableTraceFlags.DEFAULT;
108+
}
92109
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.api.trace;
7+
8+
/**
9+
* {@link TraceFlagsBuilder} is used to construct {@link TraceFlags} instances which satisfy the
10+
* https://www.w3.org/TR/trace-context-2/#trace-flags specification.
11+
*
12+
* <p>This is a simple usage example:
13+
*
14+
* <pre>{@code
15+
* TraceFlags traceFlags = TraceFlags.builder().setSampled(true).build();
16+
* }</pre>
17+
*
18+
* <p>Implementation note: no new objects are created by the methods defined by this interface when
19+
* the default implementation, {@link ImmutableTraceFlags}, is used.
20+
*/
21+
public interface TraceFlagsBuilder {
22+
23+
/**
24+
* Returns an instance of {@link TraceFlagsBuilder} which represents a {@link TraceFlags} object
25+
* which has the SAMPLED bit set if the argument is {@code true} and the SAMPLED bit cleared when
26+
* the argument is {@code false}. Other bits remain unchanged. The operation does not modify this
27+
* object.
28+
*
29+
* @param isSampled the new value for the SAMPLED bit
30+
* @return a {@link TraceFlagsBuilder} object representing the modified {@link TraceFlags}
31+
*/
32+
TraceFlagsBuilder setSampled(boolean isSampled);
33+
34+
/**
35+
* Returns an instance of {@link TraceFlagsBuilder} which represents a {@link TraceFlags} object
36+
* which has the RANDOM_TRACE_ID bit set if the argument is {@code true} and the RANDOM_TRACE_ID
37+
* bit cleared when the argument is {@code false}. Other bits remain unchanged. The operation does
38+
* not modify this object.
39+
*
40+
* @param isRandomTraceId the new value for the RANDOM_TRACE_ID bit
41+
* @return a {@link TraceFlagsBuilder} object representing the modified {@link TraceFlags}
42+
*/
43+
TraceFlagsBuilder setRandomTraceId(boolean isRandomTraceId);
44+
45+
/**
46+
* Returns {@link TraceFlags} represented by this object.
47+
*
48+
* @return a {@link TraceFlags} object with the bits set as configured
49+
*/
50+
TraceFlags build();
51+
}

api/all/src/test/java/io/opentelemetry/api/trace/TraceFlagsTest.java

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,59 @@
99

1010
import org.junit.jupiter.api.Test;
1111

12-
/** Unit tests for {@link TraceFlags}. */
12+
/** Unit tests for {@link TraceFlags} and {@link TraceFlagsBuilder}. */
1313
class TraceFlagsTest {
1414

1515
@Test
1616
void defaultInstances() {
1717
assertThat(TraceFlags.getDefault().asHex()).isEqualTo("00");
18-
assertThat(TraceFlags.getSampled().asHex()).isEqualTo("01");
18+
assertThat(TraceFlags.builder().build().asHex()).isEqualTo("00");
19+
assertThat(TraceFlags.builder().setSampled(true).build().asHex()).isEqualTo("01");
20+
assertThat(TraceFlags.builder().setSampled(false).build().asHex()).isEqualTo("00");
21+
assertThat(TraceFlags.builder().setRandomTraceId(true).build().asHex()).isEqualTo("02");
22+
assertThat(TraceFlags.builder().setRandomTraceId(false).build().asHex()).isEqualTo("00");
23+
assertThat(TraceFlags.builder().setSampled(true).setRandomTraceId(true).build().asHex())
24+
.isEqualTo("03");
25+
assertThat(TraceFlags.builder().setRandomTraceId(true).setSampled(true).build().asHex())
26+
.isEqualTo("03");
27+
}
28+
29+
@Test
30+
void idempotency() {
31+
assertThat(TraceFlags.builder().setRandomTraceId(true).setRandomTraceId(true).build().asHex())
32+
.isEqualTo("02");
33+
assertThat(TraceFlags.builder().setSampled(true).setSampled(true).build().asHex())
34+
.isEqualTo("01");
35+
}
36+
37+
@Test
38+
void setAndClear() {
39+
assertThat(TraceFlags.builder().setRandomTraceId(true).setRandomTraceId(false).build().asHex())
40+
.isEqualTo("00");
41+
assertThat(TraceFlags.builder().setSampled(true).setSampled(false).build().asHex())
42+
.isEqualTo("00");
1943
}
2044

2145
@Test
2246
void isSampled() {
2347
assertThat(TraceFlags.fromByte((byte) 0xff).isSampled()).isTrue();
2448
assertThat(TraceFlags.fromByte((byte) 0x01).isSampled()).isTrue();
49+
assertThat(TraceFlags.fromByte((byte) 0x02).isSampled()).isFalse();
50+
assertThat(TraceFlags.fromByte((byte) 0x03).isSampled()).isTrue();
2551
assertThat(TraceFlags.fromByte((byte) 0x05).isSampled()).isTrue();
2652
assertThat(TraceFlags.fromByte((byte) 0x00).isSampled()).isFalse();
2753
}
2854

55+
@Test
56+
void isTraceIdRandom() {
57+
assertThat(TraceFlags.fromByte((byte) 0xff).isTraceIdRandom()).isTrue();
58+
assertThat(TraceFlags.fromByte((byte) 0x01).isTraceIdRandom()).isFalse();
59+
assertThat(TraceFlags.fromByte((byte) 0x02).isTraceIdRandom()).isTrue();
60+
assertThat(TraceFlags.fromByte((byte) 0x03).isTraceIdRandom()).isTrue();
61+
assertThat(TraceFlags.fromByte((byte) 0x05).isTraceIdRandom()).isFalse();
62+
assertThat(TraceFlags.fromByte((byte) 0x00).isTraceIdRandom()).isFalse();
63+
}
64+
2965
@Test
3066
void toFromHex() {
3167
for (int i = 0; i < 256; i++) {
Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,11 @@
11
Comparing source compatibility of opentelemetry-api-1.60.0-SNAPSHOT.jar against opentelemetry-api-1.59.0.jar
2-
No changes.
2+
*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.api.trace.TraceFlags (not serializable)
3+
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0
4+
+++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.trace.TraceFlagsBuilder builder()
5+
+++ NEW METHOD: PUBLIC(+) boolean isTraceIdRandom()
6+
+++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.trace.TraceFlagsBuilder (not serializable)
7+
+++ CLASS FILE FORMAT VERSION: 52.0 <- n.a.
8+
+++ NEW SUPERCLASS: java.lang.Object
9+
+++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.trace.TraceFlags build()
10+
+++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.trace.TraceFlagsBuilder setRandomTraceId(boolean)
11+
+++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.trace.TraceFlagsBuilder setSampled(boolean)
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
Comparing source compatibility of opentelemetry-sdk-trace-1.60.0-SNAPSHOT.jar against opentelemetry-sdk-trace-1.59.0.jar
2-
No changes.
2+
*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.sdk.trace.IdGenerator (not serializable)
3+
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0
4+
+++ NEW METHOD: PUBLIC(+) boolean generatesRandomTraceIds()

sdk/trace/src/main/java/io/opentelemetry/sdk/trace/IdGenerator.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,16 @@ static IdGenerator random() {
3838
* @return a new valid {@code TraceId}.
3939
*/
4040
String generateTraceId();
41+
42+
/**
43+
* Declares whether TraceIds generated by this IdGenerator have their lower 56 bits uniformly
44+
* distributed over the [0..2^56-1]interval, making them compatible with W3C Trace Context Level 2
45+
* recommendation @see <a href=
46+
* "https://www.w3.org/TR/trace-context-2/#random-trace-id-flag">Random TraceId flag</a>.
47+
*
48+
* @return true if the generated TraceIds are random
49+
*/
50+
default boolean generatesRandomTraceIds() {
51+
return false;
52+
}
4153
}

sdk/trace/src/main/java/io/opentelemetry/sdk/trace/RandomIdGenerator.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ public String generateTraceId() {
3838
return TraceId.fromLongs(idHi, idLo);
3939
}
4040

41+
@Override
42+
public boolean generatesRandomTraceIds() {
43+
return true;
44+
}
45+
4146
@Override
4247
public String toString() {
4348
return "RandomIdGenerator{}";

sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
import io.opentelemetry.api.trace.Span;
1717
import io.opentelemetry.api.trace.SpanBuilder;
1818
import io.opentelemetry.api.trace.SpanContext;
19+
import io.opentelemetry.api.trace.SpanId;
1920
import io.opentelemetry.api.trace.SpanKind;
2021
import io.opentelemetry.api.trace.TraceFlags;
22+
import io.opentelemetry.api.trace.TraceId;
2123
import io.opentelemetry.api.trace.TraceState;
2224
import io.opentelemetry.context.Context;
2325
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
@@ -35,6 +37,10 @@
3537
/** {@link SdkSpanBuilder} is SDK implementation of {@link SpanBuilder}. */
3638
class SdkSpanBuilder implements SpanBuilder {
3739

40+
private static final Context ROOT_CONTEXT_WITH_RANDOM_TRACE_ID_BIT =
41+
preparePrimordialContext(
42+
TraceFlags.builder().setRandomTraceId(true).build(), TraceState.getDefault());
43+
3844
private final String spanName;
3945
private final InstrumentationScopeInfo instrumentationScopeInfo;
4046
private final TracerSharedState tracerSharedState;
@@ -58,6 +64,17 @@ class SdkSpanBuilder implements SpanBuilder {
5864
this.spanLimits = spanLimits;
5965
}
6066

67+
/*
68+
* A primordial context can be passed as the parent context for a root span
69+
* if a non-default TraceFlags or TraceState need to be passed to the sampler
70+
*/
71+
private static Context preparePrimordialContext(TraceFlags traceFlags, TraceState traceState) {
72+
SpanContext spanContext =
73+
SpanContext.create(TraceId.getInvalid(), SpanId.getInvalid(), traceFlags, traceState);
74+
Span span = Span.wrap(spanContext);
75+
return span.storeInContext(Context.root());
76+
}
77+
6178
@Override
6279
public SpanBuilder setParent(Context context) {
6380
if (context == null) {
@@ -170,14 +187,25 @@ public Span startSpan() {
170187
Span parentSpan = Span.fromContext(parentContext);
171188
SpanContext parentSpanContext = parentSpan.getSpanContext();
172189
String traceId;
190+
boolean isTraceIdRandom;
173191
IdGenerator idGenerator = tracerSharedState.getIdGenerator();
174192
String spanId = idGenerator.generateSpanId();
193+
194+
Context parentContextForSampler = parentContext;
175195
if (!parentSpanContext.isValid()) {
176196
// New root span.
177197
traceId = idGenerator.generateTraceId();
198+
if (idGenerator.generatesRandomTraceIds()) {
199+
isTraceIdRandom = true;
200+
// Replace parentContext for sampling with one with RANDOM_TRACE_ID bit set
201+
parentContextForSampler = ROOT_CONTEXT_WITH_RANDOM_TRACE_ID_BIT;
202+
} else {
203+
isTraceIdRandom = false;
204+
}
178205
} else {
179206
// New child span.
180207
traceId = parentSpanContext.getTraceId();
208+
isTraceIdRandom = parentSpanContext.getTraceFlags().isTraceIdRandom();
181209
}
182210
List<LinkData> currentLinks = links;
183211
List<LinkData> immutableLinks =
@@ -190,7 +218,12 @@ public Span startSpan() {
190218
tracerSharedState
191219
.getSampler()
192220
.shouldSample(
193-
parentContext, traceId, spanName, spanKind, immutableAttributes, immutableLinks);
221+
parentContextForSampler,
222+
traceId,
223+
spanName,
224+
spanKind,
225+
immutableAttributes,
226+
immutableLinks);
194227
SamplingDecision samplingDecision = samplingResult.getDecision();
195228

196229
TraceState samplingResultTraceState =
@@ -199,7 +232,10 @@ public Span startSpan() {
199232
ImmutableSpanContext.create(
200233
traceId,
201234
spanId,
202-
isSampled(samplingDecision) ? TraceFlags.getSampled() : TraceFlags.getDefault(),
235+
TraceFlags.builder()
236+
.setSampled(isSampled(samplingDecision))
237+
.setRandomTraceId(isTraceIdRandom)
238+
.build(),
203239
samplingResultTraceState,
204240
/* remote= */ false,
205241
tracerSharedState.isIdGeneratorSafeToSkipIdValidation());

0 commit comments

Comments
 (0)