Create TagContext during B3 headers extration
MODIFY
dd-trace-ot/src/main/java/datadog/opentracing/propagation/B3HttpCodec.java
dd-trace-ot/src/main/java/datadog/opentracing/propagation/B3HttpCodec.java
@@ -9,10 +9,17 @@ import io.opentracing.SpanContext;
import io.opentracing.propagation.TextMap;
import java.math.BigInteger;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
-/** A codec designed for HTTP transport via headers using B3 headers */
+/**
+ * A codec designed for HTTP transport via headers using B3 headers
+ *
+ * <p>TODO: there is fair amount of code duplication between DatadogHttpCodec and this class,
+ * especiall in part where TagContext is handled. We may want to refactor that and avoid special
+ * handling of TagContext in other places (i.e. CompoundExtractor).
+ */
@Slf4j
class B3HttpCodec {
@@ -31,7 +38,6 @@ class B3HttpCodec {
@Override
public void inject(final DDSpanContext context, final TextMap carrier) {
-
try {
// TODO: should we better store ids as BigInteger in context to avoid parsing it twice.
final BigInteger traceId = new BigInteger(context.getTraceId());
@@ -58,9 +64,19 @@ class B3HttpCodec {
public static class Extractor implements HttpCodec.Extractor {
+ private final Map<String, String> taggedHeaders;
+
+ public Extractor(final Map<String, String> taggedHeaders) {
+ this.taggedHeaders = new HashMap<>();
+ for (final Map.Entry<String, String> mapping : taggedHeaders.entrySet()) {
+ this.taggedHeaders.put(mapping.getKey().trim().toLowerCase(), mapping.getValue());
+ }
+ }
+
@Override
public SpanContext extract(final TextMap carrier) {
try {
+ Map<String, String> tags = Collections.emptyMap();
String traceId = ZERO;
String spanId = ZERO;
int samplingPriority = PrioritySampling.UNSET;
@@ -80,6 +96,13 @@ class B3HttpCodec {
} else if (SAMPLING_PRIORITY_KEY.equalsIgnoreCase(key)) {
samplingPriority = convertSamplingPriority(value);
}
+
+ if (taggedHeaders.containsKey(key)) {
+ if (tags.isEmpty()) {
+ tags = new HashMap<>();
+ }
+ tags.put(taggedHeaders.get(key), HttpCodec.decode(value));
+ }
}
if (!ZERO.equals(traceId)) {
@@ -90,11 +113,14 @@ class B3HttpCodec {
samplingPriority,
null,
Collections.<String, String>emptyMap(),
- Collections.<String, String>emptyMap());
+ tags);
context.lockSamplingPriority();
log.debug("{} - Parent context extracted", context.getTraceId());
return context;
+ } else if (!tags.isEmpty()) {
+ log.debug("Tags context extracted");
+ return new TagContext(null, tags);
}
} catch (final RuntimeException e) {
log.debug("Exception when extracting context", e);

MODIFY
dd-trace-ot/src/main/java/datadog/opentracing/propagation/DatadogHttpCodec.java
dd-trace-ot/src/main/java/datadog/opentracing/propagation/DatadogHttpCodec.java
@@ -7,9 +7,6 @@ import datadog.opentracing.DDSpanContext;
import datadog.trace.api.sampling.PrioritySampling;
import io.opentracing.SpanContext;
import io.opentracing.propagation.TextMap;
-import java.io.UnsupportedEncodingException;
-import java.net.URLDecoder;
-import java.net.URLEncoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -44,20 +41,10 @@ class DatadogHttpCodec {
}
for (final Map.Entry<String, String> entry : context.baggageItems()) {
- carrier.put(OT_BAGGAGE_PREFIX + entry.getKey(), encode(entry.getValue()));
+ carrier.put(OT_BAGGAGE_PREFIX + entry.getKey(), HttpCodec.encode(entry.getValue()));
}
log.debug("{} - Datadog parent context injected", context.getTraceId());
}
-
- private String encode(final String value) {
- String encoded = value;
- try {
- encoded = URLEncoder.encode(value, "UTF-8");
- } catch (final UnsupportedEncodingException e) {
- log.info("Failed to encode value - {}", value);
- }
- return encoded;
- }
}
public static class Extractor implements HttpCodec.Extractor {
@@ -100,14 +87,14 @@ class DatadogHttpCodec {
if (baggage.isEmpty()) {
baggage = new HashMap<>();
}
- baggage.put(key.replace(OT_BAGGAGE_PREFIX, ""), decode(value));
+ baggage.put(key.replace(OT_BAGGAGE_PREFIX, ""), HttpCodec.decode(value));
}
if (taggedHeaders.containsKey(key)) {
if (tags.isEmpty()) {
tags = new HashMap<>();
}
- tags.put(taggedHeaders.get(key), decode(value));
+ tags.put(taggedHeaders.get(key), HttpCodec.decode(value));
}
}
@@ -128,15 +115,5 @@ class DatadogHttpCodec {
return null;
}
-
- private String decode(final String value) {
- String decoded = value;
- try {
- decoded = URLDecoder.decode(value, "UTF-8");
- } catch (final UnsupportedEncodingException e) {
- log.info("Failed to decode value - {}", value);
- }
- return decoded;
- }
}
}

MODIFY
dd-trace-ot/src/main/java/datadog/opentracing/propagation/HttpCodec.java
dd-trace-ot/src/main/java/datadog/opentracing/propagation/HttpCodec.java
@@ -4,7 +4,10 @@ import datadog.opentracing.DDSpanContext;
import datadog.trace.api.Config;
import io.opentracing.SpanContext;
import io.opentracing.propagation.TextMap;
+import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -52,7 +55,7 @@ public class HttpCodec {
continue;
}
if (style == Config.PropagationStyle.B3) {
- extractors.add(new B3HttpCodec.Extractor());
+ extractors.add(new B3HttpCodec.Extractor(taggedHeaders));
continue;
}
log.debug("No implementation found to extract propagation style: {}", style);
@@ -86,13 +89,15 @@ public class HttpCodec {
@Override
public SpanContext extract(final TextMap carrier) {
+ SpanContext context = null;
for (final Extractor extractor : extractors) {
- final SpanContext context = extractor.extract(carrier);
- if (context != null) {
+ context = extractor.extract(carrier);
+ // Use incomplete TagContext only as last resort
+ if (context != null && (context instanceof ExtractedContext)) {
return context;
}
}
- return null;
+ return context;
}
}
@@ -116,4 +121,26 @@ public class HttpCodec {
// We use decimals
return parsedValue.toString();
}
+
+ /** URL encode value */
+ static String encode(final String value) {
+ String encoded = value;
+ try {
+ encoded = URLEncoder.encode(value, "UTF-8");
+ } catch (final UnsupportedEncodingException e) {
+ log.info("Failed to encode value - {}", value);
+ }
+ return encoded;
+ }
+
+ /** URL decode value */
+ static String decode(final String value) {
+ String decoded = value;
+ try {
+ decoded = URLDecoder.decode(value, "UTF-8");
+ } catch (final UnsupportedEncodingException e) {
+ log.info("Failed to decode value - {}", value);
+ }
+ return decoded;
+ }
}

MODIFY
dd-trace-ot/src/test/groovy/datadog/opentracing/propagation/B3HttpExtractorTest.groovy
dd-trace-ot/src/test/groovy/datadog/opentracing/propagation/B3HttpExtractorTest.groovy
@@ -12,13 +12,14 @@ import static datadog.opentracing.propagation.HttpCodec.UINT64_MAX
class B3HttpExtractorTest extends Specification {
- HttpCodec.Extractor extractor = new B3HttpCodec.Extractor()
+ HttpCodec.Extractor extractor = new B3HttpCodec.Extractor(["SOME_HEADER": "some-tag"])
def "extract http headers"() {
setup:
final Map<String, String> actual = [
(TRACE_ID_KEY.toUpperCase()): traceId.toString(16).toLowerCase(),
(SPAN_ID_KEY.toUpperCase()) : spanId.toString(16).toLowerCase(),
+ SOME_HEADER : "my-interesting-info",
]
if (samplingPriority != null) {
@@ -32,7 +33,7 @@ class B3HttpExtractorTest extends Specification {
context.traceId == traceId.toString()
context.spanId == spanId.toString()
context.baggage == [:]
- context.tags == [:]
+ context.tags == ["some-tag": "my-interesting-info"]
context.samplingPriority == expectedSamplingPriority
context.origin == null
@@ -45,6 +46,19 @@ class B3HttpExtractorTest extends Specification {
UINT64_MAX.minus(1) | UINT64_MAX | 1 | PrioritySampling.SAMPLER_KEEP
}
+ def "extract header tags with no propagation"() {
+ when:
+ TagContext context = extractor.extract(new TextMapExtractAdapter(headers))
+
+ then:
+ !(context instanceof ExtractedContext)
+ context.getTags() == ["some-tag": "my-interesting-info"]
+
+ where:
+ headers | _
+ [SOME_HEADER: "my-interesting-info"] | _
+ }
+
def "extract empty headers returns null"() {
expect:
extractor.extract(new TextMapExtractAdapter(["ignored-header": "ignored-value"])) == null

Create TagContext during B3 headers extration