Skip to content

Commit 7ce4dd9

Browse files
committed
feat(core): Introduce tracing propagator
1 parent dbbe55c commit 7ce4dd9

File tree

8 files changed

+426
-2
lines changed

8 files changed

+426
-2
lines changed

dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import static datadog.trace.api.DDTags.DJM_ENABLED;
66
import static datadog.trace.api.DDTags.DSM_ENABLED;
77
import static datadog.trace.api.DDTags.PROFILING_CONTEXT_ENGINE;
8+
import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.TRACING_CONCERN;
89
import static datadog.trace.common.metrics.MetricsAggregatorFactory.createMetricsAggregator;
910
import static datadog.trace.util.AgentThreadFactory.AGENT_THREAD_GROUP;
1011
import static datadog.trace.util.CollectionUtils.tryMakeImmutableMap;
@@ -18,6 +19,7 @@
1819
import datadog.communication.ddagent.SharedCommunicationObjects;
1920
import datadog.communication.monitor.Monitoring;
2021
import datadog.communication.monitor.Recording;
22+
import datadog.context.propagation.Propagators;
2123
import datadog.trace.api.ClassloaderConfigurationOverrides;
2224
import datadog.trace.api.Config;
2325
import datadog.trace.api.DDSpanId;
@@ -86,6 +88,7 @@
8688
import datadog.trace.core.propagation.ExtractedContext;
8789
import datadog.trace.core.propagation.HttpCodec;
8890
import datadog.trace.core.propagation.PropagationTags;
91+
import datadog.trace.core.propagation.TracingPropagator;
8992
import datadog.trace.core.scopemanager.ContinuableScopeManager;
9093
import datadog.trace.core.taginterceptor.RuleFlags;
9194
import datadog.trace.core.taginterceptor.TagInterceptor;
@@ -719,6 +722,9 @@ private CoreTracer(
719722
this.propagation =
720723
new CorePropagation(builtExtractor, injector, injectors, dataStreamContextInjector);
721724

725+
Propagators.register(TracingPropagator.concern(), new TracingPropagator(injector, extractor));
726+
Propagators.register(TRACING_CONCERN, new TracingPropagator(injector, extractor));
727+
722728
this.tagInterceptor =
723729
null == tagInterceptor ? new TagInterceptor(new RuleFlags(config)) : tagInterceptor;
724730

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package datadog.trace.core.propagation;
2+
3+
import static datadog.trace.bootstrap.instrumentation.api.AgentSpan.fromContext;
4+
import static datadog.trace.bootstrap.instrumentation.api.AgentSpan.fromSpanContext;
5+
6+
import datadog.context.Context;
7+
import datadog.context.propagation.CarrierSetter;
8+
import datadog.context.propagation.CarrierVisitor;
9+
import datadog.context.propagation.Propagator;
10+
import datadog.trace.bootstrap.instrumentation.api.AgentPropagation;
11+
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
12+
import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext;
13+
import datadog.trace.bootstrap.instrumentation.api.TagContext;
14+
import datadog.trace.core.DDSpanContext;
15+
import datadog.trace.core.propagation.HttpCodec.Extractor;
16+
import datadog.trace.core.propagation.HttpCodec.Injector;
17+
import javax.annotation.ParametersAreNonnullByDefault;
18+
19+
/** Propagator for tracing concern. */
20+
@ParametersAreNonnullByDefault
21+
public class TracingPropagator implements Propagator {
22+
private final Injector injector;
23+
private final Extractor extractor;
24+
25+
/**
26+
* Constructor.
27+
*
28+
* @param injector The {@link Injector} used for tracing context injection.
29+
* @param extractor The {@link Extractor} used for tracing context extraction.
30+
*/
31+
public TracingPropagator(Injector injector, Extractor extractor) {
32+
this.injector = injector;
33+
this.extractor = extractor;
34+
}
35+
36+
@Override
37+
public <C> void inject(Context context, C carrier, CarrierSetter<C> setter) {
38+
AgentSpan span;
39+
//noinspection ConstantValue
40+
if (context == null
41+
|| carrier == null
42+
|| setter == null
43+
|| (span = fromContext(context)) == null) {
44+
return;
45+
}
46+
AgentSpanContext spanContext = span.context();
47+
if (spanContext instanceof DDSpanContext) {
48+
DDSpanContext ddSpanContext = (DDSpanContext) spanContext;
49+
ddSpanContext.getTraceCollector().setSamplingPriorityIfNecessary();
50+
this.injector.inject(ddSpanContext, carrier, setter::set);
51+
}
52+
}
53+
54+
@Override
55+
public <C> Context extract(Context context, C carrier, CarrierVisitor<C> visitor) {
56+
//noinspection ConstantValue
57+
if (context == null || carrier == null || visitor == null) {
58+
return context;
59+
}
60+
TagContext spanContext = this.extractor.extract(carrier, toContextVisitor(visitor));
61+
// If the extraction fails, return the original context
62+
if (spanContext == null) {
63+
return context;
64+
}
65+
// Otherwise, append a fake span wrapper to context
66+
return context.with(fromSpanContext(spanContext));
67+
}
68+
69+
private static <C> AgentPropagation.ContextVisitor<C> toContextVisitor(
70+
CarrierVisitor<C> visitor) {
71+
if (visitor instanceof AgentPropagation.ContextVisitor) {
72+
return (AgentPropagation.ContextVisitor<C>) visitor;
73+
}
74+
return (carrier1, classifier) -> visitor.forEachKeyValue(carrier1, classifier::accept);
75+
}
76+
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
package datadog.trace.core.propagation
2+
3+
import datadog.context.Context
4+
import datadog.context.propagation.CarrierSetter
5+
import datadog.context.propagation.Propagators
6+
import datadog.trace.api.sampling.PrioritySampling
7+
import datadog.trace.bootstrap.instrumentation.api.AgentPropagation
8+
import datadog.trace.common.writer.LoggingWriter
9+
import datadog.trace.core.ControllableSampler
10+
import datadog.trace.core.test.DDCoreSpecification
11+
12+
import static datadog.trace.api.sampling.PrioritySampling.SAMPLER_KEEP
13+
import static datadog.trace.api.sampling.PrioritySampling.USER_DROP
14+
import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.XRAY_TRACING_CONCERN
15+
16+
class TracingPropagatorTest extends DDCoreSpecification {
17+
HttpCodec.Injector injector
18+
HttpCodec.Extractor extractor
19+
TracingPropagator propagator
20+
21+
def setup() {
22+
injector = Mock(HttpCodec.Injector)
23+
extractor = Mock(HttpCodec.Extractor)
24+
this.propagator = new TracingPropagator(injector, extractor)
25+
}
26+
27+
def 'test tracing propagator context injection'() {
28+
setup:
29+
def tracer = tracerBuilder().build()
30+
def span = tracer.buildSpan('test', 'operation').start()
31+
def setter = Mock(CarrierSetter)
32+
def carrier = new Object()
33+
34+
when:
35+
this.propagator.inject(span, carrier, setter)
36+
37+
then:
38+
1 * injector.inject(span.context(), carrier, _)
39+
40+
cleanup:
41+
span.finish()
42+
tracer.close()
43+
}
44+
45+
def 'test tracing propagator context extractor'() {
46+
setup:
47+
def context = Context.root()
48+
// TODO Use ContextVisitor mock as getter once extractor API is refactored
49+
def getter = Mock(AgentPropagation.ContextVisitor)
50+
def carrier = new Object()
51+
52+
when:
53+
this.propagator.extract(context, carrier, getter)
54+
55+
then:
56+
1 * extractor.extract(carrier, _)
57+
}
58+
59+
def 'span priority set when injecting'() {
60+
given:
61+
injectSysConfig('writer.type', 'LoggingWriter')
62+
def tracer = tracerBuilder().build()
63+
def setter = Mock(CarrierSetter)
64+
def carrier = new Object()
65+
66+
when:
67+
def root = tracer.buildSpan('test', 'parent').start()
68+
def child = tracer.buildSpan('test', 'child').asChildOf(root).start()
69+
Propagators.defaultPropagator().inject(child, carrier, setter)
70+
71+
then:
72+
root.getSamplingPriority() == SAMPLER_KEEP as int
73+
child.getSamplingPriority() == root.getSamplingPriority()
74+
1 * setter.set(carrier, DatadogHttpCodec.SAMPLING_PRIORITY_KEY, String.valueOf(SAMPLER_KEEP))
75+
76+
cleanup:
77+
child.finish()
78+
root.finish()
79+
tracer.close()
80+
}
81+
82+
def 'span priority only set after first injection'() {
83+
given:
84+
def sampler = new ControllableSampler()
85+
def tracer = tracerBuilder().writer(new LoggingWriter()).sampler(sampler).build()
86+
def setter = Mock(AgentPropagation.Setter)
87+
def carrier = new Object()
88+
89+
when:
90+
def root = tracer.buildSpan('test', 'parent').start()
91+
def child = tracer.buildSpan('test', 'child').asChildOf(root).start()
92+
Propagators.defaultPropagator().inject(child, carrier, setter)
93+
94+
then:
95+
root.getSamplingPriority() == SAMPLER_KEEP as int
96+
child.getSamplingPriority() == root.getSamplingPriority()
97+
1 * setter.set(carrier, DatadogHttpCodec.SAMPLING_PRIORITY_KEY, String.valueOf(SAMPLER_KEEP))
98+
99+
when:
100+
sampler.nextSamplingPriority = PrioritySampling.SAMPLER_DROP as int
101+
def child2 = tracer.buildSpan('test', 'child2').asChildOf(root).start()
102+
Propagators.defaultPropagator().inject(child2, carrier, setter)
103+
104+
then:
105+
root.getSamplingPriority() == SAMPLER_KEEP as int
106+
child.getSamplingPriority() == root.getSamplingPriority()
107+
child2.getSamplingPriority() == root.getSamplingPriority()
108+
1 * setter.set(carrier, DatadogHttpCodec.SAMPLING_PRIORITY_KEY, String.valueOf(SAMPLER_KEEP))
109+
110+
cleanup:
111+
child.finish()
112+
child2.finish()
113+
root.finish()
114+
tracer.close()
115+
}
116+
117+
def 'injection does not override set priority'() {
118+
given:
119+
def sampler = new ControllableSampler()
120+
def tracer = tracerBuilder().writer(new LoggingWriter()).sampler(sampler).build()
121+
def setter = Mock(AgentPropagation.Setter)
122+
def carrier = new Object()
123+
124+
when:
125+
def root = tracer.buildSpan('test', 'root').start()
126+
def child = tracer.buildSpan('test', 'child').asChildOf(root).start()
127+
child.setSamplingPriority(USER_DROP)
128+
Propagators.defaultPropagator().inject(child, carrier, setter)
129+
130+
then:
131+
root.getSamplingPriority() == USER_DROP as int
132+
child.getSamplingPriority() == root.getSamplingPriority()
133+
1 * setter.set(carrier, DatadogHttpCodec.SAMPLING_PRIORITY_KEY, String.valueOf(USER_DROP))
134+
135+
cleanup:
136+
child.finish()
137+
root.finish()
138+
tracer.close()
139+
}
140+
141+
def 'test ASM standalone billing propagator stop propagation'() {
142+
setup:
143+
injectSysConfig('experimental.appsec.standalone.enabled', standaloneAsmEnabled.toString())
144+
def tracer = tracerBuilder().build()
145+
def span = tracer.buildSpan('test', 'operation').start()
146+
def setter = Mock(CarrierSetter)
147+
def carrier = new Object()
148+
149+
when:
150+
Propagators.defaultPropagator().inject(span, carrier, setter)
151+
152+
then:
153+
if (standaloneAsmEnabled) {
154+
0 * setter.set(_, _, _)
155+
} else {
156+
(1.._) * setter.set(_, _, _)
157+
}
158+
159+
cleanup:
160+
span.finish()
161+
tracer.close()
162+
163+
where:
164+
standaloneAsmEnabled << [true, false]
165+
}
166+
167+
def 'test AWS X-Ray propagator'() {
168+
setup:
169+
def tracer = tracerBuilder().build()
170+
def span = tracer.buildSpan('test', 'operation').start()
171+
def propagator = Propagators.forConcerns(XRAY_TRACING_CONCERN)
172+
def setter = Mock(CarrierSetter)
173+
def carrier = new Object()
174+
175+
when:
176+
propagator.inject(span, carrier, setter)
177+
178+
then:
179+
1 * setter.set(carrier, 'X-Amzn-Trace-Id', _)
180+
181+
cleanup:
182+
span.finish()
183+
tracer.close()
184+
}
185+
}

internal-api/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ excludedClassesCoverage += [
6464
"datadog.trace.bootstrap.instrumentation.api.Tags",
6565
"datadog.trace.bootstrap.instrumentation.api.CommonTagValues",
6666
// Caused by empty 'default' interface method
67+
"datadog.trace.bootstrap.instrumentation.api.AgentPropagation",
68+
"datadog.trace.bootstrap.instrumentation.api.AgentPropagation.ContextVisitor",
6769
"datadog.trace.bootstrap.instrumentation.api.AgentSpan",
6870
"datadog.trace.bootstrap.instrumentation.api.AgentSpanContext",
6971
"datadog.trace.bootstrap.instrumentation.api.AgentTracer",

internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentPropagation.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
package datadog.trace.bootstrap.instrumentation.api;
22

3+
import datadog.context.propagation.CarrierSetter;
4+
import datadog.context.propagation.CarrierVisitor;
5+
import datadog.context.propagation.Concern;
36
import datadog.trace.api.TracePropagationStyle;
47
import java.util.LinkedHashMap;
8+
import java.util.function.BiConsumer;
9+
import javax.annotation.ParametersAreNonnullByDefault;
510

611
public interface AgentPropagation {
12+
Concern TRACING_CONCERN = Concern.named("tracing");
13+
714
<C> void inject(AgentSpan span, C carrier, Setter<C> setter);
815

916
<C> void inject(AgentSpanContext context, C carrier, Setter<C> setter);
@@ -25,7 +32,7 @@ <C> void injectPathwayContext(
2532
<C> void injectPathwayContextWithoutSendingStats(
2633
AgentSpan span, C carrier, Setter<C> setter, LinkedHashMap<String, String> sortedTags);
2734

28-
interface Setter<C> {
35+
interface Setter<C> extends CarrierSetter<C> {
2936
void set(C carrier, String key, String value);
3037
}
3138

@@ -35,7 +42,18 @@ interface KeyClassifier {
3542
boolean accept(String key, String value);
3643
}
3744

38-
interface ContextVisitor<C> {
45+
interface ContextVisitor<C> extends CarrierVisitor<C> {
3946
void forEachKey(C carrier, KeyClassifier classifier);
47+
48+
@ParametersAreNonnullByDefault
49+
@Override
50+
default void forEachKeyValue(C carrier, BiConsumer<String, String> visitor) {
51+
forEachKey(
52+
carrier,
53+
(key, value) -> {
54+
visitor.accept(key, value);
55+
return true;
56+
});
57+
}
4058
}
4159
}

internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,27 @@
1010
import datadog.trace.api.gateway.IGSpanInfo;
1111
import datadog.trace.api.gateway.RequestContext;
1212
import datadog.trace.api.interceptor.MutableSpan;
13+
import datadog.trace.bootstrap.instrumentation.api.AgentTracer.NoopAgentSpan;
14+
import datadog.trace.bootstrap.instrumentation.api.AgentTracer.NoopContext;
1315
import java.util.Map;
1416
import javax.annotation.Nullable;
1517

1618
public interface AgentSpan
1719
extends MutableSpan, ImplicitContextKeyed, Context, IGSpanInfo, WithAgentSpan {
1820

21+
// TODO Javadoc
22+
static AgentSpan fromContext(Context context) {
23+
return context.get(SPAN_KEY);
24+
}
25+
26+
// TODO Javadoc
27+
static AgentSpan fromSpanContext(AgentSpanContext spanContext) {
28+
if (spanContext == null || spanContext == NoopContext.INSTANCE) {
29+
return NoopAgentSpan.INSTANCE;
30+
}
31+
return new AgentTracer.ExtractedSpan(spanContext);
32+
}
33+
1934
DDTraceId getTraceId();
2035

2136
long getSpanId();

0 commit comments

Comments
 (0)