Configure tags to be added to every span
MODIFY
dd-trace-ot/src/main/java/datadog/opentracing/DDSpanContext.java
dd-trace-ot/src/main/java/datadog/opentracing/DDSpanContext.java
@@ -148,7 +148,7 @@ public class DDSpanContext implements io.opentracing.SpanContext {
this.spanType = spanType;
}
- public void setSamplingPriority(int newPriority) {
+ public void setSamplingPriority(final int newPriority) {
if (samplingPriorityLocked) {
log.warn(
"samplingPriority locked at {}. Refusing to set to {}", samplingPriority, newPriority);
@@ -272,7 +272,7 @@ public class DDSpanContext implements io.opentracing.SpanContext {
public synchronized Map<String, Object> getTags() {
if (tags.isEmpty()) {
- tags = Maps.newHashMapWithExpectedSize(2);
+ tags = Maps.newHashMapWithExpectedSize(3);
}
tags.put(DDTags.THREAD_NAME, threadName);
tags.put(DDTags.THREAD_ID, threadId);

MODIFY
dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java
dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java
package datadog.opentracing;
import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.google.common.collect.Maps;
import datadog.opentracing.decorators.AbstractDecorator;
import datadog.opentracing.decorators.DDDecoratorsFactory;
import datadog.opentracing.propagation.Codec;
@@ -44,6 +45,9 @@ public class DDTracer extends ThreadLocalScopeManager implements io.opentracing.
/** Sampler defines the sampling policy in order to reduce the number of traces for instance */
final Sampler sampler;
+ /** A set of tags that are added to every span */
+ private final Map<String, Object> spanTags;
+
/** Span context decorators */
private final Map<String, List<AbstractDecorator>> spanContextDecorators = new HashMap<>();
@@ -63,7 +67,8 @@ public class DDTracer extends ThreadLocalScopeManager implements io.opentracing.
this(
config.getProperty(DDTraceConfig.SERVICE_NAME),
Writer.Builder.forConfig(config),
- Sampler.Builder.forConfig(config));
+ Sampler.Builder.forConfig(config),
+ DDTraceConfig.parseMap(config.getProperty(DDTraceConfig.SPAN_TAGS)));
log.debug("Using config: {}", config);
// Create decorators from resource files
@@ -75,10 +80,20 @@ public class DDTracer extends ThreadLocalScopeManager implements io.opentracing.
}
public DDTracer(final String serviceName, final Writer writer, final Sampler sampler) {
+ this(serviceName, writer, sampler, Collections.<String, Object>emptyMap());
+ }
+
+ public DDTracer(
+ final String serviceName,
+ final Writer writer,
+ final Sampler sampler,
+ final Map<String, Object> spanTags) {
this.serviceName = serviceName;
this.writer = writer;
this.writer.start();
this.sampler = sampler;
+ this.spanTags = spanTags;
+
registry = new CodecRegistry();
registry.register(Format.Builtin.HTTP_HEADERS, new HTTPCodec());
registry.register(Format.Builtin.TEXT_MAP, new HTTPCodec());
@@ -90,7 +105,11 @@ public class DDTracer extends ThreadLocalScopeManager implements io.opentracing.
}
public DDTracer(final Writer writer) {
- this(UNASSIGNED_DEFAULT_SERVICE_NAME, writer, new AllSampler());
+ this(
+ UNASSIGNED_DEFAULT_SERVICE_NAME,
+ writer,
+ new AllSampler(),
+ DDTraceConfig.parseMap(new DDTraceConfig().getProperty(DDTraceConfig.SPAN_TAGS)));
}
/**
@@ -185,6 +204,8 @@ public class DDTracer extends ThreadLocalScopeManager implements io.opentracing.
+ writer
+ ", sampler="
+ sampler
+ + ", tags="
+ + spanTags
+ '}';
}
@@ -236,7 +257,8 @@ public class DDTracer extends ThreadLocalScopeManager implements io.opentracing.
private final String operationName;
// Builder attributes
- private Map<String, Object> tags = Collections.emptyMap();
+ private Map<String, Object> tags =
+ spanTags.isEmpty() ? Collections.<String, Object>emptyMap() : Maps.newHashMap(spanTags);
private long timestamp;
private SpanContext parent;
private String serviceName;
@@ -361,7 +383,7 @@ public class DDTracer extends ThreadLocalScopeManager implements io.opentracing.
// Private methods
private DDSpanBuilder withTag(final String tag, final Object value) {
if (this.tags.isEmpty()) {
- this.tags = new HashMap<>();
+ this.tags = Maps.newHashMap();
}
this.tags.put(tag, value);
return this;

MODIFY
dd-trace-ot/src/main/java/datadog/trace/common/DDTraceConfig.java
dd-trace-ot/src/main/java/datadog/trace/common/DDTraceConfig.java
package datadog.trace.common;
+import com.google.common.collect.Maps;
import datadog.opentracing.DDTracer;
import datadog.trace.common.writer.DDAgentWriter;
import datadog.trace.common.writer.Writer;
+import java.util.Collections;
+import java.util.Map;
import java.util.Properties;
+import lombok.extern.slf4j.Slf4j;
/**
* Config gives priority to system properties and falls back to environment variables. It also
@@ -14,6 +18,7 @@ import java.util.Properties;
* <p>System properties are {@link DDTraceConfig#PREFIX}'ed. Environment variables are the same as
* the system property, but uppercased with '.' -> '_'.
*/
+@Slf4j
public class DDTraceConfig extends Properties {
/** Config keys below */
private static final String PREFIX = "dd.";
@@ -23,12 +28,14 @@ public class DDTraceConfig extends Properties {
public static final String AGENT_HOST = "agent.host";
public static final String AGENT_PORT = "agent.port";
public static final String PRIORITY_SAMPLING = "priority.sampling";
+ public static final String SPAN_TAGS = "trace.span.tags";
private final String serviceName = getPropOrEnv(PREFIX + SERVICE_NAME);
private final String writerType = getPropOrEnv(PREFIX + WRITER_TYPE);
private final String agentHost = getPropOrEnv(PREFIX + AGENT_HOST);
private final String agentPort = getPropOrEnv(PREFIX + AGENT_PORT);
private final String prioritySampling = getPropOrEnv(PREFIX + PRIORITY_SAMPLING);
+ private final String spanTags = getPropOrEnv(PREFIX + SPAN_TAGS);
public DDTraceConfig() {
super();
@@ -45,6 +52,7 @@ public class DDTraceConfig extends Properties {
setIfNotNull(AGENT_HOST, agentHost);
setIfNotNull(AGENT_PORT, agentPort);
setIfNotNull(PRIORITY_SAMPLING, prioritySampling);
+ setIfNotNull(SPAN_TAGS, spanTags);
}
public DDTraceConfig(final String serviceName) {
@@ -65,4 +73,23 @@ public class DDTraceConfig extends Properties {
static String propToEnvName(final String name) {
return name.toUpperCase().replace(".", "_");
}
+
+ public static Map<String, Object> parseMap(final String str) {
+ if (str == null || str.trim().isEmpty()) {
+ return Collections.emptyMap();
+ }
+ if (!str.matches("(([^,:]+:[^,:]+,)*([^,:]+:[^,:]+),?)?")) {
+ log.warn("Invalid config '{}'. Must match 'key1:value1,key2:value2'.", str);
+ return Collections.emptyMap();
+ }
+
+ final String[] tokens = str.split(",");
+ final Map<String, Object> map = Maps.newHashMapWithExpectedSize(tokens.length);
+
+ for (final String token : tokens) {
+ final String[] keyValue = token.split(":");
+ map.put(keyValue[0].trim(), keyValue[1].trim());
+ }
+ return Collections.unmodifiableMap(map);
+ }
}

ADD
dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanBuilderTest.groovy
+package datadog.opentracing
+
+import datadog.trace.api.DDTags
+import datadog.trace.common.writer.ListWriter
+import spock.lang.Specification
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS
+import static org.mockito.Mockito.mock
+import static org.mockito.Mockito.when
+
+class DDSpanBuilderTest extends Specification {
+ def writer = new ListWriter()
+ def tracer = new DDTracer(writer)
+
+ def "build simple span"() {
+ setup:
+ final DDSpan span = tracer.buildSpan("op name").withServiceName("foo").start()
+
+ expect:
+ span.operationName == "op name"
+ }
+
+ def "build complex span"() {
+ setup:
+ def expectedName = "fakeName"
+ def tags = [
+ "1": true,
+ "2": "fakeString",
+ "3": 42.0,
+ ]
+
+ DDTracer.DDSpanBuilder builder = tracer
+ .buildSpan(expectedName)
+ .withServiceName("foo")
+ tags.each {
+ builder = builder.withTag(it.key, it.value)
+ }
+
+ when:
+ DDSpan span = builder.start()
+
+ then:
+ span.getOperationName() == expectedName
+ span.tags.subMap(tags.keySet()) == tags
+
+
+ when:
+ span = tracer.buildSpan(expectedName).withServiceName("foo").start()
+
+ then:
+ span.getTags() == [(DDTags.THREAD_NAME): Thread.currentThread().getName(),
+ (DDTags.THREAD_ID) : Thread.currentThread().getId(),
+ (DDTags.SPAN_TYPE) : null]
+
+ when:
+ // with all custom fields provided
+ final String expectedResource = "fakeResource"
+ final String expectedService = "fakeService"
+ final String expectedType = "fakeType"
+
+ span =
+ tracer
+ .buildSpan(expectedName)
+ .withServiceName("foo")
+ .withResourceName(expectedResource)
+ .withServiceName(expectedService)
+ .withErrorFlag()
+ .withSpanType(expectedType)
+ .start()
+
+ final DDSpanContext context = span.context()
+
+ then:
+ context.getResourceName() == expectedResource
+ context.getErrorFlag()
+ context.getServiceName() == expectedService
+ context.getSpanType() == expectedType
+
+ context.tags[DDTags.THREAD_NAME] == Thread.currentThread().getName()
+ context.tags[DDTags.THREAD_ID] == Thread.currentThread().getId()
+ }
+
+ def "should build span timestamp in nano"() {
+ setup:
+ // time in micro
+ final long expectedTimestamp = 487517802L * 1000 * 1000L
+ final String expectedName = "fakeName"
+
+ DDSpan span =
+ tracer
+ .buildSpan(expectedName)
+ .withServiceName("foo")
+ .withStartTimestamp(expectedTimestamp)
+ .start()
+
+ expect:
+ // get return nano time
+ span.getStartTime() == expectedTimestamp * 1000L
+
+ when:
+ // auto-timestamp in nanoseconds
+ def start = System.currentTimeMillis()
+ span = tracer.buildSpan(expectedName).withServiceName("foo").start()
+ def stop = System.currentTimeMillis()
+
+ then:
+ // Give a range of +/- 5 millis
+ span.getStartTime() >= MILLISECONDS.toNanos(start - 1)
+ span.getStartTime() <= MILLISECONDS.toNanos(stop + 1)
+ }
+
+ def "should link to parent span"() {
+ setup:
+ final long spanId = 1L
+ final long expectedParentId = spanId
+
+ final DDSpanContext mockedContext = mock(DDSpanContext)
+
+ when(mockedContext.getSpanId()).thenReturn(spanId)
+ when(mockedContext.getServiceName()).thenReturn("foo")
+
+ final String expectedName = "fakeName"
+
+ final DDSpan span =
+ tracer
+ .buildSpan(expectedName)
+ .withServiceName("foo")
+ .asChildOf(mockedContext)
+ .start()
+
+ final DDSpanContext actualContext = span.context()
+
+ expect:
+ actualContext.getParentId() == expectedParentId
+ }
+
+ def "should inherit the DD parent attributes"() {
+ setup:
+ def expectedName = "fakeName"
+ def expectedParentServiceName = "fakeServiceName"
+ def expectedParentResourceName = "fakeResourceName"
+ def expectedParentType = "fakeType"
+ def expectedChildServiceName = "fakeServiceName-child"
+ def expectedChildResourceName = "fakeResourceName-child"
+ def expectedChildType = "fakeType-child"
+ def expectedBaggageItemKey = "fakeKey"
+ def expectedBaggageItemValue = "fakeValue"
+
+ final DDSpan parent =
+ tracer
+ .buildSpan(expectedName)
+ .withServiceName("foo")
+ .withResourceName(expectedParentResourceName)
+ .withSpanType(expectedParentType)
+ .start()
+
+ parent.setBaggageItem(expectedBaggageItemKey, expectedBaggageItemValue)
+
+ // ServiceName and SpanType are always set by the parent if they are not present in the child
+ DDSpan span =
+ tracer
+ .buildSpan(expectedName)
+ .withServiceName(expectedParentServiceName)
+ .asChildOf(parent)
+ .start()
+
+ expect:
+ span.getOperationName() == expectedName
+ span.getBaggageItem(expectedBaggageItemKey) == expectedBaggageItemValue
+ span.context().getServiceName() == expectedParentServiceName
+ span.context().getResourceName() == expectedName
+ span.context().getSpanType() == expectedParentType
+
+ when:
+ // ServiceName and SpanType are always overwritten by the child if they are present
+ span =
+ tracer
+ .buildSpan(expectedName)
+ .withServiceName(expectedChildServiceName)
+ .withResourceName(expectedChildResourceName)
+ .withSpanType(expectedChildType)
+ .asChildOf(parent)
+ .start()
+
+ then:
+ span.getOperationName() == expectedName
+ span.getBaggageItem(expectedBaggageItemKey) == expectedBaggageItemValue
+ span.context().getServiceName() == expectedChildServiceName
+ span.context().getResourceName() == expectedChildResourceName
+ span.context().getSpanType() == expectedChildType
+ }
+
+ def "should track all spans in trace"() {
+ setup:
+ List<DDSpan> spans = []
+ final int nbSamples = 10
+
+ // root (aka spans[0]) is the parent
+ // others are just for fun
+
+ def root = tracer.buildSpan("fake_O").withServiceName("foo").start()
+ spans.add(root)
+
+ final long tickEnd = System.currentTimeMillis()
+
+ for (int i = 1; i <= 10; i++) {
+ spans.add(tracer
+ .buildSpan("fake_" + i)
+ .withServiceName("foo")
+ .asChildOf(spans.get(i - 1))
+ .start())
+ }
+ spans.get(1).finish(tickEnd)
+
+ expect:
+ root.context().getTrace().size() == nbSamples + 1
+ root.context().getTrace().containsAll(spans)
+ spans[(int) (Math.random() * nbSamples)].context.trace.containsAll(spans)
+ }
+
+ def "global span tags populated on each span"() {
+ setup:
+ System.setProperty("dd.trace.span.tags", tagString)
+ tracer = new DDTracer(writer)
+ def span = tracer.buildSpan("op name").withServiceName("foo").start()
+ tags.putAll([(DDTags.THREAD_NAME): Thread.currentThread().getName(),
+ (DDTags.THREAD_ID) : Thread.currentThread().getId(),
+ (DDTags.SPAN_TYPE) : null])
+
+ expect:
+ span.tags == tags
+
+ where:
+ tagString | tags
+ "" | [:]
+ "in:val:id" | [:]
+ "a:x" | [a: "x"]
+ "a:a,a:b,a:c" | [a: "c"]
+ "a:1,b-c:d" | [a: "1", "b-c": "d"]
+ }
+}

MODIFY
dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanTest.groovy
dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanTest.groovy
@@ -120,7 +120,7 @@ class DDSpanTest extends Specification {
def between = System.currentTimeMillis()
def betweenDur = System.currentTimeMillis() - between
span.finish()
- def total = System.currentTimeMillis() - start
+ def total = Math.max(1, System.currentTimeMillis() - start)
expect:
span.durationNano >= TimeUnit.MILLISECONDS.toNanos(betweenDur)

MODIFY
dd-trace-ot/src/test/groovy/datadog/trace/DDTraceConfigTest.groovy
dd-trace-ot/src/test/groovy/datadog/trace/DDTraceConfigTest.groovy
@@ -118,4 +118,35 @@ class DDTraceConfigTest extends Specification {
"writer" | "agent.host" | "somethingelse" | "DDAgentWriter { api=DDApi { tracesEndpoint=http://somethingelse:8126/v0.3/traces } }"
"writer" | "agent.port" | "9999" | "DDAgentWriter { api=DDApi { tracesEndpoint=http://localhost:9999/v0.3/traces } }"
}
+
+ def "parsing valid string returns a map"() {
+ expect:
+ DDTraceConfig.parseMap(str) == map
+
+ where:
+ str | map
+ "a:a;" | [a: "a;"]
+ "a:1, a:2, a:3" | [a: "3"]
+ "a:b,c:d" | [a: "b", c: "d"]
+ "key 1!:va|ue_1," | ["key 1!": "va|ue_1"]
+ " key1 :value1 ,\t key2: value2" | [key1: "value1", key2: "value2"]
+ }
+
+ def "parsing an invalid string returns an empty map"() {
+ expect:
+ DDTraceConfig.parseMap(str) == map
+
+ where:
+ str | map
+ null | [:]
+ "" | [:]
+ "1" | [:]
+ "a" | [:]
+ "a:" | [:]
+ "a,1" | [:]
+ "in:val:id" | [:]
+ "a:b:c:d" | [:]
+ "a:b,c,d" | [:]
+ "!a" | [:]
+ }
}

DELETE
dd-trace-ot/src/test/java/datadog/opentracing/DDSpanBuilderTest.java
-package datadog.opentracing;
-
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import datadog.trace.api.DDTags;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public class DDSpanBuilderTest {
-
- private DDTracer tracer;
-
- @Before
- public void setUp() throws Exception {
- tracer = new DDTracer();
- }
-
- @After
- public void tearDown() throws Exception {}
-
- @Test
- public void shouldBuildSimpleSpan() {
-
- final String expectedName = "fakeName";
- final DDSpan span = tracer.buildSpan(expectedName).withServiceName("foo").startManual();
- assertThat(span.getOperationName()).isEqualTo(expectedName);
- }
-
- @Test
- public void shouldBuildMoreComplexSpan() {
-
- final String expectedName = "fakeName";
- final Map tags =
- new HashMap<String, Object>() {
- {
- put("1", true);
- put("2", "fakeString");
- put("3", 42.0);
- }
- };
-
- DDSpan span =
- tracer
- .buildSpan(expectedName)
- .withServiceName("foo")
- .withTag("1", (Boolean) tags.get("1"))
- .withTag("2", (String) tags.get("2"))
- .withTag("3", (Number) tags.get("3"))
- .startManual();
-
- assertThat(span.getOperationName()).isEqualTo(expectedName);
- assertThat(span.getTags()).containsAllEntriesOf(tags);
-
- // with no tag provided
-
- span = tracer.buildSpan(expectedName).withServiceName("foo").startManual();
-
- assertThat(span.getTags()).isNotNull();
- assertThat(span.getTags().size()).isEqualTo(3);
-
- // with all custom fields provided
- final String expectedResource = "fakeResource";
- final String expectedService = "fakeService";
- final String expectedType = "fakeType";
-
- span =
- tracer
- .buildSpan(expectedName)
- .withServiceName("foo")
- .withResourceName(expectedResource)
- .withServiceName(expectedService)
- .withErrorFlag()
- .withSpanType(expectedType)
- .startManual();
-
- final DDSpanContext actualContext = span.context();
-
- assertThat(actualContext.getResourceName()).isEqualTo(expectedResource);
- assertThat(actualContext.getErrorFlag()).isTrue();
- assertThat(actualContext.getServiceName()).isEqualTo(expectedService);
- assertThat(actualContext.getSpanType()).isEqualTo(expectedType);
- assertThat(actualContext.getTags().get(DDTags.THREAD_NAME))
- .isEqualTo(Thread.currentThread().getName());
- assertThat(actualContext.getTags().get(DDTags.THREAD_ID))
- .isEqualTo(Thread.currentThread().getId());
- }
-
- @Test
- public void shouldBuildSpanTimestampInNano() {
-
- // time in micro
- final long expectedTimestamp = 487517802L * 1000 * 1000L;
- final String expectedName = "fakeName";
-
- DDSpan span =
- tracer
- .buildSpan(expectedName)
- .withServiceName("foo")
- .withStartTimestamp(expectedTimestamp)
- .start();
-
- // get return nano time
- assertThat(span.getStartTime()).isEqualTo(expectedTimestamp * 1000L);
-
- // auto-timestamp in nanoseconds
- final long tick = System.currentTimeMillis();
- span = tracer.buildSpan(expectedName).withServiceName("foo").startManual();
-
- // Give a range of +/- 5 millis
- assertThat(span.getStartTime())
- .isBetween(MILLISECONDS.toNanos(tick - 5), MILLISECONDS.toNanos(tick + 5));
- }
-
- @Test
- public void shouldLinkToParentSpan() {
-
- final long spanId = 1L;
- final long expectedParentId = spanId;
-
- final DDSpanContext mockedContext = mock(DDSpanContext.class);
-
- when(mockedContext.getSpanId()).thenReturn(spanId);
- when(mockedContext.getServiceName()).thenReturn("foo");
-
- final String expectedName = "fakeName";
-
- final DDSpan span =
- tracer
- .buildSpan(expectedName)
- .withServiceName("foo")
- .asChildOf(mockedContext)
- .startManual();
-
- final DDSpanContext actualContext = span.context();
-
- assertThat(actualContext.getParentId()).isEqualTo(expectedParentId);
- }
-
- @Test
- public void shouldInheritOfTheDDParentAttributes() {
-
- final String expectedName = "fakeName";
- final String expectedParentServiceName = "fakeServiceName";
- final String expectedParentResourceName = "fakeResourceName";
- final String expectedParentType = "fakeType";
- final String expectedChildServiceName = "fakeServiceName-child";
- final String expectedChildResourceName = "fakeResourceName-child";
- final String expectedChildType = "fakeType-child";
- final String expectedBaggageItemKey = "fakeKey";
- final String expectedBaggageItemValue = "fakeValue";
-
- final DDSpan parent =
- tracer
- .buildSpan(expectedName)
- .withServiceName("foo")
- .withResourceName(expectedParentResourceName)
- .withSpanType(expectedParentType)
- .startManual();
-
- parent.setBaggageItem(expectedBaggageItemKey, expectedBaggageItemValue);
-
- // ServiceName and SpanType are always set by the parent if they are not present in the child
- DDSpan span =
- tracer
- .buildSpan(expectedName)
- .withServiceName(expectedParentServiceName)
- .asChildOf(parent)
- .startManual();
-
- assertThat(span.getOperationName()).isEqualTo(expectedName);
- assertThat(span.getBaggageItem(expectedBaggageItemKey)).isEqualTo(expectedBaggageItemValue);
- assertThat(span.context().getServiceName()).isEqualTo(expectedParentServiceName);
- assertThat(span.context().getResourceName()).isNotEqualTo(expectedParentResourceName);
- assertThat(span.context().getSpanType()).isEqualTo(expectedParentType);
-
- // ServiceName and SpanType are always overwritten by the child if they are present
- span =
- tracer
- .buildSpan(expectedName)
- .withServiceName(expectedChildServiceName)
- .withResourceName(expectedChildResourceName)
- .withSpanType(expectedChildType)
- .asChildOf(parent)
- .startManual();
-
- assertThat(span.getOperationName()).isEqualTo(expectedName);
- assertThat(span.getBaggageItem(expectedBaggageItemKey)).isEqualTo(expectedBaggageItemValue);
- assertThat(span.context().getServiceName()).isEqualTo(expectedChildServiceName);
- assertThat(span.context().getResourceName()).isEqualTo(expectedChildResourceName);
- assertThat(span.context().getSpanType()).isEqualTo(expectedChildType);
- }
-
- @Test
- public void shouldTrackAllSpanInTrace() throws InterruptedException {
-
- final ArrayList<DDSpan> spans = new ArrayList<>();
- final int nbSamples = 10;
-
- // root (aka spans[0]) is the parent
- // others are just for fun
-
- final DDSpan root = tracer.buildSpan("fake_O").withServiceName("foo").startManual();
- spans.add(root);
-
- Thread.sleep(200);
- final long tickEnd = System.currentTimeMillis();
-
- for (int i = 1; i <= 10; i++) {
- spans.add(
- tracer
- .buildSpan("fake_" + i)
- .withServiceName("foo")
- .asChildOf(spans.get(i - 1))
- .startManual());
- }
- spans.get(1).finish(tickEnd);
-
- assertThat(root.context().getTrace()).hasSize(nbSamples + 1);
- assertThat(root.context().getTrace()).containsAll(spans);
- assertThat(spans.get((int) (Math.random() * nbSamples)).context().getTrace())
- .containsAll(spans);
- }
-}

Configure tags to be added to every span