diff --git a/api/all/src/main/java/io/opentelemetry/api/trace/ImmutableSpanContext.java b/api/all/src/main/java/io/opentelemetry/api/trace/ImmutableSpanContext.java index 05d44479fa..0743d01eee 100644 --- a/api/all/src/main/java/io/opentelemetry/api/trace/ImmutableSpanContext.java +++ b/api/all/src/main/java/io/opentelemetry/api/trace/ImmutableSpanContext.java @@ -26,7 +26,11 @@ abstract class ImmutableSpanContext implements SpanContext { } static SpanContext create( - String traceIdHex, String spanIdHex, byte traceFlags, TraceState traceState, boolean remote) { + String traceIdHex, + String spanIdHex, + TraceFlags traceFlags, + TraceState traceState, + boolean remote) { return new AutoValue_ImmutableSpanContext( traceIdHex, spanIdHex, traceFlags, traceState, remote); } 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 60d4c03203..4e43ebb5f7 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 @@ -18,8 +18,8 @@ import javax.annotation.concurrent.Immutable; * equals/hashCode implementations. If an implementation does not strictly conform to these * requirements, behavior of the OpenTelemetry APIs and default SDK cannot be guaranteed. It is * strongly suggested that you use the implementation that is provided here via {@link - * #create(String, String, byte, TraceState)} or {@link #createFromRemoteParent(String, String, - * byte, TraceState)}. + * #create(String, String, TraceFlags, TraceState)} or {@link #createFromRemoteParent(String, + * String, TraceFlags, TraceState)}. */ @Immutable public interface SpanContext { @@ -36,14 +36,14 @@ public interface SpanContext { /** * Creates a new {@code SpanContext} with the given identifiers and options. * - * @param traceIdHex the trace identifier of the span context. - * @param spanIdHex the span identifier of the span context. - * @param traceFlags the byte representation of the {@link TraceFlags} - * @param traceState the trace state for the span context. + * @param traceIdHex the trace identifier of the {@code SpanContext}. + * @param spanIdHex the span identifier of the {@code SpanContext}. + * @param traceFlags the trace flags of the {@code SpanContext}. + * @param traceState the trace state for the {@code SpanContext}. * @return a new {@code SpanContext} with the given identifiers and options. */ static SpanContext create( - String traceIdHex, String spanIdHex, byte traceFlags, TraceState traceState) { + String traceIdHex, String spanIdHex, TraceFlags traceFlags, TraceState traceState) { return ImmutableSpanContext.create( traceIdHex, spanIdHex, traceFlags, traceState, /* remote=*/ false); } @@ -52,14 +52,14 @@ public interface SpanContext { * Creates a new {@code SpanContext} that was propagated from a remote parent, with the given * identifiers and options. * - * @param traceIdHex the trace identifier of the span context. - * @param spanIdHex the span identifier of the span context. - * @param traceFlags the byte representation of the {@link TraceFlags} - * @param traceState the trace state for the span context. + * @param traceIdHex the trace identifier of the {@code SpanContext}. + * @param spanIdHex the span identifier of the {@code SpanContext}. + * @param traceFlags the trace flags of the {@code SpanContext}. + * @param traceState the trace state for the {@code SpanContext}. * @return a new {@code SpanContext} with the given identifiers and options. */ static SpanContext createFromRemoteParent( - String traceIdHex, String spanIdHex, byte traceFlags, TraceState traceState) { + String traceIdHex, String spanIdHex, TraceFlags traceFlags, TraceState traceState) { return ImmutableSpanContext.create( traceIdHex, spanIdHex, traceFlags, traceState, /* remote=*/ true); } @@ -101,15 +101,15 @@ public interface SpanContext { /** Whether the span in this context is sampled. */ default boolean isSampled() { - return (getTraceFlags() & 1) == 1; + return getTraceFlags().isSampled(); } - /** The byte-representation of {@link TraceFlags}. */ - byte getTraceFlags(); - - default void copyTraceFlagsHexTo(char[] dest, int destOffset) { - BigendianEncoding.byteToBase16(getTraceFlags(), dest, destOffset); - } + /** + * Returns the trace flags associated with this {@link SpanContext}. + * + * @return the trace flags associated with this {@link SpanContext}. + */ + TraceFlags getTraceFlags(); /** * Returns the {@code TraceState} associated with this {@code SpanContext}. 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 0294a17dec..81344a4c4f 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 @@ -5,50 +5,137 @@ package io.opentelemetry.api.trace; +import java.util.Objects; import javax.annotation.concurrent.Immutable; /** - * Helper methods for dealing with trace flags options. These options are propagated to all child - * {@link Span spans}. These determine features such as whether a {@code Span} should be traced. It - * is implemented as a bitmask. + * Helper methods for dealing with trace flags options. A valid trace flags is a 2 character + * lowercase hex (base16) String. + * + *
These options are propagated to all child {@link Span spans}. These determine features such as
+ * whether a {@code Span} should be traced.
  */
 @Immutable
 public final class TraceFlags {
-  private TraceFlags() {}
-
+  private static final TraceFlags[] INSTANCES = buildInstances();
   // Bit to represent whether trace is sampled or not.
-  private static final byte IS_SAMPLED = 0x1;
-  // the default flags are a 0 byte.
-  private static final byte DEFAULT = 0x0;
+  private static final byte SAMPLED_BIT = 0x01;
 
-  private static final int SIZE = 1;
-  private static final int HEX_SIZE = 2 * SIZE;
+  private static final TraceFlags DEFAULT = INSTANCES[byteToUnsignedInt((byte) 0x00)];
+  private static final TraceFlags SAMPLED = INSTANCES[byteToUnsignedInt(SAMPLED_BIT)];
 
-  /** Returns the size in Hex of trace flags. */
-  public static int getHexLength() {
-    return HEX_SIZE;
+  private static final int HEX_LENGTH = 2;
+
+  private final String hexRep;
+  private final byte byteRep;
+
+  /**
+   * Returns the length of the lowercase hex (base16) representation of the {@link TraceFlags}.
+   *
+   * @return the length of the lowercase hex (base16) representation of the {@link TraceFlags}.
+   */
+  public static int getLength() {
+    return HEX_LENGTH;
   }
 
   /**
-   * Returns the default byte representation of the flags.
+   * Returns the default (with all flag bits off) byte representation of the {@link TraceFlags}.
    *
-   * @return the default byte representation of the flags.
+   * @return the default (with all flag bits off) byte representation of the {@link TraceFlags}.
    */
-  public static byte getDefault() {
+  public static TraceFlags getDefault() {
     return DEFAULT;
   }
 
   /**
-   * Returns the byte representation of the flags with the sampling bit set to {@code 1}.
+   * Returns the lowercase hex (base16) representation of the {@link TraceFlags} with the sampling
+   * flag bit on.
    *
-   * @return the byte representation of the flags with the sampling bit set to {@code 1}.
+   * @return the lowercase hex (base16) representation of the {@link TraceFlags} with the sampling
+   *     flag bit on.
    */
-  public static byte getSampled() {
-    return IS_SAMPLED;
+  public static TraceFlags getSampled() {
+    return SAMPLED;
   }
 
-  /** Extract the byte representation of the flags from a hex-representation. */
-  public static byte byteFromHex(CharSequence src, int srcOffset) {
-    return BigendianEncoding.byteFromBase16(src.charAt(srcOffset), src.charAt(srcOffset + 1));
+  /**
+   * Returns the {@link TraceFlags} converted from the given lowercase hex (base16) representation.
+   *
+   * @param src the buffer where the hex (base16) representation of the {@link TraceFlags} is.
+   * @param srcOffset the offset int buffer.
+   * @return the {@link TraceFlags} converted from the given lowercase hex (base16) representation.
+   * @throws NullPointerException if {@code src} is null.
+   * @throws IndexOutOfBoundsException if {@code src} is too short.
+   * @throws IllegalArgumentException if invalid characters in the {@code src}.
+   */
+  public static TraceFlags fromHex(CharSequence src, int srcOffset) {
+    Objects.requireNonNull(src, "src");
+    return INSTANCES[
+        byteToUnsignedInt(
+            BigendianEncoding.byteFromBase16(src.charAt(srcOffset), src.charAt(srcOffset + 1)))];
+  }
+
+  /**
+   * Returns the {@link TraceFlags} converted from the given byte representation.
+   *
+   * @param traceFlagsByte the byte representation of the {@link TraceFlags}.
+   * @return the {@link TraceFlags} converted from the given byte representation.
+   */
+  public static TraceFlags fromByte(byte traceFlagsByte) {
+    return INSTANCES[byteToUnsignedInt(traceFlagsByte)];
+  }
+
+  private static TraceFlags[] buildInstances() {
+    TraceFlags[] instances = new TraceFlags[256];
+    for (int i = 0; i < 256; i++) {
+      instances[i] = new TraceFlags((byte) i);
+    }
+    return instances;
+  }
+
+  private static int byteToUnsignedInt(byte x) {
+    // Equivalent with Byte.toUnsignedInt(), but cannot use it because of Android.
+    return x & 255;
+  }
+
+  private TraceFlags(byte byteRep) {
+    char[] result = new char[2];
+    BigendianEncoding.byteToBase16(byteRep, result, 0);
+    this.hexRep = new String(result);
+    this.byteRep = byteRep;
+  }
+
+  /**
+   * Returns {@code true} if the sampling bit is on for this {@link TraceFlags}, otherwise {@code
+   * false}.
+   *
+   * @return {@code true} if the sampling bit is on for this {@link TraceFlags}, otherwise {@code *
+   *     false}.
+   */
+  public boolean isSampled() {
+    return (this.byteRep & SAMPLED_BIT) != 0;
+  }
+
+  /**
+   * Returns the lowercase hex (base16) representation of this {@link TraceFlags}.
+   *
+   * @return the byte representation of the {@link TraceFlags}.
+   */
+  public String asHex() {
+    return this.hexRep;
+  }
+
+  /**
+   * Returns the byte representation of this {@link TraceFlags}.
+   *
+   * @return the byte representation of the {@link TraceFlags}.
+   */
+  public byte asByte() {
+    return this.byteRep;
+  }
+
+  @Override
+  public String toString() {
+    return asHex();
   }
 }
diff --git a/api/all/src/main/java/io/opentelemetry/api/trace/propagation/W3CTraceContextPropagator.java b/api/all/src/main/java/io/opentelemetry/api/trace/propagation/W3CTraceContextPropagator.java
index ba9a12130d..f59e248730 100644
--- a/api/all/src/main/java/io/opentelemetry/api/trace/propagation/W3CTraceContextPropagator.java
+++ b/api/all/src/main/java/io/opentelemetry/api/trace/propagation/W3CTraceContextPropagator.java
@@ -50,7 +50,7 @@ public final class W3CTraceContextPropagator implements TextMapPropagator {
   private static final int TRACEPARENT_DELIMITER_SIZE = 1;
   private static final int TRACE_ID_HEX_SIZE = TraceId.getLength();
   private static final int SPAN_ID_HEX_SIZE = SpanId.getLength();
-  private static final int TRACE_OPTION_HEX_SIZE = TraceFlags.getHexLength();
+  private static final int TRACE_OPTION_HEX_SIZE = TraceFlags.getLength();
   private static final int TRACE_ID_OFFSET = VERSION_SIZE + TRACEPARENT_DELIMITER_SIZE;
   private static final int SPAN_ID_OFFSET =
       TRACE_ID_OFFSET + TRACE_ID_HEX_SIZE + TRACEPARENT_DELIMITER_SIZE;
@@ -124,7 +124,9 @@ public final class W3CTraceContextPropagator implements TextMapPropagator {
     }
 
     chars[TRACE_OPTION_OFFSET - 1] = TRACEPARENT_DELIMITER;
-    spanContext.copyTraceFlagsHexTo(chars, TRACE_OPTION_OFFSET);
+    String traceFlagsHex = spanContext.getTraceFlags().asHex();
+    chars[TRACE_OPTION_OFFSET] = traceFlagsHex.charAt(0);
+    chars[TRACE_OPTION_OFFSET + 1] = traceFlagsHex.charAt(1);
     setter.set(carrier, TRACE_PARENT, new String(chars, 0, TRACEPARENT_HEADER_SIZE));
     TraceState traceState = spanContext.getTraceState();
     if (traceState.isEmpty()) {
@@ -213,7 +215,7 @@ public final class W3CTraceContextPropagator implements TextMapPropagator {
           traceparent.substring(TRACE_ID_OFFSET, TRACE_ID_OFFSET + TraceId.getLength());
       String spanId = traceparent.substring(SPAN_ID_OFFSET, SPAN_ID_OFFSET + SpanId.getLength());
       if (TraceId.isValid(traceId) && SpanId.isValid(spanId)) {
-        byte traceFlags = TraceFlags.byteFromHex(traceparent, TRACE_OPTION_OFFSET);
+        TraceFlags traceFlags = TraceFlags.fromHex(traceparent, TRACE_OPTION_OFFSET);
         return SpanContext.createFromRemoteParent(
             traceId, spanId, traceFlags, TraceState.getDefault());
       }
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 39018946ed..d6ef529f00 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
@@ -13,15 +13,34 @@ import org.junit.jupiter.api.Test;
 class TraceFlagsTest {
 
   @Test
-  void isDefaultSampled() {
-    assertThat(TraceFlags.getDefault()).isEqualTo((byte) 0x0);
+  void defaultInstances() {
+    assertThat(TraceFlags.getDefault().asHex()).isEqualTo("00");
+    assertThat(TraceFlags.getSampled().asHex()).isEqualTo("01");
   }
 
   @Test
-  void toByteFromBase16() {
-    assertThat(TraceFlags.byteFromHex("ff", 0)).isEqualTo((byte) 0xff);
-    assertThat(TraceFlags.byteFromHex("01", 0)).isEqualTo((byte) 0x1);
-    assertThat(TraceFlags.byteFromHex("05", 0)).isEqualTo((byte) 0x5);
-    assertThat(TraceFlags.byteFromHex("00", 0)).isEqualTo((byte) 0x0);
+  void isSampled() {
+    assertThat(TraceFlags.fromByte((byte) 0xff).isSampled()).isTrue();
+    assertThat(TraceFlags.fromByte((byte) 0x01).isSampled()).isTrue();
+    assertThat(TraceFlags.fromByte((byte) 0x05).isSampled()).isTrue();
+    assertThat(TraceFlags.fromByte((byte) 0x00).isSampled()).isFalse();
+  }
+
+  @Test
+  void toFromHex() {
+    for (int i = 0; i < 256; i++) {
+      String hex = Integer.toHexString(i);
+      if (hex.length() == 1) {
+        hex = "0" + hex;
+      }
+      assertThat(TraceFlags.fromHex(hex, 0).asHex()).isEqualTo(hex);
+    }
+  }
+
+  @Test
+  void toFromByte() {
+    for (int i = 0; i < 256; i++) {
+      assertThat(TraceFlags.fromByte((byte) i).asByte()).isEqualTo((byte) i);
+    }
   }
 }
diff --git a/api/all/src/test/java/io/opentelemetry/api/trace/propagation/W3CTraceContextPropagatorTest.java b/api/all/src/test/java/io/opentelemetry/api/trace/propagation/W3CTraceContextPropagatorTest.java
index e2e98ea46d..285e656080 100644
--- a/api/all/src/test/java/io/opentelemetry/api/trace/propagation/W3CTraceContextPropagatorTest.java
+++ b/api/all/src/test/java/io/opentelemetry/api/trace/propagation/W3CTraceContextPropagatorTest.java
@@ -32,7 +32,6 @@ class W3CTraceContextPropagatorTest {
       TraceState.builder().set("foo", "bar").set("bar", "baz").build();
   private static final String TRACE_ID_BASE16 = "ff000000000000000000000000000041";
   private static final String SPAN_ID_BASE16 = "ff00000000000041";
-  private static final byte SAMPLED_TRACE_OPTIONS = TraceFlags.getSampled();
   private static final String TRACEPARENT_HEADER_SAMPLED =
       "00-" + TRACE_ID_BASE16 + "-" + SPAN_ID_BASE16 + "-01";
   private static final String TRACEPARENT_HEADER_NOT_SAMPLED =
@@ -79,7 +78,7 @@ class W3CTraceContextPropagatorTest {
     Context context =
         withSpanContext(
             SpanContext.create(
-                TRACE_ID_BASE16, SPAN_ID_BASE16, SAMPLED_TRACE_OPTIONS, TraceState.getDefault()),
+                TRACE_ID_BASE16, SPAN_ID_BASE16, TraceFlags.getSampled(), TraceState.getDefault()),
             Context.current());
     w3cTraceContextPropagator.inject(
         context,
@@ -97,7 +96,7 @@ class W3CTraceContextPropagatorTest {
             SpanContext.create(
                 TraceId.getInvalid(),
                 SpanId.getInvalid(),
-                SAMPLED_TRACE_OPTIONS,
+                TraceFlags.getSampled(),
                 TraceState.builder().set("foo", "bar").build()),
             Context.current()),
         carrier,
@@ -111,7 +110,7 @@ class W3CTraceContextPropagatorTest {
     Context context =
         withSpanContext(
             SpanContext.create(
-                TRACE_ID_BASE16, SPAN_ID_BASE16, SAMPLED_TRACE_OPTIONS, TraceState.getDefault()),
+                TRACE_ID_BASE16, SPAN_ID_BASE16, TraceFlags.getSampled(), TraceState.getDefault()),
             Context.current());
     w3cTraceContextPropagator.inject(context, carrier, setter);
     assertThat(carrier)
@@ -137,7 +136,8 @@ class W3CTraceContextPropagatorTest {
     Map