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.
- Loading branch information
Showing
3 changed files
with
283 additions
and
0 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
160 changes: 160 additions & 0 deletions
160
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,160 @@ | ||
/* | ||
* Copyright (c) 2019, The Jaeger Authors | ||
* Copyright (c) 2017, 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 io.jaegertracing.internal.JaegerObjectFactory; | ||
import io.jaegertracing.internal.JaegerSpanContext; | ||
import io.jaegertracing.internal.utils.Utils; | ||
import io.jaegertracing.spi.Codec; | ||
import io.opentracing.propagation.TextMap; | ||
import java.util.Collections; | ||
import java.util.Map; | ||
|
||
|
||
/** | ||
* This format follows the Trace Context specification https://www.w3.org/TR/trace-context/ | ||
* | ||
* <p> | ||
* Example usage: | ||
* | ||
* <pre>{@code | ||
* traceContextCodec = new TraceContextCodec(); | ||
* tracer = new JaegerTracer.Builder(serviceName, reporter, sampler) | ||
* .registerInjector(Format.Builtin.HTTP_HEADERS, traceContextCodec) | ||
* .registerExtractor(Format.Builtin.HTTP_HEADERS, traceContextCodec) | ||
* ... | ||
* }</pre> | ||
* | ||
* <p> | ||
*/ | ||
public class TraceContextCodec implements Codec<TextMap> { | ||
protected static final String TRACE_CONTEXT_NAME = "tracecontext"; | ||
|
||
private static final int VERSION_SIZE = 2; | ||
private static final int TRACEPARENT_DELIMITER_SIZE = 1; | ||
private static final int TRACE_ID_HEX_SIZE = 32; | ||
private static final int SPAN_ID_HEX_SIZE = 16; | ||
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 SAMPLED_FLAG = 1; | ||
|
||
private static final int JAEGER_SAMPLED_FLAG = 1; | ||
|
||
|
||
private final JaegerObjectFactory objectFactory; | ||
private final Long version; | ||
|
||
/** | ||
* @deprecated use {@link Builder} instead | ||
*/ | ||
@Deprecated | ||
public TraceContextCodec() { | ||
this(new Builder()); | ||
} | ||
|
||
private TraceContextCodec(Builder builder) { | ||
this.objectFactory = builder.objectFactory; | ||
this.version = builder.version; | ||
} | ||
|
||
@Override | ||
public void inject(JaegerSpanContext spanContext, TextMap carrier) { | ||
long traceIdHigh = spanContext.getTraceIdHigh(); | ||
|
||
//From the specification: | ||
//When a system operates with a trace-id that is shorter than 16 bytes, it SHOULD fill-in the extra bytes with random values rather than zeroes. | ||
// Let's say the system works with an 8-byte trace-id like 3ce929d0e0e4736. | ||
// Instead of setting trace-id value to 0000000000000003ce929d0e0e4736 it SHOULD generate a value like 4bf92f3577b34da6a3ce929d0e0e4736 | ||
// where 4bf92f3577b34da6a is a random value or a function of time and host value. | ||
if(traceIdHigh == 0L) { | ||
traceIdHigh = Utils.uniqueId(); | ||
} | ||
carrier.put(TRACE_CONTEXT_NAME, String.format("%02d-%s-%s-%s", | ||
version, | ||
HexCodec.toUpperHex(traceIdHigh, spanContext.getTraceIdLow()), | ||
HexCodec.toLowerHex(spanContext.getSpanId()), | ||
spanContext.isSampled() ? "01" : "00" | ||
)); | ||
} | ||
|
||
private boolean isValidId(Long id) { | ||
return id != null && id != 0; | ||
} | ||
|
||
@Override | ||
public JaegerSpanContext extract(TextMap carrier) { | ||
Long traceIdLow = 0L; | ||
Long traceIdHigh = 0L; | ||
Long spanId = Utils.uniqueId(); | ||
Long parentId = 0L; // Conventionally, parent id == 0 means the root span | ||
byte flags = 0; | ||
for (Map.Entry<String, String> entry : carrier) { | ||
if (entry.getKey().equalsIgnoreCase(TRACE_CONTEXT_NAME)) { | ||
String value = entry.getValue(); | ||
traceIdHigh = HexCodec.hexToUnsignedLong(value, TRACE_ID_OFFSET, TRACE_ID_OFFSET + 16); | ||
traceIdLow = HexCodec.hexToUnsignedLong(value, TRACE_ID_OFFSET+16, TRACE_ID_OFFSET + 32); | ||
parentId = HexCodec.hexToUnsignedLong(value, SPAN_ID_OFFSET, SPAN_ID_OFFSET+16); | ||
long traceContextFlags = HexCodec.hexToUnsignedLong(value, TRACE_OPTION_OFFSET, TRACE_OPTION_OFFSET + 2); | ||
|
||
if ((traceContextFlags & SAMPLED_FLAG) == SAMPLED_FLAG) { | ||
flags |= JAEGER_SAMPLED_FLAG; | ||
} | ||
} | ||
} | ||
|
||
//Follow Trace Context specification | ||
if (isValidId(traceIdLow) && // If the trace-id value is invalid (for example if it contains non-allowed characters or all zeros), vendors MUST ignore the traceparent. | ||
isValidId(parentId)) { // Vendors MUST ignore the traceparent when the parent-id is invalid (for example, if it contains non-lowercase hex characters). | ||
JaegerSpanContext spanContext = objectFactory.createSpanContext( | ||
traceIdHigh, | ||
traceIdLow, | ||
spanId, | ||
parentId, | ||
flags, | ||
Collections.<String, String>emptyMap(), | ||
null // debugId | ||
); | ||
return spanContext; | ||
} | ||
return null; | ||
} | ||
|
||
public static class Builder { | ||
private JaegerObjectFactory objectFactory = new JaegerObjectFactory(); | ||
private Long version = 0L; | ||
|
||
/** | ||
* 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 Builder withVersion(Long version) { | ||
this.version = version; | ||
return this; | ||
} | ||
|
||
public TraceContextCodec build() { | ||
return new TraceContextCodec(this); | ||
} | ||
} | ||
} |
110 changes: 110 additions & 0 deletions
110
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,110 @@ | ||
/* | ||
* Copyright (c) 2016, 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 io.jaegertracing.internal.JaegerSpanContext; | ||
import io.jaegertracing.internal.utils.Utils; | ||
import io.opentracing.propagation.TextMap; | ||
import java.util.Iterator; | ||
import java.util.LinkedHashMap; | ||
import java.util.Map; | ||
import org.junit.Test; | ||
|
||
import static org.junit.Assert.*; | ||
|
||
/** | ||
* | ||
*/ | ||
public class TraceContextCodecTest { | ||
|
||
private TraceContextCodec traceContextCodec = new TraceContextCodec.Builder().build(); | ||
|
||
@Test | ||
public void support128BitTraceIdExtraction() { | ||
String hex128Bits = "463ac35c9f6413ad48485a3953bb6124"; | ||
String parentSpan = "d1595c6ec91668af"; | ||
|
||
String tracecontext = "00-463ac35c9f6413ad48485a3953bb6124-d1595c6ec91668af-01"; | ||
|
||
DelegatingTextMap textMap = new DelegatingTextMap(); | ||
textMap.put(TraceContextCodec.TRACE_CONTEXT_NAME, 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.getParentId()); | ||
assertTrue(context.isSampled()); | ||
} | ||
|
||
@Test | ||
public void testInject() { | ||
TraceContextCodec traceContextCodec = new TraceContextCodec.Builder() | ||
.build(); | ||
|
||
DelegatingTextMap entries = new DelegatingTextMap(); | ||
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, entries); | ||
assertEquals(1, entries.delegate.size()); | ||
assertNotNull(entries.delegate.get(TraceContextCodec.TRACE_CONTEXT_NAME)); | ||
assertEquals("00-c281c27976c856810000000000000001-0000000000000002-00",entries.delegate.get(TraceContextCodec.TRACE_CONTEXT_NAME)); | ||
} | ||
|
||
@Test | ||
public void testInvalidTraceId() { | ||
TraceContextCodec traceContextCodec = new TraceContextCodec.Builder() | ||
.build(); | ||
|
||
DelegatingTextMap textMap = new DelegatingTextMap(); | ||
textMap.put(TraceContextCodec.TRACE_CONTEXT_NAME, "00-00000000000000000000000000000000-0000000000000002-00"); | ||
JaegerSpanContext spanContext = traceContextCodec.extract(textMap); | ||
assertNull(spanContext); | ||
} | ||
|
||
@Test | ||
public void testInvalidParentId() { | ||
TraceContextCodec traceContextCodec = new TraceContextCodec.Builder() | ||
.build(); | ||
|
||
DelegatingTextMap textMap = new DelegatingTextMap(); | ||
textMap.put(TraceContextCodec.TRACE_CONTEXT_NAME, "00-00000000000000000000000000000001-0000000000000000-00"); | ||
JaegerSpanContext spanContext = traceContextCodec.extract(textMap); | ||
assertNull(spanContext); | ||
} | ||
|
||
static class DelegatingTextMap implements TextMap { | ||
final Map<String, String> delegate = new LinkedHashMap<>(); | ||
|
||
@Override | ||
public Iterator<Map.Entry<String, String>> iterator() { | ||
return delegate.entrySet().iterator(); | ||
} | ||
|
||
@Override | ||
public void put(String key, String value) { | ||
delegate.put(key, value); | ||
} | ||
|
||
public String get(String key) { | ||
return delegate.get(key); | ||
} | ||
} | ||
} |