Skip to content
This repository was archived by the owner on Jul 1, 2022. It is now read-only.

Commit 568ab68

Browse files
pavolloffaybackjo
andauthored
Add W3C TraceContext codec/propagation (#694)
* Add support for tracecontext Signed-off-by: Jonah Back <[email protected]> * Address formatting issues Signed-off-by: Jonah Back <[email protected]> * Add additional tests, remove unused constructor Signed-off-by: Jonah Back <[email protected]> * Add test for version builder Signed-off-by: Jonah Back <[email protected]> * Fix incorrect header name Signed-off-by: Jonah Back <[email protected]> * Remove bit where we generate left hand side of trace id when we only had 64 bits of data Signed-off-by: Jonah Back <[email protected]> * Add W3C TraceContext codec Signed-off-by: Pavol Loffay <[email protected]> * Some cleanup Signed-off-by: Pavol Loffay <[email protected]> * smaller nits Signed-off-by: Pavol Loffay <[email protected]> * Fix padding with zeros in test Signed-off-by: Pavol Loffay <[email protected]> * Add tracestate header Signed-off-by: Pavol Loffay <[email protected]> * Just propagate tracestate Signed-off-by: Pavol Loffay <[email protected]> * Cleanup Signed-off-by: Pavol Loffay <[email protected]> * Add tracestate factory method to span builder Signed-off-by: Pavol Loffay <[email protected]> * Remove header Signed-off-by: Pavol Loffay <[email protected]> Co-authored-by: Jonah Back <[email protected]>
1 parent f726853 commit 568ab68

File tree

5 files changed

+321
-4
lines changed

5 files changed

+321
-4
lines changed

jaeger-core/src/main/java/io/jaegertracing/Configuration.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import io.jaegertracing.internal.propagation.BinaryCodec;
2323
import io.jaegertracing.internal.propagation.CompositeCodec;
2424
import io.jaegertracing.internal.propagation.TextMapCodec;
25+
import io.jaegertracing.internal.propagation.TraceContextCodec;
2526
import io.jaegertracing.internal.reporters.CompositeReporter;
2627
import io.jaegertracing.internal.reporters.LoggingReporter;
2728
import io.jaegertracing.internal.reporters.RemoteReporter;
@@ -167,7 +168,12 @@ public enum Propagation {
167168
/**
168169
* The Zipkin B3 trace context propagation format.
169170
*/
170-
B3
171+
B3,
172+
173+
/**
174+
* The W3C TraceContext propagation format.
175+
*/
176+
W3C
171177
}
172178

173179
/**
@@ -467,6 +473,10 @@ public CodecConfiguration withPropagation(Propagation propagation) {
467473
addCodec(codecs, Format.Builtin.HTTP_HEADERS, new B3TextMapCodec.Builder().build());
468474
addCodec(codecs, Format.Builtin.TEXT_MAP, new B3TextMapCodec.Builder().build());
469475
break;
476+
case W3C:
477+
addCodec(codecs, Format.Builtin.HTTP_HEADERS, new TraceContextCodec.Builder().build());
478+
addCodec(codecs, Format.Builtin.TEXT_MAP, new TraceContextCodec.Builder().build());
479+
break;
470480
default:
471481
log.error("Unhandled propagation format '" + propagation + "'");
472482
}

jaeger-core/src/main/java/io/jaegertracing/internal/JaegerSpanContext.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public class JaegerSpanContext implements SpanContext {
3636
private final JaegerObjectFactory objectFactory;
3737
private final String traceIdAsString;
3838
private final String spanIdAsString;
39+
private String traceState;
3940

4041
public JaegerSpanContext(long traceIdHigh, long traceIdLow, long spanId, long parentId, byte flags) {
4142
this(
@@ -125,6 +126,10 @@ public byte getFlags() {
125126
return flags;
126127
}
127128

129+
public String getTraceState() {
130+
return traceState;
131+
}
132+
128133
public boolean isSampled() {
129134
return (flags & flagSampled) == flagSampled;
130135
}
@@ -163,6 +168,13 @@ public JaegerSpanContext withFlags(byte flags) {
163168
return objectFactory.createSpanContext(traceIdHigh, traceIdLow, spanId, parentId, flags, baggage, debugId);
164169
}
165170

171+
public JaegerSpanContext withTraceState(String traceState) {
172+
JaegerSpanContext spanContext = objectFactory
173+
.createSpanContext(traceIdHigh, traceIdLow, spanId, parentId, flags, baggage, debugId);
174+
spanContext.traceState = traceState;
175+
return spanContext;
176+
}
177+
166178
/**
167179
* @return true when the instance of the context contains a non-zero trace and span ID,
168180
* indicating a valid trace. It may return false if the context was created with only
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/*
2+
* Copyright 2020, OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.jaegertracing.internal.propagation;
18+
19+
import io.jaegertracing.internal.JaegerObjectFactory;
20+
import io.jaegertracing.internal.JaegerSpanContext;
21+
import io.jaegertracing.spi.Codec;
22+
import io.opentracing.propagation.TextMap;
23+
import java.util.Collections;
24+
import java.util.Map;
25+
import lombok.extern.slf4j.Slf4j;
26+
27+
/**
28+
* Implementation of the TraceContext propagation protocol. See <a
29+
* href=https://github.com/w3c/distributed-tracing>w3c/distributed-tracing</a>.
30+
*
31+
* This implementation is mostly copied over from OpenTelemetry Java SDK
32+
* https://github.com/open-telemetry/opentelemetry-java/blob/ed98c35c0569a48f66339769913670334d6c8a95/api/src/main/java/io/opentelemetry/trace/propagation/HttpTraceContext.java#L40
33+
*/
34+
@Slf4j
35+
public class TraceContextCodec implements Codec<TextMap> {
36+
37+
static final String TRACE_PARENT = "traceparent";
38+
static final String TRACE_STATE = "tracestate";
39+
40+
private static final String VERSION = "00";
41+
private static final int VERSION_SIZE = 2;
42+
private static final char TRACEPARENT_DELIMITER = '-';
43+
private static final int TRACEPARENT_DELIMITER_SIZE = 1;
44+
private static final int TRACE_ID_HEX_SIZE = 2 * 16;
45+
private static final int SPAN_ID_HEX_SIZE = 2 * 8;
46+
private static final int TRACE_FLAGS_HEX_SIZE = 2;
47+
private static final int TRACE_ID_OFFSET = VERSION_SIZE + TRACEPARENT_DELIMITER_SIZE;
48+
private static final int SPAN_ID_OFFSET =
49+
TRACE_ID_OFFSET + TRACE_ID_HEX_SIZE + TRACEPARENT_DELIMITER_SIZE;
50+
private static final int TRACE_OPTION_OFFSET =
51+
SPAN_ID_OFFSET + SPAN_ID_HEX_SIZE + TRACEPARENT_DELIMITER_SIZE;
52+
private static final int TRACEPARENT_HEADER_SIZE = TRACE_OPTION_OFFSET + TRACE_FLAGS_HEX_SIZE;
53+
private static final byte SAMPLED_FLAG = 1;
54+
55+
private final JaegerObjectFactory objectFactory;
56+
57+
private TraceContextCodec(Builder builder) {
58+
this.objectFactory = builder.objectFactory;
59+
}
60+
61+
private JaegerSpanContext extractContextFromTraceParent(String traceparent, String tracestate) {
62+
// TODO(bdrutu): Do we need to verify that version is hex and that
63+
// for the version the length is the expected one?
64+
boolean isValid =
65+
traceparent != null
66+
&& traceparent.charAt(TRACE_OPTION_OFFSET - 1) == TRACEPARENT_DELIMITER
67+
&& (traceparent.length() == TRACEPARENT_HEADER_SIZE
68+
|| (traceparent.length() > TRACEPARENT_HEADER_SIZE
69+
&& traceparent.charAt(TRACEPARENT_HEADER_SIZE) == TRACEPARENT_DELIMITER))
70+
&& traceparent.charAt(SPAN_ID_OFFSET - 1) == TRACEPARENT_DELIMITER
71+
&& traceparent.charAt(TRACE_OPTION_OFFSET - 1) == TRACEPARENT_DELIMITER;
72+
if (!isValid) {
73+
log.warn("Unparseable traceparent header. Returning null span context.");
74+
return null;
75+
}
76+
77+
Long traceIdHigh = HexCodec.hexToUnsignedLong(traceparent, TRACE_ID_OFFSET, TRACE_ID_OFFSET + 16);
78+
Long traceIdLow = HexCodec.hexToUnsignedLong(traceparent, TRACE_ID_OFFSET + 16, TRACE_ID_OFFSET + 32);
79+
Long spanId = HexCodec.hexToUnsignedLong(traceparent, SPAN_ID_OFFSET, SPAN_ID_OFFSET + 16);
80+
81+
boolean sampled = false;
82+
long traceContextFlags = HexCodec.hexToUnsignedLong(traceparent, TRACE_OPTION_OFFSET, TRACE_OPTION_OFFSET + 2);
83+
if ((traceContextFlags & SAMPLED_FLAG) == SAMPLED_FLAG) {
84+
sampled = true;
85+
}
86+
87+
if (traceIdLow == null || traceIdLow == 0 || spanId == null || spanId == 0) {
88+
log.warn("Unparseable traceparent header. Returning null span context.");
89+
return null;
90+
}
91+
92+
JaegerSpanContext spanContext = this.objectFactory.createSpanContext(
93+
traceIdHigh,
94+
traceIdLow,
95+
spanId,
96+
0,
97+
sampled ? (byte) 1 : (byte) 0,
98+
Collections.<String, String>emptyMap(), null);
99+
return spanContext.withTraceState(tracestate);
100+
}
101+
102+
@Override
103+
public JaegerSpanContext extract(TextMap carrier) {
104+
String traceParent = null;
105+
String traceState = null;
106+
for (Map.Entry<String, String> entry: carrier) {
107+
if (TRACE_PARENT.equals(entry.getKey())) {
108+
traceParent = entry.getValue();
109+
}
110+
if (TRACE_STATE.equals(entry.getKey())) {
111+
traceState = entry.getValue();
112+
}
113+
}
114+
return extractContextFromTraceParent(traceParent, traceState);
115+
}
116+
117+
@Override
118+
public void inject(JaegerSpanContext spanContext, TextMap carrier) {
119+
char[] chars = new char[TRACEPARENT_HEADER_SIZE];
120+
chars[0] = VERSION.charAt(0);
121+
chars[1] = VERSION.charAt(1);
122+
chars[2] = TRACEPARENT_DELIMITER;
123+
HexCodec.writeHexLong(chars, TRACE_ID_OFFSET, spanContext.getTraceIdHigh());
124+
HexCodec.writeHexLong(chars, TRACE_ID_OFFSET + 16, spanContext.getTraceIdLow());
125+
chars[SPAN_ID_OFFSET - 1] = TRACEPARENT_DELIMITER;
126+
HexCodec.writeHexLong(chars, SPAN_ID_OFFSET, spanContext.getSpanId());
127+
chars[TRACE_OPTION_OFFSET - 1] = TRACEPARENT_DELIMITER;
128+
chars[TRACE_OPTION_OFFSET] = '0';
129+
chars[TRACE_OPTION_OFFSET + 1] = spanContext.isSampled() ? '1' : '0';
130+
carrier.put(TRACE_PARENT, new String(chars));
131+
132+
if (spanContext.getTraceState() != null && !spanContext.getTraceState().isEmpty()) {
133+
carrier.put(TRACE_STATE, spanContext.getTraceState());
134+
}
135+
}
136+
137+
public static class Builder {
138+
private JaegerObjectFactory objectFactory = new JaegerObjectFactory();
139+
140+
/**
141+
* Specify JaegerSpanContext factory. Used for creating new span contexts. The default factory
142+
* is an instance of {@link JaegerObjectFactory}.
143+
*/
144+
public Builder withObjectFactory(JaegerObjectFactory objectFactory) {
145+
this.objectFactory = objectFactory;
146+
return this;
147+
}
148+
149+
public TraceContextCodec build() {
150+
return new TraceContextCodec(this);
151+
}
152+
}
153+
}

jaeger-core/src/test/java/io/jaegertracing/ConfigurationTest.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import io.jaegertracing.internal.propagation.BinaryCodec;
3535
import io.jaegertracing.internal.propagation.TestBinaryCarrier;
3636
import io.jaegertracing.internal.propagation.TextMapCodec;
37+
import io.jaegertracing.internal.propagation.TraceContextCodec;
3738
import io.jaegertracing.internal.samplers.ConstSampler;
3839
import io.jaegertracing.internal.samplers.ProbabilisticSampler;
3940
import io.jaegertracing.internal.samplers.RateLimitingSampler;
@@ -480,15 +481,20 @@ public void testB3CodecsWith128BitTraceId() {
480481
@Test
481482
public void testCodecFromString() {
482483
CodecConfiguration codecConfiguration = CodecConfiguration
483-
.fromString(String.format("%s,%s", Propagation.B3.name(), Propagation.JAEGER.name()));
484+
.fromString(String.format("%s,%s,%s",
485+
Propagation.B3.name(),
486+
Propagation.JAEGER.name(),
487+
Propagation.W3C.name()));
484488
assertEquals(2, codecConfiguration.getCodecs().size());
485-
assertEquals(2, codecConfiguration.getCodecs().get(Builtin.HTTP_HEADERS).size());
486-
assertEquals(2, codecConfiguration.getCodecs().get(Builtin.TEXT_MAP).size());
489+
assertEquals(3, codecConfiguration.getCodecs().get(Builtin.HTTP_HEADERS).size());
490+
assertEquals(3, codecConfiguration.getCodecs().get(Builtin.TEXT_MAP).size());
487491
assertEquals(1, codecConfiguration.getBinaryCodecs().get(Builtin.BINARY).size());
488492
assertTrue(codecConfiguration.getCodecs().get(Builtin.HTTP_HEADERS).get(0) instanceof B3TextMapCodec);
489493
assertTrue(codecConfiguration.getCodecs().get(Builtin.HTTP_HEADERS).get(1) instanceof TextMapCodec);
494+
assertTrue(codecConfiguration.getCodecs().get(Builtin.HTTP_HEADERS).get(2) instanceof TraceContextCodec);
490495
assertTrue(codecConfiguration.getCodecs().get(Builtin.TEXT_MAP).get(0) instanceof B3TextMapCodec);
491496
assertTrue(codecConfiguration.getCodecs().get(Builtin.TEXT_MAP).get(1) instanceof TextMapCodec);
497+
assertTrue(codecConfiguration.getCodecs().get(Builtin.TEXT_MAP).get(2) instanceof TraceContextCodec);
492498
assertTrue(codecConfiguration.getBinaryCodecs().get(Builtin.BINARY).get(0) instanceof BinaryCodec);
493499
}
494500

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Copyright (c) 2020, Uber Technologies, Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
15+
package io.jaegertracing.internal.propagation;
16+
17+
import static io.jaegertracing.internal.propagation.TraceContextCodec.TRACE_PARENT;
18+
import static io.jaegertracing.internal.propagation.TraceContextCodec.TRACE_STATE;
19+
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertNotNull;
21+
import static org.junit.Assert.assertNull;
22+
import static org.junit.Assert.assertTrue;
23+
24+
import io.jaegertracing.internal.JaegerSpanContext;
25+
import io.opentracing.propagation.TextMapAdapter;
26+
import java.util.HashMap;
27+
import java.util.Map;
28+
import org.junit.Test;
29+
30+
public class TraceContextCodecTest {
31+
32+
private static final JaegerSpanContext SPAN_CONTEXT =
33+
new JaegerSpanContext(0, 1, 2, 3, (byte)0);
34+
private static final String EXAMPLE_TRACE_PARENT = "00-00000000000000000000000000000001-0000000000000002-00";
35+
36+
private TraceContextCodec traceContextCodec = new TraceContextCodec.Builder().build();
37+
38+
@Test
39+
public void support128BitTraceIdExtraction() {
40+
String hex128Bits = "463ac35c9f6413ad48485a3953bb6124";
41+
String parentSpan = "d1595c6ec91668af";
42+
String tracecontext = String.format("00-%s-%s-01", hex128Bits, parentSpan);
43+
44+
TextMapAdapter textMap = new TextMapAdapter(new HashMap<>());
45+
textMap.put(TRACE_PARENT, tracecontext);
46+
JaegerSpanContext context = traceContextCodec.extract(textMap);
47+
48+
assertNotNull(HexCodec.lowerHexToUnsignedLong(parentSpan));
49+
assertEquals(HexCodec.lowerHexToUnsignedLong(hex128Bits).longValue(), context.getTraceIdLow());
50+
assertEquals(HexCodec.higherHexToUnsignedLong(hex128Bits).longValue(), context.getTraceIdHigh());
51+
assertEquals(HexCodec.lowerHexToUnsignedLong(parentSpan).longValue(), context.getSpanId());
52+
assertTrue(context.isSampled());
53+
}
54+
55+
@Test
56+
public void testInject() {
57+
Map<String, String> carrier = new HashMap<>();
58+
TextMapAdapter textMap = new TextMapAdapter(carrier);
59+
long traceIdLow = 1;
60+
long spanId = 2;
61+
long parentId = 3;
62+
long traceIdHigh = HexCodec.hexToUnsignedLong("c281c27976c85681", 0, 16);
63+
JaegerSpanContext spanContext = new JaegerSpanContext(traceIdHigh, traceIdLow, spanId, parentId, (byte) 0);
64+
65+
traceContextCodec.inject(spanContext, textMap);
66+
67+
String expectedTraceContextHeader = "00-c281c27976c856810000000000000001-0000000000000002-00";
68+
assertEquals(1, carrier.size());
69+
assertNotNull(carrier.get(TRACE_PARENT));
70+
assertEquals(expectedTraceContextHeader, carrier.get(TRACE_PARENT));
71+
}
72+
73+
@Test
74+
public void testInjectWith64bit() {
75+
Map<String, String> carrier = new HashMap<>();
76+
TextMapAdapter textMap = new TextMapAdapter(carrier);
77+
78+
traceContextCodec.inject(SPAN_CONTEXT, textMap);
79+
assertEquals(1, carrier.size());
80+
81+
String traceParent = carrier.get(TRACE_PARENT);
82+
assertEquals(EXAMPLE_TRACE_PARENT, traceParent);
83+
JaegerSpanContext extractedContext = traceContextCodec.extract(textMap);
84+
assertEquals("1:2:0:0", extractedContext.toString());
85+
}
86+
87+
@Test
88+
public void testInvalidTraceId() {
89+
TextMapAdapter textMap = new TextMapAdapter(new HashMap<>());
90+
textMap.put(TRACE_PARENT, "00-00000000000000000000000000000000-0000000000000002-00");
91+
JaegerSpanContext spanContext = traceContextCodec.extract(textMap);
92+
assertNull(spanContext);
93+
}
94+
95+
@Test
96+
public void testNoTraceHeader() {
97+
TextMapAdapter textMap = new TextMapAdapter(new HashMap<>());
98+
JaegerSpanContext spanContext = traceContextCodec.extract(textMap);
99+
assertNull(spanContext);
100+
}
101+
102+
@Test
103+
public void testInvalidParentId() {
104+
TextMapAdapter textMap = new TextMapAdapter(new HashMap<>());
105+
textMap.put(TRACE_PARENT, "00-00000000000000000000000000000001-0000000000000000-00");
106+
JaegerSpanContext spanContext = traceContextCodec.extract(textMap);
107+
assertNull(spanContext);
108+
}
109+
110+
@Test
111+
public void testTraceStatePropagation() {
112+
Map<String, String> extractCarrier = new HashMap<>();
113+
TextMapAdapter textMap = new TextMapAdapter(extractCarrier);
114+
textMap.put(TRACE_PARENT, EXAMPLE_TRACE_PARENT);
115+
textMap.put(TRACE_STATE, "whatever");
116+
JaegerSpanContext spanContext = traceContextCodec.extract(textMap);
117+
118+
Map<String, String> injectCarrier = new HashMap<>();
119+
traceContextCodec.inject(spanContext, new TextMapAdapter(injectCarrier));
120+
assertEquals(extractCarrier, injectCarrier);
121+
}
122+
123+
@Test
124+
public void testEmptyTraceStateNotPropagated() {
125+
Map<String, String> extractCarrier = new HashMap<>();
126+
TextMapAdapter textMap = new TextMapAdapter(extractCarrier);
127+
textMap.put(TRACE_PARENT, EXAMPLE_TRACE_PARENT);
128+
textMap.put(TRACE_STATE, "");
129+
JaegerSpanContext spanContext = traceContextCodec.extract(textMap);
130+
131+
Map<String, String> injectCarrier = new HashMap<>();
132+
traceContextCodec.inject(spanContext, new TextMapAdapter(injectCarrier));
133+
assertEquals(1, injectCarrier.size());
134+
assertEquals(EXAMPLE_TRACE_PARENT, injectCarrier.get(TRACE_PARENT));
135+
}
136+
}

0 commit comments

Comments
 (0)