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

Commit

Permalink
Add support for tracecontext
Browse files Browse the repository at this point in the history
  • Loading branch information
backjo committed Aug 22, 2019
1 parent f1a7ca9 commit b187b82
Show file tree
Hide file tree
Showing 3 changed files with 283 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,19 @@ static String toLowerHex(long high, long low) {
return new String(result);
}

/**
* Returns a 32 character hex string
*/
static String toUpperHex(long high, long low) {
char[] result = new char[32];
int pos = 0;
writeHexLong(result, pos, high);
pos += 16;
writeHexLong(result, pos, low);
return new String(result);
}


/**
* Inspired by {@code okio.Buffer.writeLong}
*/
Expand Down
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);
}
}
}
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);
}
}
}

0 comments on commit b187b82

Please sign in to comment.