-
Notifications
You must be signed in to change notification settings - Fork 297
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implementation of BaggagePropagator and BaggageContext #8330
Changes from all commits
0192caf
fd89905
fd19e75
c6d3a0e
7aa0d4b
2ef1887
1987e9d
11d1350
64b1303
73e0384
5ef9357
b53e449
22d8a31
a73bc87
b37dcbd
36940e8
188e82b
58234e4
660529d
a09abe1
2140924
a3dda56
bfe69fc
a0df83a
831b564
e0a9709
b2731e8
e7f533d
61803e5
9916590
586468c
21d2a43
bebdf7c
c4f6d7d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ | |
import static datadog.trace.api.DDTags.DJM_ENABLED; | ||
import static datadog.trace.api.DDTags.DSM_ENABLED; | ||
import static datadog.trace.api.DDTags.PROFILING_CONTEXT_ENGINE; | ||
import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.BAGGAGE_CONCERN; | ||
import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.DSM_CONCERN; | ||
import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.TRACING_CONCERN; | ||
import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.XRAY_TRACING_CONCERN; | ||
|
@@ -77,6 +78,7 @@ | |
import datadog.trace.common.writer.WriterFactory; | ||
import datadog.trace.common.writer.ddintake.DDIntakeTraceInterceptor; | ||
import datadog.trace.context.TraceScope; | ||
import datadog.trace.core.baggage.BaggagePropagator; | ||
import datadog.trace.core.datastreams.DataStreamsMonitoring; | ||
import datadog.trace.core.datastreams.DefaultDataStreamsMonitoring; | ||
import datadog.trace.core.flare.TracerFlarePoller; | ||
|
@@ -719,6 +721,9 @@ private CoreTracer( | |
if (config.isDataStreamsEnabled()) { | ||
Propagators.register(DSM_CONCERN, this.dataStreamsMonitoring.propagator()); | ||
} | ||
if (config.isBaggagePropagationEnabled()) { | ||
Propagators.register(BAGGAGE_CONCERN, new BaggagePropagator(config)); | ||
} | ||
Comment on lines
+724
to
+726
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Open for discussion: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a good question. The way I see it is that baggage is a feature for Datadog users that want to send more information downstream. IMO the users should have full say on whether they want baggage to be enabled, and instrumentation baggage is just a supporting cast. Thus I think we can return a noop propagator in those situations. WDYT? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the noop would be the right thing to do. Mostly wondering if it will conflict with OTel support or not. |
||
|
||
this.tagInterceptor = | ||
null == tagInterceptor ? new TagInterceptor(new RuleFlags(config)) : tagInterceptor; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
package datadog.trace.core.baggage; | ||
|
||
import datadog.context.Context; | ||
import datadog.context.propagation.CarrierSetter; | ||
import datadog.context.propagation.CarrierVisitor; | ||
import datadog.context.propagation.Propagator; | ||
import datadog.trace.api.Config; | ||
import datadog.trace.bootstrap.instrumentation.api.BaggageContext; | ||
import datadog.trace.core.util.EscapedData; | ||
import datadog.trace.core.util.PercentEscaper; | ||
import java.io.UnsupportedEncodingException; | ||
import java.net.URLDecoder; | ||
import java.util.Collections; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.function.BiConsumer; | ||
import javax.annotation.ParametersAreNonnullByDefault; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
@ParametersAreNonnullByDefault | ||
public class BaggagePropagator implements Propagator { | ||
private static final Logger log = LoggerFactory.getLogger(BaggagePropagator.class); | ||
private static final PercentEscaper UTF_ESCAPER = PercentEscaper.create(); | ||
static final String BAGGAGE_KEY = "baggage"; | ||
private final Config config; | ||
private final boolean injectBaggage; | ||
private final boolean extractBaggage; | ||
|
||
public BaggagePropagator(Config config) { | ||
this.injectBaggage = config.isBaggageInject(); | ||
this.extractBaggage = config.isBaggageExtract(); | ||
this.config = config; | ||
} | ||
|
||
// use primarily for testing purposes | ||
public BaggagePropagator(boolean injectBaggage, boolean extractBaggage) { | ||
this.injectBaggage = injectBaggage; | ||
this.extractBaggage = extractBaggage; | ||
config = Config.get(); | ||
} | ||
|
||
@Override | ||
public <C> void inject(Context context, C carrier, CarrierSetter<C> setter) { | ||
int maxItems = config.getTraceBaggageMaxItems(); | ||
int maxBytes = config.getTraceBaggageMaxBytes(); | ||
//noinspection ConstantValue | ||
if (!this.injectBaggage | ||
|| maxItems == 0 | ||
|| maxBytes == 0 | ||
|| context == null | ||
|| carrier == null | ||
|| setter == null) { | ||
return; | ||
} | ||
|
||
BaggageContext baggageContext = BaggageContext.fromContext(context); | ||
mhlidd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (baggageContext == null) { | ||
log.debug("BaggageContext instance is missing from the following context {}", context); | ||
return; | ||
} | ||
|
||
String baggageHeader = baggageContext.getW3cBaggageHeader(); | ||
if (baggageHeader != null) { | ||
setter.set(carrier, BAGGAGE_KEY, baggageHeader); | ||
return; | ||
} | ||
|
||
int processedBaggage = 0; | ||
int currentBytes = 0; | ||
StringBuilder baggageText = new StringBuilder(); | ||
for (final Map.Entry<String, String> entry : baggageContext.asMap().entrySet()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment: this is where using |
||
// if there are already baggage items processed, add and allocate bytes for a comma | ||
int extraBytes = 1; | ||
if (processedBaggage != 0) { | ||
baggageText.append(','); | ||
extraBytes++; | ||
} | ||
|
||
EscapedData escapedKey = UTF_ESCAPER.escapeKey(entry.getKey()); | ||
EscapedData escapedVal = UTF_ESCAPER.escapeValue(entry.getValue()); | ||
|
||
baggageText.append(escapedKey.getData()); | ||
baggageText.append('='); | ||
baggageText.append(escapedVal.getData()); | ||
|
||
processedBaggage++; | ||
// reached the max number of baggage items allowed | ||
if (processedBaggage == maxItems) { | ||
break; | ||
} | ||
// Drop newest k/v pair if adding it leads to exceeding the limit | ||
if (currentBytes + escapedKey.getSize() + escapedVal.getSize() + extraBytes > maxBytes) { | ||
baggageText.setLength(currentBytes); | ||
mhlidd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
break; | ||
} | ||
currentBytes += escapedKey.getSize() + escapedVal.getSize() + extraBytes; | ||
} | ||
|
||
String baggageString = baggageText.toString(); | ||
baggageContext.setW3cBaggageHeader(baggageString); | ||
setter.set(carrier, BAGGAGE_KEY, baggageString); | ||
} | ||
|
||
@Override | ||
public <C> Context extract(Context context, C carrier, CarrierVisitor<C> visitor) { | ||
//noinspection ConstantValue | ||
if (!this.extractBaggage || context == null || carrier == null || visitor == null) { | ||
return context; | ||
} | ||
BaggageContextExtractor baggageContextExtractor = new BaggageContextExtractor(); | ||
visitor.forEachKeyValue(carrier, baggageContextExtractor); | ||
BaggageContext extractedContext = baggageContextExtractor.extractedContext; | ||
if (extractedContext == null) { | ||
return context; | ||
} | ||
return extractedContext.storeInto(context); | ||
} | ||
|
||
public static class BaggageContextExtractor implements BiConsumer<String, String> { | ||
private BaggageContext extractedContext; | ||
|
||
BaggageContextExtractor() {} | ||
|
||
/** URL decode value */ | ||
private String decode(final String value) { | ||
String decoded = value; | ||
try { | ||
decoded = URLDecoder.decode(value, "UTF-8"); | ||
} catch (final UnsupportedEncodingException | IllegalArgumentException e) { | ||
log.debug("Failed to decode {}", value); | ||
} | ||
return decoded; | ||
} | ||
|
||
private Map<String, String> parseBaggageHeaders(String input) { | ||
Map<String, String> baggage = new HashMap<>(); | ||
char keyValueSeparator = '='; | ||
char pairSeparator = ','; | ||
int start = 0; | ||
|
||
int pairSeparatorInd = input.indexOf(pairSeparator); | ||
pairSeparatorInd = pairSeparatorInd == -1 ? input.length() : pairSeparatorInd; | ||
int kvSeparatorInd = input.indexOf(keyValueSeparator); | ||
while (kvSeparatorInd != -1) { | ||
int end = pairSeparatorInd; | ||
if (kvSeparatorInd > end) { | ||
log.debug( | ||
"Dropping baggage headers due to key with no value {}", input.substring(start, end)); | ||
return Collections.emptyMap(); | ||
mhlidd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
String key = decode(input.substring(start, kvSeparatorInd).trim()); | ||
String value = decode(input.substring(kvSeparatorInd + 1, end).trim()); | ||
if (key.isEmpty() || value.isEmpty()) { | ||
log.debug("Dropping baggage headers due to empty k/v {}:{}", key, value); | ||
return Collections.emptyMap(); | ||
PerfectSlayer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
baggage.put(key, value); | ||
|
||
kvSeparatorInd = input.indexOf(keyValueSeparator, pairSeparatorInd + 1); | ||
pairSeparatorInd = input.indexOf(pairSeparator, pairSeparatorInd + 1); | ||
pairSeparatorInd = pairSeparatorInd == -1 ? input.length() : pairSeparatorInd; | ||
start = end + 1; | ||
} | ||
return baggage; | ||
} | ||
|
||
@Override | ||
public void accept(String key, String value) { | ||
// Only process tags that are relevant to baggage | ||
if (key != null && key.equalsIgnoreCase(BAGGAGE_KEY)) { | ||
Map<String, String> baggage = parseBaggageHeaders(value); | ||
if (!baggage.isEmpty()) { | ||
extractedContext = BaggageContext.create(baggage, value); | ||
} | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package datadog.trace.core.util; | ||
|
||
public class EscapedData { | ||
private String data; | ||
private int size; | ||
|
||
public EscapedData(String data, int size) { | ||
this.data = data; | ||
this.size = size; | ||
} | ||
|
||
public EscapedData() { | ||
this.data = ""; | ||
this.size = 0; | ||
} | ||
|
||
public String getData() { | ||
return data; | ||
} | ||
|
||
public int getSize() { | ||
return size; | ||
} | ||
|
||
public void setData(String data) { | ||
this.data = data; | ||
} | ||
|
||
public void incrementSize() { | ||
size++; | ||
} | ||
|
||
public void addSize(int delta) { | ||
size += delta; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't forget to skip this value from
HttpCoded
createExtractor
andcreateInjector
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Neither inject or extract currently have a case handling Baggage!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The goal is to avoid emitting the following log entries for Baggage propagation style:
"No implementation found to inject propagation style: {}"
"No implementation found to extract propagation style: {}"
It will be misleading during escalation 😓
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah that makes sense. I added a case to just break and not add anything!