Skip to content

Commit

Permalink
Add xray propagators that prioritizes xray environment variable
Browse files Browse the repository at this point in the history
This will allow support for AWS's environment variable with no additional config keys and no changes to instrumentation required.
I included a propagator that did just the env var propagation without the standard xray propagation as well as one that does both but prioritizes xray env propagation first.
  • Loading branch information
tylerbenson committed Sep 8, 2023
1 parent 43cabe4 commit d2c846a
Show file tree
Hide file tree
Showing 9 changed files with 463 additions and 39 deletions.
1 change: 1 addition & 0 deletions aws-xray-propagator/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ otelJava.moduleName.set("io.opentelemetry.contrib.awsxray.propagator")
dependencies {
api("io.opentelemetry:opentelemetry-api")
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi")
testImplementation("uk.org.webcompere:system-stubs-jupiter:2.0.2")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.awsxray.propagator;

import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider;

/**
* A {@link ConfigurablePropagatorProvider} which allows enabling the {@link AwsXrayEnvPropagator}
* with the propagator name {@code xray-env}.
*/
public final class AwsXrayEnvConfigurablePropagator implements ConfigurablePropagatorProvider {
@Override
public TextMapPropagator getPropagator(ConfigProperties config) {
return AwsXrayEnvPropagator.getInstance();
}

@Override
public String getName() {
return "xray-env";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.awsxray.propagator;

import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.TextMapGetter;
import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.context.propagation.TextMapSetter;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;

/**
* Implementation of the AWS X-Ray Trace Header propagation protocol only uses Lambda's {@code
* _X_AMZN_TRACE_ID} environment variable and {@code com.amazonaws.xray.traceHeader} system property
* as the carrier. Carrier passed into {@link #inject(Context, Object, TextMapSetter)} and {@link
* #extract(Context, Object, TextMapGetter)} are ignored.
*
* <p>To register the X-Ray propagator together with default propagator when using the SDK:
*
* <pre>{@code
* OpenTelemetrySdk.builder()
* .setPropagators(
* ContextPropagators.create(
* TextMapPropagator.composite(
* W3CTraceContextPropagator.getInstance(),
* AwsXrayEnvPropagator.getInstance())))
* .build();
* }</pre>
*/
public final class AwsXrayEnvPropagator implements TextMapPropagator {

private static final String AWS_TRACE_HEADER_ENV_KEY = "_X_AMZN_TRACE_ID";
private static final String AWS_TRACE_HEADER_PROP = "com.amazonaws.xray.traceHeader";
private final AwsXrayPropagator xrayPropagator = AwsXrayPropagator.getInstance();

private static final AwsXrayEnvPropagator INSTANCE = new AwsXrayEnvPropagator();

private AwsXrayEnvPropagator() {
// singleton
}

public static AwsXrayEnvPropagator getInstance() {
return INSTANCE;
}

@Override
public List<String> fields() {
return xrayPropagator.fields();
}

@Override
public <C> void inject(Context context, @Nullable C carrier, TextMapSetter<C> setter) {}

@Override
public <C> Context extract(Context context, @Nullable C carrier, TextMapGetter<C> ignored) {
String traceHeader = System.getProperty(AWS_TRACE_HEADER_PROP);
if (isEmptyOrNull(traceHeader)) {
traceHeader = System.getenv(AWS_TRACE_HEADER_ENV_KEY);
}
if (isEmptyOrNull(traceHeader)) {
return context;
}
return xrayPropagator.extract(
context,
Collections.singletonMap(AwsXrayPropagator.TRACE_HEADER_KEY, traceHeader),
MapGetter.INSTANCE);
}

private static boolean isEmptyOrNull(@Nullable String value) {
return value == null || value.isEmpty();
}

private enum MapGetter implements TextMapGetter<Map<String, String>> {
INSTANCE;

@Override
public Set<String> keys(Map<String, String> map) {
return map.keySet();
}

@Override
@Nullable
public String get(@Nullable Map<String, String> map, String s) {
return map == null ? null : map.get(s);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.awsxray.propagator;

import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider;

/**
* A {@link ConfigurablePropagatorProvider} which allows enabling the {@link
* AwsXrayLambdaPropagator} with the propagator name {@code aws}.
*/
public final class AwsXrayLambdaConfigurablePropagator implements ConfigurablePropagatorProvider {
@Override
public TextMapPropagator getPropagator(ConfigProperties config) {
return AwsXrayLambdaPropagator.getInstance();
}

@Override
public String getName() {
return "aws";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.awsxray.propagator;

import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.TextMapGetter;
import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.context.propagation.TextMapSetter;
import java.util.List;
import javax.annotation.Nullable;

/**
* Implementation of the AWS X-Ray Trace Header propagation protocol but with special handling for
* Lambda's {@code _X_AMZN_TRACE_ID} environment variable and {@code com.amazonaws.xray.traceHeader}
* system property.
*
* <p>To register the X-Ray propagator together with default propagator when using the SDK:
*
* <pre>{@code
* OpenTelemetrySdk.builder()
* .setPropagators(
* ContextPropagators.create(
* TextMapPropagator.composite(
* W3CTraceContextPropagator.getInstance(),
* AwsXrayLambdaPropagator.getInstance())))
* .build();
* }</pre>
*/
public final class AwsXrayLambdaPropagator implements TextMapPropagator {
private static final AwsXrayPropagator XRAY = AwsXrayPropagator.getInstance();
private static final AwsXrayEnvPropagator XRAY_ENV = AwsXrayEnvPropagator.getInstance();
private static final AwsXrayLambdaPropagator INSTANCE = new AwsXrayLambdaPropagator();

private AwsXrayLambdaPropagator() {
// singleton
}

public static AwsXrayLambdaPropagator getInstance() {
return INSTANCE;
}

@Override
public List<String> fields() {
return XRAY.fields();
}

@Override
public <C> void inject(Context context, @Nullable C carrier, TextMapSetter<C> setter) {
XRAY.inject(context, carrier, setter);
// XRAY_ENV.inject is a no-op, so no need to invoke.
}

@Override
public <C> Context extract(Context context, @Nullable C carrier, TextMapGetter<C> getter) {
context = XRAY.extract(context, carrier, getter);
// Currently last one wins, so invoke XRAY_ENV second to allow parent to be overwritten.
context = XRAY_ENV.extract(context, carrier, getter);
return context;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
* ContextPropagators.create(
* TextMapPropagator.composite(
* W3CTraceContextPropagator.getInstance(),
* AWSXrayPropagator.getInstance())))
* AwsXrayPropagator.getInstance())))
* .build();
* }</pre>
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.awsxray.propagator;

import static io.opentelemetry.contrib.awsxray.propagator.AwsXrayPropagator.TRACE_HEADER_KEY;
import static io.opentelemetry.contrib.awsxray.propagator.AwsXrayPropagatorTest.GETTER;
import static io.opentelemetry.contrib.awsxray.propagator.AwsXrayPropagatorTest.SETTER;
import static io.opentelemetry.contrib.awsxray.propagator.AwsXrayPropagatorTest.SPAN_ID;
import static io.opentelemetry.contrib.awsxray.propagator.AwsXrayPropagatorTest.TRACE_ID;
import static io.opentelemetry.contrib.awsxray.propagator.AwsXrayPropagatorTest.getSpanContext;
import static io.opentelemetry.contrib.awsxray.propagator.AwsXrayPropagatorTest.withSpanContext;
import static org.assertj.core.api.Assertions.assertThat;

import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.api.trace.TraceFlags;
import io.opentelemetry.api.trace.TraceState;
import io.opentelemetry.context.Context;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import uk.org.webcompere.systemstubs.environment.EnvironmentVariables;
import uk.org.webcompere.systemstubs.jupiter.SystemStub;
import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension;
import uk.org.webcompere.systemstubs.properties.SystemProperties;

@ExtendWith(SystemStubsExtension.class)
class AwsXrayEnvPropagatorTest {
private final AwsXrayEnvPropagator subject = AwsXrayEnvPropagator.getInstance();

@SystemStub final EnvironmentVariables environmentVariables = new EnvironmentVariables();
@SystemStub final SystemProperties systemProperties = new SystemProperties();

@Test
void fields_valid() {
assertThat(subject.fields()).containsOnly("X-Amzn-Trace-Id");
}

@Test
void inject_doesNothing() {
Map<String, String> carrier = new LinkedHashMap<>();
subject.inject(
withSpanContext(
SpanContext.create(TRACE_ID, SPAN_ID, TraceFlags.getSampled(), TraceState.getDefault()),
Context.current()),
carrier,
SETTER);

assertThat(carrier).isEmpty();
}

@Test
void extract_carrierIgnored() {
Map<String, String> carrier =
Collections.singletonMap(
TRACE_HEADER_KEY,
"Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=1");
assertThat(subject.extract(Context.current(), carrier, GETTER)).isEqualTo(Context.current());
}

@Test
void extract_fromEnvironmentVariable() {
environmentVariables.set(
"_X_AMZN_TRACE_ID",
"Root=1-00000000-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=1;Foo=Bar");

assertThat(getSpanContext(subject.extract(Context.current(), Collections.emptyMap(), GETTER)))
.isEqualTo(
SpanContext.createFromRemoteParent(
"00000000d188f8fa79d48a391a778fa6",
SPAN_ID,
TraceFlags.getSampled(),
TraceState.getDefault()));
}

@Test
void extract_fromSystemProperty() {
systemProperties.set(
"com.amazonaws.xray.traceHeader",
"Root=1-00000000-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=1;Foo=Bar");

assertThat(getSpanContext(subject.extract(Context.current(), Collections.emptyMap(), GETTER)))
.isEqualTo(
SpanContext.createFromRemoteParent(
"00000000d188f8fa79d48a391a778fa6",
SPAN_ID,
TraceFlags.getSampled(),
TraceState.getDefault()));
}

@Test
void extract_systemPropertyBeforeEnvironmentVariable() {
environmentVariables.set(
"_X_AMZN_TRACE_ID",
"Root=1-00000000-240000000000000000000001;Parent=1600000000000001;Sampled=1;Foo=Bar");
systemProperties.set(
"com.amazonaws.xray.traceHeader",
"Root=1-00000000-240000000000000000000002;Parent=1600000000000002;Sampled=1;Foo=Baz");

assertThat(getSpanContext(subject.extract(Context.current(), Collections.emptyMap(), GETTER)))
.isEqualTo(
SpanContext.createFromRemoteParent(
"00000000240000000000000000000002",
"1600000000000002",
TraceFlags.getSampled(),
TraceState.getDefault()));
}
}
Loading

0 comments on commit d2c846a

Please sign in to comment.