Skip to content

Commit 9fdd241

Browse files
committed
Move X-Ray Env Variable propagation to span link instead of parent
open-telemetry/opentelemetry-specification#3166 Per discussion in the FAAS SIG, we decided that the AWS X-Ray environment variable should be moved to a span link to avoid interfering with the configured propagators.
1 parent ee781f5 commit 9fdd241

File tree

10 files changed

+210
-220
lines changed

10 files changed

+210
-220
lines changed

instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/internal/ApiGatewayProxyRequest.java

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,7 @@ public abstract class ApiGatewayProxyRequest {
3030
private static boolean noHttpPropagationNeeded() {
3131
Collection<String> fields =
3232
GlobalOpenTelemetry.getPropagators().getTextMapPropagator().fields();
33-
return fields.isEmpty() || xrayPropagationFieldsOnly(fields);
34-
}
35-
36-
private static boolean xrayPropagationFieldsOnly(Collection<String> fields) {
37-
// ugly but faster than typical convert-to-set-and-check-contains-only
38-
return (fields.size() == 1)
39-
&& ParentContextExtractor.AWS_TRACE_HEADER_PROPAGATOR_KEY.equalsIgnoreCase(
40-
fields.iterator().next());
33+
return fields.isEmpty();
4134
}
4235

4336
public static ApiGatewayProxyRequest forStream(InputStream source) {

instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/internal/AwsLambdaFunctionInstrumenter.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
1212
import io.opentelemetry.instrumentation.api.internal.ContextPropagationDebug;
1313
import io.opentelemetry.instrumentation.awslambdacore.v1_0.AwsLambdaRequest;
14+
import java.util.Locale;
1415
import java.util.Map;
1516
import javax.annotation.Nullable;
1617

@@ -46,15 +47,25 @@ public void end(
4647
}
4748

4849
public Context extract(AwsLambdaRequest input) {
49-
return ParentContextExtractor.extract(input.getHeaders(), this);
50-
}
51-
52-
public Context extract(Map<String, String> headers, TextMapGetter<Map<String, String>> getter) {
5350
ContextPropagationDebug.debugContextLeakIfEnabled();
5451

5552
return openTelemetry
5653
.getPropagators()
5754
.getTextMapPropagator()
58-
.extract(Context.root(), headers, getter);
55+
.extract(Context.root(), input.getHeaders(), MapGetter.INSTANCE);
56+
}
57+
58+
private enum MapGetter implements TextMapGetter<Map<String, String>> {
59+
INSTANCE;
60+
61+
@Override
62+
public Iterable<String> keys(Map<String, String> map) {
63+
return map.keySet();
64+
}
65+
66+
@Override
67+
public String get(Map<String, String> map, String s) {
68+
return map.get(s.toLowerCase(Locale.ROOT));
69+
}
5970
}
6071
}

instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/internal/AwsLambdaFunctionInstrumenterFactory.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public static AwsLambdaFunctionInstrumenter createInstrumenter(OpenTelemetry ope
2323
openTelemetry,
2424
"io.opentelemetry.aws-lambda-core-1.0",
2525
AwsLambdaFunctionInstrumenterFactory::spanName)
26+
.addSpanLinksExtractor(new AwsXRayEnvSpanLinksExtractor())
2627
.addAttributesExtractor(new AwsLambdaFunctionAttributesExtractor())
2728
.buildInstrumenter(SpanKindExtractor.alwaysServer()));
2829
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.awslambdacore.v1_0.internal;
7+
8+
import io.opentelemetry.api.trace.Span;
9+
import io.opentelemetry.api.trace.SpanContext;
10+
import io.opentelemetry.context.Context;
11+
import io.opentelemetry.context.propagation.TextMapGetter;
12+
import io.opentelemetry.contrib.awsxray.propagator.AwsXrayPropagator;
13+
import io.opentelemetry.instrumentation.api.instrumenter.SpanLinksBuilder;
14+
import io.opentelemetry.instrumentation.api.instrumenter.SpanLinksExtractor;
15+
import io.opentelemetry.instrumentation.awslambdacore.v1_0.AwsLambdaRequest;
16+
import java.util.Collections;
17+
import java.util.Locale;
18+
import java.util.Map;
19+
20+
/**
21+
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
22+
* any time.
23+
*/
24+
public final class AwsXRayEnvSpanLinksExtractor implements SpanLinksExtractor<AwsLambdaRequest> {
25+
26+
private static final String AWS_TRACE_HEADER_ENV_KEY = "_X_AMZN_TRACE_ID";
27+
28+
// lower-case map getter used for extraction
29+
private static final String AWS_TRACE_HEADER_PROPAGATOR_KEY = "x-amzn-trace-id";
30+
31+
@Override
32+
public void extract(
33+
SpanLinksBuilder spanLinks,
34+
io.opentelemetry.context.Context parentContext,
35+
AwsLambdaRequest awsLambdaRequest) {
36+
extract(spanLinks);
37+
}
38+
39+
public static void extract(SpanLinksBuilder spanLinks) {
40+
String parentTraceHeader = System.getenv(AWS_TRACE_HEADER_ENV_KEY);
41+
if (parentTraceHeader == null || parentTraceHeader.isEmpty()) {
42+
return;
43+
}
44+
Context parentCtx =
45+
AwsXrayPropagator.getInstance()
46+
.extract(
47+
// see BaseTracer#extract() on why we're using root() here
48+
Context.root(),
49+
Collections.singletonMap(AWS_TRACE_HEADER_PROPAGATOR_KEY, parentTraceHeader),
50+
MapGetter.INSTANCE);
51+
SpanContext parent = Span.fromContext(parentCtx).getSpanContext();
52+
if (parent.isValid()) {
53+
spanLinks.addLink(parent);
54+
}
55+
}
56+
57+
private enum MapGetter implements TextMapGetter<Map<String, String>> {
58+
INSTANCE;
59+
60+
@Override
61+
public Iterable<String> keys(Map<String, String> map) {
62+
return map.keySet();
63+
}
64+
65+
@Override
66+
public String get(Map<String, String> map, String s) {
67+
return map.get(s.toLowerCase(Locale.ROOT));
68+
}
69+
}
70+
}

instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/internal/ParentContextExtractor.java

Lines changed: 0 additions & 81 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.awslambdacore.v1_0.internal;
7+
8+
import static org.assertj.core.api.Assertions.assertThat;
9+
import static org.mockito.Mockito.mock;
10+
import static org.mockito.Mockito.verify;
11+
import static org.mockito.Mockito.verifyNoInteractions;
12+
13+
import io.opentelemetry.api.trace.SpanContext;
14+
import io.opentelemetry.instrumentation.api.instrumenter.SpanLinksBuilder;
15+
import org.junit.jupiter.api.Test;
16+
import org.junit.jupiter.api.extension.ExtendWith;
17+
import org.mockito.ArgumentCaptor;
18+
import uk.org.webcompere.systemstubs.environment.EnvironmentVariables;
19+
import uk.org.webcompere.systemstubs.jupiter.SystemStub;
20+
import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension;
21+
22+
/**
23+
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
24+
* any time.
25+
*/
26+
@ExtendWith(SystemStubsExtension.class)
27+
class AwsXRayEnvSpanLinksExtractorTest {
28+
29+
@SystemStub final EnvironmentVariables environmentVariables = new EnvironmentVariables();
30+
31+
@Test
32+
void shouldIgnoreIfEnvVarEmpty() {
33+
// given
34+
SpanLinksBuilder spanLinksBuilder = mock(SpanLinksBuilder.class);
35+
environmentVariables.set("_X_AMZN_TRACE_ID", "");
36+
37+
// when
38+
AwsXRayEnvSpanLinksExtractor.extract(spanLinksBuilder);
39+
// then
40+
verifyNoInteractions(spanLinksBuilder);
41+
}
42+
43+
@Test
44+
void shouldLinkAwsParentHeaderIfValidAndNotSampled() {
45+
// given
46+
SpanLinksBuilder spanLinksBuilder = mock(SpanLinksBuilder.class);
47+
environmentVariables.set(
48+
"_X_AMZN_TRACE_ID",
49+
"Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=0000000000000456;Sampled=0");
50+
51+
// when
52+
AwsXRayEnvSpanLinksExtractor.extract(spanLinksBuilder);
53+
// then
54+
ArgumentCaptor<SpanContext> captor = ArgumentCaptor.forClass(SpanContext.class);
55+
verify(spanLinksBuilder).addLink(captor.capture());
56+
SpanContext spanContext = captor.getValue();
57+
assertThat(spanContext.isValid()).isTrue();
58+
assertThat(spanContext.isSampled()).isFalse();
59+
assertThat(spanContext.getSpanId()).isEqualTo("0000000000000456");
60+
assertThat(spanContext.getTraceId()).isEqualTo("8a3c60f7d188f8fa79d48a391a778fa6");
61+
}
62+
63+
@Test
64+
void shouldLinkAwsParentHeaderIfValidAndSampled() {
65+
// given
66+
SpanLinksBuilder spanLinksBuilder = mock(SpanLinksBuilder.class);
67+
environmentVariables.set(
68+
"_X_AMZN_TRACE_ID",
69+
"Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=0000000000000456;Sampled=1");
70+
71+
// when
72+
AwsXRayEnvSpanLinksExtractor.extract(spanLinksBuilder);
73+
// then
74+
ArgumentCaptor<SpanContext> captor = ArgumentCaptor.forClass(SpanContext.class);
75+
verify(spanLinksBuilder).addLink(captor.capture());
76+
SpanContext spanContext = captor.getValue();
77+
assertThat(spanContext.isValid()).isTrue();
78+
assertThat(spanContext.isSampled()).isTrue();
79+
assertThat(spanContext.getSpanId()).isEqualTo("0000000000000456");
80+
assertThat(spanContext.getTraceId()).isEqualTo("8a3c60f7d188f8fa79d48a391a778fa6");
81+
}
82+
}

0 commit comments

Comments
 (0)