Skip to content

Commit 068e678

Browse files
committed
initial attempt to add W3CBaggageHttpCodec
1 parent 140b9b4 commit 068e678

File tree

3 files changed

+201
-0
lines changed

3 files changed

+201
-0
lines changed

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 propagation style
25+
// https://www.w3.org/TR/baggage/
26+
BAGGAGE,
2427
// None does not extract or inject
2528
NONE;
2629

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

Lines changed: 5 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+
result.put(style, W3CBaggageHttpCodec.newInjector(reverseBaggageMapping));
128130
default:
129131
log.debug("No implementation found to inject propagation style: {}", style);
130132
break;
@@ -159,6 +161,9 @@ public static Extractor createExtractor(
159161
case TRACECONTEXT:
160162
extractors.add(W3CHttpCodec.newExtractor(config, traceConfigSupplier));
161163
break;
164+
case BAGGAGE:
165+
extractors.add(W3CBaggageHttpCodec.newExtractor(config, traceConfigSupplier));
166+
break;
162167
default:
163168
log.debug("No implementation found to extract propagation style: {}", style);
164169
break;
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
package datadog.trace.core.propagation;
2+
3+
import static datadog.trace.api.TracePropagationStyle.TRACECONTEXT;
4+
import static datadog.trace.api.sampling.PrioritySampling.SAMPLER_DROP;
5+
import static datadog.trace.api.sampling.PrioritySampling.SAMPLER_KEEP;
6+
import static datadog.trace.core.propagation.HttpCodec.firstHeaderValue;
7+
import static datadog.trace.core.propagation.PropagationTags.HeaderType.W3C;
8+
9+
import static java.util.concurrent.TimeUnit.MILLISECONDS;
10+
import static java.util.concurrent.TimeUnit.NANOSECONDS;
11+
12+
import datadog.trace.api.Config;
13+
import datadog.trace.api.DD128bTraceId;
14+
import datadog.trace.api.DDSpanId;
15+
import datadog.trace.api.DDTags;
16+
import datadog.trace.api.DDTraceId;
17+
import datadog.trace.api.TraceConfig;
18+
import datadog.trace.api.TracePropagationStyle;
19+
import datadog.trace.api.internal.util.LongStringUtils;
20+
import datadog.trace.api.sampling.PrioritySampling;
21+
import datadog.trace.api.sampling.SamplingMechanism;
22+
import datadog.trace.bootstrap.instrumentation.api.AgentPropagation;
23+
import datadog.trace.bootstrap.instrumentation.api.TagContext;
24+
import datadog.trace.core.DDSpanContext;
25+
26+
import java.util.Map;
27+
import java.util.TreeMap;
28+
import java.util.function.Supplier;
29+
30+
import java.net.URLEncoder;
31+
import java.nio.charset.StandardCharsets;
32+
33+
import org.slf4j.Logger;
34+
import org.slf4j.LoggerFactory;
35+
36+
/** A codec designed for HTTP transport via headers using W3C "baggage" header */
37+
class W3CBaggageHttpCodec {
38+
private static final Logger log = LoggerFactory.getLogger(W3CHttpCodec.class);
39+
40+
static final String BAGGAGE_KEY = "baggage";
41+
42+
private W3CBaggageHttpCodec() {
43+
// This class should not be created. This also makes code coverage checks happy.
44+
}
45+
46+
public static HttpCodec.Injector newInjector(Map<String, String> invertedBaggageMapping) {
47+
return new Injector(invertedBaggageMapping);
48+
}
49+
50+
private static class Injector implements HttpCodec.Injector {
51+
52+
private final Map<String, String> invertedBaggageMapping;
53+
54+
public Injector(Map<String, String> invertedBaggageMapping) {
55+
assert invertedBaggageMapping != null;
56+
this.invertedBaggageMapping = invertedBaggageMapping;
57+
}
58+
59+
@Override
60+
public <C> void inject(
61+
final DDSpanContext context, final C carrier, final AgentPropagation.Setter<C> setter) {
62+
StringBuilder baggageHeader = new StringBuilder();
63+
64+
for (final Map.Entry<String, String> entry : context.baggageItems()) {
65+
if (baggageHeader.length() > 0) {
66+
baggageHeader.append(",");
67+
}
68+
69+
String header = invertedBaggageMapping.get(entry.getKey());
70+
header = header != null ? header : entry.getKey();
71+
String value = HttpCodec.encodeBaggage(entry.getValue());
72+
73+
baggageHeader.append(header).append("=").append(value);
74+
}
75+
76+
setter.set(carrier, BAGGAGE_KEY, baggageHeader.toString());
77+
}
78+
}
79+
80+
public static HttpCodec.Extractor newExtractor(
81+
Config config, Supplier<TraceConfig> traceConfigSupplier) {
82+
return new TagContextExtractor(traceConfigSupplier, () -> new W3CBaggageContextInterpreter(config));
83+
}
84+
85+
private static class W3CBaggageContextInterpreter extends ContextInterpreter {
86+
87+
private W3CBaggageContextInterpreter(Config config) {
88+
super(config);
89+
}
90+
91+
@Override
92+
public TracePropagationStyle style() {
93+
return BAGGAGE;
94+
}
95+
96+
@Override
97+
public boolean accept(String key, String value) {
98+
if (null == key || key.isEmpty()) {
99+
return true;
100+
}
101+
if (LOG_EXTRACT_HEADER_NAMES) {
102+
log.debug("Header: {}", key);
103+
}
104+
105+
if (first == 'b' && BAGGAGE_KEY.equalsIgnoreCase(key)) {
106+
try {
107+
if (baggage.isEmpty()) {
108+
baggage = new TreeMap<>();
109+
}
110+
111+
// Split by comma
112+
for (String entry : header.split(",")) {
113+
// Split into key=value
114+
String[] keyValue = entry.split("=", 2);
115+
if (keyValue.length == 2) {
116+
baggage.put(
117+
HttpCodec.decode(keyValue[0].trim()),
118+
HttpCodec.decode(keyValue[1].trim())
119+
);
120+
}
121+
}
122+
} catch (RuntimeException e) {
123+
invalidateContext();
124+
log.debug("Exception when extracting baggage", e);
125+
return false;
126+
}
127+
}
128+
return true;
129+
}
130+
131+
private long extractEndToEndStartTime(String value) {
132+
try {
133+
return MILLISECONDS.toNanos(Long.parseLong(value));
134+
} catch (RuntimeException e) {
135+
log.debug("Ignoring invalid end-to-end start time {}", value, e);
136+
return 0;
137+
}
138+
}
139+
140+
private boolean storeTraceParent(String value) {
141+
String trimmed = trim(value);
142+
if (traceparentHeader != null) {
143+
// We should not accept multiple traceparent headers
144+
if (log.isDebugEnabled()) {
145+
log.debug(
146+
"Multiple traceparent headers. Had '{}' and got '{}'", traceparentHeader, trimmed);
147+
}
148+
onlyTagContext();
149+
} else {
150+
traceparentHeader = trimmed;
151+
}
152+
return true;
153+
}
154+
155+
private boolean storeTraceState(String value) {
156+
String trimmed = trim(value);
157+
if (!trimmed.isEmpty()) {
158+
// Yes, this is not efficient for multiple headers, but that is hopefully not common
159+
tracestateHeader = tracestateHeader == null ? trimmed : tracestateHeader + "," + trimmed;
160+
}
161+
return true;
162+
}
163+
164+
private static String trim(String input) {
165+
if (input == null) {
166+
return "";
167+
}
168+
final int last = input.length() - 1;
169+
if (last == 0) {
170+
return input;
171+
}
172+
int start;
173+
for (start = 0; start <= last; start++) {
174+
char c = input.charAt(start);
175+
if (c != '\t' && c != ' ') {
176+
break;
177+
}
178+
}
179+
int end;
180+
for (end = last; end > start; end--) {
181+
char c = input.charAt(end);
182+
if (c != '\t' && c != ' ') {
183+
break;
184+
}
185+
}
186+
if (start == 0 && end == last) {
187+
return input;
188+
} else {
189+
return input.substring(start, end + 1);
190+
}
191+
}
192+
}
193+
}

0 commit comments

Comments
 (0)