Skip to content

Commit

Permalink
Allows extraction of just a trace ID (not span ID) from an incoming c…
Browse files Browse the repository at this point in the history
…arrier (#512)
  • Loading branch information
adriancole committed Oct 7, 2017
1 parent 0be5039 commit b8afd93
Show file tree
Hide file tree
Showing 14 changed files with 474 additions and 143 deletions.
27 changes: 21 additions & 6 deletions brave/README.md
Expand Up @@ -318,18 +318,33 @@ injector.inject(span.context(), request);
Here's what server-side propagation might look like
```java
// configure a function that extracts the trace context from a request
extractor = tracing.propagation().extractor(Request::getHeader);
extracted = tracing.propagation().extractor(Request::getHeader);

// when a server receives a request, it joins or starts a new trace
span = tracer.nextSpan(extractor, request);
span = tracer.nextSpan(extracted, request);
```

### Extracting a propagated context
The `TraceContext.Extractor<C>` reads trace identifiers and sampling status
from an incoming request or message. The carrier is usually a request object
or headers.

This utility is used in standard instrumentation like [HttpServerHandler](../instrumentation/http/src/main/java/brave/http/HttpServerHandler.java),
but can also be used for custom RPC or messaging code.

`Extractor.extract` returns a union type `TraceContextOrSamplingFlags` which
contains one of:
* `TraceContext` if trace and span IDs were present.
* `TraceIdContext` if a trace ID was present, but not span IDs.
* `SamplingFlags` if no identifiers were present

### Sharing span IDs between client and server

Most instrumentation use `Tracer.joinSpan` to create a server span ID. This
attempts to reuse trace identifiers from incoming headers for the server
operation, restarting the trace as needed. When span ID is shared, data
reported includes a flag saying so.
A normal instrumentation pattern is creating a span representing the server
side of an RPC. `Extractor.extract` might return a complete trace context when
applied to an incoming client request. `Tracer.joinSpan` attempts to continue
the this trace, using the same span ID if supported, or creating a child span
if not. When span ID is shared, data reported includes a flag saying so.

Here's an example of B3 propagation:

Expand Down
29 changes: 25 additions & 4 deletions brave/src/main/java/brave/Tracer.java
Expand Up @@ -8,6 +8,7 @@
import brave.propagation.TraceContext;
import brave.propagation.TraceContext.Extractor;
import brave.propagation.TraceContextOrSamplingFlags;
import brave.propagation.TraceIdContext;
import brave.sampler.Sampler;
import java.io.Closeable;
import java.util.concurrent.atomic.AtomicBoolean;
Expand Down Expand Up @@ -150,7 +151,7 @@ public Tracer build() {
* instead.
*/
public Span newTrace() {
return toSpan(nextContext(null, SamplingFlags.EMPTY));
return toSpan(nextContext(null, null, SamplingFlags.EMPTY));
}

/**
Expand Down Expand Up @@ -190,6 +191,13 @@ public final Span joinSpan(TraceContext context) {
return toSpan(builder.build());
}

/**
* Like {@link #newChild(TraceContext)}, where the trace ID is present, but not a span ID.
*/
public Span newTrace(TraceIdContext traceIdContext) {
return toSpan(nextContext(null, traceIdContext, null));
}

/**
* Like {@link #newTrace()}, but supports parameterized sampling, for example limiting on
* operation or url pattern.
Expand All @@ -208,7 +216,7 @@ public final Span joinSpan(TraceContext context) {
* }</pre>
*/
public Span newTrace(SamplingFlags samplingFlags) {
return toSpan(nextContext(null, samplingFlags));
return toSpan(nextContext(null, null, samplingFlags));
}

/** Converts the context as-is to a Span object */
Expand All @@ -229,10 +237,14 @@ public Span newChild(TraceContext parent) {
if (Boolean.FALSE.equals(parent.sampled())) {
return NoopSpan.create(parent);
}
return toSpan(nextContext(parent, parent));
return toSpan(nextContext(parent, null, null));
}

TraceContext nextContext(@Nullable TraceContext parent, SamplingFlags samplingFlags) {
TraceContext nextContext(
@Nullable TraceContext parent,
@Nullable TraceIdContext traceIdContext,
@Nullable SamplingFlags samplingFlags
) {
long nextId = Platform.get().randomLong();
if (parent != null) {
return parent.toBuilder()
Expand All @@ -241,6 +253,15 @@ TraceContext nextContext(@Nullable TraceContext parent, SamplingFlags samplingFl
.shared(false)
.build();
}
if (traceIdContext != null) {
Boolean sampled = traceIdContext.sampled();
if (sampled == null) sampled = sampler.isSampled(traceIdContext.traceId());
return TraceContext.newBuilder()
.sampled(sampled)
.debug(traceIdContext.debug())
.traceIdHigh(traceIdContext.traceIdHigh()).traceId(traceIdContext.traceId())
.spanId(nextId).build();
}
Boolean sampled = samplingFlags.sampled();
if (sampled == null) sampled = sampler.isSampled(nextId);
return TraceContext.newBuilder()
Expand Down
6 changes: 2 additions & 4 deletions brave/src/main/java/brave/propagation/B3Propagation.java
Expand Up @@ -134,14 +134,12 @@ static final class B3Extractor<C, K> implements TraceContext.Extractor<C> {
traceIdString.length() == 32 ? lowerHexToUnsignedLong(traceIdString, 0) : 0
);
result.traceId(lowerHexToUnsignedLong(traceIdString));
if (spanIdString != null) {
result.spanId(lowerHexToUnsignedLong(spanIdString));
}
result.spanId(lowerHexToUnsignedLong(spanIdString));
String parentSpanIdString = getter.get(carrier, propagation.parentSpanIdKey);
if (parentSpanIdString != null) {
result.parentId(lowerHexToUnsignedLong(parentSpanIdString));
}
return TraceContextOrSamplingFlags.create(result);
return TraceContextOrSamplingFlags.create(result.build());
}
}
}
Expand Up @@ -5,7 +5,13 @@
import javax.annotation.concurrent.Immutable;

/**
* Union type that contains either a trace context or sampling flags, but not both.
* Union type that contains only one of trace context, trace ID context or sampling flags.
*
* <pre><ul>
* <li>If you have the trace and span ID, use {@link #create(TraceContext)}</li>
* <li>If you have only a trace ID, use {@link #create(TraceIdContext)}</li>
* <li>Otherwise, use {@link #create(SamplingFlags)}</li>
* </ul></pre>
*
* <p>This is a port of {@code com.github.kristofa.brave.TraceData}, which served the same purpose.
*
Expand All @@ -14,30 +20,38 @@
@Immutable
@AutoValue
public abstract class TraceContextOrSamplingFlags {

/** When present, create the span via {@link brave.Tracer#joinSpan(TraceContext)} */
@Nullable public abstract TraceContext context();

/** When present, create the span via {@link brave.Tracer#newTrace(TraceIdContext)} */
@Nullable public abstract TraceIdContext traceIdContext();

/** When present, create the span via {@link brave.Tracer#newTrace(SamplingFlags)} */
@Nullable public abstract SamplingFlags samplingFlags();

public static TraceContextOrSamplingFlags create(SamplingFlags flags) {
return new AutoValue_TraceContextOrSamplingFlags(null, flags);
public static TraceContextOrSamplingFlags create(TraceContext context) {
return new AutoValue_TraceContextOrSamplingFlags(context, null, null);
}

public static TraceContextOrSamplingFlags create(TraceContext context) {
return new AutoValue_TraceContextOrSamplingFlags(context, null);
public static TraceContextOrSamplingFlags create(TraceIdContext traceIdContext) {
return new AutoValue_TraceContextOrSamplingFlags(null, traceIdContext, null);
}

public static TraceContextOrSamplingFlags create(SamplingFlags flags) {
return new AutoValue_TraceContextOrSamplingFlags(null, null, flags);
}

/** @deprecated call one of the other factory methods vs allocating an exception */
@Deprecated
public static TraceContextOrSamplingFlags create(TraceContext.Builder builder) {
if (builder == null) throw new NullPointerException("builder == null");
try {
return new AutoValue_TraceContextOrSamplingFlags(builder.build(), null);
return new AutoValue_TraceContextOrSamplingFlags(builder.build(), null, null);
} catch (IllegalStateException e) { // no trace IDs, but it might have sampling flags
SamplingFlags flags = new SamplingFlags.Builder()
.sampled(builder.sampled())
.debug(builder.debug()).build();
return new AutoValue_TraceContextOrSamplingFlags(null, flags);
return new AutoValue_TraceContextOrSamplingFlags(null, null, flags);
}
}

Expand Down
68 changes: 68 additions & 0 deletions brave/src/main/java/brave/propagation/TraceIdContext.java
@@ -0,0 +1,68 @@
package brave.propagation;

import com.google.auto.value.AutoValue;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

import static brave.internal.HexCodec.writeHexLong;

/**
* Contains inbound trace ID and sampling flags, used when users control the root trace ID, but not
* the span ID (ex Amazon X-Ray or other correlation).
*/
@Immutable
@AutoValue
public abstract class TraceIdContext extends SamplingFlags {

public static Builder newBuilder() {
return new AutoValue_TraceIdContext.Builder().traceIdHigh(0L).debug(false);
}

/** When non-zero, the trace containing this span uses 128-bit trace identifiers. */
public abstract long traceIdHigh();

/** Unique 8-byte identifier for a trace, set on all spans within it. */
public abstract long traceId();

// override as auto-value can't currently read the super-class's nullable annotation.
@Override @Nullable public abstract Boolean sampled();

public abstract Builder toBuilder();

/** Returns {@code $traceId} */
@Override
public String toString() {
boolean traceHi = traceIdHigh() != 0;
char[] result = new char[traceHi ? 32 : 16];
int pos = 0;
if (traceHi) {
writeHexLong(result, pos, traceIdHigh());
pos += 16;
}
writeHexLong(result, pos, traceId());
return new String(result);
}

@AutoValue.Builder
public static abstract class Builder {
/** @see TraceIdContext#traceIdHigh() */
public abstract Builder traceIdHigh(long traceIdHigh);

/** @see TraceIdContext#traceId() */
public abstract Builder traceId(long traceId);

/** @see TraceIdContext#sampled */
public abstract Builder sampled(@Nullable Boolean nullableSampled);

/** @see TraceIdContext#debug() */
public abstract Builder debug(boolean debug);

public abstract TraceIdContext build();

Builder() { // no external implementations
}
}

TraceIdContext() { // no external implementations
}
}
Expand Up @@ -8,51 +8,73 @@ public class TraceContextOrSamplingFlagsTest {

@Test public void contextWhenIdsAreSet() {
TraceContext.Builder builder = TraceContext.newBuilder().traceId(333L).spanId(1L);
TraceContextOrSamplingFlags contextOrFlags = TraceContextOrSamplingFlags.create(builder);
TraceContextOrSamplingFlags extracted = TraceContextOrSamplingFlags.create(builder.build());

assertThat(contextOrFlags.context())
assertThat(extracted.context())
.isEqualTo(builder.build());
assertThat(contextOrFlags.samplingFlags())
assertThat(extracted.traceIdContext())
.isNull();
assertThat(extracted.samplingFlags())
.isNull();
}

@Test public void contextWhenIdsAndSamplingAreSet() {
TraceContext.Builder builder = TraceContext.newBuilder().traceId(333L).spanId(1L).sampled(true);
TraceContextOrSamplingFlags contextOrFlags = TraceContextOrSamplingFlags.create(builder);
TraceContextOrSamplingFlags extracted = TraceContextOrSamplingFlags.create(builder.build());

assertThat(contextOrFlags.context())
assertThat(extracted.context())
.isEqualTo(builder.build());
assertThat(contextOrFlags.samplingFlags())
assertThat(extracted.traceIdContext())
.isNull();
assertThat(extracted.samplingFlags())
.isNull();
}

@Test public void flagsWhenMissingTraceId() {
@Test @Deprecated public void contextWhenTraceIdAndSampledAreSet() {
TraceIdContext.Builder builder = TraceIdContext.newBuilder().traceId(333L).sampled(true);
TraceContextOrSamplingFlags extracted = TraceContextOrSamplingFlags.create(builder.build());

assertThat(extracted.context())
.isNull();
assertThat(extracted.traceIdContext())
.isEqualTo(builder.build());
assertThat(extracted.samplingFlags())
.isNull();
}

@Test @Deprecated public void deprecatedFlagsWhenMissingTraceId() {
TraceContext.Builder builder = TraceContext.newBuilder().spanId(1L);
TraceContextOrSamplingFlags contextOrFlags = TraceContextOrSamplingFlags.create(builder);
TraceContextOrSamplingFlags extracted = TraceContextOrSamplingFlags.create(builder);

assertThat(contextOrFlags.context())
assertThat(extracted.context())
.isNull();
assertThat(extracted.traceIdContext())
.isNull();
assertThat(contextOrFlags.samplingFlags())
assertThat(extracted.samplingFlags())
.isSameAs(SamplingFlags.EMPTY);
}

@Test public void flagsWhenMissingSpanId() {
@Test @Deprecated public void deprecatedFlagsWhenMissingSpanId() {
TraceContext.Builder builder = TraceContext.newBuilder().traceId(333L).sampled(true);
TraceContextOrSamplingFlags contextOrFlags = TraceContextOrSamplingFlags.create(builder);
TraceContextOrSamplingFlags extracted = TraceContextOrSamplingFlags.create(builder);

assertThat(contextOrFlags.context())
assertThat(extracted.context())
.isNull();
assertThat(contextOrFlags.samplingFlags())
assertThat(extracted.traceIdContext())
.isNull();
assertThat(extracted.samplingFlags())
.isSameAs(SamplingFlags.SAMPLED);
}

@Test public void flags() {
TraceContext.Builder builder = TraceContext.newBuilder().sampled(true);
TraceContextOrSamplingFlags contextOrFlags = TraceContextOrSamplingFlags.create(builder);
TraceContextOrSamplingFlags extracted =
TraceContextOrSamplingFlags.create(SamplingFlags.SAMPLED);

assertThat(contextOrFlags.context())
assertThat(extracted.context())
.isNull();
assertThat(extracted.traceIdContext())
.isNull();
assertThat(contextOrFlags.samplingFlags())
assertThat(extracted.samplingFlags())
.isSameAs(SamplingFlags.SAMPLED);
}
}
29 changes: 29 additions & 0 deletions brave/src/test/java/brave/propagation/TraceIdContextTest.java
@@ -0,0 +1,29 @@
package brave.propagation;

import org.junit.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class TraceIdContextTest {
TraceIdContext context = TraceIdContext.newBuilder().traceId(333L).build();

@Test public void compareUnequalIds() {
assertThat(context)
.isNotEqualTo(context.toBuilder().traceIdHigh(222L).build());
}

@Test public void compareEqualIds() {
assertThat(context)
.isEqualTo(TraceIdContext.newBuilder().traceId(333L).build());
}

@Test public void testToString_lo() {
assertThat(context.toString())
.isEqualTo("000000000000014d");
}

@Test public void testToString() {
assertThat(context.toBuilder().traceIdHigh(222L).build().toString())
.isEqualTo("00000000000000de000000000000014d");
}
}

0 comments on commit b8afd93

Please sign in to comment.