diff --git a/jaeger-core/src/main/java/io/jaegertracing/internal/Constants.java b/jaeger-core/src/main/java/io/jaegertracing/internal/Constants.java
index 47aadad3b..934646b24 100644
--- a/jaeger-core/src/main/java/io/jaegertracing/internal/Constants.java
+++ b/jaeger-core/src/main/java/io/jaegertracing/internal/Constants.java
@@ -35,6 +35,15 @@ public class Constants {
*/
public static final String DEBUG_ID_HEADER_KEY = "jaeger-debug-id";
+ /**
+ * The name of HTTP header or a TextMap carrier key that can be used to pass
+ * additional baggage to the span, e.g. when executing an ad-hoc curl request:
+ *
+ * curl -H 'jaeger-baggage: k1=v1,k2=v2' http://...
+ *
+ */
+ public static final String BAGGAGE_HEADER_KEY = "jaeger-baggage";
+
/**
* The name of the tag used to report client version.
*/
diff --git a/jaeger-core/src/main/java/io/jaegertracing/internal/JaegerSpanContext.java b/jaeger-core/src/main/java/io/jaegertracing/internal/JaegerSpanContext.java
index dbf36181e..1df4136e8 100644
--- a/jaeger-core/src/main/java/io/jaegertracing/internal/JaegerSpanContext.java
+++ b/jaeger-core/src/main/java/io/jaegertracing/internal/JaegerSpanContext.java
@@ -53,7 +53,7 @@ protected JaegerSpanContext(
String debugId,
JaegerObjectFactory objectFactory) {
if (baggage == null) {
- throw new NullPointerException();
+ baggage = Collections.emptyMap();
}
this.traceId = traceId;
this.spanId = spanId;
diff --git a/jaeger-core/src/main/java/io/jaegertracing/internal/JaegerTracer.java b/jaeger-core/src/main/java/io/jaegertracing/internal/JaegerTracer.java
index ecdbf5bed..d97519797 100644
--- a/jaeger-core/src/main/java/io/jaegertracing/internal/JaegerTracer.java
+++ b/jaeger-core/src/main/java/io/jaegertracing/internal/JaegerTracer.java
@@ -410,9 +410,7 @@ public JaegerSpan start() {
}
String debugId = debugId();
- if (references.isEmpty()) {
- context = createNewContext(null);
- } else if (debugId != null) {
+ if (references.isEmpty() || debugId != null) {
context = createNewContext(debugId);
} else {
context = createChildContext();
diff --git a/jaeger-core/src/main/java/io/jaegertracing/internal/propagation/TextMapCodec.java b/jaeger-core/src/main/java/io/jaegertracing/internal/propagation/TextMapCodec.java
index e035cba7f..a8d1387dd 100644
--- a/jaeger-core/src/main/java/io/jaegertracing/internal/propagation/TextMapCodec.java
+++ b/jaeger-core/src/main/java/io/jaegertracing/internal/propagation/TextMapCodec.java
@@ -22,15 +22,18 @@
import io.jaegertracing.internal.exceptions.MalformedTracerStateStringException;
import io.jaegertracing.spi.Codec;
import io.opentracing.propagation.TextMap;
+
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URLDecoder;
import java.net.URLEncoder;
-import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
public class TextMapCodec implements Codec {
/**
* Key used to store serialized span context representation
@@ -125,24 +128,35 @@ public JaegerSpanContext extract(TextMap carrier) {
baggage = new HashMap();
}
baggage.put(keys.unprefixedKey(key, baggagePrefix), decodedValue(entry.getValue()));
+ } else if (key.equals(Constants.BAGGAGE_HEADER_KEY)) {
+ baggage = parseBaggageHeader(decodedValue(entry.getValue()), baggage);
}
}
- if (context == null) {
- if (debugId != null) {
- return objectFactory.createSpanContext(
- 0,
- 0,
- 0,
- (byte) 0,
- Collections.emptyMap(),
- debugId);
- }
- return null;
- }
- if (baggage == null) {
+ if (debugId == null && baggage == null) {
return context;
}
- return context.withBaggage(baggage);
+ return objectFactory.createSpanContext(
+ context == null ? 0 : context.getTraceId(),
+ context == null ? 0 : context.getSpanId(),
+ context == null ? 0 : context.getParentId(),
+ context == null ? (byte)0 : context.getFlags(),
+ baggage,
+ debugId);
+ }
+
+ private Map parseBaggageHeader(String header, Map baggage) {
+ for (String part : header.split("\\s*,\\s*")) {
+ String[] kv = part.split("\\s*=\\s*");
+ if (kv.length == 2) {
+ if (baggage == null) {
+ baggage = new HashMap();
+ }
+ baggage.put(kv[0], kv[1]);
+ } else {
+ log.debug("malformed token in {} header: {}", Constants.BAGGAGE_HEADER_KEY, part);
+ }
+ }
+ return baggage;
}
@Override
diff --git a/jaeger-core/src/test/java/io/jaegertracing/internal/JaegerTracerTest.java b/jaeger-core/src/test/java/io/jaegertracing/internal/JaegerTracerTest.java
index 040de5ea4..c2abf62c5 100644
--- a/jaeger-core/src/test/java/io/jaegertracing/internal/JaegerTracerTest.java
+++ b/jaeger-core/src/test/java/io/jaegertracing/internal/JaegerTracerTest.java
@@ -37,8 +37,13 @@
import io.opentracing.Span;
import io.opentracing.propagation.Format;
import io.opentracing.propagation.TextMap;
+import io.opentracing.propagation.TextMapExtractAdapter;
+import io.opentracing.propagation.TextMapInjectAdapter;
import io.opentracing.tag.Tags;
import java.io.Closeable;
+import java.util.HashMap;
+import java.util.Map;
+
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
@@ -193,4 +198,29 @@ public void testCustomSpanOnSpanManager() {
// check
assertEquals(activeSpan, tracer.activeSpan());
}
+
+ @Test
+ public void testStartTraceWithAdhocBaggage() {
+ traceWithAdhocBaggage(new HashMap());
+ }
+
+ @Test
+ public void testJoinTraceWithAdhocBaggage() {
+ Span span = tracer.buildSpan("test").start();
+ Map headers = new HashMap();
+ tracer.inject(span.context(), Format.Builtin.HTTP_HEADERS, new TextMapInjectAdapter(headers));
+ assertEquals(1, headers.size());
+
+ traceWithAdhocBaggage(headers);
+ }
+
+ private void traceWithAdhocBaggage(Map headers) {
+ headers.put("jaeger-baggage", "k1=v1, k2 = v2");
+
+ JaegerSpanContext parent = tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapExtractAdapter(headers));
+ Span span = tracer.buildSpan("test").asChildOf(parent).start();
+
+ assertEquals("must have baggage", "v1", span.getBaggageItem("k1"));
+ assertEquals("must have baggage", "v2", span.getBaggageItem("k2"));
+ }
}
diff --git a/jaeger-core/src/test/java/io/jaegertracing/internal/PropagationTest.java b/jaeger-core/src/test/java/io/jaegertracing/internal/PropagationTest.java
index 59edb7ddf..08407b5d3 100644
--- a/jaeger-core/src/test/java/io/jaegertracing/internal/PropagationTest.java
+++ b/jaeger-core/src/test/java/io/jaegertracing/internal/PropagationTest.java
@@ -16,6 +16,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
@@ -33,6 +34,8 @@
import io.opentracing.propagation.TextMap;
import io.opentracing.propagation.TextMapExtractAdapter;
import java.util.Collections;
+import java.util.Map;
+
import org.junit.Test;
public class PropagationTest {
@@ -42,9 +45,11 @@ public void testDebugCorrelationId() {
.withReporter(new InMemoryReporter())
.withSampler(new ConstSampler(true))
.build();
- TextMap carrier = new TextMapExtractAdapter(Collections.singletonMap(Constants.DEBUG_ID_HEADER_KEY, "Coraline"));
+ Map headers = Collections.singletonMap(Constants.DEBUG_ID_HEADER_KEY, "Coraline");
+ TextMap carrier = new TextMapExtractAdapter(headers);
JaegerSpanContext jaegerSpanContext = tracer.extract(Format.Builtin.TEXT_MAP, carrier);
+ assertNotNull(jaegerSpanContext);
assertTrue(jaegerSpanContext.isDebugIdContainerOnly());
assertEquals("Coraline", jaegerSpanContext.getDebugId());
diff --git a/jaeger-core/src/test/java/io/jaegertracing/internal/propagation/TextMapCodecTest.java b/jaeger-core/src/test/java/io/jaegertracing/internal/propagation/TextMapCodecTest.java
index 28874e909..5a186543b 100644
--- a/jaeger-core/src/test/java/io/jaegertracing/internal/propagation/TextMapCodecTest.java
+++ b/jaeger-core/src/test/java/io/jaegertracing/internal/propagation/TextMapCodecTest.java
@@ -21,6 +21,12 @@
import io.jaegertracing.internal.JaegerSpanContext;
import io.jaegertracing.internal.exceptions.EmptyTracerStateStringException;
import io.jaegertracing.internal.exceptions.MalformedTracerStateStringException;
+import io.opentracing.propagation.TextMapExtractAdapter;
+import io.opentracing.propagation.TextMapInjectAdapter;
+
+import java.util.HashMap;
+import java.util.Map;
+
import org.junit.Test;
public class TextMapCodecTest {
@@ -83,4 +89,34 @@ public void testContextAsStringFormatsPositiveFields() {
assertEquals(parentId, contextFromStr.getParentId());
assertEquals(flags, contextFromStr.getFlags());
}
+
+ /**
+ * Tests that the codec will include baggage from header "jaeger-baggage".
+ */
+ @Test
+ public void testAdhocBaggageWithTraceId() {
+ TextMapCodec codec = new TextMapCodec(false);
+ Map headers = new HashMap();
+ codec.inject(new JaegerSpanContext(42, 1, 0, (byte)1), new TextMapInjectAdapter(headers));
+ headers.put("jaeger-baggage", "k1=v1, k2 = v2");
+ JaegerSpanContext context = codec.extract(new TextMapExtractAdapter(headers));
+ assertEquals("must have trace ID", 42, context.getTraceId());
+ assertEquals("must have bagggae", "v1", context.getBaggageItem("k1"));
+ assertEquals("must have bagggae", "v2", context.getBaggageItem("k2"));
+ }
+
+ /**
+ * Tests that the codec will return non-null SpanContext even if the only header
+ * present is "jaeger-baggage".
+ */
+ @Test
+ public void testAdhocBaggageWithoutTraceId() {
+ Map headers = new HashMap();
+ headers.put("jaeger-baggage", "k1=v1, k2 = v2, k3=v3=d3");
+ TextMapCodec codec = new TextMapCodec(false);
+ JaegerSpanContext context = codec.extract(new TextMapExtractAdapter(headers));
+ assertEquals("v1", context.getBaggageItem("k1"));
+ assertEquals("v2", context.getBaggageItem("k2"));
+ assertEquals(null, context.getBaggageItem("k3"));
+ }
}