Skip to content

Commit e24705d

Browse files
thpiercebjrara
andauthored
Add AttributePropagatingSpanProcessor component to AWS X-Ray (#856)
Co-authored-by: Mengyi Zhou <[email protected]>
1 parent fdc3060 commit e24705d

File tree

4 files changed

+322
-0
lines changed

4 files changed

+322
-0
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.awsxray;
7+
8+
import io.opentelemetry.api.common.AttributeKey;
9+
import io.opentelemetry.api.trace.Span;
10+
import io.opentelemetry.api.trace.SpanContext;
11+
import io.opentelemetry.api.trace.SpanKind;
12+
import io.opentelemetry.context.Context;
13+
import io.opentelemetry.sdk.trace.ReadWriteSpan;
14+
import io.opentelemetry.sdk.trace.ReadableSpan;
15+
import io.opentelemetry.sdk.trace.SpanProcessor;
16+
import java.util.List;
17+
import javax.annotation.concurrent.Immutable;
18+
19+
/**
20+
* AttributePropagatingSpanProcessor handles the propagation of attributes from parent spans to
21+
* child spans, specified in {@link #attributesKeysToPropagate}. AttributePropagatingSpanProcessor
22+
* also propagates the parent span name to child spans, as a new attribute specified by {@link
23+
* #spanNamePropagationKey}. Span name propagation only starts from local root server/consumer
24+
* spans, but from there will be propagated to any descendant spans.
25+
*/
26+
@Immutable
27+
public final class AttributePropagatingSpanProcessor implements SpanProcessor {
28+
29+
private final AttributeKey<String> spanNamePropagationKey;
30+
private final List<AttributeKey<String>> attributesKeysToPropagate;
31+
32+
public static AttributePropagatingSpanProcessor create(
33+
AttributeKey<String> spanNamePropagationKey,
34+
List<AttributeKey<String>> attributesKeysToPropagate) {
35+
return new AttributePropagatingSpanProcessor(spanNamePropagationKey, attributesKeysToPropagate);
36+
}
37+
38+
private AttributePropagatingSpanProcessor(
39+
AttributeKey<String> spanNamePropagationKey,
40+
List<AttributeKey<String>> attributesKeysToPropagate) {
41+
this.spanNamePropagationKey = spanNamePropagationKey;
42+
this.attributesKeysToPropagate = attributesKeysToPropagate;
43+
}
44+
45+
@Override
46+
public void onStart(Context parentContext, ReadWriteSpan span) {
47+
Span parentSpan = Span.fromContextOrNull(parentContext);
48+
if (!(parentSpan instanceof ReadableSpan)) {
49+
return;
50+
}
51+
ReadableSpan parentReadableSpan = (ReadableSpan) parentSpan;
52+
53+
String spanNameToPropagate;
54+
if (isLocalRoot(parentReadableSpan.getParentSpanContext())
55+
&& isServerOrConsumer(parentReadableSpan)) {
56+
spanNameToPropagate = parentReadableSpan.getName();
57+
} else {
58+
spanNameToPropagate = parentReadableSpan.getAttribute(spanNamePropagationKey);
59+
}
60+
61+
if (spanNameToPropagate != null) {
62+
span.setAttribute(spanNamePropagationKey, spanNameToPropagate);
63+
}
64+
65+
for (AttributeKey<String> keyToPropagate : attributesKeysToPropagate) {
66+
String valueToPropagate = parentReadableSpan.getAttribute(keyToPropagate);
67+
if (valueToPropagate != null) {
68+
span.setAttribute(keyToPropagate, valueToPropagate);
69+
}
70+
}
71+
}
72+
73+
private static boolean isLocalRoot(SpanContext parentSpanContext) {
74+
return !parentSpanContext.isValid() || parentSpanContext.isRemote();
75+
}
76+
77+
private static boolean isServerOrConsumer(ReadableSpan span) {
78+
return span.getKind() == SpanKind.SERVER || span.getKind() == SpanKind.CONSUMER;
79+
}
80+
81+
@Override
82+
public boolean isStartRequired() {
83+
return true;
84+
}
85+
86+
@Override
87+
public void onEnd(ReadableSpan span) {}
88+
89+
@Override
90+
public boolean isEndRequired() {
91+
return false;
92+
}
93+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.awsxray;
7+
8+
import static java.util.Objects.requireNonNull;
9+
10+
import com.google.errorprone.annotations.CanIgnoreReturnValue;
11+
import io.opentelemetry.api.common.AttributeKey;
12+
import java.util.Arrays;
13+
import java.util.Collections;
14+
import java.util.List;
15+
16+
/**
17+
* AttributePropagatingSpanProcessorBuilder is used to construct a {@link
18+
* AttributePropagatingSpanProcessor}. If {@link #setSpanNamePropagationKey} or {@link
19+
* #setAttributesKeysToPropagate} are not invoked, the builder defaults to using specific {@link
20+
* AwsAttributeKeys} as propagation targets.
21+
*/
22+
public class AttributePropagatingSpanProcessorBuilder {
23+
24+
private AttributeKey<String> spanNamePropagationKey = AwsAttributeKeys.AWS_LOCAL_OPERATION;
25+
private List<AttributeKey<String>> attributesKeysToPropagate =
26+
Arrays.asList(AwsAttributeKeys.AWS_REMOTE_SERVICE, AwsAttributeKeys.AWS_REMOTE_OPERATION);
27+
28+
public static AttributePropagatingSpanProcessorBuilder create() {
29+
return new AttributePropagatingSpanProcessorBuilder();
30+
}
31+
32+
private AttributePropagatingSpanProcessorBuilder() {}
33+
34+
@CanIgnoreReturnValue
35+
public AttributePropagatingSpanProcessorBuilder setSpanNamePropagationKey(
36+
AttributeKey<String> spanNamePropagationKey) {
37+
requireNonNull(spanNamePropagationKey, "spanNamePropagationKey");
38+
this.spanNamePropagationKey = spanNamePropagationKey;
39+
return this;
40+
}
41+
42+
@CanIgnoreReturnValue
43+
public AttributePropagatingSpanProcessorBuilder setAttributesKeysToPropagate(
44+
List<AttributeKey<String>> attributesKeysToPropagate) {
45+
requireNonNull(attributesKeysToPropagate, "attributesKeysToPropagate");
46+
this.attributesKeysToPropagate = Collections.unmodifiableList(attributesKeysToPropagate);
47+
return this;
48+
}
49+
50+
public AttributePropagatingSpanProcessor build() {
51+
return AttributePropagatingSpanProcessor.create(
52+
spanNamePropagationKey, attributesKeysToPropagate);
53+
}
54+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.awsxray;
7+
8+
import io.opentelemetry.api.common.AttributeKey;
9+
10+
/** Utility class holding attribute keys with special meaning to AWS components */
11+
final class AwsAttributeKeys {
12+
13+
private AwsAttributeKeys() {}
14+
15+
static final AttributeKey<String> AWS_LOCAL_OPERATION =
16+
AttributeKey.stringKey("aws.local.operation");
17+
18+
static final AttributeKey<String> AWS_REMOTE_SERVICE =
19+
AttributeKey.stringKey("aws.remote.service");
20+
21+
static final AttributeKey<String> AWS_REMOTE_OPERATION =
22+
AttributeKey.stringKey("aws.remote.operation");
23+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.awsxray;
7+
8+
import static org.assertj.core.api.Assertions.assertThat;
9+
10+
import io.opentelemetry.api.common.AttributeKey;
11+
import io.opentelemetry.api.trace.Span;
12+
import io.opentelemetry.api.trace.SpanContext;
13+
import io.opentelemetry.api.trace.SpanKind;
14+
import io.opentelemetry.api.trace.TraceFlags;
15+
import io.opentelemetry.api.trace.TraceState;
16+
import io.opentelemetry.api.trace.Tracer;
17+
import io.opentelemetry.context.Context;
18+
import io.opentelemetry.sdk.trace.ReadableSpan;
19+
import io.opentelemetry.sdk.trace.SdkTracerProvider;
20+
import java.util.Arrays;
21+
import org.junit.jupiter.api.BeforeEach;
22+
import org.junit.jupiter.api.Test;
23+
24+
class AttributePropagatingSpanProcessorTest {
25+
26+
private Tracer tracer;
27+
28+
AttributeKey<String> spanNameKey = AttributeKey.stringKey("spanName");
29+
AttributeKey<String> testKey1 = AttributeKey.stringKey("key1");
30+
AttributeKey<String> testKey2 = AttributeKey.stringKey("key2");
31+
32+
@BeforeEach
33+
public void setup() {
34+
tracer =
35+
SdkTracerProvider.builder()
36+
.addSpanProcessor(
37+
AttributePropagatingSpanProcessor.create(
38+
spanNameKey, Arrays.asList(testKey1, testKey2)))
39+
.build()
40+
.get("awsxray");
41+
}
42+
43+
@Test
44+
public void testAttributesPropagation() {
45+
Span spanWithAppOnly = tracer.spanBuilder("parent").startSpan();
46+
spanWithAppOnly.setAttribute(testKey1, "testValue1");
47+
validateSpanAttributesInheritance(spanWithAppOnly, null, "testValue1", null);
48+
49+
Span spanWithOpOnly = tracer.spanBuilder("parent").startSpan();
50+
spanWithOpOnly.setAttribute(testKey2, "testValue2");
51+
validateSpanAttributesInheritance(spanWithOpOnly, null, null, "testValue2");
52+
53+
Span spanWithAppAndOp = tracer.spanBuilder("parent").startSpan();
54+
spanWithAppAndOp.setAttribute(testKey1, "testValue1");
55+
spanWithAppAndOp.setAttribute(testKey2, "testValue2");
56+
validateSpanAttributesInheritance(spanWithAppAndOp, null, "testValue1", "testValue2");
57+
}
58+
59+
@Test
60+
public void testOverrideAttributes() {
61+
Span parentSpan = tracer.spanBuilder("parent").startSpan();
62+
parentSpan.setAttribute(testKey1, "testValue1");
63+
parentSpan.setAttribute(testKey2, "testValue2");
64+
65+
Span transmitSpans1 = createNestedSpan(parentSpan, 2);
66+
67+
Span childSpan =
68+
tracer.spanBuilder("child:1").setParent(Context.current().with(transmitSpans1)).startSpan();
69+
70+
childSpan.setAttribute(testKey2, "testValue3");
71+
72+
Span transmitSpans2 = createNestedSpan(childSpan, 2);
73+
74+
assertThat(((ReadableSpan) transmitSpans2).getAttribute(testKey2)).isEqualTo("testValue3");
75+
}
76+
77+
@Test
78+
public void testAttributesDoNotExist() {
79+
Span span = tracer.spanBuilder("parent").startSpan();
80+
validateSpanAttributesInheritance(span, null, null, null);
81+
}
82+
83+
@Test
84+
public void testSpanNamePropagationBySpanKind() {
85+
for (SpanKind value : SpanKind.values()) {
86+
Span span = tracer.spanBuilder("parent").setSpanKind(value).startSpan();
87+
if (value == SpanKind.SERVER || value == SpanKind.CONSUMER) {
88+
validateSpanAttributesInheritance(span, "parent", null, null);
89+
} else {
90+
validateSpanAttributesInheritance(span, null, null, null);
91+
}
92+
}
93+
}
94+
95+
@Test
96+
public void testSpanNamePropagationWithRemoteParentSpan() {
97+
Span remoteParent =
98+
Span.wrap(
99+
SpanContext.createFromRemoteParent(
100+
"00000000000000000000000000000001",
101+
"0000000000000002",
102+
TraceFlags.getSampled(),
103+
TraceState.getDefault()));
104+
Context parentcontext = Context.root().with(remoteParent);
105+
Span span =
106+
tracer
107+
.spanBuilder("parent")
108+
.setSpanKind(SpanKind.SERVER)
109+
.setParent(parentcontext)
110+
.startSpan();
111+
validateSpanAttributesInheritance(span, "parent", null, null);
112+
}
113+
114+
private Span createNestedSpan(Span parentSpan, int depth) {
115+
if (depth == 0) {
116+
return parentSpan;
117+
}
118+
Span childSpan =
119+
tracer
120+
.spanBuilder("child:" + depth)
121+
.setParent(Context.current().with(parentSpan))
122+
.startSpan();
123+
try {
124+
return createNestedSpan(childSpan, depth - 1);
125+
} finally {
126+
childSpan.end();
127+
}
128+
}
129+
130+
private void validateSpanAttributesInheritance(
131+
Span parentSpan, String propagatedName, String propagationValue1, String propagatedValue2) {
132+
ReadableSpan leafSpan = (ReadableSpan) createNestedSpan(parentSpan, 10);
133+
134+
assertThat(leafSpan.getParentSpanContext()).isNotNull();
135+
assertThat(leafSpan.getName()).isEqualTo("child:1");
136+
if (propagatedName != null) {
137+
assertThat(leafSpan.getAttribute(spanNameKey)).isEqualTo(propagatedName);
138+
} else {
139+
assertThat(leafSpan.getAttribute(spanNameKey)).isNull();
140+
}
141+
if (propagationValue1 != null) {
142+
assertThat(leafSpan.getAttribute(testKey1)).isEqualTo(propagationValue1);
143+
} else {
144+
assertThat(leafSpan.getAttribute(testKey1)).isNull();
145+
}
146+
if (propagatedValue2 != null) {
147+
assertThat(leafSpan.getAttribute(testKey2)).isEqualTo(propagatedValue2);
148+
} else {
149+
assertThat(leafSpan.getAttribute(testKey2)).isNull();
150+
}
151+
}
152+
}

0 commit comments

Comments
 (0)