diff --git a/api/all/src/main/java/io/opentelemetry/api/trace/ImmutableTraceFlags.java b/api/all/src/main/java/io/opentelemetry/api/trace/ImmutableTraceFlags.java index 7cd15f6077a..ca007995abe 100644 --- a/api/all/src/main/java/io/opentelemetry/api/trace/ImmutableTraceFlags.java +++ b/api/all/src/main/java/io/opentelemetry/api/trace/ImmutableTraceFlags.java @@ -10,10 +10,13 @@ import javax.annotation.concurrent.Immutable; @Immutable -final class ImmutableTraceFlags implements TraceFlags { +final class ImmutableTraceFlags implements TraceFlags, TraceFlagsBuilder { private static final ImmutableTraceFlags[] INSTANCES = buildInstances(); // Bit to represent whether trace is sampled or not. private static final byte SAMPLED_BIT = 0x01; + // Bit to indicate that the lower 56 bits of the trace id have been randomly generated with + // uniform distribution + private static final byte RANDOM_TRACE_ID_BIT = 0x02; static final ImmutableTraceFlags DEFAULT = fromByte((byte) 0x00); static final ImmutableTraceFlags SAMPLED = fromByte(SAMPLED_BIT); @@ -55,6 +58,11 @@ public boolean isSampled() { return (this.byteRep & SAMPLED_BIT) != 0; } + @Override + public boolean isTraceIdRandom() { + return (this.byteRep & RANDOM_TRACE_ID_BIT) != 0; + } + @Override public String asHex() { return this.hexRep; @@ -65,6 +73,26 @@ public byte asByte() { return this.byteRep; } + @Override + public ImmutableTraceFlags setSampled(boolean isSampled) { + byte newByte = isSampled ? (byte) (asByte() | SAMPLED_BIT) : (byte) (asByte() & ~SAMPLED_BIT); + return fromByte(newByte); + } + + @Override + public ImmutableTraceFlags setRandomTraceId(boolean isRandomTraceId) { + byte newByte = + isRandomTraceId + ? (byte) (asByte() | RANDOM_TRACE_ID_BIT) + : (byte) (asByte() & ~RANDOM_TRACE_ID_BIT); + return fromByte(newByte); + } + + @Override + public TraceFlags build() { + return this; + } + @Override public String toString() { return asHex(); diff --git a/api/all/src/main/java/io/opentelemetry/api/trace/SpanContext.java b/api/all/src/main/java/io/opentelemetry/api/trace/SpanContext.java index f7832c62b07..c01f1b8e961 100644 --- a/api/all/src/main/java/io/opentelemetry/api/trace/SpanContext.java +++ b/api/all/src/main/java/io/opentelemetry/api/trace/SpanContext.java @@ -12,9 +12,8 @@ /** * A class that represents a span context. A span context contains the state that must propagate to * child {@link Span}s and across process boundaries. It contains the identifiers (a {@link TraceId - * trace_id} and {@link SpanId span_id}) associated with the {@link Span} and a set of options - * (currently only whether the context is sampled or not), as well as the {@link TraceState - * traceState} and the {@link boolean remote} flag. + * trace_id} and {@link SpanId span_id}) associated with the {@link Span}, {@link TraceFlags}, as + * well as the {@link TraceState traceState} and the {@link boolean remote} flag. * *
Implementations of this interface *must* be immutable and have well-defined value-based * equals/hashCode implementations. If an implementation does not strictly conform to these diff --git a/api/all/src/main/java/io/opentelemetry/api/trace/TraceFlags.java b/api/all/src/main/java/io/opentelemetry/api/trace/TraceFlags.java index 936a1a6e06c..9f918b7c8e5 100644 --- a/api/all/src/main/java/io/opentelemetry/api/trace/TraceFlags.java +++ b/api/all/src/main/java/io/opentelemetry/api/trace/TraceFlags.java @@ -76,6 +76,18 @@ static TraceFlags fromByte(byte traceFlagsByte) { */ boolean isSampled(); + /** + * Returns {@code true} if the TraceId accompanying this {@link TraceFlags} is known to be + * generated by a truly random Id generator, otherwise {@code false}. Providing default + * implementation just to maintain compatibility. + * + * @return {@code true} if the randomTraceId bit is on for this {@link TraceFlags}, otherwise + * {@code false}. + */ + default boolean isTraceIdRandom() { + return false; + } + /** * Returns the lowercase hex (base16) representation of this {@link TraceFlags}. * @@ -89,4 +101,9 @@ static TraceFlags fromByte(byte traceFlagsByte) { * @return the byte representation of the {@link TraceFlags}. */ byte asByte(); + + /** Returns an instance of {@link TraceFlagsBuilder} for {@link TraceFlags}. */ + static TraceFlagsBuilder builder() { + return ImmutableTraceFlags.DEFAULT; + } } diff --git a/api/all/src/main/java/io/opentelemetry/api/trace/TraceFlagsBuilder.java b/api/all/src/main/java/io/opentelemetry/api/trace/TraceFlagsBuilder.java new file mode 100644 index 00000000000..de95a9a43f5 --- /dev/null +++ b/api/all/src/main/java/io/opentelemetry/api/trace/TraceFlagsBuilder.java @@ -0,0 +1,51 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.trace; + +/** + * {@link TraceFlagsBuilder} is used to construct {@link TraceFlags} instances which satisfy the + * https://www.w3.org/TR/trace-context-2/#trace-flags specification. + * + *
This is a simple usage example: + * + *
{@code
+ * TraceFlags traceFlags = TraceFlags.builder().setSampled(true).build();
+ * }
+ *
+ * Implementation note: no new objects are created by the methods defined by this interface when
+ * the default implementation, {@link ImmutableTraceFlags}, is used.
+ */
+public interface TraceFlagsBuilder {
+
+ /**
+ * Returns an instance of {@link TraceFlagsBuilder} which represents a {@link TraceFlags} object
+ * which has the SAMPLED bit set if the argument is {@code true} and the SAMPLED bit cleared when
+ * the argument is {@code false}. Other bits remain unchanged. The operation does not modify this
+ * object.
+ *
+ * @param isSampled the new value for the SAMPLED bit
+ * @return a {@link TraceFlagsBuilder} object representing the modified {@link TraceFlags}
+ */
+ TraceFlagsBuilder setSampled(boolean isSampled);
+
+ /**
+ * Returns an instance of {@link TraceFlagsBuilder} which represents a {@link TraceFlags} object
+ * which has the RANDOM_TRACE_ID bit set if the argument is {@code true} and the RANDOM_TRACE_ID
+ * bit cleared when the argument is {@code false}. Other bits remain unchanged. The operation does
+ * not modify this object.
+ *
+ * @param isRandomTraceId the new value for the RANDOM_TRACE_ID bit
+ * @return a {@link TraceFlagsBuilder} object representing the modified {@link TraceFlags}
+ */
+ TraceFlagsBuilder setRandomTraceId(boolean isRandomTraceId);
+
+ /**
+ * Returns {@link TraceFlags} represented by this object.
+ *
+ * @return a {@link TraceFlags} object with the bits set as configured
+ */
+ TraceFlags build();
+}
diff --git a/api/all/src/test/java/io/opentelemetry/api/trace/TraceFlagsTest.java b/api/all/src/test/java/io/opentelemetry/api/trace/TraceFlagsTest.java
index d6ef529f004..fc5c52e8ed5 100644
--- a/api/all/src/test/java/io/opentelemetry/api/trace/TraceFlagsTest.java
+++ b/api/all/src/test/java/io/opentelemetry/api/trace/TraceFlagsTest.java
@@ -9,23 +9,59 @@
import org.junit.jupiter.api.Test;
-/** Unit tests for {@link TraceFlags}. */
+/** Unit tests for {@link TraceFlags} and {@link TraceFlagsBuilder}. */
class TraceFlagsTest {
@Test
void defaultInstances() {
assertThat(TraceFlags.getDefault().asHex()).isEqualTo("00");
- assertThat(TraceFlags.getSampled().asHex()).isEqualTo("01");
+ assertThat(TraceFlags.builder().build().asHex()).isEqualTo("00");
+ assertThat(TraceFlags.builder().setSampled(true).build().asHex()).isEqualTo("01");
+ assertThat(TraceFlags.builder().setSampled(false).build().asHex()).isEqualTo("00");
+ assertThat(TraceFlags.builder().setRandomTraceId(true).build().asHex()).isEqualTo("02");
+ assertThat(TraceFlags.builder().setRandomTraceId(false).build().asHex()).isEqualTo("00");
+ assertThat(TraceFlags.builder().setSampled(true).setRandomTraceId(true).build().asHex())
+ .isEqualTo("03");
+ assertThat(TraceFlags.builder().setRandomTraceId(true).setSampled(true).build().asHex())
+ .isEqualTo("03");
+ }
+
+ @Test
+ void idempotency() {
+ assertThat(TraceFlags.builder().setRandomTraceId(true).setRandomTraceId(true).build().asHex())
+ .isEqualTo("02");
+ assertThat(TraceFlags.builder().setSampled(true).setSampled(true).build().asHex())
+ .isEqualTo("01");
+ }
+
+ @Test
+ void setAndClear() {
+ assertThat(TraceFlags.builder().setRandomTraceId(true).setRandomTraceId(false).build().asHex())
+ .isEqualTo("00");
+ assertThat(TraceFlags.builder().setSampled(true).setSampled(false).build().asHex())
+ .isEqualTo("00");
}
@Test
void isSampled() {
assertThat(TraceFlags.fromByte((byte) 0xff).isSampled()).isTrue();
assertThat(TraceFlags.fromByte((byte) 0x01).isSampled()).isTrue();
+ assertThat(TraceFlags.fromByte((byte) 0x02).isSampled()).isFalse();
+ assertThat(TraceFlags.fromByte((byte) 0x03).isSampled()).isTrue();
assertThat(TraceFlags.fromByte((byte) 0x05).isSampled()).isTrue();
assertThat(TraceFlags.fromByte((byte) 0x00).isSampled()).isFalse();
}
+ @Test
+ void isTraceIdRandom() {
+ assertThat(TraceFlags.fromByte((byte) 0xff).isTraceIdRandom()).isTrue();
+ assertThat(TraceFlags.fromByte((byte) 0x01).isTraceIdRandom()).isFalse();
+ assertThat(TraceFlags.fromByte((byte) 0x02).isTraceIdRandom()).isTrue();
+ assertThat(TraceFlags.fromByte((byte) 0x03).isTraceIdRandom()).isTrue();
+ assertThat(TraceFlags.fromByte((byte) 0x05).isTraceIdRandom()).isFalse();
+ assertThat(TraceFlags.fromByte((byte) 0x00).isTraceIdRandom()).isFalse();
+ }
+
@Test
void toFromHex() {
for (int i = 0; i < 256; i++) {
diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt
index 6bd0e35a4a7..08d05547f55 100644
--- a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt
+++ b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt
@@ -1,2 +1,11 @@
Comparing source compatibility of opentelemetry-api-1.60.0-SNAPSHOT.jar against opentelemetry-api-1.59.0.jar
-No changes.
\ No newline at end of file
+*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.api.trace.TraceFlags (not serializable)
+ === CLASS FILE FORMAT VERSION: 52.0 <- 52.0
+ +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.trace.TraceFlagsBuilder builder()
+ +++ NEW METHOD: PUBLIC(+) boolean isTraceIdRandom()
++++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.trace.TraceFlagsBuilder (not serializable)
+ +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a.
+ +++ NEW SUPERCLASS: java.lang.Object
+ +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.trace.TraceFlags build()
+ +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.trace.TraceFlagsBuilder setRandomTraceId(boolean)
+ +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.trace.TraceFlagsBuilder setSampled(boolean)
diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt
index e62044ac46a..5e8cfb5214e 100644
--- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt
+++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt
@@ -1,2 +1,4 @@
Comparing source compatibility of opentelemetry-sdk-trace-1.60.0-SNAPSHOT.jar against opentelemetry-sdk-trace-1.59.0.jar
-No changes.
\ No newline at end of file
+*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.sdk.trace.IdGenerator (not serializable)
+ === CLASS FILE FORMAT VERSION: 52.0 <- 52.0
+ +++ NEW METHOD: PUBLIC(+) boolean generatesRandomTraceIds()
diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/IdGenerator.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/IdGenerator.java
index e0ecd8efac5..50c808b660f 100644
--- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/IdGenerator.java
+++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/IdGenerator.java
@@ -38,4 +38,16 @@ static IdGenerator random() {
* @return a new valid {@code TraceId}.
*/
String generateTraceId();
+
+ /**
+ * Declares whether TraceIds generated by this IdGenerator have their lower 56 bits uniformly
+ * distributed over the [0..2^56-1]interval, making them compatible with W3C Trace Context Level 2
+ * recommendation @see Random TraceId flag.
+ *
+ * @return true if the generated TraceIds are random
+ */
+ default boolean generatesRandomTraceIds() {
+ return false;
+ }
}
diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/RandomIdGenerator.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/RandomIdGenerator.java
index 14045a5a923..5456afcbb39 100644
--- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/RandomIdGenerator.java
+++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/RandomIdGenerator.java
@@ -38,6 +38,11 @@ public String generateTraceId() {
return TraceId.fromLongs(idHi, idLo);
}
+ @Override
+ public boolean generatesRandomTraceIds() {
+ return true;
+ }
+
@Override
public String toString() {
return "RandomIdGenerator{}";
diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java
index 3ba8d575dd1..f8f70724644 100644
--- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java
+++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java
@@ -16,8 +16,10 @@
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanBuilder;
import io.opentelemetry.api.trace.SpanContext;
+import io.opentelemetry.api.trace.SpanId;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.TraceFlags;
+import io.opentelemetry.api.trace.TraceId;
import io.opentelemetry.api.trace.TraceState;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
@@ -35,6 +37,10 @@
/** {@link SdkSpanBuilder} is SDK implementation of {@link SpanBuilder}. */
class SdkSpanBuilder implements SpanBuilder {
+ private static final Context ROOT_CONTEXT_WITH_RANDOM_TRACE_ID_BIT =
+ preparePrimordialContext(
+ TraceFlags.builder().setRandomTraceId(true).build(), TraceState.getDefault());
+
private final String spanName;
private final InstrumentationScopeInfo instrumentationScopeInfo;
private final TracerSharedState tracerSharedState;
@@ -58,6 +64,17 @@ class SdkSpanBuilder implements SpanBuilder {
this.spanLimits = spanLimits;
}
+ /*
+ * A primordial context can be passed as the parent context for a root span
+ * if a non-default TraceFlags or TraceState need to be passed to the sampler
+ */
+ private static Context preparePrimordialContext(TraceFlags traceFlags, TraceState traceState) {
+ SpanContext spanContext =
+ SpanContext.create(TraceId.getInvalid(), SpanId.getInvalid(), traceFlags, traceState);
+ Span span = Span.wrap(spanContext);
+ return span.storeInContext(Context.root());
+ }
+
@Override
public SpanBuilder setParent(Context context) {
if (context == null) {
@@ -170,14 +187,25 @@ public Span startSpan() {
Span parentSpan = Span.fromContext(parentContext);
SpanContext parentSpanContext = parentSpan.getSpanContext();
String traceId;
+ boolean isTraceIdRandom;
IdGenerator idGenerator = tracerSharedState.getIdGenerator();
String spanId = idGenerator.generateSpanId();
+
+ Context parentContextForSampler = parentContext;
if (!parentSpanContext.isValid()) {
// New root span.
traceId = idGenerator.generateTraceId();
+ if (idGenerator.generatesRandomTraceIds()) {
+ isTraceIdRandom = true;
+ // Replace parentContext for sampling with one with RANDOM_TRACE_ID bit set
+ parentContextForSampler = ROOT_CONTEXT_WITH_RANDOM_TRACE_ID_BIT;
+ } else {
+ isTraceIdRandom = false;
+ }
} else {
// New child span.
traceId = parentSpanContext.getTraceId();
+ isTraceIdRandom = parentSpanContext.getTraceFlags().isTraceIdRandom();
}
List