
com.wavefront.opentracing.propagation.JaegerWavefrontPropagator Maven / Gradle / Ivy
Show all versions of wavefront-opentracing-sdk-java Show documentation
package com.wavefront.opentracing.propagation;
import com.wavefront.opentracing.WavefrontSpanContext;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URLDecoder;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import javax.annotation.Nullable;
import io.opentracing.propagation.TextMap;
/**
* Bridge for extracting/injecting Jaeger headers to/from a WavefrontSpanContext.
*
* Essentially allows for extracting Jaeger HTTP headers and creating a WavefrontSpanContext or
* injecting Jaeger aware HTTP headers from a WavefrontSpanContext.
*
* @author [email protected]
*
*
* Example usage:
*
*
{@code
* JaegerWavefrontPropagator propogator = new JaegerWavefrontPropagator.Builder()
* .withBaggagePrefix("uberctx-")
* .withTraceIdHeader("uber-trace-id").build();
* tracerBuilder = new WavefrontTracer.Builder(..);
* tracerBuilder.registerPropagator(Format.Builtin.HTTP_HEADERS, propogator);
* tracerBuilder.registerPropagator(Format.Builtin.TEXT_MAP, propogator);
* WavefrontTracer tracer = tracerBuilder.build();
* ...
* }
*
*
*/
public class JaegerWavefrontPropagator implements Propagator {
private static final String BAGGAGE_PREFIX = "baggage-";
private static final String TRACE_ID_KEY = "trace-id";
private static final String PARENT_ID_KEY = "parent-id";
private static final String SAMPLING_DECISION_KEY = "sampling-decision";
private final String traceIdHeader;
private final String baggagePrefix;
private JaegerWavefrontPropagator(Builder builder) {
this.traceIdHeader = builder.traceIdHeader;
this.baggagePrefix = builder.baggagePrefix;
}
@Nullable
@Override
public WavefrontSpanContext extract(TextMap carrier) {
UUID traceId = null;
UUID spanId = null;
String parentId = null;
Boolean samplingDecision = null;
Map baggage = new HashMap<>();
for (Map.Entry entry : carrier) {
String k = entry.getKey().toLowerCase();
if (k.equalsIgnoreCase(traceIdHeader)) {
String[] traceData = contextFromTraceIdHeader(entry.getValue());
if (traceData == null) {
continue;
}
traceId = toUuid(traceData[0]);
spanId = toUuid(traceData[1]);
// setting parentId as current spanId
parentId = spanId.toString();
samplingDecision = traceData[3].equals("1");
} else if (k.startsWith(baggagePrefix.toLowerCase())) {
baggage.put(strippedPrefix(entry.getKey()), entry.getValue());
}
}
if (traceId == null || spanId == null) {
return null;
}
if (parentId.trim().length() > 0 && !"null".equals(parentId)) {
baggage.put(PARENT_ID_KEY, parentId);
}
return new WavefrontSpanContext(traceId, spanId, baggage, samplingDecision);
}
@Override
public void inject(WavefrontSpanContext spanContext, TextMap carrier) {
carrier.put(traceIdHeader, contextToTraceIdHeader(spanContext));
for (Map.Entry entry : spanContext.baggageItems()) {
carrier.put(baggagePrefix + entry.getKey(), entry.getValue());
}
if (spanContext.isSampled()) {
carrier.put(SAMPLING_DECISION_KEY, spanContext.getSamplingDecision().toString());
}
}
/**
* Extracts traceId, spanId, parentId and samplingDecision from the 'uber-trace-id' HTTP header
* value that's in the format traceId:spanId:parentId:samplingDecision.
*
* @param value 'uber-trace-id' header value
* @return extracted string array with ids
*/
@Nullable
private String[] contextFromTraceIdHeader(String value) {
if (value == null || value.equals("")) {
return null;
}
try {
// Jaeger HTTP headers may be URL encoded, so we need to decode before splitting
value = URLDecoder.decode(value, "UTF-8");
} catch (UnsupportedEncodingException e) {
return null;
}
String[] toks = value.split(":");
if (toks.length != 4) {
return null;
}
if (toks[0] == null || toks[0].length() == 0) {
return null;
}
return toks;
}
/**
* Extracts traceId and spanId from a WavefrontSpanContext and constructs a Jaeger client
* compatible header of the form traceId:spanId:parentId:samplingDecision.
*
* @param context a WavefrontSpanContext
* @return formatted header as string
*/
private String contextToTraceIdHeader(WavefrontSpanContext context) {
BigInteger traceId = uuidToBigInteger(context.getTraceId());
BigInteger spanId = uuidToBigInteger(context.getSpanId());
BigInteger parentId;
try {
parentId = uuidToBigInteger(UUID.fromString(context.getBaggageItem(PARENT_ID_KEY)));
} catch (Exception e) {
parentId = BigInteger.ZERO;
}
Boolean samplingDecision = context.getSamplingDecision();
if (samplingDecision == null) {
samplingDecision = false;
}
StringBuilder outCtx = new StringBuilder();
outCtx.append(traceId.toString(16)).append(":").
append(spanId.toString(16)).append(":").
append(parentId.toString(16)).append(":").
append(samplingDecision ? "1" : "0");
return outCtx.toString();
}
private String strippedPrefix(String val) {
return val.substring(baggagePrefix.length());
}
/**
* Parses a UUID and converts to BigInteger.
*
* @param id UUID of the traceId or spanId
* @return BigInteger for UUID.
*/
BigInteger uuidToBigInteger(UUID id) {
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
bb.putLong(id.getMostSignificantBits());
bb.putLong(id.getLeastSignificantBits());
return new BigInteger(1, bb.array());
}
/**
* Parses a full (low + high) traceId, trimming the lower 64 bits.
* Inspired by {@code io.jaegertracing.internal.propagation}
*
* @param hexString a full traceId
* @return the long value of the higher 64 bits for a 128 bit traceId or 0 for 64 bit traceIds
*/
private static long high(String hexString) {
if (hexString.length() <= 16) {
return 0L;
}
int highLength = hexString.length() - 16;
String highString = hexString.substring(0, highLength);
return new BigInteger(highString, 16).longValue();
}
/**
* Constructs UUID for traceId/spanId represented as hexString consisting of (low + high) 64 bits.
*
* UUID is generated with long value of high 64 bits(if any) and long value of low 64 bits and is
* consistent with the Wavefront approach.
*
* @param id hexString form of traceId/spanId
* @return UUID for traceId/spanId as expected by WavefrontSpanContext
*/
UUID toUuid(String id) {
long idLow = new BigInteger(id, 16).longValue();
long idHigh = high(id);
return new UUID(idHigh, idLow);
}
/**
* Gets a new {@link JaegerWavefrontPropagator.Builder} instance.
*
* @return a {@link JaegerWavefrontPropagator.Builder}
*/
public static Builder builder() {
return new Builder();
}
/**
* A builder for {@link JaegerWavefrontPropagator} instances.
*/
public static class Builder {
private String traceIdHeader = TRACE_ID_KEY;
private String baggagePrefix = BAGGAGE_PREFIX;
public Builder withTraceIdHeader(String traceIdHeader) {
this.traceIdHeader = traceIdHeader;
return this;
}
public Builder withBaggagePrefix(String baggagePrefix) {
this.baggagePrefix = baggagePrefix;
return this;
}
/**
* Builds and returns a JaegerWavefrontPropagator instance based on the given configuration.
*
* @return a {@link JaegerWavefrontPropagator}
*/
public JaegerWavefrontPropagator build() {
return new JaegerWavefrontPropagator(this);
}
}
}