diff --git a/api/src/jmh/java/io/opentelemetry/api/trace/propagation/HttpTraceContextExtractBenchmark.java b/api/src/jmh/java/io/opentelemetry/api/trace/propagation/HttpTraceContextExtractBenchmark.java index 43219ad01bd..7f7521d071f 100644 --- a/api/src/jmh/java/io/opentelemetry/api/trace/propagation/HttpTraceContextExtractBenchmark.java +++ b/api/src/jmh/java/io/opentelemetry/api/trace/propagation/HttpTraceContextExtractBenchmark.java @@ -40,6 +40,11 @@ public class HttpTraceContextExtractBenchmark { private final HttpTraceContext httpTraceContext = HttpTraceContext.getInstance(); private final Getter> getter = new Getter>() { + @Override + public Iterable keys(Map carrier) { + return carrier.keySet(); + } + @Override public String get(Map carrier, String key) { return carrier.get(key); diff --git a/api/src/test/java/io/opentelemetry/api/baggage/propagation/W3CBaggagePropagatorFuzzTest.java b/api/src/test/java/io/opentelemetry/api/baggage/propagation/W3CBaggagePropagatorFuzzTest.java index a5b8b1d30c8..0d4b5a3a3e4 100644 --- a/api/src/test/java/io/opentelemetry/api/baggage/propagation/W3CBaggagePropagatorFuzzTest.java +++ b/api/src/test/java/io/opentelemetry/api/baggage/propagation/W3CBaggagePropagatorFuzzTest.java @@ -11,7 +11,9 @@ import edu.berkeley.cs.jqf.fuzz.Fuzz; import edu.berkeley.cs.jqf.fuzz.JQF; import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapPropagator.Getter; import java.util.Map; +import javax.annotation.Nullable; import org.junit.runner.RunWith; @RunWith(JQF.class) @@ -22,7 +24,21 @@ public class W3CBaggagePropagatorFuzzTest { @Fuzz public void safeForRandomInputs(String baggage) { Context context = - baggagePropagator.extract(Context.root(), ImmutableMap.of("baggage", baggage), Map::get); + baggagePropagator.extract( + Context.root(), + ImmutableMap.of("baggage", baggage), + new Getter>() { + @Override + public Iterable keys(Map carrier) { + return carrier.keySet(); + } + + @Nullable + @Override + public String get(Map carrier, String key) { + return carrier.get(key); + } + }); assertThat(context).isNotNull(); } } diff --git a/api/src/test/java/io/opentelemetry/api/baggage/propagation/W3CBaggagePropagatorTest.java b/api/src/test/java/io/opentelemetry/api/baggage/propagation/W3CBaggagePropagatorTest.java index f74f5243f9b..f0e729a9487 100644 --- a/api/src/test/java/io/opentelemetry/api/baggage/propagation/W3CBaggagePropagatorTest.java +++ b/api/src/test/java/io/opentelemetry/api/baggage/propagation/W3CBaggagePropagatorTest.java @@ -12,12 +12,28 @@ import io.opentelemetry.api.baggage.Baggage; import io.opentelemetry.api.baggage.EntryMetadata; import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapPropagator.Getter; import java.util.HashMap; import java.util.Map; +import javax.annotation.Nullable; import org.junit.jupiter.api.Test; class W3CBaggagePropagatorTest { + private static final Getter> getter = + new Getter>() { + @Override + public Iterable keys(Map carrier) { + return carrier.keySet(); + } + + @Nullable + @Override + public String get(Map carrier, String key) { + return carrier.get(key); + } + }; + @Test void fields() { assertThat(W3CBaggagePropagator.getInstance().fields()).containsExactly("baggage"); @@ -27,8 +43,7 @@ void fields() { void extract_noBaggageHeader() { W3CBaggagePropagator propagator = W3CBaggagePropagator.getInstance(); - Context result = - propagator.extract(Context.root(), ImmutableMap.of(), Map::get); + Context result = propagator.extract(Context.root(), ImmutableMap.of(), getter); assertThat(result).isEqualTo(Context.root()); } @@ -37,8 +52,7 @@ void extract_noBaggageHeader() { void extract_emptyBaggageHeader() { W3CBaggagePropagator propagator = W3CBaggagePropagator.getInstance(); - Context result = - propagator.extract(Context.root(), ImmutableMap.of("baggage", ""), ImmutableMap::get); + Context result = propagator.extract(Context.root(), ImmutableMap.of("baggage", ""), getter); assertThat(Baggage.fromContext(result)).isEqualTo(Baggage.empty()); } @@ -48,8 +62,7 @@ void extract_singleEntry() { W3CBaggagePropagator propagator = W3CBaggagePropagator.getInstance(); Context result = - propagator.extract( - Context.root(), ImmutableMap.of("baggage", "key=value"), ImmutableMap::get); + propagator.extract(Context.root(), ImmutableMap.of("baggage", "key=value"), getter); Baggage expectedBaggage = Baggage.builder().put("key", "value").build(); assertThat(Baggage.fromContext(result)).isEqualTo(expectedBaggage); @@ -61,9 +74,7 @@ void extract_multiEntry() { Context result = propagator.extract( - Context.root(), - ImmutableMap.of("baggage", "key1=value1,key2=value2"), - ImmutableMap::get); + Context.root(), ImmutableMap.of("baggage", "key1=value1,key2=value2"), getter); Baggage expectedBaggage = Baggage.builder().put("key1", "value1").put("key2", "value2").build(); assertThat(Baggage.fromContext(result)).isEqualTo(expectedBaggage); @@ -75,7 +86,7 @@ void extract_duplicateKeys() { Context result = propagator.extract( - Context.root(), ImmutableMap.of("baggage", "key=value1,key=value2"), ImmutableMap::get); + Context.root(), ImmutableMap.of("baggage", "key=value1,key=value2"), getter); Baggage expectedBaggage = Baggage.builder().put("key", "value2").build(); assertThat(Baggage.fromContext(result)).isEqualTo(expectedBaggage); @@ -89,7 +100,7 @@ void extract_withMetadata() { propagator.extract( Context.root(), ImmutableMap.of("baggage", "key=value;metadata-key=value;othermetadata"), - ImmutableMap::get); + getter); Baggage expectedBaggage = Baggage.builder() @@ -109,7 +120,7 @@ void extract_fullComplexities() { "baggage", "key1= value1; metadata-key = value; othermetadata, " + "key2 =value2 , key3 =\tvalue3 ; "), - ImmutableMap::get); + getter); Baggage expectedBaggage = Baggage.builder() @@ -135,7 +146,7 @@ void extract_invalidHeader() { "baggage", "key1= v;alsdf;-asdflkjasdf===asdlfkjadsf ,,a sdf9asdf-alue1; metadata-key = " + "value; othermetadata, key2 =value2 , key3 =\tvalue3 ; "), - ImmutableMap::get); + getter); assertThat(Baggage.fromContext(result)).isEqualTo(Baggage.empty()); } diff --git a/api/src/test/java/io/opentelemetry/api/trace/propagation/HttpTraceContextFuzzTest.java b/api/src/test/java/io/opentelemetry/api/trace/propagation/HttpTraceContextFuzzTest.java index 7ef2947ec90..e1127e5f433 100644 --- a/api/src/test/java/io/opentelemetry/api/trace/propagation/HttpTraceContextFuzzTest.java +++ b/api/src/test/java/io/opentelemetry/api/trace/propagation/HttpTraceContextFuzzTest.java @@ -11,7 +11,9 @@ import edu.berkeley.cs.jqf.fuzz.Fuzz; import edu.berkeley.cs.jqf.fuzz.JQF; import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapPropagator.Getter; import java.util.Map; +import javax.annotation.Nullable; import org.junit.runner.RunWith; @RunWith(JQF.class) @@ -25,7 +27,19 @@ public void safeForRandomInputs(String traceParentHeader, String traceStateHeade httpTraceContext.extract( Context.root(), ImmutableMap.of("traceparent", traceParentHeader, "tracestate", traceStateHeader), - Map::get); + new Getter>() { + @Override + public Iterable keys(Map carrier) { + return carrier.keySet(); + } + + @Nullable + @Override + public String get(Map carrier, String key) { + return carrier.get(key); + } + }); + assertThat(context).isNotNull(); } } diff --git a/api/src/test/java/io/opentelemetry/api/trace/propagation/HttpTraceContextTest.java b/api/src/test/java/io/opentelemetry/api/trace/propagation/HttpTraceContextTest.java index cf7742339ea..8497a4236b0 100644 --- a/api/src/test/java/io/opentelemetry/api/trace/propagation/HttpTraceContextTest.java +++ b/api/src/test/java/io/opentelemetry/api/trace/propagation/HttpTraceContextTest.java @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; +import javax.annotation.Nullable; import org.junit.jupiter.api.Test; /** Unit tests for {@link HttpTraceContext}. */ @@ -39,7 +40,19 @@ class HttpTraceContextTest { private static final String TRACEPARENT_HEADER_NOT_SAMPLED = "00-" + TRACE_ID_BASE16 + "-" + SPAN_ID_BASE16 + "-00"; private static final Setter> setter = Map::put; - private static final Getter> getter = Map::get; + private static final Getter> getter = + new Getter>() { + @Override + public Iterable keys(Map carrier) { + return carrier.keySet(); + } + + @Nullable + @Override + public String get(Map carrier, String key) { + return carrier.get(key); + } + }; // Encoding preserves the order which is the reverse order of adding. private static final String TRACESTATE_NOT_DEFAULT_ENCODING = "bar=baz,foo=bar"; private static final String TRACESTATE_NOT_DEFAULT_ENCODING_WITH_SPACES = @@ -151,7 +164,7 @@ void extract_Nothing() { // Context remains untouched. assertThat( httpTraceContext.extract( - Context.current(), Collections.emptyMap(), Map::get)) + Context.current(), Collections.emptyMap(), getter)) .isSameAs(Context.current()); } @@ -169,9 +182,7 @@ void extract_SampledContext() { void extract_NullCarrier() { Map carrier = new LinkedHashMap<>(); carrier.put(TRACE_PARENT, TRACEPARENT_HEADER_SAMPLED); - assertThat( - getSpanContext( - httpTraceContext.extract(Context.current(), null, (c, k) -> carrier.get(k)))) + assertThat(getSpanContext(httpTraceContext.extract(Context.current(), carrier, getter))) .isEqualTo( SpanContext.createFromRemoteParent( TRACE_ID_BASE16, SPAN_ID_BASE16, SAMPLED_TRACE_OPTIONS, TRACE_STATE_DEFAULT)); diff --git a/context/src/main/java/io/opentelemetry/context/propagation/TextMapPropagator.java b/context/src/main/java/io/opentelemetry/context/propagation/TextMapPropagator.java index dff28b00844..dba8bd8f494 100644 --- a/context/src/main/java/io/opentelemetry/context/propagation/TextMapPropagator.java +++ b/context/src/main/java/io/opentelemetry/context/propagation/TextMapPropagator.java @@ -115,6 +115,14 @@ interface Setter { */ interface Getter { + /** + * Returns all the keys in the given carrier. + * + * @param carrier carrier of propagation fields, such as an http request. + * @since 0.10.0 + */ + Iterable keys(C carrier); + /** * Returns the first value of the given propagation {@code key} or returns {@code null}. * diff --git a/context/src/test/java/io/opentelemetry/context/propagation/DefaultPropagatorsTest.java b/context/src/test/java/io/opentelemetry/context/propagation/DefaultPropagatorsTest.java index 6b2adc63e74..94c8f6ce4d6 100644 --- a/context/src/test/java/io/opentelemetry/context/propagation/DefaultPropagatorsTest.java +++ b/context/src/test/java/io/opentelemetry/context/propagation/DefaultPropagatorsTest.java @@ -155,6 +155,11 @@ private MapSetter() {} private static final class MapGetter implements TextMapPropagator.Getter> { private static final MapGetter INSTANCE = new MapGetter(); + @Override + public Iterable keys(Map map) { + return map.keySet(); + } + @Override public String get(Map map, String key) { return map.get(key); diff --git a/extensions/trace-propagators/src/jmh/java/io/opentelemetry/extension/trace/propagation/PropagatorContextExtractBenchmark.java b/extensions/trace-propagators/src/jmh/java/io/opentelemetry/extension/trace/propagation/PropagatorContextExtractBenchmark.java index 505400e28f5..40e15457da3 100644 --- a/extensions/trace-propagators/src/jmh/java/io/opentelemetry/extension/trace/propagation/PropagatorContextExtractBenchmark.java +++ b/extensions/trace-propagators/src/jmh/java/io/opentelemetry/extension/trace/propagation/PropagatorContextExtractBenchmark.java @@ -94,6 +94,11 @@ public static class JaegerContextExtractBenchmark extends AbstractContextExtract private final TextMapPropagator.Getter> getter = new TextMapPropagator.Getter>() { + @Override + public Iterable keys(Map carrier) { + return carrier.keySet(); + } + @Override public String get(Map carrier, String key) { return carrier.get(key); @@ -137,6 +142,11 @@ public static class JaegerUrlEncodedContextExtractBenchmark private final TextMapPropagator.Getter> getter = new TextMapPropagator.Getter>() { + @Override + public Iterable keys(Map carrier) { + return carrier.keySet(); + } + @Override public String get(Map carrier, String key) { return carrier.get(key); @@ -180,6 +190,11 @@ public static class B3SingleHeaderContextExtractBenchmark private final TextMapPropagator.Getter> getter = new TextMapPropagator.Getter>() { + @Override + public Iterable keys(Map carrier) { + return carrier.keySet(); + } + @Override public String get(Map carrier, String key) { return carrier.get(key); @@ -226,6 +241,11 @@ private static Map createHeaders( private final TextMapPropagator.Getter> getter = new TextMapPropagator.Getter>() { + @Override + public Iterable keys(Map carrier) { + return carrier.keySet(); + } + @Override public String get(Map carrier, String key) { return carrier.get(key); diff --git a/extensions/trace-propagators/src/main/java/io/opentelemetry/extension/trace/propagation/JaegerPropagator.java b/extensions/trace-propagators/src/main/java/io/opentelemetry/extension/trace/propagation/JaegerPropagator.java index 1265151e468..ce8dd3646e6 100644 --- a/extensions/trace-propagators/src/main/java/io/opentelemetry/extension/trace/propagation/JaegerPropagator.java +++ b/extensions/trace-propagators/src/main/java/io/opentelemetry/extension/trace/propagation/JaegerPropagator.java @@ -5,6 +5,7 @@ package io.opentelemetry.extension.trace.propagation; +import io.opentelemetry.api.baggage.Baggage; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.api.trace.SpanId; @@ -34,6 +35,8 @@ public class JaegerPropagator implements TextMapPropagator { private static final Logger logger = Logger.getLogger(JaegerPropagator.class.getName()); static final String PROPAGATION_HEADER = "uber-trace-id"; + static final String BAGGAGE_HEADER = "jaeger-baggage"; + static final String BAGGAGE_PREFIX = "uberctx-"; // Parent span has been deprecated but Jaeger propagation protocol requires it static final char DEPRECATED_PARENT_SPAN = '0'; static final char PROPAGATION_HEADER_DELIMITER = ':'; @@ -84,10 +87,15 @@ public void inject(Context context, C carrier, Setter setter) { Objects.requireNonNull(setter, "setter"); SpanContext spanContext = Span.fromContext(context).getSpanContext(); - if (!spanContext.isValid()) { - return; + if (spanContext.isValid()) { + injectSpan(spanContext, carrier, setter); } + injectBaggage(Baggage.fromContext(context), carrier, setter); + } + + private static void injectSpan(SpanContext spanContext, C carrier, Setter setter) { + char[] chars = new char[PROPAGATION_HEADER_SIZE]; String traceId = spanContext.getTraceIdAsHexString(); @@ -108,16 +116,28 @@ public void inject(Context context, C carrier, Setter setter) { setter.set(carrier, PROPAGATION_HEADER, new String(chars)); } + private static void injectBaggage(Baggage baggage, C carrier, Setter setter) { + baggage.forEach( + (key, value, metadata) -> { + setter.set(carrier, BAGGAGE_PREFIX + key, value); + }); + } + @Override public Context extract(Context context, @Nullable C carrier, Getter getter) { Objects.requireNonNull(getter, "getter"); SpanContext spanContext = getSpanContextFromHeader(carrier, getter); - if (!spanContext.isValid()) { - return context; + if (spanContext.isValid()) { + context = context.with(Span.wrap(spanContext)); + } + + Baggage baggage = getBaggageFromHeader(carrier, getter); + if (baggage != null) { + context = context.with(baggage); } - return context.with(Span.wrap(spanContext)); + return context; } @SuppressWarnings("StringSplitter") @@ -187,6 +207,42 @@ private static SpanContext getSpanContextFromHeader(C carrier, Getter get return buildSpanContext(traceId, spanId, flags); } + private static Baggage getBaggageFromHeader(C carrier, Getter getter) { + Baggage.Builder builder = null; + + for (String key : getter.keys(carrier)) { + if (key.startsWith(BAGGAGE_PREFIX)) { + if (key.length() == BAGGAGE_PREFIX.length()) { + continue; + } + + if (builder == null) { + builder = Baggage.builder(); + } + builder.put(key.substring(BAGGAGE_PREFIX.length()), getter.get(carrier, key)); + } else if (key.equals(BAGGAGE_HEADER)) { + builder = parseBaggageHeader(getter.get(carrier, key), builder); + } + } + return builder == null ? null : builder.build(); + } + + @SuppressWarnings("StringSplitter") + private static Baggage.Builder parseBaggageHeader(String header, Baggage.Builder builder) { + for (String part : header.split("\\s*,\\s*")) { + String[] kv = part.split("\\s*=\\s*"); + if (kv.length == 2) { + if (builder == null) { + builder = Baggage.builder(); + } + builder.put(kv[0], kv[1]); + } else { + logger.fine("malformed token in " + BAGGAGE_HEADER + " header: " + part); + } + } + return builder; + } + private static SpanContext buildSpanContext(String traceId, String spanId, String flags) { try { int flagsInt = Integer.parseInt(flags); diff --git a/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/AwsXRayPropagatorTest.java b/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/AwsXRayPropagatorTest.java index 6d11395f76a..d4b9dfd490b 100644 --- a/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/AwsXRayPropagatorTest.java +++ b/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/AwsXRayPropagatorTest.java @@ -31,6 +31,11 @@ class AwsXRayPropagatorTest { private static final TextMapPropagator.Setter> setter = Map::put; private static final TextMapPropagator.Getter> getter = new TextMapPropagator.Getter>() { + @Override + public Iterable keys(Map carrier) { + return carrier.keySet(); + } + @Nullable @Override public String get(Map carrier, String key) { @@ -99,7 +104,7 @@ void extract_Nothing() { // Context remains untouched. assertThat( xrayPropagator.extract( - Context.current(), Collections.emptyMap(), Map::get)) + Context.current(), Collections.emptyMap(), getter)) .isSameAs(Context.current()); } diff --git a/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/B3PropagatorTest.java b/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/B3PropagatorTest.java index 1084da26928..321bce6313f 100644 --- a/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/B3PropagatorTest.java +++ b/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/B3PropagatorTest.java @@ -39,6 +39,11 @@ class B3PropagatorTest { private static final Setter> setter = Map::put; private static final Getter> getter = new Getter>() { + @Override + public Iterable keys(Map carrier) { + return carrier.keySet(); + } + @Nullable @Override public String get(Map carrier, String key) { @@ -118,8 +123,7 @@ void inject_NotSampledContext() { void extract_Nothing() { // Context remains untouched. assertThat( - b3Propagator.extract( - Context.current(), Collections.emptyMap(), Map::get)) + b3Propagator.extract(Context.current(), Collections.emptyMap(), getter)) .isSameAs(Context.current()); } @@ -329,8 +333,7 @@ void inject_NotSampledContext_SingleHeader() { void extract_Nothing_SingleHeader() { // Context remains untouched. assertThat( - b3Propagator.extract( - Context.current(), Collections.emptyMap(), Map::get)) + b3Propagator.extract(Context.current(), Collections.emptyMap(), getter)) .isSameAs(Context.current()); } diff --git a/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/JaegerPropagatorTest.java b/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/JaegerPropagatorTest.java index 19ee5244827..05492e58959 100644 --- a/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/JaegerPropagatorTest.java +++ b/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/JaegerPropagatorTest.java @@ -5,13 +5,19 @@ package io.opentelemetry.extension.trace.propagation; +import static io.opentelemetry.api.baggage.Baggage.fromContext; +import static io.opentelemetry.extension.trace.propagation.JaegerPropagator.BAGGAGE_HEADER; +import static io.opentelemetry.extension.trace.propagation.JaegerPropagator.BAGGAGE_PREFIX; import static io.opentelemetry.extension.trace.propagation.JaegerPropagator.DEPRECATED_PARENT_SPAN; import static io.opentelemetry.extension.trace.propagation.JaegerPropagator.PROPAGATION_HEADER; import static io.opentelemetry.extension.trace.propagation.JaegerPropagator.PROPAGATION_HEADER_DELIMITER; import static org.assertj.core.api.Assertions.assertThat; +import com.google.common.collect.ImmutableMap; import io.jaegertracing.internal.JaegerSpanContext; import io.jaegertracing.internal.propagation.TextMapCodec; +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.baggage.EntryMetadata; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.api.trace.SpanId; @@ -49,6 +55,11 @@ class JaegerPropagatorTest { private static final TextMapPropagator.Setter> setter = Map::put; private static final TextMapPropagator.Getter> getter = new TextMapPropagator.Getter>() { + @Override + public Iterable keys(Map carrier) { + return carrier.keySet(); + } + @Nullable @Override public String get(Map carrier, String key) { @@ -133,12 +144,49 @@ void inject_NotSampledContext() { TRACE_ID_BASE16, SPAN_ID_BASE16, DEPRECATED_PARENT_SPAN, "0")); } + @Test + void inject_SampledContext_withBaggage() { + Map carrier = new LinkedHashMap<>(); + Context context = + Context.current() + .with( + Span.wrap( + SpanContext.create( + TRACE_ID, SPAN_ID, SAMPLED_TRACE_OPTIONS, TRACE_STATE_DEFAULT))) + .with(Baggage.builder().put("foo", "bar").build()); + + jaegerPropagator.inject(context, carrier, setter); + assertThat(carrier) + .containsEntry( + PROPAGATION_HEADER, + generateTraceIdHeaderValue( + TRACE_ID_BASE16, SPAN_ID_BASE16, DEPRECATED_PARENT_SPAN, "1")); + assertThat(carrier).containsEntry(BAGGAGE_PREFIX + "foo", "bar"); + } + + @Test + void inject_baggageOnly() { + // Metadata won't be propagated, but it MUST NOT cause ay problem. + Baggage baggage = + Baggage.builder() + .put("nometa", "nometa-value") + .put("meta", "meta-value", EntryMetadata.create("somemetadata; someother=foo")) + .build(); + Map carrier = new LinkedHashMap<>(); + jaegerPropagator.inject(Context.root().with(baggage), carrier, Map::put); + assertThat(carrier) + .containsExactlyInAnyOrderEntriesOf( + ImmutableMap.of( + BAGGAGE_PREFIX + "nometa", "nometa-value", + BAGGAGE_PREFIX + "meta", "meta-value")); + } + @Test void extract_Nothing() { // Context remains untouched. assertThat( jaegerPropagator.extract( - Context.current(), Collections.emptyMap(), Map::get)) + Context.current(), Collections.emptyMap(), getter)) .isSameAs(Context.current()); } @@ -313,6 +361,88 @@ void extract_UrlEncodedContext() throws UnsupportedEncodingException { TRACE_ID, SPAN_ID, SAMPLED_TRACE_OPTIONS, TRACE_STATE_DEFAULT)); } + @Test + void extract_SampledContext_withBaggage() { + Map carrier = new LinkedHashMap<>(); + JaegerSpanContext context = + new JaegerSpanContext( + TRACE_ID_HI, TRACE_ID_LOW, SPAN_ID_LONG, DEPRECATED_PARENT_SPAN_LONG, (byte) 5); + carrier.put(PROPAGATION_HEADER, TextMapCodec.contextAsString(context)); + carrier.put(BAGGAGE_PREFIX + "foo", "bar"); + + assertThat(getSpanContext(jaegerPropagator.extract(Context.current(), carrier, getter))) + .isEqualTo( + SpanContext.createFromRemoteParent( + TRACE_ID, SPAN_ID, SAMPLED_TRACE_OPTIONS, TRACE_STATE_DEFAULT)); + assertThat(fromContext(jaegerPropagator.extract(Context.current(), carrier, getter))) + .isEqualTo(Baggage.builder().put("foo", "bar").build()); + } + + @Test + void extract_baggageOnly_withPrefix() { + Map carrier = new LinkedHashMap<>(); + carrier.put(BAGGAGE_PREFIX + "nometa", "nometa-value"); + carrier.put(BAGGAGE_PREFIX + "meta", "meta-value"); + carrier.put("another", "value"); + + assertThat(fromContext(jaegerPropagator.extract(Context.current(), carrier, getter))) + .isEqualTo( + Baggage.builder().put("nometa", "nometa-value").put("meta", "meta-value").build()); + } + + @Test + void extract_baggageOnly_withPrefix_emptyKey() { + Map carrier = new LinkedHashMap<>(); + carrier.put(BAGGAGE_PREFIX, "value"); // Not really a valid key. + + assertThat(fromContext(jaegerPropagator.extract(Context.current(), carrier, getter))) + .isEqualTo(Baggage.empty()); + } + + @Test + void extract_baggageOnly_withHeader() { + Map carrier = new LinkedHashMap<>(); + carrier.put(BAGGAGE_HEADER, "nometa=nometa-value,meta=meta-value"); + + assertThat(fromContext(jaegerPropagator.extract(Context.current(), carrier, getter))) + .isEqualTo( + Baggage.builder().put("nometa", "nometa-value").put("meta", "meta-value").build()); + } + + @Test + void extract_baggageOnly_withHeader_andSpaces() { + Map carrier = new LinkedHashMap<>(); + carrier.put(BAGGAGE_HEADER, "nometa = nometa-value , meta = meta-value"); + + assertThat(fromContext(jaegerPropagator.extract(Context.current(), carrier, getter))) + .isEqualTo( + Baggage.builder().put("nometa", "nometa-value").put("meta", "meta-value").build()); + } + + @Test + void extract_baggageOnly_withHeader_invalid() { + Map carrier = new LinkedHashMap<>(); + carrier.put(BAGGAGE_HEADER, "nometa+novalue"); + + assertThat(fromContext(jaegerPropagator.extract(Context.current(), carrier, getter))) + .isEqualTo(Baggage.empty()); + } + + @Test + void extract_baggageOnly_withHeader_andPrefix() { + Map carrier = new LinkedHashMap<>(); + carrier.put(BAGGAGE_HEADER, "nometa=nometa-value,meta=meta-value"); + carrier.put(BAGGAGE_PREFIX + "foo", "bar"); + + assertThat(fromContext(jaegerPropagator.extract(Context.current(), carrier, getter))) + .isEqualTo( + Baggage.builder() + .put("nometa", "nometa-value") + .put("meta", "meta-value") + .put("foo", "bar") + .build()); + } + private static String generateTraceIdHeaderValue( String traceId, String spanId, char parentSpan, String sampled) { return traceId diff --git a/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/OtTracerPropagatorTest.java b/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/OtTracerPropagatorTest.java index c0a9ce278d2..e2f0ea84243 100644 --- a/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/OtTracerPropagatorTest.java +++ b/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/OtTracerPropagatorTest.java @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; +import javax.annotation.Nullable; import org.junit.jupiter.api.Test; class OtTracerPropagatorTest { @@ -31,7 +32,19 @@ class OtTracerPropagatorTest { private static final String SPAN_ID = "ff00000000000041"; private static final byte SAMPLED_TRACE_OPTIONS = TraceFlags.getSampled(); private static final Setter> setter = Map::put; - private static final Getter> getter = Map::get; + private static final Getter> getter = + new Getter>() { + @Override + public Iterable keys(Map carrier) { + return carrier.keySet(); + } + + @Nullable + @Override + public String get(Map carrier, String key) { + return carrier.get(key); + } + }; private final OtTracerPropagator propagator = OtTracerPropagator.getInstance(); private static SpanContext getSpanContext(Context context) { @@ -104,7 +117,7 @@ void inject_NotSampledContext() { void extract_Nothing() { // Context remains untouched. assertThat( - propagator.extract(Context.current(), Collections.emptyMap(), Map::get)) + propagator.extract(Context.current(), Collections.emptyMap(), getter)) .isSameAs(Context.current()); } diff --git a/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/TraceMultiPropagatorTest.java b/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/TraceMultiPropagatorTest.java index 0908795a5bb..0d56cdea3ab 100644 --- a/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/TraceMultiPropagatorTest.java +++ b/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/TraceMultiPropagatorTest.java @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.annotation.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -34,6 +35,20 @@ class TraceMultiPropagatorTest { B3Propagator.builder().injectMultipleHeaders().build(); private static final TextMapPropagator PROPAGATOR3 = HttpTraceContext.getInstance(); + private static final TextMapPropagator.Getter> getter = + new TextMapPropagator.Getter>() { + @Override + public Iterable keys(Map carrier) { + return carrier.keySet(); + } + + @Nullable + @Override + public String get(Map carrier, String key) { + return carrier.get(key); + } + }; + private static final Span SPAN = Span.wrap( SpanContext.createFromRemoteParent( @@ -112,15 +127,15 @@ void inject_allFormats() { prop.inject(Context.current().with(SPAN), carrier, Map::put); assertThat( - Span.fromContext(PROPAGATOR1.extract(Context.current(), carrier, Map::get)) + Span.fromContext(PROPAGATOR1.extract(Context.current(), carrier, getter)) .getSpanContext()) .isEqualTo(SPAN.getSpanContext()); assertThat( - Span.fromContext(PROPAGATOR2.extract(Context.current(), carrier, Map::get)) + Span.fromContext(PROPAGATOR2.extract(Context.current(), carrier, getter)) .getSpanContext()) .isEqualTo(SPAN.getSpanContext()); assertThat( - Span.fromContext(PROPAGATOR3.extract(Context.current(), carrier, Map::get)) + Span.fromContext(PROPAGATOR3.extract(Context.current(), carrier, getter)) .getSpanContext()) .isEqualTo(SPAN.getSpanContext()); } @@ -131,7 +146,7 @@ void extract_noPropagators() { Map carrier = new HashMap<>(); Context context = Context.current(); - Context resContext = prop.extract(context, carrier, Map::get); + Context resContext = prop.extract(context, carrier, getter); assertThat(context).isSameAs(resContext); } @@ -146,8 +161,7 @@ void extract_found() { Map carrier = new HashMap<>(); PROPAGATOR2.inject(Context.current().with(SPAN), carrier, Map::put); - assertThat( - Span.fromContext(prop.extract(Context.current(), carrier, Map::get)).getSpanContext()) + assertThat(Span.fromContext(prop.extract(Context.current(), carrier, getter)).getSpanContext()) .isEqualTo(SPAN.getSpanContext()); } @@ -157,7 +171,7 @@ void extract_notFound() { Map carrier = new HashMap<>(); PROPAGATOR3.inject(Context.current().with(SPAN), carrier, Map::put); - assertThat(prop.extract(Context.current(), carrier, Map::get)).isEqualTo(Context.current()); + assertThat(prop.extract(Context.current(), carrier, getter)).isEqualTo(Context.current()); } @Test @@ -171,8 +185,7 @@ void extract_stopWhenFound() { Map carrier = new HashMap<>(); PROPAGATOR3.inject(Context.current().with(SPAN), carrier, Map::put); - assertThat( - Span.fromContext(prop.extract(Context.current(), carrier, Map::get)).getSpanContext()) + assertThat(Span.fromContext(prop.extract(Context.current(), carrier, getter)).getSpanContext()) .isEqualTo(SPAN.getSpanContext()); verify(mockPropagator).fields(); verifyNoMoreInteractions(mockPropagator); diff --git a/integration-tests/tracecontext/src/main/java/io/opentelemetry/Application.java b/integration-tests/tracecontext/src/main/java/io/opentelemetry/Application.java index 6020c7f254f..300e14b000c 100644 --- a/integration-tests/tracecontext/src/main/java/io/opentelemetry/Application.java +++ b/integration-tests/tracecontext/src/main/java/io/opentelemetry/Application.java @@ -11,19 +11,24 @@ import io.opentelemetry.api.trace.propagation.HttpTraceContext; import io.opentelemetry.context.Context; import io.opentelemetry.context.propagation.DefaultContextPropagators; +import io.opentelemetry.context.propagation.TextMapPropagator.Getter; +import io.opentelemetry.context.propagation.TextMapPropagator.Setter; import java.util.ArrayList; +import java.util.Collections; import java.util.Enumeration; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; +import javax.servlet.http.HttpServletRequest; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.RequestBody; -import okhttp3.Response; +import spark.Request; +import spark.Response; +import spark.Route; import spark.Spark; public class Application { - private static final Logger logger = Logger.getLogger(Application.class.getName()); private static final OpenTelemetry openTelemetry; @@ -46,82 +51,101 @@ public static void main(String[] args) { Spark.port(5000); Spark.post( "verify-tracecontext", - (request, response) -> { - final Gson gson = new Gson(); + new Route() { + @Override + public Object handle(Request request, Response response) { + final Gson gson = new Gson(); + + final io.opentelemetry.Request[] requests = + gson.fromJson(request.body(), io.opentelemetry.Request[].class); + + Context context = + openTelemetry + .getPropagators() + .getTextMapPropagator() + .extract( + Context.current(), + request.raw(), + new Getter() { + @Override + public Iterable keys(HttpServletRequest carrier) { + return Collections.list(carrier.getHeaderNames()); + } + + @Override + public String get(HttpServletRequest carrier, String key) { + Enumeration headers = carrier.getHeaders(key); + if (headers == null || !headers.hasMoreElements()) { + return null; + } + List values = new ArrayList<>(); + while (headers.hasMoreElements()) { + String nextElement = headers.nextElement(); + if (!nextElement.trim().isEmpty()) { + values.add(nextElement); + } + } + if (values.isEmpty()) { + return null; + } + if (values.size() == 1) { + return values.get(0); + } + StringBuilder builder = new StringBuilder(values.get(0)); + for (int i = 1; i < values.size(); i++) { + builder.append(",").append(values.get(i)); + } + + return builder.toString(); + } + }); + + for (io.opentelemetry.Request req : requests) { + Span span = + openTelemetry + .getTracer("validation-server") + .spanBuilder("Entering Validation Server") + .setParent(context) + .startSpan(); + + Context withSpanContext = context.with(span); - final io.opentelemetry.Request[] requests = - gson.fromJson(request.body(), io.opentelemetry.Request[].class); + // Make a new request using the builder + okhttp3.Request.Builder reqBuilder = new okhttp3.Request.Builder(); - Context context = + // Inject the current context into the new request. openTelemetry .getPropagators() .getTextMapPropagator() - .extract( - Context.current(), - request.raw(), - (carrier, key) -> { - Enumeration headers = carrier.getHeaders(key); - if (headers == null || !headers.hasMoreElements()) { - return null; + .inject( + withSpanContext, + reqBuilder, + new Setter() { + @Override + public void set(okhttp3.Request.Builder carrier, String key, String value) { + carrier.addHeader(key, value); } - List values = new ArrayList<>(); - while (headers.hasMoreElements()) { - String nextElement = headers.nextElement(); - if (!nextElement.trim().isEmpty()) { - values.add(nextElement); - } - } - if (values.isEmpty()) { - return null; - } - if (values.size() == 1) { - return values.get(0); - } - StringBuilder builder = new StringBuilder(values.get(0)); - for (int i = 1; i < values.size(); i++) { - builder.append(",").append(values.get(i)); - } - - return builder.toString(); }); - for (io.opentelemetry.Request req : requests) { - Span span = - openTelemetry - .getTracer("validation-server") - .spanBuilder("Entering Validation Server") - .setParent(context) - .startSpan(); - - Context withSpanContext = context.with(span); - - // Make a new request using the builder - okhttp3.Request.Builder reqBuilder = new okhttp3.Request.Builder(); - - // Inject the current context into the new request. - openTelemetry - .getPropagators() - .getTextMapPropagator() - .inject(withSpanContext, reqBuilder, okhttp3.Request.Builder::addHeader); - - // Add the post body and build the request - String argumentsJson = gson.toJson(req.getArguments()); - RequestBody argumentsBody = - RequestBody.create( - MediaType.parse("application/json; charset=utf-8"), argumentsJson); - okhttp3.Request newRequest = reqBuilder.url(req.getUrl()).post(argumentsBody).build(); - - // Execute the request - OkHttpClient client = new OkHttpClient(); - try (Response res = client.newCall(newRequest).execute()) { - logger.info("response: " + res.code()); - } catch (Exception e) { - logger.log(Level.SEVERE, "failed to send", e); + // Add the post body and build the request + String argumentsJson = gson.toJson(req.getArguments()); + RequestBody argumentsBody = + RequestBody.create( + MediaType.parse("application/json; charset=utf-8"), argumentsJson); + okhttp3.Request newRequest = reqBuilder.url(req.getUrl()).post(argumentsBody).build(); + + // Execute the request + OkHttpClient client = new OkHttpClient(); + try (okhttp3.Response res = client.newCall(newRequest).execute()) { + logger.info("response: " + res.code()); + } catch (Exception e) { + logger.log(Level.SEVERE, "failed to send", e); + } + span.end(); } - span.end(); - } - return "Done"; + return "Done"; + } }); Spark.awaitInitialization(); diff --git a/opentracing-shim/src/main/java/io/opentelemetry/opentracingshim/Propagation.java b/opentracing-shim/src/main/java/io/opentelemetry/opentracingshim/Propagation.java index c68d52dd841..42f9f0278d0 100644 --- a/opentracing-shim/src/main/java/io/opentelemetry/opentracingshim/Propagation.java +++ b/opentracing-shim/src/main/java/io/opentelemetry/opentracingshim/Propagation.java @@ -65,6 +65,12 @@ private TextMapGetter() {} public static final TextMapGetter INSTANCE = new TextMapGetter(); + @Nullable + @Override + public Iterable keys(Map carrier) { + return carrier.keySet(); + } + @Override public String get(Map carrier, String key) { return carrier.get(key); diff --git a/sdk/tracing/src/test/java/io/opentelemetry/sdk/trace/testbed/clientserver/Server.java b/sdk/tracing/src/test/java/io/opentelemetry/sdk/trace/testbed/clientserver/Server.java index 36cdbe509cf..ac76918636a 100644 --- a/sdk/tracing/src/test/java/io/opentelemetry/sdk/trace/testbed/clientserver/Server.java +++ b/sdk/tracing/src/test/java/io/opentelemetry/sdk/trace/testbed/clientserver/Server.java @@ -33,6 +33,11 @@ private void process(Message message) { Context.current(), message, new Getter() { + @Override + public Iterable keys(Message carrier) { + return carrier.keySet(); + } + @Nullable @Override public String get(Message carrier, String key) {