Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

re-uses w3c traceparent parser from brave #532

Merged
merged 3 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ mockito = "5.8.0"
wiremock = "3.0.1"
testcontainers = "1.19.3"
braveBom = "6.0.0"
bravePropagationW3c = "0.2.0"
# TODO: Update to 3.1.1 after https://github.com/open-telemetry/opentelemetry-java/pull/6129
# Then, add an optional bean zipkin2.reporter.BytesEncoder<brave.handler.MutableSpan>
# When present, use the overload of AsyncZipkinSpanHandler.build, allowing custom formats like
Expand All @@ -41,6 +42,7 @@ aspectjweaver = { module = "org.aspectj:aspectjweaver", version.ref = "aspectjwe
assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" }
awaitility = { module = "org.awaitility:awaitility", version.ref = "awaitility" }
braveBom = { module = "io.zipkin.brave:brave-bom", version.ref = "braveBom" }
bravePropagationW3c = { module = "io.zipkin.contrib.brave-propagation-w3c:brave-propagation-tracecontext", version.ref = "bravePropagationW3c" }
jakartaWeb = { module = "jakarta.platform:jakarta.jakartaee-web-api", version.ref = "jakartaWeb" }
javaFormatForPlugins = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "javaFormatForPlugins" }
javaxServlet = { module = "javax.servlet:javax.servlet-api", version.ref = "javaxServlet" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dependencies {
api 'io.zipkin.brave:brave-context-slf4j'
api 'io.zipkin.brave:brave-instrumentation-http'
api libs.zipkinAws
api libs.bravePropagationW3c

testImplementation project(':micrometer-tracing-test')
testImplementation 'io.micrometer:micrometer-core'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,18 @@
import brave.propagation.Propagation;
import brave.propagation.TraceContext;
import brave.propagation.TraceContextOrSamplingFlags;
import brave.propagation.tracecontext.TraceparentFormat;
import io.micrometer.common.lang.Nullable;
import io.micrometer.common.util.StringUtils;
import io.micrometer.common.util.internal.logging.InternalLogger;
import io.micrometer.common.util.internal.logging.InternalLoggerFactory;
import io.micrometer.tracing.Baggage;
import io.micrometer.tracing.BaggageManager;
import io.micrometer.tracing.internal.EncodingUtils;

import java.util.*;

import static brave.propagation.tracecontext.TraceContextPropagation.TRACEPARENT;
import static brave.propagation.tracecontext.TraceContextPropagation.TRACESTATE;
import static java.util.Collections.singletonList;

/**
Expand All @@ -44,55 +46,9 @@
@SuppressWarnings({ "unchecked", "deprecation" })
public class W3CPropagation extends Propagation.Factory implements Propagation<String> {

static final String TRACE_PARENT = "traceparent";

static final String TRACE_STATE = "tracestate";

private static final InternalLogger logger = InternalLoggerFactory.getInstance(W3CPropagation.class.getName());

private static final List<String> FIELDS = Collections.unmodifiableList(Arrays.asList(TRACE_PARENT, TRACE_STATE));

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 LONG_BYTES = Long.SIZE / Byte.SIZE;

private static final int BYTE_BASE16 = 2;

private static final int LONG_BASE16 = BYTE_BASE16 * LONG_BYTES;

private static final int TRACE_ID_HEX_SIZE = 2 * LONG_BASE16;

private static final int SPAN_ID_SIZE = 8;

private static final int SPAN_ID_HEX_SIZE = 2 * SPAN_ID_SIZE;

private static final int FLAGS_SIZE = 1;

private static final int TRACE_OPTION_HEX_SIZE = 2 * FLAGS_SIZE;

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_OPTION_HEX_SIZE;

private static final String INVALID_TRACE_ID = "00000000000000000000000000000000";

private static final String INVALID_SPAN_ID = "0000000000000000";

// private static final char TRACESTATE_ENTRY_DELIMITER = ',';

private static final Set<String> VALID_VERSIONS;

private static final String VERSION_00 = "00";
private static final List<String> FIELDS = Collections.unmodifiableList(Arrays.asList(TRACEPARENT, TRACESTATE));

@Nullable
private final W3CBaggagePropagator baggagePropagator;
Expand All @@ -118,61 +74,6 @@ public W3CPropagation() {
this.braveBaggageManager = null;
}

private static boolean isTraceIdValid(CharSequence traceId) {
return (traceId.length() == TRACE_ID_HEX_SIZE) && !INVALID_TRACE_ID.contentEquals(traceId)
&& EncodingUtils.isValidBase16String(traceId);
}

private static boolean isSpanIdValid(String spanId) {
return (spanId.length() == SPAN_ID_HEX_SIZE) && !INVALID_SPAN_ID.equals(spanId)
&& EncodingUtils.isValidBase16String(spanId);
}

private static TraceContext extractContextFromTraceParent(String traceparent) {
// 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.length() == TRACEPARENT_HEADER_SIZE
|| (traceparent.length() > TRACEPARENT_HEADER_SIZE
&& traceparent.charAt(TRACEPARENT_HEADER_SIZE) == TRACEPARENT_DELIMITER))
&& traceparent.charAt(TRACE_ID_OFFSET - 1) == TRACEPARENT_DELIMITER
&& traceparent.charAt(SPAN_ID_OFFSET - 1) == TRACEPARENT_DELIMITER
&& traceparent.charAt(TRACE_OPTION_OFFSET - 1) == TRACEPARENT_DELIMITER;
if (!isValid) {
logger.info("Unparseable traceparent header. Returning INVALID span context.");
return null;
}

try {
String version = traceparent.substring(0, 2);
if (!VALID_VERSIONS.contains(version)) {
return null;
}
if (version.equals(VERSION_00) && traceparent.length() > TRACEPARENT_HEADER_SIZE) {
return null;
}

String traceId = traceparent.substring(TRACE_ID_OFFSET, TRACE_ID_OFFSET + TRACE_ID_HEX_SIZE);
String spanId = traceparent.substring(SPAN_ID_OFFSET, SPAN_ID_OFFSET + SPAN_ID_HEX_SIZE);
if (isTraceIdValid(traceId) && isSpanIdValid(spanId)) {
String traceIdHigh = traceId.substring(0, traceId.length() / 2);
String traceIdLow = traceId.substring(traceId.length() / 2);
byte isSampled = TraceFlags.byteFromHex(traceparent, TRACE_OPTION_OFFSET);
return TraceContext.newBuilder()
.shared(true)
.traceIdHigh(EncodingUtils.longFromBase16String(traceIdHigh))
.traceId(EncodingUtils.longFromBase16String(traceIdLow))
.spanId(EncodingUtils.longFromBase16String(spanId))
.sampled(isSampled == TraceFlags.IS_SAMPLED)
.build();
}
return null;
}
catch (IllegalArgumentException e) {
logger.info("Unparseable traceparent header. Returning INVALID span context.");
return null;
}
}

@Override
public Propagation<String> get() {
return this;
Expand All @@ -188,22 +89,7 @@ public <R> TraceContext.Injector<R> injector(Setter<R, String> setter) {
return (context, carrier) -> {
Objects.requireNonNull(context, "context");
Objects.requireNonNull(setter, "setter");
char[] chars = TemporaryBuffers.chars(TRACEPARENT_HEADER_SIZE);
chars[0] = VERSION.charAt(0);
chars[1] = VERSION.charAt(1);
chars[2] = TRACEPARENT_DELIMITER;
String traceId = padLeftWithZeros(context.traceIdString(), TRACE_ID_HEX_SIZE);
for (int i = 0; i < traceId.length(); i++) {
chars[TRACE_ID_OFFSET + i] = traceId.charAt(i);
}
chars[SPAN_ID_OFFSET - 1] = TRACEPARENT_DELIMITER;
String spanId = context.spanIdString();
for (int i = 0; i < spanId.length(); i++) {
chars[SPAN_ID_OFFSET + i] = spanId.charAt(i);
}
chars[TRACE_OPTION_OFFSET - 1] = TRACEPARENT_DELIMITER;
copyTraceFlagsHexTo(chars, TRACE_OPTION_OFFSET, context);
setter.put(carrier, TRACE_PARENT, new String(chars, 0, TRACEPARENT_HEADER_SIZE));
setter.put(carrier, TRACEPARENT, TraceparentFormat.get().write(context));
addTraceState(setter, context, carrier);
if (this.baggagePropagator != null) {
this.baggagePropagator.injector(setter).inject(context, carrier);
Expand All @@ -213,49 +99,46 @@ public <R> TraceContext.Injector<R> injector(Setter<R, String> setter) {

private <R> void addTraceState(Setter<R, String> setter, TraceContext context, R carrier) {
if (carrier != null && this.braveBaggageManager != null) {
Baggage baggage = this.braveBaggageManager.getBaggage(BraveTraceContext.fromBrave(context), TRACE_STATE);
Baggage baggage = this.braveBaggageManager.getBaggage(BraveTraceContext.fromBrave(context), TRACESTATE);
if (baggage == null) {
return;
}
String traceState = baggage.get(BraveTraceContext.fromBrave(context));
if (StringUtils.isNotBlank(traceState)) {
setter.put(carrier, TRACE_STATE, traceState);
}
}
}

private String padLeftWithZeros(String string, int length) {
if (string.length() >= length) {
return string;
}
else {
StringBuilder sb = new StringBuilder(length);
for (int i = string.length(); i < length; i++) {
sb.append('0');
setter.put(carrier, TRACESTATE, traceState);
}

return sb.append(string).toString();
}
}

void copyTraceFlagsHexTo(char[] dest, int destOffset, TraceContext context) {
dest[destOffset] = '0';
dest[destOffset + 1] = Boolean.TRUE.equals(context.sampled()) ? '1' : '0';
}

@Override
public <R> TraceContext.Extractor<R> extractor(Getter<R, String> getter) {
Objects.requireNonNull(getter, "getter");
return carrier -> {
String traceParent = getter.get(carrier, TRACE_PARENT);
String traceParent = getter.get(carrier, TRACEPARENT);
if (traceParent == null) {
return withBaggage(TraceContextOrSamplingFlags.EMPTY, carrier, getter);
}
TraceContext contextFromParentHeader = extractContextFromTraceParent(traceParent);
TraceContext contextFromParentHeader = TraceparentFormat.get().parse(traceParent);
if (contextFromParentHeader == null) {
return withBaggage(TraceContextOrSamplingFlags.EMPTY, carrier, getter);
}
String traceStateHeader = getter.get(carrier, TRACE_STATE);
else {
// TODO: the previous implementation always set the shared flag to true.
// This is incorrect because it could be a messaging span which is never
// shared. Setting it to shared would cause message consumers to all join
// the same producer span and make a mess in UI and analysis. Also, some
// RPC spans are intentionally not shared. Finally, b3's "don't sample"
// "b3=0" is conflated with "not yet sampled" unless you propagate the b3
// bits.
//
// W3C traceparent has no field to indicate shared status, which is why
// tracestate exists, to carry the extra data not possible to encode in
// traceparent, such as intentionally unsampled, debug and shared flags.
// https://github.com/openzipkin-contrib/brave-propagation-w3c has a
// correct impl.
contextFromParentHeader = contextFromParentHeader.toBuilder().shared(true).build();
codefromthecrypt marked this conversation as resolved.
Show resolved Hide resolved
}
String traceStateHeader = getter.get(carrier, TRACESTATE);
TraceContextOrSamplingFlags context = context(contextFromParentHeader, traceStateHeader);
if (this.baggagePropagator == null || this.braveBaggageManager == null) {
return context;
Expand Down Expand Up @@ -293,19 +176,6 @@ TraceContextOrSamplingFlags context(TraceContext contextFromParentHeader, String
}
}

static {
// A valid version is 1 byte representing an 8-bit unsigned integer, version ff is
// invalid.
VALID_VERSIONS = new HashSet<>();
for (int i = 0; i < 255; i++) {
String version = Long.toHexString(i);
if (version.length() < 2) {
version = '0' + version;
}
VALID_VERSIONS.add(version);
}
}

}

/**
Expand Down Expand Up @@ -400,67 +270,3 @@ List<AbstractMap.SimpleEntry<Baggage, String>> addBaggageToContext(String baggag
}

}

/**
* Taken from OpenTelemetry API.
* <p>
* {@link ThreadLocal} buffers for use when creating new derived objects such as
* {@link String}s. These buffers are reused within a single thread - it is _not safe_ to
* use the buffer to generate multiple derived objects at the same time because the same
* memory will be used. In general, you should get a temporary buffer, fill it with data,
* and finish by converting into the derived object within the same method to avoid
* multiple usages of the same buffer.
*/
final class TemporaryBuffers {

private static final ThreadLocal<char[]> CHAR_ARRAY = new ThreadLocal<>();

private TemporaryBuffers() {
}

/**
* A {@link ThreadLocal} {@code char[]} of size {@code len}. Take care when using a
* large value of {@code len} as this buffer will remain for the lifetime of the
* thread. The returned buffer will not be zeroed and may be larger than the requested
* size, you must make sure to fill the entire content to the desired value and set
* the length explicitly when converting to a {@link String}.
*/
public static char[] chars(int len) {
char[] buffer = CHAR_ARRAY.get();
if (buffer == null) {
buffer = new char[len];
CHAR_ARRAY.set(buffer);
}
else if (buffer.length < len) {
buffer = new char[len];
CHAR_ARRAY.set(buffer);
}
return buffer;
}

// Visible for testing
static void clearChars() {
CHAR_ARRAY.set(null);
}

}

/**
* Taken from OpenTelemetry API.
*/
final class TraceFlags {

// Bit to represent whether trace is sampled or not.
static final byte IS_SAMPLED = 0x1;

private TraceFlags() {
}

/**
* Extract the byte representation of the flags from a hex-representation.
*/
static byte byteFromHex(CharSequence src, int srcOffset) {
return EncodingUtils.byteFromBase16String(src, srcOffset);
}

}
Loading