diff --git a/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java b/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java index fe360b81dd8..8352066ae15 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java @@ -77,6 +77,7 @@ public final class ConfigDefaults { static final int DEFAULT_SCOPE_ITERATION_KEEP_ALIVE = 30; // in seconds static final int DEFAULT_PARTIAL_FLUSH_MIN_SPANS = 1000; static final boolean DEFAULT_PROPAGATION_EXTRACT_LOG_HEADER_NAMES_ENABLED = false; + // TODO: add BAGGAGE to the list of default propagation styles static final Set DEFAULT_TRACE_PROPAGATION_STYLE = new LinkedHashSet<>(asList(DATADOG, TRACECONTEXT)); static final Set DEFAULT_PROPAGATION_STYLE = diff --git a/dd-trace-api/src/main/java/datadog/trace/api/TracePropagationStyle.java b/dd-trace-api/src/main/java/datadog/trace/api/TracePropagationStyle.java index 192978cc388..af01e638284 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/TracePropagationStyle.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/TracePropagationStyle.java @@ -21,6 +21,9 @@ public enum TracePropagationStyle { // W3C trace context propagation style // https://www.w3.org/TR/trace-context-1/ TRACECONTEXT, + // W3C baggage propagation style + // https://www.w3.org/TR/baggage/ + BAGGAGE, // None does not extract or inject NONE; diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/HttpCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/HttpCodec.java index 120d14f7ee5..f67aaec0cd9 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/HttpCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/HttpCodec.java @@ -125,6 +125,8 @@ private static Map createInjectors( case TRACECONTEXT: result.put(style, W3CHttpCodec.newInjector(reverseBaggageMapping)); break; + case BAGGAGE: + result.put(style, W3CBaggageHttpCodec.newInjector(reverseBaggageMapping)); default: log.debug("No implementation found to inject propagation style: {}", style); break; @@ -159,6 +161,9 @@ public static Extractor createExtractor( case TRACECONTEXT: extractors.add(W3CHttpCodec.newExtractor(config, traceConfigSupplier)); break; + case BAGGAGE: + extractors.add(W3CBaggageHttpCodec.newExtractor(config, traceConfigSupplier)); + break; default: log.debug("No implementation found to extract propagation style: {}", style); break; @@ -228,6 +233,7 @@ public TagContext extract( context = extractedContext; // Stop extraction if only extracting first valid context and drop everything else if (this.extractFirst) { + // TODO: change this logic to always extract baggage break; } } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/W3CBaggageHttpCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/W3CBaggageHttpCodec.java new file mode 100644 index 00000000000..fe384cdd42a --- /dev/null +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/W3CBaggageHttpCodec.java @@ -0,0 +1,169 @@ +package datadog.trace.core.propagation; + +import static datadog.trace.api.TracePropagationStyle.BAGGAGE; +import static datadog.trace.api.sampling.PrioritySampling.SAMPLER_DROP; +import static datadog.trace.api.sampling.PrioritySampling.SAMPLER_KEEP; +import static datadog.trace.core.propagation.HttpCodec.firstHeaderValue; +import static datadog.trace.core.propagation.PropagationTags.HeaderType.W3C; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +import datadog.trace.api.Config; +import datadog.trace.api.DD128bTraceId; +import datadog.trace.api.DDSpanId; +import datadog.trace.api.DDTags; +import datadog.trace.api.DDTraceId; +import datadog.trace.api.TraceConfig; +import datadog.trace.api.TracePropagationStyle; +import datadog.trace.api.internal.util.LongStringUtils; +import datadog.trace.api.sampling.PrioritySampling; +import datadog.trace.api.sampling.SamplingMechanism; +import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; +import datadog.trace.bootstrap.instrumentation.api.TagContext; +import datadog.trace.core.DDSpanContext; + +import java.util.Map; +import java.util.TreeMap; +import java.util.function.Supplier; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** A codec designed for HTTP transport via headers using W3C "baggage" header */ +class W3CBaggageHttpCodec { + private static final Logger log = LoggerFactory.getLogger(W3CHttpCodec.class); + + static final String BAGGAGE_KEY = "baggage"; + + private W3CBaggageHttpCodec() { + // This class should not be created. This also makes code coverage checks happy. + } + + public static HttpCodec.Injector newInjector(Map invertedBaggageMapping) { + return new Injector(invertedBaggageMapping); + } + + private static class Injector implements HttpCodec.Injector { + + private final Map invertedBaggageMapping; + + public Injector(Map invertedBaggageMapping) { + assert invertedBaggageMapping != null; + this.invertedBaggageMapping = invertedBaggageMapping; + } + + @Override + public void inject( + final DDSpanContext context, final C carrier, final AgentPropagation.Setter setter) { + StringBuilder baggageHeader = new StringBuilder(); + + for (final Map.Entry entry : context.baggageItems()) { + if (baggageHeader.length() > 0) { + baggageHeader.append(","); + } + + String header = invertedBaggageMapping.get(entry.getKey()); + header = header != null ? header : entry.getKey(); + String value = HttpCodec.encodeBaggage(entry.getValue()); + + baggageHeader.append(header).append("=").append(value); + } + + setter.set(carrier, BAGGAGE_KEY, baggageHeader.toString()); + } + } + + public static HttpCodec.Extractor newExtractor( + Config config, Supplier traceConfigSupplier) { + return new TagContextExtractor(traceConfigSupplier, () -> new W3CBaggageContextInterpreter(config)); + } + + private static class W3CBaggageContextInterpreter extends ContextInterpreter { + + private W3CBaggageContextInterpreter(Config config) { + super(config); + } + + @Override + public TracePropagationStyle style() { + return BAGGAGE; + } + + @Override + public boolean accept(String key, String value) { + if (null == key || key.isEmpty()) { + return true; + } + if (LOG_EXTRACT_HEADER_NAMES) { + log.debug("Header: {}", key); + } + + char first = Character.toLowerCase(key.charAt(0)); + + if (first == 'b' && BAGGAGE_KEY.equalsIgnoreCase(key)) { + try { + if (baggage.isEmpty()) { + baggage = new TreeMap<>(); + } + + for (String entry : value.split(",")) { + String[] keyValue = entry.split("=", 2); + if (keyValue.length == 2) { + baggage.put( + HttpCodec.decode(keyValue[0].trim()), + HttpCodec.decode(keyValue[1].trim()) + ); + } + } + } catch (RuntimeException e) { + invalidateContext(); + log.debug("Exception when extracting baggage", e); + return false; + } + } + return true; + } + + private long extractEndToEndStartTime(String value) { + try { + return MILLISECONDS.toNanos(Long.parseLong(value)); + } catch (RuntimeException e) { + log.debug("Ignoring invalid end-to-end start time {}", value, e); + return 0; + } + } + + private static String trim(String input) { + if (input == null) { + return ""; + } + final int last = input.length() - 1; + if (last == 0) { + return input; + } + int start; + for (start = 0; start <= last; start++) { + char c = input.charAt(start); + if (c != '\t' && c != ' ') { + break; + } + } + int end; + for (end = last; end > start; end--) { + char c = input.charAt(end); + if (c != '\t' && c != ' ') { + break; + } + } + if (start == 0 && end == last) { + return input; + } else { + return input.substring(start, end + 1); + } + } + } +}