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 231
Add W3C TraceContext codec/propagation #694
Merged
pavolloffay
merged 15 commits into
jaegertracing:master
from
pavolloffay:w3c-tracecontext
Mar 11, 2020
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
1a997da
Add support for tracecontext
backjo 17fa41d
Address formatting issues
backjo 6f97731
Add additional tests, remove unused constructor
backjo 7a7465b
Add test for version builder
backjo 34da107
Fix incorrect header name
backjo e6f47a8
Remove bit where we generate left hand side of trace id when we only …
backjo a26b742
Add W3C TraceContext codec
pavolloffay 4fb8d77
Some cleanup
pavolloffay d43b40f
smaller nits
pavolloffay 748b9bd
Fix padding with zeros in test
pavolloffay f34e525
Add tracestate header
pavolloffay 130366e
Just propagate tracestate
pavolloffay ee6f0e0
Cleanup
pavolloffay d81885b
Add tracestate factory method to span builder
pavolloffay 525480b
Remove header
pavolloffay File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should be carrying all Jaeger flags, not just sampled bit: #702 |
||
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)); | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://github.com/w3c/trace-context/blob/master/spec/20-http_header_format.md#trace-flags