Skip to content

Commit 6295876

Browse files
Support adding W3C baggage as span tags (#9169)
* WIP: baggage span tags * simple test * fix build errors * adding more tests * Add baggage span tags functionality and tests * Move baggage tags feature to HttpServerDecorator.getExtractedSpanContext and update it to check the W3C baggage held in the new Context API (the baggage map in CoreTracer is OpenTracing only, and won't contain W3C baggage) * Fix potential corner-case where TagContext.getTags() may return an immutable EMPTY TagMap * Move baggage tags unit test to HttpServerDecoratorTest * Baggage tags unit tests have been moved to HttpServerDecoratorTest * Move W3C baggage tag injection to just before the span is serialized * Update test trace writer so injected baggage metadata can be asserted as tags * spotless format * Remove unused method * Fix test code * Support recording of baggage, that would be sent to agent as trace metadata, during tests --------- Co-authored-by: Stuart McCulloch <[email protected]>
1 parent 8fc887a commit 6295876

File tree

11 files changed

+152
-3
lines changed

11 files changed

+152
-3
lines changed

dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import datadog.trace.bootstrap.instrumentation.api.Tags
3939
import datadog.trace.bootstrap.instrumentation.api.URIDataAdapter
4040
import datadog.trace.bootstrap.instrumentation.api.URIUtils
4141
import datadog.trace.core.DDSpan
42+
import datadog.trace.core.Metadata
4243
import datadog.trace.core.datastreams.StatsGroup
4344
import datadog.trace.test.util.Flaky
4445
import groovy.json.JsonOutput
@@ -778,6 +779,62 @@ abstract class HttpServerTest<SERVER> extends WithHttpServer<SERVER> {
778779
'GET' | null | 'x-datadog-test-request-header' | 'bar' | ['request_header_tag': 'bar']
779780
}
780781

782+
def "test baggage span tags are properly added"() {
783+
setup:
784+
def recordedBaggageTags = [:]
785+
TEST_WRITER.metadataConsumer = { Metadata md ->
786+
md.baggage.forEach { k, v ->
787+
// record non-internal baggage sent to agent as trace metadata
788+
if (!k.startsWith("_dd.")) {
789+
recordedBaggageTags.put(k, v)
790+
}
791+
}
792+
}
793+
// Use default configuration for TRACE_BAGGAGE_TAG_KEYS (user.id, session.id, account.id)
794+
def baggageHeader = "user.id=test-user,session.id=test-session,account.id=test-account,language=en"
795+
def request = request(SUCCESS, 'GET', null)
796+
.header("baggage", baggageHeader)
797+
.build()
798+
def response = client.newCall(request).execute()
799+
if (isDataStreamsEnabled()) {
800+
TEST_DATA_STREAMS_WRITER.waitForGroups(1)
801+
}
802+
803+
expect:
804+
response.code() == SUCCESS.status
805+
response.body().string() == SUCCESS.body
806+
807+
and:
808+
assertTraces(1) {
809+
trace(spanCount(SUCCESS)) {
810+
sortSpansByStart()
811+
// Verify baggage tags are added for default configured keys only
812+
serverSpan(it, null, null, 'GET', SUCCESS)
813+
if (hasHandlerSpan()) {
814+
handlerSpan(it)
815+
}
816+
controllerSpan(it)
817+
if (hasResponseSpan(SUCCESS)) {
818+
responseSpan(it, SUCCESS)
819+
}
820+
}
821+
}
822+
recordedBaggageTags == [
823+
"baggage.user.id": "test-user",
824+
"baggage.session.id": "test-session",
825+
"baggage.account.id": "test-account"
826+
// "baggage.language" should NOT be present since it's not in default config
827+
]
828+
829+
and:
830+
if (isDataStreamsEnabled()) {
831+
StatsGroup first = TEST_DATA_STREAMS_WRITER.groups.find { it.parentHash == 0 }
832+
verifyAll(first) {
833+
tags == DSM_EDGE_TAGS
834+
}
835+
}
836+
}
837+
781838
@Flaky(value = "https://github.com/DataDog/dd-trace-java/issues/4690", suites = ["MuleHttpServerForkedTest"])
782839
def "test QUERY_ENCODED_BOTH with response header x-ig-response-header tag mapping"() {
783840
setup:

dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ public final class ConfigDefaults {
8484
new LinkedHashSet<>(asList(PropagationStyle.DATADOG));
8585
static final int DEFAULT_TRACE_BAGGAGE_MAX_ITEMS = 64;
8686
static final int DEFAULT_TRACE_BAGGAGE_MAX_BYTES = 8192;
87+
static final List<String> DEFAULT_TRACE_BAGGAGE_TAG_KEYS =
88+
Arrays.asList("user.id", "session.id", "account.id");
8789
static final boolean DEFAULT_JMX_FETCH_ENABLED = true;
8890
static final boolean DEFAULT_TRACE_AGENT_V05_ENABLED = false;
8991

dd-trace-api/src/main/java/datadog/trace/api/config/TracerConfig.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ public final class TracerConfig {
9898
public static final String TRACE_PROPAGATION_EXTRACT_FIRST = "trace.propagation.extract.first";
9999
public static final String TRACE_BAGGAGE_MAX_ITEMS = "trace.baggage.max.items";
100100
public static final String TRACE_BAGGAGE_MAX_BYTES = "trace.baggage.max.bytes";
101+
public static final String TRACE_BAGGAGE_TAG_KEYS = "trace.baggage.tag.keys";
101102

102103
public static final String ENABLE_TRACE_AGENT_V05 = "trace.agent.v0.5.enabled";
103104

dd-trace-core/src/main/java/datadog/trace/common/writer/ListWriter.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ public class ListWriter extends CopyOnWriteArrayList<List<DDSpan>> implements Wr
2626

2727
private Filter filter = ACCEPT_ALL;
2828

29+
private MetadataConsumer metadataConsumer = MetadataConsumer.NO_OP;
30+
2931
public List<DDSpan> firstTrace() {
3032
return get(0);
3133
}
@@ -38,7 +40,7 @@ public void write(List<DDSpan> trace) {
3840
for (DDSpan span : trace) {
3941
// This is needed to properly do all delayed processing to make this writer even
4042
// remotely realistic so the test actually test something
41-
span.processTagsAndBaggage(MetadataConsumer.NO_OP);
43+
span.processTagsAndBaggage(metadataConsumer);
4244
}
4345

4446
add(trace);
@@ -117,6 +119,11 @@ public void setFilter(Filter filter) {
117119
this.filter = filter;
118120
}
119121

122+
/** Set a {@link MetadataConsumer} to capture what trace metadata would be sent to the agent. */
123+
public void setMetadataConsumer(MetadataConsumer metadataConsumer) {
124+
this.metadataConsumer = metadataConsumer;
125+
}
126+
120127
private boolean isReported(DDSpan span) {
121128
for (List<DDSpan> trace : this) {
122129
for (DDSpan aSpan : trace) {

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
@@ -59,6 +59,7 @@
5959
import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext;
6060
import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink;
6161
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
62+
import datadog.trace.bootstrap.instrumentation.api.Baggage;
6263
import datadog.trace.bootstrap.instrumentation.api.BlackHoleSpan;
6364
import datadog.trace.bootstrap.instrumentation.api.ProfilingContextIntegration;
6465
import datadog.trace.bootstrap.instrumentation.api.SpanAttributes;
@@ -1591,6 +1592,7 @@ private DDSpanContext buildSpanContext() {
15911592
final long spanId;
15921593
final long parentSpanId;
15931594
final Map<String, String> baggage;
1595+
final Baggage w3cBaggage;
15941596
final TraceCollector parentTraceCollector;
15951597
final int samplingPriority;
15961598
final CharSequence origin;
@@ -1645,6 +1647,7 @@ private DDSpanContext buildSpanContext() {
16451647
traceId = ddsc.getTraceId();
16461648
parentSpanId = ddsc.getSpanId();
16471649
baggage = ddsc.getBaggageItems();
1650+
w3cBaggage = null;
16481651
parentTraceCollector = ddsc.getTraceCollector();
16491652
samplingPriority = PrioritySampling.UNSET;
16501653
origin = null;
@@ -1706,6 +1709,7 @@ private DDSpanContext buildSpanContext() {
17061709
coreTagsNeedsIntercept = true; // maybe intercept isn't needed?
17071710
origin = tc.getOrigin();
17081711
baggage = tc.getBaggage();
1712+
w3cBaggage = tc.getW3CBaggage();
17091713
requestContextDataAppSec = tc.getRequestContextDataAppSec();
17101714
requestContextDataIast = tc.getRequestContextDataIast();
17111715
ciVisibilityContextData = tc.getCiVisibilityContextData();
@@ -1715,6 +1719,7 @@ private DDSpanContext buildSpanContext() {
17151719
coreTagsNeedsIntercept = false;
17161720
origin = null;
17171721
baggage = null;
1722+
w3cBaggage = null;
17181723
requestContextDataAppSec = null;
17191724
requestContextDataIast = null;
17201725
ciVisibilityContextData = null;
@@ -1805,6 +1810,7 @@ private DDSpanContext buildSpanContext() {
18051810
samplingPriority,
18061811
origin,
18071812
baggage,
1813+
w3cBaggage,
18081814
errorFlag,
18091815
spanType,
18101816
tagsSize,

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import static datadog.trace.api.cache.RadixTreeCache.HTTP_STATUSES;
66
import static datadog.trace.bootstrap.instrumentation.api.ErrorPriorities.UNSET;
77

8+
import datadog.trace.api.Config;
89
import datadog.trace.api.DDSpanId;
910
import datadog.trace.api.DDTags;
1011
import datadog.trace.api.DDTraceId;
@@ -23,6 +24,7 @@
2324
import datadog.trace.api.sampling.SamplingMechanism;
2425
import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext;
2526
import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink;
27+
import datadog.trace.bootstrap.instrumentation.api.Baggage;
2628
import datadog.trace.bootstrap.instrumentation.api.ProfilerContext;
2729
import datadog.trace.bootstrap.instrumentation.api.ProfilingContextIntegration;
2830
import datadog.trace.bootstrap.instrumentation.api.ResourceNamePriorities;
@@ -77,6 +79,8 @@ public class DDSpanContext
7779
/** Baggage is associated with the whole trace and shared with other spans */
7880
private volatile Map<String, String> baggageItems;
7981

82+
private final Baggage w3cBaggage;
83+
8084
// Not Shared with other span contexts
8185
private final DDTraceId traceId;
8286
private final long spanId;
@@ -187,6 +191,7 @@ public DDSpanContext(
187191
samplingPriority,
188192
origin,
189193
baggageItems,
194+
null,
190195
errorFlag,
191196
spanType,
192197
tagsSize,
@@ -233,6 +238,7 @@ public DDSpanContext(
233238
samplingPriority,
234239
origin,
235240
baggageItems,
241+
null,
236242
errorFlag,
237243
spanType,
238244
tagsSize,
@@ -279,6 +285,7 @@ public DDSpanContext(
279285
samplingPriority,
280286
origin,
281287
baggageItems,
288+
null,
282289
errorFlag,
283290
spanType,
284291
tagsSize,
@@ -304,6 +311,7 @@ public DDSpanContext(
304311
final int samplingPriority,
305312
final CharSequence origin,
306313
final Map<String, String> baggageItems,
314+
final Baggage w3cBaggage,
307315
final boolean errorFlag,
308316
final CharSequence spanType,
309317
final int tagsSize,
@@ -331,6 +339,7 @@ public DDSpanContext(
331339
} else {
332340
this.baggageItems = new ConcurrentHashMap<>(baggageItems);
333341
}
342+
this.w3cBaggage = w3cBaggage;
334343

335344
this.requestContextDataAppSec = requestContextDataAppSec;
336345
this.requestContextDataIast = requestContextDataIast;
@@ -926,6 +935,9 @@ public void processTagsAndBaggage(
926935
Map<String, String> baggageItemsWithPropagationTags;
927936
if (injectBaggageAsTags) {
928937
baggageItemsWithPropagationTags = new HashMap<>(baggageItems);
938+
if (w3cBaggage != null) {
939+
injectW3CBaggageTags(baggageItemsWithPropagationTags);
940+
}
929941
propagationTags.fillTagMap(baggageItemsWithPropagationTags);
930942
} else {
931943
baggageItemsWithPropagationTags = propagationTags.createTagMap();
@@ -948,6 +960,27 @@ public void processTagsAndBaggage(
948960
}
949961
}
950962

963+
void injectW3CBaggageTags(Map<String, String> baggageItemsWithPropagationTags) {
964+
List<String> baggageTagKeys = Config.get().getTraceBaggageTagKeys();
965+
if (baggageTagKeys.isEmpty()) {
966+
return;
967+
}
968+
Map<String, String> w3cBaggageItems = w3cBaggage.asMap();
969+
for (String key : baggageTagKeys) {
970+
if ("*".equals(key)) {
971+
// If the key is "*", we add all baggage items
972+
for (Map.Entry<String, String> entry : w3cBaggageItems.entrySet()) {
973+
baggageItemsWithPropagationTags.put("baggage." + entry.getKey(), entry.getValue());
974+
}
975+
break;
976+
}
977+
String value = w3cBaggageItems.get(key);
978+
if (value != null) {
979+
baggageItemsWithPropagationTags.put("baggage." + key, value);
980+
}
981+
}
982+
}
983+
951984
@Override
952985
public void setIntegrationName(CharSequence integrationName) {
953986
this.integrationName = integrationName;

dd-trace-core/src/main/java/datadog/trace/core/baggage/BaggagePropagator.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
import datadog.context.propagation.CarrierVisitor;
88
import datadog.context.propagation.Propagator;
99
import datadog.trace.api.Config;
10+
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
11+
import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext;
1012
import datadog.trace.bootstrap.instrumentation.api.Baggage;
13+
import datadog.trace.bootstrap.instrumentation.api.TagContext;
1114
import datadog.trace.core.util.PercentEscaper;
1215
import datadog.trace.core.util.PercentEscaper.Escaped;
1316
import java.io.UnsupportedEncodingException;
@@ -109,7 +112,21 @@ public <C> Context extract(Context context, C carrier, CarrierVisitor<C> visitor
109112
}
110113
BaggageExtractor baggageExtractor = new BaggageExtractor();
111114
visitor.forEachKeyValue(carrier, baggageExtractor);
112-
return baggageExtractor.extracted == null ? context : context.with(baggageExtractor.extracted);
115+
Baggage baggage = baggageExtractor.extracted;
116+
if (baggage == null) {
117+
return context;
118+
}
119+
120+
// TODO: consider a better way to link baggage with the extracted (legacy) TagContext
121+
AgentSpan extractedSpan = AgentSpan.fromContext(context);
122+
if (extractedSpan != null) {
123+
AgentSpanContext extractedSpanContext = extractedSpan.context();
124+
if (extractedSpanContext instanceof TagContext) {
125+
((TagContext) extractedSpanContext).setW3CBaggage(baggage);
126+
}
127+
}
128+
129+
return context.with(baggage);
113130
}
114131

115132
private class BaggageExtractor implements BiConsumer<String, String> {

dd-trace-core/src/test/groovy/datadog/trace/core/test/DDCoreSpecification.groovy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ abstract class DDCoreSpecification extends DDSpecification {
7070
prioritySampling,
7171
null,
7272
[:],
73+
null,
7374
false,
7475
spanType,
7576
0,

internal-api/src/main/java/datadog/trace/api/Config.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@
151151
import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_ANALYTICS_ENABLED;
152152
import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_BAGGAGE_MAX_BYTES;
153153
import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_BAGGAGE_MAX_ITEMS;
154+
import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_BAGGAGE_TAG_KEYS;
154155
import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_CLOUD_PAYLOAD_TAGGING_SERVICES;
155156
import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_EXPERIMENTAL_FEATURES_ENABLED;
156157
import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_HTTP_RESOURCE_REMOVE_TRAILING_SLASH;
@@ -583,6 +584,7 @@
583584
import static datadog.trace.api.config.TracerConfig.TRACE_ANALYTICS_ENABLED;
584585
import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_MAX_BYTES;
585586
import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_MAX_ITEMS;
587+
import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_TAG_KEYS;
586588
import static datadog.trace.api.config.TracerConfig.TRACE_CLIENT_IP_HEADER;
587589
import static datadog.trace.api.config.TracerConfig.TRACE_CLIENT_IP_RESOLVER_ENABLED;
588590
import static datadog.trace.api.config.TracerConfig.TRACE_CLOUD_PAYLOAD_TAGGING_MAX_DEPTH;
@@ -823,6 +825,7 @@ public static String getHostName() {
823825
private final boolean tracePropagationExtractFirst;
824826
private final int traceBaggageMaxItems;
825827
private final int traceBaggageMaxBytes;
828+
private final List<String> traceBaggageTagKeys;
826829
private final int clockSyncPeriod;
827830
private final boolean logsInjectionEnabled;
828831

@@ -1694,6 +1697,11 @@ private Config(final ConfigProvider configProvider, final InstrumenterConfig ins
16941697
// If we have a new setting, we log a warning
16951698
logOverriddenDeprecatedSettingWarning(PROPAGATION_STYLE_INJECT, injectOrigin, inject);
16961699
}
1700+
1701+
// Parse the baggage tag keys configuration
1702+
traceBaggageTagKeys =
1703+
configProvider.getList(TRACE_BAGGAGE_TAG_KEYS, DEFAULT_TRACE_BAGGAGE_TAG_KEYS);
1704+
16971705
// Now we can check if we should pick the default injection/extraction
16981706

16991707
tracePropagationStylesToExtract =
@@ -2966,6 +2974,10 @@ public Map<String, String> getBaggageMapping() {
29662974
return baggageMapping;
29672975
}
29682976

2977+
public List<String> getTraceBaggageTagKeys() {
2978+
return traceBaggageTagKeys;
2979+
}
2980+
29692981
public Map<String, String> getHttpServerPathResourceNameMapping() {
29702982
return httpServerPathResourceNameMapping;
29712983
}
@@ -5370,6 +5382,8 @@ public String toString() {
53705382
+ traceKeepLatencyThreshold
53715383
+ ", traceStrictWritesEnabled="
53725384
+ traceStrictWritesEnabled
5385+
+ ", traceBaggageTagKeys="
5386+
+ traceBaggageTagKeys
53735387
+ ", tracePropagationStylesToExtract="
53745388
+ tracePropagationStylesToExtract
53755389
+ ", tracePropagationStylesToInject="

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414

1515
public final class AgentPropagation {
1616
public static final Concern TRACING_CONCERN = named("tracing");
17-
public static final Concern BAGGAGE_CONCERN = named("baggage");
17+
// TODO: Baggage propagator should run after tracing so it can link baggage with the span context
18+
// TODO: remove this priority once we have a story for replacing TagContext with the Context API
19+
public static final Concern BAGGAGE_CONCERN = withPriority("baggage", 105);
1820
public static final Concern XRAY_TRACING_CONCERN = named("tracing-xray");
1921

2022
// TODO DSM propagator should run after the other propagators as it stores the pathway context

0 commit comments

Comments
 (0)