diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java b/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java index 2a4f50668b..73bef15792 100644 --- a/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java +++ b/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java @@ -88,7 +88,8 @@ public class DDTracer implements io.opentracing.Tracer { Writer.Builder.forConfig(config), Sampler.Builder.forConfig(config), DDTraceConfig.parseMap(config.getProperty(DDTraceConfig.SPAN_TAGS)), - DDTraceConfig.parseMap(config.getProperty(DDTraceConfig.SERVICE_MAPPING))); + DDTraceConfig.parseMap(config.getProperty(DDTraceConfig.SERVICE_MAPPING)), + DDTraceConfig.parseMap(config.getProperty(DDTraceConfig.HEADER_TAGS))); log.debug("Using config: {}", config); } @@ -98,6 +99,7 @@ public class DDTracer implements io.opentracing.Tracer { writer, sampler, Collections.emptyMap(), + Collections.emptyMap(), Collections.emptyMap()); } @@ -106,7 +108,8 @@ public class DDTracer implements io.opentracing.Tracer { final Writer writer, final Sampler sampler, final Map defaultSpanTags, - final Map serviceNameMappings) { + final Map serviceNameMappings, + final Map taggedHeaders) { this.serviceName = serviceName; this.writer = writer; this.writer.start(); @@ -123,8 +126,8 @@ public class DDTracer implements io.opentracing.Tracer { }); registry = new CodecRegistry(); - registry.register(Format.Builtin.HTTP_HEADERS, new HTTPCodec()); - registry.register(Format.Builtin.TEXT_MAP, new HTTPCodec()); + registry.register(Format.Builtin.HTTP_HEADERS, new HTTPCodec(taggedHeaders)); + registry.register(Format.Builtin.TEXT_MAP, new HTTPCodec(taggedHeaders)); if (this.writer instanceof DDAgentWriter && sampler instanceof DDApi.ResponseListener) { final DDApi api = ((DDAgentWriter) this.writer).getApi(); api.addResponseListener((DDApi.ResponseListener) this.sampler); @@ -149,7 +152,8 @@ public class DDTracer implements io.opentracing.Tracer { writer, new AllSampler(), DDTraceConfig.parseMap(new DDTraceConfig().getProperty(DDTraceConfig.SPAN_TAGS)), - DDTraceConfig.parseMap(new DDTraceConfig().getProperty(DDTraceConfig.SERVICE_MAPPING))); + DDTraceConfig.parseMap(new DDTraceConfig().getProperty(DDTraceConfig.SERVICE_MAPPING)), + DDTraceConfig.parseMap(new DDTraceConfig().getProperty(DDTraceConfig.HEADER_TAGS))); } /** @@ -489,6 +493,12 @@ public class DDTracer implements io.opentracing.Tracer { traceId = ddsc.getTraceId(); parentSpanId = ddsc.getSpanId(); baggage = ddsc.getBaggage(); + if (this.tags.isEmpty() && !ddsc.getTags().isEmpty()) { + this.tags = new HashMap<>(); + } + if (!ddsc.getTags().isEmpty()) { + tags.putAll(ddsc.getTags()); + } parentTrace = new PendingTrace(DDTracer.this, traceId); samplingPriority = ddsc.getSamplingPriority(); diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/propagation/ExtractedContext.java b/dd-trace-ot/src/main/java/datadog/opentracing/propagation/ExtractedContext.java index f388f6b5c2..b67e6c0523 100644 --- a/dd-trace-ot/src/main/java/datadog/opentracing/propagation/ExtractedContext.java +++ b/dd-trace-ot/src/main/java/datadog/opentracing/propagation/ExtractedContext.java @@ -9,17 +9,20 @@ public class ExtractedContext implements SpanContext { private final Long spanId; private final int samplingPriority; private final Map baggage; + private final Map tags; private final AtomicBoolean samplingPriorityLocked = new AtomicBoolean(false); public ExtractedContext( final Long traceId, final Long spanId, final int samplingPriority, - final Map baggage) { + final Map baggage, + final Map tags) { this.traceId = traceId; this.spanId = spanId; this.samplingPriority = samplingPriority; this.baggage = baggage; + this.tags = tags; } @Override @@ -47,6 +50,10 @@ public class ExtractedContext implements SpanContext { return baggage; } + public Map getTags() { + return tags; + } + public boolean getSamplingPriorityLocked() { return samplingPriorityLocked.get(); } diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/propagation/HTTPCodec.java b/dd-trace-ot/src/main/java/datadog/opentracing/propagation/HTTPCodec.java index f6401a8fde..6e961d8f7b 100644 --- a/dd-trace-ot/src/main/java/datadog/opentracing/propagation/HTTPCodec.java +++ b/dd-trace-ot/src/main/java/datadog/opentracing/propagation/HTTPCodec.java @@ -20,6 +20,15 @@ public class HTTPCodec implements Codec { private static final String SPAN_ID_KEY = "x-datadog-parent-id"; private static final String SAMPLING_PRIORITY_KEY = "x-datadog-sampling-priority"; + private final Map taggedHeaders; + + public HTTPCodec(final Map taggedHeaders) { + this.taggedHeaders = new HashMap<>(); + for (final Map.Entry mapping : taggedHeaders.entrySet()) { + this.taggedHeaders.put(mapping.getKey().trim().toLowerCase(), mapping.getValue()); + } + } + @Override public void inject(final DDSpanContext context, final TextMap carrier) { carrier.put(TRACE_ID_KEY, String.valueOf(context.getTraceId())); @@ -38,6 +47,7 @@ public class HTTPCodec implements Codec { public ExtractedContext extract(final TextMap carrier) { Map baggage = Collections.emptyMap(); + Map tags = Collections.emptyMap(); Long traceId = 0L; Long spanId = 0L; int samplingPriority = PrioritySampling.UNSET; @@ -56,10 +66,17 @@ public class HTTPCodec implements Codec { } else if (key.equalsIgnoreCase(SAMPLING_PRIORITY_KEY)) { samplingPriority = Integer.parseInt(entry.getValue()); } + + if (taggedHeaders.containsKey(key)) { + if (tags.isEmpty()) { + tags = new HashMap<>(); + } + tags.put(taggedHeaders.get(key), decode(entry.getValue())); + } } ExtractedContext context = null; if (traceId != 0L) { - context = new ExtractedContext(traceId, spanId, samplingPriority, baggage); + context = new ExtractedContext(traceId, spanId, samplingPriority, baggage, tags); context.lockSamplingPriority(); log.debug("{} - Parent context extracted", context.getTraceId()); diff --git a/dd-trace-ot/src/main/java/datadog/trace/common/DDTraceConfig.java b/dd-trace-ot/src/main/java/datadog/trace/common/DDTraceConfig.java index c5a1fbce34..c1c4285db8 100644 --- a/dd-trace-ot/src/main/java/datadog/trace/common/DDTraceConfig.java +++ b/dd-trace-ot/src/main/java/datadog/trace/common/DDTraceConfig.java @@ -30,6 +30,7 @@ public class DDTraceConfig extends Properties { public static final String AGENT_PORT = "agent.port"; public static final String PRIORITY_SAMPLING = "priority.sampling"; public static final String SPAN_TAGS = "trace.span.tags"; + public static final String HEADER_TAGS = "trace.header.tags"; private final String serviceName = getPropOrEnv(PREFIX + SERVICE_NAME); private final String serviceMapping = getPropOrEnv(PREFIX + SERVICE_MAPPING); @@ -38,6 +39,7 @@ public class DDTraceConfig extends Properties { private final String agentPort = getPropOrEnv(PREFIX + AGENT_PORT); private final String prioritySampling = getPropOrEnv(PREFIX + PRIORITY_SAMPLING); private final String spanTags = getPropOrEnv(PREFIX + SPAN_TAGS); + private final String headerTags = getPropOrEnv(PREFIX + HEADER_TAGS); public DDTraceConfig() { super(); @@ -56,6 +58,7 @@ public class DDTraceConfig extends Properties { setIfNotNull(AGENT_PORT, agentPort); setIfNotNull(PRIORITY_SAMPLING, prioritySampling); setIfNotNull(SPAN_TAGS, spanTags); + setIfNotNull(HEADER_TAGS, headerTags); } public DDTraceConfig(final String serviceName) { diff --git a/dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanBuilderTest.groovy b/dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanBuilderTest.groovy index a796b5a1c4..c507671dcc 100644 --- a/dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanBuilderTest.groovy +++ b/dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanBuilderTest.groovy @@ -1,5 +1,6 @@ package datadog.opentracing +import datadog.opentracing.propagation.ExtractedContext import datadog.trace.api.DDTags import datadog.trace.common.writer.ListWriter import spock.lang.Specification @@ -222,6 +223,24 @@ class DDSpanBuilderTest extends Specification { spans[(int) (Math.random() * nbSamples)].context.trace.containsAll(spans) } + def "ExtractedContext should populate new span details"() { + setup: + final DDSpan span = tracer.buildSpan("op name") + .asChildOf(extractedContext).start() + + expect: + span.traceId == extractedContext.traceId + span.parentId == extractedContext.spanId + span.samplingPriority == extractedContext.samplingPriority + span.context().baggageItems == extractedContext.baggage + span.context().@tags == extractedContext.tags + + where: + extractedContext | _ + new ExtractedContext(1, 2, 0, [:], [:]) | _ + new ExtractedContext(3, 4, 1, ["asdf": "qwer"], ["zxcv": "1234"]) | _ + } + def "global span tags populated on each span"() { setup: System.setProperty("dd.trace.span.tags", tagString) diff --git a/dd-trace-ot/src/test/groovy/datadog/opentracing/propagation/HTTPCodecTest.groovy b/dd-trace-ot/src/test/groovy/datadog/opentracing/propagation/HTTPCodecTest.groovy index 0dc542ef53..2a34b9b713 100644 --- a/dd-trace-ot/src/test/groovy/datadog/opentracing/propagation/HTTPCodecTest.groovy +++ b/dd-trace-ot/src/test/groovy/datadog/opentracing/propagation/HTTPCodecTest.groovy @@ -20,6 +20,8 @@ class HTTPCodecTest extends Specification { @Shared private static final String SAMPLING_PRIORITY_KEY = "x-datadog-sampling-priority" + HTTPCodec codec = new HTTPCodec(["SOME_HEADER": "some-tag"]) + def "inject http headers"() { setup: def writer = new ListWriter() @@ -47,7 +49,6 @@ class HTTPCodecTest extends Specification { final Map carrier = new HashMap<>() - final HTTPCodec codec = new HTTPCodec() codec.inject(mockedContext, new TextMapInjectAdapter(carrier)) expect: @@ -70,13 +71,13 @@ class HTTPCodecTest extends Specification { (SPAN_ID_KEY.toUpperCase()) : "2", (OT_BAGGAGE_PREFIX.toUpperCase() + "k1"): "v1", (OT_BAGGAGE_PREFIX.toUpperCase() + "k2"): "v2", + SOME_HEADER : "my-interesting-info", ] if (samplingPriority != PrioritySampling.UNSET) { actual.put(SAMPLING_PRIORITY_KEY, String.valueOf(samplingPriority)) } - final HTTPCodec codec = new HTTPCodec() final ExtractedContext context = codec.extract(new TextMapExtractAdapter(actual)) expect: @@ -84,6 +85,7 @@ class HTTPCodecTest extends Specification { context.getSpanId() == 2l context.getBaggage().get("k1") == "v1" context.getBaggage().get("k2") == "v2" + context.getTags() == ["some-tag": "my-interesting-info"] context.getSamplingPriority() == samplingPriority where: diff --git a/dd-trace-ot/src/test/groovy/datadog/trace/DDTraceConfigTest.groovy b/dd-trace-ot/src/test/groovy/datadog/trace/DDTraceConfigTest.groovy index 1ec78e2c42..2cce35068d 100644 --- a/dd-trace-ot/src/test/groovy/datadog/trace/DDTraceConfigTest.groovy +++ b/dd-trace-ot/src/test/groovy/datadog/trace/DDTraceConfigTest.groovy @@ -14,6 +14,7 @@ import spock.lang.Specification import static datadog.trace.common.DDTraceConfig.AGENT_HOST import static datadog.trace.common.DDTraceConfig.AGENT_PORT +import static datadog.trace.common.DDTraceConfig.HEADER_TAGS import static datadog.trace.common.DDTraceConfig.PREFIX import static datadog.trace.common.DDTraceConfig.SERVICE_MAPPING import static datadog.trace.common.DDTraceConfig.SERVICE_NAME @@ -114,16 +115,19 @@ class DDTraceConfigTest extends Specification { setup: System.setProperty(PREFIX + SERVICE_MAPPING, mapString) System.setProperty(PREFIX + SPAN_TAGS, mapString) + System.setProperty(PREFIX + HEADER_TAGS, mapString) when: def tracer = new DDTracer() ServiceNameDecorator decorator = tracer.spanContextDecorators.values().flatten().find { it instanceof ServiceNameDecorator } + def taggedHeaders = tracer.registry.codecs.values().first().taggedHeaders then: tracer.spanTags == map decorator.mappings == map + taggedHeaders == map where: mapString | map