This repository has been archived by the owner on Jul 1, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 232
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add W3C TraceContext codec/propagation (#694)
* Add support for tracecontext Signed-off-by: Jonah Back <jonah@jonahback.com> * Address formatting issues Signed-off-by: Jonah Back <jonah@jonahback.com> * Add additional tests, remove unused constructor Signed-off-by: Jonah Back <jonah@jonahback.com> * Add test for version builder Signed-off-by: Jonah Back <jonah@jonahback.com> * Fix incorrect header name Signed-off-by: Jonah Back <jonah@jonahback.com> * Remove bit where we generate left hand side of trace id when we only had 64 bits of data Signed-off-by: Jonah Back <jonah@jonahback.com> * Add W3C TraceContext codec Signed-off-by: Pavol Loffay <ploffay@redhat.com> * Some cleanup Signed-off-by: Pavol Loffay <ploffay@redhat.com> * smaller nits Signed-off-by: Pavol Loffay <ploffay@redhat.com> * Fix padding with zeros in test Signed-off-by: Pavol Loffay <ploffay@redhat.com> * Add tracestate header Signed-off-by: Pavol Loffay <ploffay@redhat.com> * Just propagate tracestate Signed-off-by: Pavol Loffay <ploffay@redhat.com> * Cleanup Signed-off-by: Pavol Loffay <ploffay@redhat.com> * Add tracestate factory method to span builder Signed-off-by: Pavol Loffay <ploffay@redhat.com> * Remove header Signed-off-by: Pavol Loffay <ploffay@redhat.com> Co-authored-by: Jonah Back <jonah@jonahback.com>
- Loading branch information
1 parent
f726853
commit 568ab68
Showing
5 changed files
with
321 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
153 changes: 153 additions & 0 deletions
153
jaeger-core/src/main/java/io/jaegertracing/internal/propagation/TraceContextCodec.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
/* | ||
* Copyright 2020, OpenTelemetry Authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package io.jaegertracing.internal.propagation; | ||
|
||
import io.jaegertracing.internal.JaegerObjectFactory; | ||
import io.jaegertracing.internal.JaegerSpanContext; | ||
import io.jaegertracing.spi.Codec; | ||
import io.opentracing.propagation.TextMap; | ||
import java.util.Collections; | ||
import java.util.Map; | ||
import lombok.extern.slf4j.Slf4j; | ||
|
||
/** | ||
* Implementation of the TraceContext propagation protocol. See <a | ||
* href=https://github.com/w3c/distributed-tracing>w3c/distributed-tracing</a>. | ||
* | ||
* This implementation is mostly copied over from OpenTelemetry Java SDK | ||
* https://github.com/open-telemetry/opentelemetry-java/blob/ed98c35c0569a48f66339769913670334d6c8a95/api/src/main/java/io/opentelemetry/trace/propagation/HttpTraceContext.java#L40 | ||
*/ | ||
@Slf4j | ||
public class TraceContextCodec implements Codec<TextMap> { | ||
|
||
static final String TRACE_PARENT = "traceparent"; | ||
static final String TRACE_STATE = "tracestate"; | ||
|
||
private static final String VERSION = "00"; | ||
private static final int VERSION_SIZE = 2; | ||
private static final char TRACEPARENT_DELIMITER = '-'; | ||
private static final int TRACEPARENT_DELIMITER_SIZE = 1; | ||
private static final int TRACE_ID_HEX_SIZE = 2 * 16; | ||
private static final int SPAN_ID_HEX_SIZE = 2 * 8; | ||
private static final int TRACE_FLAGS_HEX_SIZE = 2; | ||
private static final int TRACE_ID_OFFSET = VERSION_SIZE + TRACEPARENT_DELIMITER_SIZE; | ||
private static final int SPAN_ID_OFFSET = | ||
TRACE_ID_OFFSET + TRACE_ID_HEX_SIZE + TRACEPARENT_DELIMITER_SIZE; | ||
private static final int TRACE_OPTION_OFFSET = | ||
SPAN_ID_OFFSET + SPAN_ID_HEX_SIZE + TRACEPARENT_DELIMITER_SIZE; | ||
private static final int TRACEPARENT_HEADER_SIZE = TRACE_OPTION_OFFSET + TRACE_FLAGS_HEX_SIZE; | ||
private static final byte SAMPLED_FLAG = 1; | ||
|
||
private final JaegerObjectFactory objectFactory; | ||
|
||
private TraceContextCodec(Builder builder) { | ||
this.objectFactory = builder.objectFactory; | ||
} | ||
|
||
private JaegerSpanContext extractContextFromTraceParent(String traceparent, String tracestate) { | ||
// TODO(bdrutu): Do we need to verify that version is hex and that | ||
// for the version the length is the expected one? | ||
boolean isValid = | ||
traceparent != null | ||
&& traceparent.charAt(TRACE_OPTION_OFFSET - 1) == TRACEPARENT_DELIMITER | ||
&& (traceparent.length() == TRACEPARENT_HEADER_SIZE | ||
|| (traceparent.length() > TRACEPARENT_HEADER_SIZE | ||
&& traceparent.charAt(TRACEPARENT_HEADER_SIZE) == TRACEPARENT_DELIMITER)) | ||
&& traceparent.charAt(SPAN_ID_OFFSET - 1) == TRACEPARENT_DELIMITER | ||
&& traceparent.charAt(TRACE_OPTION_OFFSET - 1) == TRACEPARENT_DELIMITER; | ||
if (!isValid) { | ||
log.warn("Unparseable traceparent header. Returning null span context."); | ||
return null; | ||
} | ||
|
||
Long traceIdHigh = HexCodec.hexToUnsignedLong(traceparent, TRACE_ID_OFFSET, TRACE_ID_OFFSET + 16); | ||
Long traceIdLow = HexCodec.hexToUnsignedLong(traceparent, TRACE_ID_OFFSET + 16, TRACE_ID_OFFSET + 32); | ||
Long spanId = HexCodec.hexToUnsignedLong(traceparent, SPAN_ID_OFFSET, SPAN_ID_OFFSET + 16); | ||
|
||
boolean sampled = false; | ||
long traceContextFlags = HexCodec.hexToUnsignedLong(traceparent, TRACE_OPTION_OFFSET, TRACE_OPTION_OFFSET + 2); | ||
if ((traceContextFlags & SAMPLED_FLAG) == SAMPLED_FLAG) { | ||
sampled = true; | ||
} | ||
|
||
if (traceIdLow == null || traceIdLow == 0 || spanId == null || spanId == 0) { | ||
log.warn("Unparseable traceparent header. Returning null span context."); | ||
return null; | ||
} | ||
|
||
JaegerSpanContext spanContext = this.objectFactory.createSpanContext( | ||
traceIdHigh, | ||
traceIdLow, | ||
spanId, | ||
0, | ||
sampled ? (byte) 1 : (byte) 0, | ||
Collections.<String, String>emptyMap(), null); | ||
return spanContext.withTraceState(tracestate); | ||
} | ||
|
||
@Override | ||
public JaegerSpanContext extract(TextMap carrier) { | ||
String traceParent = null; | ||
String traceState = null; | ||
for (Map.Entry<String, String> entry: carrier) { | ||
if (TRACE_PARENT.equals(entry.getKey())) { | ||
traceParent = entry.getValue(); | ||
} | ||
if (TRACE_STATE.equals(entry.getKey())) { | ||
traceState = entry.getValue(); | ||
} | ||
} | ||
return extractContextFromTraceParent(traceParent, traceState); | ||
} | ||
|
||
@Override | ||
public void inject(JaegerSpanContext spanContext, TextMap carrier) { | ||
char[] chars = new char[TRACEPARENT_HEADER_SIZE]; | ||
chars[0] = VERSION.charAt(0); | ||
chars[1] = VERSION.charAt(1); | ||
chars[2] = TRACEPARENT_DELIMITER; | ||
HexCodec.writeHexLong(chars, TRACE_ID_OFFSET, spanContext.getTraceIdHigh()); | ||
HexCodec.writeHexLong(chars, TRACE_ID_OFFSET + 16, spanContext.getTraceIdLow()); | ||
chars[SPAN_ID_OFFSET - 1] = TRACEPARENT_DELIMITER; | ||
HexCodec.writeHexLong(chars, SPAN_ID_OFFSET, spanContext.getSpanId()); | ||
chars[TRACE_OPTION_OFFSET - 1] = TRACEPARENT_DELIMITER; | ||
chars[TRACE_OPTION_OFFSET] = '0'; | ||
chars[TRACE_OPTION_OFFSET + 1] = spanContext.isSampled() ? '1' : '0'; | ||
carrier.put(TRACE_PARENT, new String(chars)); | ||
|
||
if (spanContext.getTraceState() != null && !spanContext.getTraceState().isEmpty()) { | ||
carrier.put(TRACE_STATE, spanContext.getTraceState()); | ||
} | ||
} | ||
|
||
public static class Builder { | ||
private JaegerObjectFactory objectFactory = new JaegerObjectFactory(); | ||
|
||
/** | ||
* Specify JaegerSpanContext factory. Used for creating new span contexts. The default factory | ||
* is an instance of {@link JaegerObjectFactory}. | ||
*/ | ||
public Builder withObjectFactory(JaegerObjectFactory objectFactory) { | ||
this.objectFactory = objectFactory; | ||
return this; | ||
} | ||
|
||
public TraceContextCodec build() { | ||
return new TraceContextCodec(this); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
136 changes: 136 additions & 0 deletions
136
jaeger-core/src/test/java/io/jaegertracing/internal/propagation/TraceContextCodecTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
/* | ||
* Copyright (c) 2020, Uber Technologies, Inc | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except | ||
* in compliance with the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software distributed under the License | ||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express | ||
* or implied. See the License for the specific language governing permissions and limitations under | ||
* the License. | ||
*/ | ||
|
||
package io.jaegertracing.internal.propagation; | ||
|
||
import static io.jaegertracing.internal.propagation.TraceContextCodec.TRACE_PARENT; | ||
import static io.jaegertracing.internal.propagation.TraceContextCodec.TRACE_STATE; | ||
import static org.junit.Assert.assertEquals; | ||
import static org.junit.Assert.assertNotNull; | ||
import static org.junit.Assert.assertNull; | ||
import static org.junit.Assert.assertTrue; | ||
|
||
import io.jaegertracing.internal.JaegerSpanContext; | ||
import io.opentracing.propagation.TextMapAdapter; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import org.junit.Test; | ||
|
||
public class TraceContextCodecTest { | ||
|
||
private static final JaegerSpanContext SPAN_CONTEXT = | ||
new JaegerSpanContext(0, 1, 2, 3, (byte)0); | ||
private static final String EXAMPLE_TRACE_PARENT = "00-00000000000000000000000000000001-0000000000000002-00"; | ||
|
||
private TraceContextCodec traceContextCodec = new TraceContextCodec.Builder().build(); | ||
|
||
@Test | ||
public void support128BitTraceIdExtraction() { | ||
String hex128Bits = "463ac35c9f6413ad48485a3953bb6124"; | ||
String parentSpan = "d1595c6ec91668af"; | ||
String tracecontext = String.format("00-%s-%s-01", hex128Bits, parentSpan); | ||
|
||
TextMapAdapter textMap = new TextMapAdapter(new HashMap<>()); | ||
textMap.put(TRACE_PARENT, tracecontext); | ||
JaegerSpanContext context = traceContextCodec.extract(textMap); | ||
|
||
assertNotNull(HexCodec.lowerHexToUnsignedLong(parentSpan)); | ||
assertEquals(HexCodec.lowerHexToUnsignedLong(hex128Bits).longValue(), context.getTraceIdLow()); | ||
assertEquals(HexCodec.higherHexToUnsignedLong(hex128Bits).longValue(), context.getTraceIdHigh()); | ||
assertEquals(HexCodec.lowerHexToUnsignedLong(parentSpan).longValue(), context.getSpanId()); | ||
assertTrue(context.isSampled()); | ||
} | ||
|
||
@Test | ||
public void testInject() { | ||
Map<String, String> carrier = new HashMap<>(); | ||
TextMapAdapter textMap = new TextMapAdapter(carrier); | ||
long traceIdLow = 1; | ||
long spanId = 2; | ||
long parentId = 3; | ||
long traceIdHigh = HexCodec.hexToUnsignedLong("c281c27976c85681", 0, 16); | ||
JaegerSpanContext spanContext = new JaegerSpanContext(traceIdHigh, traceIdLow, spanId, parentId, (byte) 0); | ||
|
||
traceContextCodec.inject(spanContext, textMap); | ||
|
||
String expectedTraceContextHeader = "00-c281c27976c856810000000000000001-0000000000000002-00"; | ||
assertEquals(1, carrier.size()); | ||
assertNotNull(carrier.get(TRACE_PARENT)); | ||
assertEquals(expectedTraceContextHeader, carrier.get(TRACE_PARENT)); | ||
} | ||
|
||
@Test | ||
public void testInjectWith64bit() { | ||
Map<String, String> carrier = new HashMap<>(); | ||
TextMapAdapter textMap = new TextMapAdapter(carrier); | ||
|
||
traceContextCodec.inject(SPAN_CONTEXT, textMap); | ||
assertEquals(1, carrier.size()); | ||
|
||
String traceParent = carrier.get(TRACE_PARENT); | ||
assertEquals(EXAMPLE_TRACE_PARENT, traceParent); | ||
JaegerSpanContext extractedContext = traceContextCodec.extract(textMap); | ||
assertEquals("1:2:0:0", extractedContext.toString()); | ||
} | ||
|
||
@Test | ||
public void testInvalidTraceId() { | ||
TextMapAdapter textMap = new TextMapAdapter(new HashMap<>()); | ||
textMap.put(TRACE_PARENT, "00-00000000000000000000000000000000-0000000000000002-00"); | ||
JaegerSpanContext spanContext = traceContextCodec.extract(textMap); | ||
assertNull(spanContext); | ||
} | ||
|
||
@Test | ||
public void testNoTraceHeader() { | ||
TextMapAdapter textMap = new TextMapAdapter(new HashMap<>()); | ||
JaegerSpanContext spanContext = traceContextCodec.extract(textMap); | ||
assertNull(spanContext); | ||
} | ||
|
||
@Test | ||
public void testInvalidParentId() { | ||
TextMapAdapter textMap = new TextMapAdapter(new HashMap<>()); | ||
textMap.put(TRACE_PARENT, "00-00000000000000000000000000000001-0000000000000000-00"); | ||
JaegerSpanContext spanContext = traceContextCodec.extract(textMap); | ||
assertNull(spanContext); | ||
} | ||
|
||
@Test | ||
public void testTraceStatePropagation() { | ||
Map<String, String> extractCarrier = new HashMap<>(); | ||
TextMapAdapter textMap = new TextMapAdapter(extractCarrier); | ||
textMap.put(TRACE_PARENT, EXAMPLE_TRACE_PARENT); | ||
textMap.put(TRACE_STATE, "whatever"); | ||
JaegerSpanContext spanContext = traceContextCodec.extract(textMap); | ||
|
||
Map<String, String> injectCarrier = new HashMap<>(); | ||
traceContextCodec.inject(spanContext, new TextMapAdapter(injectCarrier)); | ||
assertEquals(extractCarrier, injectCarrier); | ||
} | ||
|
||
@Test | ||
public void testEmptyTraceStateNotPropagated() { | ||
Map<String, String> extractCarrier = new HashMap<>(); | ||
TextMapAdapter textMap = new TextMapAdapter(extractCarrier); | ||
textMap.put(TRACE_PARENT, EXAMPLE_TRACE_PARENT); | ||
textMap.put(TRACE_STATE, ""); | ||
JaegerSpanContext spanContext = traceContextCodec.extract(textMap); | ||
|
||
Map<String, String> injectCarrier = new HashMap<>(); | ||
traceContextCodec.inject(spanContext, new TextMapAdapter(injectCarrier)); | ||
assertEquals(1, injectCarrier.size()); | ||
assertEquals(EXAMPLE_TRACE_PARENT, injectCarrier.get(TRACE_PARENT)); | ||
} | ||
} |