Skip to content

Commit ffe33cd

Browse files
authored
Implementation of BaggagePropagator and BaggageContext (#8330)
* initial implementation and unit tests for inject/extract * adding break to switch statement[C * better check for max bytes * updating key encoder and adding test case * cleanup * updating encoder and byte checks * cleanup * introducing baggagecontext * initial migration to contextAPI rebasing * updating tests for inject/extract rebasing * finishing migration to baggage propagator * adding support for injector only or extractor only rebasing * updating PR comments * cleanup * adding null context check * updating PR comments rebasing * updating tests * adding baggagecache * updating baggagecache * cleanup * updating pr comments * updating PR comments and fixing CI * adding OTEL PercentEncoder and utilizing it in BaggagePropagator * nit fixes * hiding unsafeKey and unsafeValue * change from atomicinteger to int[] * creating EscapedData class to store escaped data * nit comments and merge conflicts * spotless * updating build file * nit update
1 parent 31fb383 commit ffe33cd

File tree

14 files changed

+913
-18
lines changed

14 files changed

+913
-18
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package datadog.trace.api;
22

3+
import static datadog.trace.api.TracePropagationStyle.BAGGAGE;
34
import static datadog.trace.api.TracePropagationStyle.DATADOG;
45
import static datadog.trace.api.TracePropagationStyle.TRACECONTEXT;
56
import static java.util.Arrays.asList;
@@ -78,9 +79,11 @@ public final class ConfigDefaults {
7879
static final int DEFAULT_PARTIAL_FLUSH_MIN_SPANS = 1000;
7980
static final boolean DEFAULT_PROPAGATION_EXTRACT_LOG_HEADER_NAMES_ENABLED = false;
8081
static final Set<TracePropagationStyle> DEFAULT_TRACE_PROPAGATION_STYLE =
81-
new LinkedHashSet<>(asList(DATADOG, TRACECONTEXT));
82+
new LinkedHashSet<>(asList(DATADOG, TRACECONTEXT, BAGGAGE));
8283
static final Set<PropagationStyle> DEFAULT_PROPAGATION_STYLE =
8384
new LinkedHashSet<>(asList(PropagationStyle.DATADOG));
85+
static final int DEFAULT_TRACE_BAGGAGE_MAX_ITEMS = 64;
86+
static final int DEFAULT_TRACE_BAGGAGE_MAX_BYTES = 8192;
8487
static final boolean DEFAULT_JMX_FETCH_ENABLED = true;
8588
static final boolean DEFAULT_TRACE_AGENT_V05_ENABLED = false;
8689

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ public enum TracePropagationStyle {
2121
// W3C trace context propagation style
2222
// https://www.w3.org/TR/trace-context-1/
2323
TRACECONTEXT,
24+
// W3C baggage support
25+
// https://www.w3.org/TR/baggage/
26+
BAGGAGE,
2427
// None does not extract or inject
2528
NONE;
2629

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ public final class TracerConfig {
9191
public static final String TRACE_PROPAGATION_STYLE_EXTRACT = "trace.propagation.style.extract";
9292
public static final String TRACE_PROPAGATION_STYLE_INJECT = "trace.propagation.style.inject";
9393
public static final String TRACE_PROPAGATION_EXTRACT_FIRST = "trace.propagation.extract.first";
94+
public static final String TRACE_BAGGAGE_MAX_ITEMS = "trace.baggage.max.items";
95+
public static final String TRACE_BAGGAGE_MAX_BYTES = "trace.baggage.max.bytes";
9496

9597
public static final String ENABLE_TRACE_AGENT_V05 = "trace.agent.v0.5.enabled";
9698

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

Lines changed: 5 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.BAGGAGE_CONCERN;
89
import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.DSM_CONCERN;
910
import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.TRACING_CONCERN;
1011
import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.XRAY_TRACING_CONCERN;
@@ -77,6 +78,7 @@
7778
import datadog.trace.common.writer.WriterFactory;
7879
import datadog.trace.common.writer.ddintake.DDIntakeTraceInterceptor;
7980
import datadog.trace.context.TraceScope;
81+
import datadog.trace.core.baggage.BaggagePropagator;
8082
import datadog.trace.core.datastreams.DataStreamsMonitoring;
8183
import datadog.trace.core.datastreams.DefaultDataStreamsMonitoring;
8284
import datadog.trace.core.flare.TracerFlarePoller;
@@ -719,6 +721,9 @@ private CoreTracer(
719721
if (config.isDataStreamsEnabled()) {
720722
Propagators.register(DSM_CONCERN, this.dataStreamsMonitoring.propagator());
721723
}
724+
if (config.isBaggagePropagationEnabled()) {
725+
Propagators.register(BAGGAGE_CONCERN, new BaggagePropagator(config));
726+
}
722727

723728
this.tagInterceptor =
724729
null == tagInterceptor ? new TagInterceptor(new RuleFlags(config)) : tagInterceptor;
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
package datadog.trace.core.baggage;
2+
3+
import datadog.context.Context;
4+
import datadog.context.propagation.CarrierSetter;
5+
import datadog.context.propagation.CarrierVisitor;
6+
import datadog.context.propagation.Propagator;
7+
import datadog.trace.api.Config;
8+
import datadog.trace.bootstrap.instrumentation.api.BaggageContext;
9+
import datadog.trace.core.util.EscapedData;
10+
import datadog.trace.core.util.PercentEscaper;
11+
import java.io.UnsupportedEncodingException;
12+
import java.net.URLDecoder;
13+
import java.util.Collections;
14+
import java.util.HashMap;
15+
import java.util.Map;
16+
import java.util.function.BiConsumer;
17+
import javax.annotation.ParametersAreNonnullByDefault;
18+
import org.slf4j.Logger;
19+
import org.slf4j.LoggerFactory;
20+
21+
@ParametersAreNonnullByDefault
22+
public class BaggagePropagator implements Propagator {
23+
private static final Logger log = LoggerFactory.getLogger(BaggagePropagator.class);
24+
private static final PercentEscaper UTF_ESCAPER = PercentEscaper.create();
25+
static final String BAGGAGE_KEY = "baggage";
26+
private final Config config;
27+
private final boolean injectBaggage;
28+
private final boolean extractBaggage;
29+
30+
public BaggagePropagator(Config config) {
31+
this.injectBaggage = config.isBaggageInject();
32+
this.extractBaggage = config.isBaggageExtract();
33+
this.config = config;
34+
}
35+
36+
// use primarily for testing purposes
37+
public BaggagePropagator(boolean injectBaggage, boolean extractBaggage) {
38+
this.injectBaggage = injectBaggage;
39+
this.extractBaggage = extractBaggage;
40+
config = Config.get();
41+
}
42+
43+
@Override
44+
public <C> void inject(Context context, C carrier, CarrierSetter<C> setter) {
45+
int maxItems = config.getTraceBaggageMaxItems();
46+
int maxBytes = config.getTraceBaggageMaxBytes();
47+
//noinspection ConstantValue
48+
if (!this.injectBaggage
49+
|| maxItems == 0
50+
|| maxBytes == 0
51+
|| context == null
52+
|| carrier == null
53+
|| setter == null) {
54+
return;
55+
}
56+
57+
BaggageContext baggageContext = BaggageContext.fromContext(context);
58+
if (baggageContext == null) {
59+
log.debug("BaggageContext instance is missing from the following context {}", context);
60+
return;
61+
}
62+
63+
String baggageHeader = baggageContext.getW3cBaggageHeader();
64+
if (baggageHeader != null) {
65+
setter.set(carrier, BAGGAGE_KEY, baggageHeader);
66+
return;
67+
}
68+
69+
int processedBaggage = 0;
70+
int currentBytes = 0;
71+
StringBuilder baggageText = new StringBuilder();
72+
for (final Map.Entry<String, String> entry : baggageContext.asMap().entrySet()) {
73+
// if there are already baggage items processed, add and allocate bytes for a comma
74+
int extraBytes = 1;
75+
if (processedBaggage != 0) {
76+
baggageText.append(',');
77+
extraBytes++;
78+
}
79+
80+
EscapedData escapedKey = UTF_ESCAPER.escapeKey(entry.getKey());
81+
EscapedData escapedVal = UTF_ESCAPER.escapeValue(entry.getValue());
82+
83+
baggageText.append(escapedKey.getData());
84+
baggageText.append('=');
85+
baggageText.append(escapedVal.getData());
86+
87+
processedBaggage++;
88+
// reached the max number of baggage items allowed
89+
if (processedBaggage == maxItems) {
90+
break;
91+
}
92+
// Drop newest k/v pair if adding it leads to exceeding the limit
93+
if (currentBytes + escapedKey.getSize() + escapedVal.getSize() + extraBytes > maxBytes) {
94+
baggageText.setLength(currentBytes);
95+
break;
96+
}
97+
currentBytes += escapedKey.getSize() + escapedVal.getSize() + extraBytes;
98+
}
99+
100+
String baggageString = baggageText.toString();
101+
baggageContext.setW3cBaggageHeader(baggageString);
102+
setter.set(carrier, BAGGAGE_KEY, baggageString);
103+
}
104+
105+
@Override
106+
public <C> Context extract(Context context, C carrier, CarrierVisitor<C> visitor) {
107+
//noinspection ConstantValue
108+
if (!this.extractBaggage || context == null || carrier == null || visitor == null) {
109+
return context;
110+
}
111+
BaggageContextExtractor baggageContextExtractor = new BaggageContextExtractor();
112+
visitor.forEachKeyValue(carrier, baggageContextExtractor);
113+
BaggageContext extractedContext = baggageContextExtractor.extractedContext;
114+
if (extractedContext == null) {
115+
return context;
116+
}
117+
return extractedContext.storeInto(context);
118+
}
119+
120+
public static class BaggageContextExtractor implements BiConsumer<String, String> {
121+
private BaggageContext extractedContext;
122+
123+
BaggageContextExtractor() {}
124+
125+
/** URL decode value */
126+
private String decode(final String value) {
127+
String decoded = value;
128+
try {
129+
decoded = URLDecoder.decode(value, "UTF-8");
130+
} catch (final UnsupportedEncodingException | IllegalArgumentException e) {
131+
log.debug("Failed to decode {}", value);
132+
}
133+
return decoded;
134+
}
135+
136+
private Map<String, String> parseBaggageHeaders(String input) {
137+
Map<String, String> baggage = new HashMap<>();
138+
char keyValueSeparator = '=';
139+
char pairSeparator = ',';
140+
int start = 0;
141+
142+
int pairSeparatorInd = input.indexOf(pairSeparator);
143+
pairSeparatorInd = pairSeparatorInd == -1 ? input.length() : pairSeparatorInd;
144+
int kvSeparatorInd = input.indexOf(keyValueSeparator);
145+
while (kvSeparatorInd != -1) {
146+
int end = pairSeparatorInd;
147+
if (kvSeparatorInd > end) {
148+
log.debug(
149+
"Dropping baggage headers due to key with no value {}", input.substring(start, end));
150+
return Collections.emptyMap();
151+
}
152+
String key = decode(input.substring(start, kvSeparatorInd).trim());
153+
String value = decode(input.substring(kvSeparatorInd + 1, end).trim());
154+
if (key.isEmpty() || value.isEmpty()) {
155+
log.debug("Dropping baggage headers due to empty k/v {}:{}", key, value);
156+
return Collections.emptyMap();
157+
}
158+
baggage.put(key, value);
159+
160+
kvSeparatorInd = input.indexOf(keyValueSeparator, pairSeparatorInd + 1);
161+
pairSeparatorInd = input.indexOf(pairSeparator, pairSeparatorInd + 1);
162+
pairSeparatorInd = pairSeparatorInd == -1 ? input.length() : pairSeparatorInd;
163+
start = end + 1;
164+
}
165+
return baggage;
166+
}
167+
168+
@Override
169+
public void accept(String key, String value) {
170+
// Only process tags that are relevant to baggage
171+
if (key != null && key.equalsIgnoreCase(BAGGAGE_KEY)) {
172+
Map<String, String> baggage = parseBaggageHeaders(value);
173+
if (!baggage.isEmpty()) {
174+
extractedContext = BaggageContext.create(baggage, value);
175+
}
176+
}
177+
}
178+
}
179+
}

dd-trace-core/src/main/java/datadog/trace/core/propagation/HttpCodec.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ private static Map<TracePropagationStyle, Injector> createInjectors(
125125
case TRACECONTEXT:
126126
result.put(style, W3CHttpCodec.newInjector(reverseBaggageMapping));
127127
break;
128+
case BAGGAGE:
129+
break;
128130
default:
129131
log.debug("No implementation found to inject propagation style: {}", style);
130132
break;
@@ -159,6 +161,8 @@ public static Extractor createExtractor(
159161
case TRACECONTEXT:
160162
extractors.add(W3CHttpCodec.newExtractor(config, traceConfigSupplier));
161163
break;
164+
case BAGGAGE:
165+
break;
162166
default:
163167
log.debug("No implementation found to extract propagation style: {}", style);
164168
break;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package datadog.trace.core.util;
2+
3+
public class EscapedData {
4+
private String data;
5+
private int size;
6+
7+
public EscapedData(String data, int size) {
8+
this.data = data;
9+
this.size = size;
10+
}
11+
12+
public EscapedData() {
13+
this.data = "";
14+
this.size = 0;
15+
}
16+
17+
public String getData() {
18+
return data;
19+
}
20+
21+
public int getSize() {
22+
return size;
23+
}
24+
25+
public void setData(String data) {
26+
this.data = data;
27+
}
28+
29+
public void incrementSize() {
30+
size++;
31+
}
32+
33+
public void addSize(int delta) {
34+
size += delta;
35+
}
36+
}

0 commit comments

Comments
 (0)