Skip to content

Commit 083acdd

Browse files
authored
Move X-Ray Env Variable propagation to span link instead of parent (#7970)
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. Also related to #5167.
1 parent 4d6ef78 commit 083acdd

File tree

9 files changed

+228
-214
lines changed

9 files changed

+228
-214
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,74 @@
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.common.AttributeKey;
9+
import io.opentelemetry.api.common.Attributes;
10+
import io.opentelemetry.api.trace.Span;
11+
import io.opentelemetry.api.trace.SpanContext;
12+
import io.opentelemetry.context.Context;
13+
import io.opentelemetry.context.propagation.TextMapGetter;
14+
import io.opentelemetry.contrib.awsxray.propagator.AwsXrayPropagator;
15+
import io.opentelemetry.instrumentation.api.instrumenter.SpanLinksBuilder;
16+
import io.opentelemetry.instrumentation.api.instrumenter.SpanLinksExtractor;
17+
import io.opentelemetry.instrumentation.awslambdacore.v1_0.AwsLambdaRequest;
18+
import java.util.Collections;
19+
import java.util.Locale;
20+
import java.util.Map;
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+
final class AwsXrayEnvSpanLinksExtractor implements SpanLinksExtractor<AwsLambdaRequest> {
27+
28+
private static final String AWS_TRACE_HEADER_ENV_KEY = "_X_AMZN_TRACE_ID";
29+
30+
// lower-case map getter used for extraction
31+
private static final String AWS_TRACE_HEADER_PROPAGATOR_KEY = "x-amzn-trace-id";
32+
33+
private static final Attributes LINK_ATTRIBUTES =
34+
Attributes.of(AttributeKey.stringKey("source"), "x-ray-env");
35+
36+
@Override
37+
public void extract(
38+
SpanLinksBuilder spanLinks,
39+
io.opentelemetry.context.Context parentContext,
40+
AwsLambdaRequest awsLambdaRequest) {
41+
extract(spanLinks);
42+
}
43+
44+
public static void extract(SpanLinksBuilder spanLinks) {
45+
String parentTraceHeader = System.getenv(AWS_TRACE_HEADER_ENV_KEY);
46+
if (parentTraceHeader == null || parentTraceHeader.isEmpty()) {
47+
return;
48+
}
49+
Context xrayContext =
50+
AwsXrayPropagator.getInstance()
51+
.extract(
52+
Context.root(),
53+
Collections.singletonMap(AWS_TRACE_HEADER_PROPAGATOR_KEY, parentTraceHeader),
54+
MapGetter.INSTANCE);
55+
SpanContext envVarSpanCtx = Span.fromContext(xrayContext).getSpanContext();
56+
if (envVarSpanCtx.isValid()) {
57+
spanLinks.addLink(envVarSpanCtx, LINK_ATTRIBUTES);
58+
}
59+
}
60+
61+
private enum MapGetter implements TextMapGetter<Map<String, String>> {
62+
INSTANCE;
63+
64+
@Override
65+
public Iterable<String> keys(Map<String, String> map) {
66+
return map.keySet();
67+
}
68+
69+
@Override
70+
public String get(Map<String, String> map, String s) {
71+
return map.get(s.toLowerCase(Locale.ROOT));
72+
}
73+
}
74+
}

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,87 @@
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.ArgumentMatchers.eq;
10+
import static org.mockito.Mockito.mock;
11+
import static org.mockito.Mockito.verify;
12+
import static org.mockito.Mockito.verifyNoInteractions;
13+
14+
import io.opentelemetry.api.common.AttributeKey;
15+
import io.opentelemetry.api.common.Attributes;
16+
import io.opentelemetry.api.trace.SpanContext;
17+
import io.opentelemetry.instrumentation.api.instrumenter.SpanLinksBuilder;
18+
import org.junit.jupiter.api.Test;
19+
import org.junit.jupiter.api.extension.ExtendWith;
20+
import org.mockito.ArgumentCaptor;
21+
import uk.org.webcompere.systemstubs.environment.EnvironmentVariables;
22+
import uk.org.webcompere.systemstubs.jupiter.SystemStub;
23+
import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension;
24+
25+
/**
26+
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
27+
* any time.
28+
*/
29+
@ExtendWith(SystemStubsExtension.class)
30+
class AwsXrayEnvSpanLinksExtractorTest {
31+
private static final Attributes EXPECTED_LINK_ATTRIBUTES =
32+
Attributes.of(AttributeKey.stringKey("source"), "x-ray-env");
33+
34+
@SystemStub final EnvironmentVariables environmentVariables = new EnvironmentVariables();
35+
36+
@Test
37+
void shouldIgnoreIfEnvVarEmpty() {
38+
// given
39+
SpanLinksBuilder spanLinksBuilder = mock(SpanLinksBuilder.class);
40+
environmentVariables.set("_X_AMZN_TRACE_ID", "");
41+
42+
// when
43+
AwsXrayEnvSpanLinksExtractor.extract(spanLinksBuilder);
44+
// then
45+
verifyNoInteractions(spanLinksBuilder);
46+
}
47+
48+
@Test
49+
void shouldLinkAwsParentHeaderIfValidAndNotSampled() {
50+
// given
51+
SpanLinksBuilder spanLinksBuilder = mock(SpanLinksBuilder.class);
52+
environmentVariables.set(
53+
"_X_AMZN_TRACE_ID",
54+
"Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=0000000000000456;Sampled=0");
55+
56+
// when
57+
AwsXrayEnvSpanLinksExtractor.extract(spanLinksBuilder);
58+
// then
59+
ArgumentCaptor<SpanContext> captor = ArgumentCaptor.forClass(SpanContext.class);
60+
verify(spanLinksBuilder).addLink(captor.capture(), eq(EXPECTED_LINK_ATTRIBUTES));
61+
SpanContext spanContext = captor.getValue();
62+
assertThat(spanContext.isValid()).isTrue();
63+
assertThat(spanContext.isSampled()).isFalse();
64+
assertThat(spanContext.getSpanId()).isEqualTo("0000000000000456");
65+
assertThat(spanContext.getTraceId()).isEqualTo("8a3c60f7d188f8fa79d48a391a778fa6");
66+
}
67+
68+
@Test
69+
void shouldLinkAwsParentHeaderIfValidAndSampled() {
70+
// given
71+
SpanLinksBuilder spanLinksBuilder = mock(SpanLinksBuilder.class);
72+
environmentVariables.set(
73+
"_X_AMZN_TRACE_ID",
74+
"Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=0000000000000456;Sampled=1");
75+
76+
// when
77+
AwsXrayEnvSpanLinksExtractor.extract(spanLinksBuilder);
78+
// then
79+
ArgumentCaptor<SpanContext> captor = ArgumentCaptor.forClass(SpanContext.class);
80+
verify(spanLinksBuilder).addLink(captor.capture(), eq(EXPECTED_LINK_ATTRIBUTES));
81+
SpanContext spanContext = captor.getValue();
82+
assertThat(spanContext.isValid()).isTrue();
83+
assertThat(spanContext.isSampled()).isTrue();
84+
assertThat(spanContext.getSpanId()).isEqualTo("0000000000000456");
85+
assertThat(spanContext.getTraceId()).isEqualTo("8a3c60f7d188f8fa79d48a391a778fa6");
86+
}
87+
}

0 commit comments

Comments
 (0)