Skip to content

Commit d9d17ec

Browse files
willarmirosAnuraag Agrawal
and
Anuraag Agrawal
authored
Improvements to no-op subsegment behavior (#294)
* various improvements to no-op subsegment behavior * removed setter Co-authored-by: Anuraag Agrawal <aanuraag@amazon.co.jp>
1 parent 297a0bf commit d9d17ec

File tree

14 files changed

+123
-33
lines changed

14 files changed

+123
-33
lines changed

aws-xray-recorder-sdk-apache-http/src/main/java/com/amazonaws/xray/proxies/apache/http/TracedHttpClient.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,12 @@ public static void addRequestInformation(Subsegment subsegment, HttpRequest requ
110110
subsegment.setNamespace(Namespace.REMOTE.toString());
111111
Segment parentSegment = subsegment.getParentSegment();
112112

113-
TraceHeader header = new TraceHeader(parentSegment.getTraceId(),
114-
parentSegment.isSampled() ? subsegment.getId() : null,
115-
parentSegment.isSampled() ? SampleDecision.SAMPLED : SampleDecision.NOT_SAMPLED);
116-
request.addHeader(TraceHeader.HEADER_KEY, header.toString());
113+
if (subsegment.shouldPropagate()) {
114+
TraceHeader header = new TraceHeader(parentSegment.getTraceId(),
115+
parentSegment.isSampled() ? subsegment.getId() : null,
116+
parentSegment.isSampled() ? SampleDecision.SAMPLED : SampleDecision.NOT_SAMPLED);
117+
request.addHeader(TraceHeader.HEADER_KEY, header.toString());
118+
}
117119

118120
Map<String, Object> requestInformation = new HashMap<>();
119121

aws-xray-recorder-sdk-aws-sdk-v2/src/main/java/com/amazonaws/xray/interceptors/TracingInterceptor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, Execu
245245
SdkHttpRequest httpRequest = context.httpRequest();
246246

247247
Subsegment subsegment = executionAttributes.getAttribute(entityKey);
248-
if (subsegment == null) {
248+
if (!subsegment.shouldPropagate()) {
249249
return httpRequest;
250250
}
251251

aws-xray-recorder-sdk-aws-sdk-v2/src/test/java/com/amazonaws/xray/interceptors/TracingInterceptorTest.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515

1616
package com.amazonaws.xray.interceptors;
1717

18+
import static org.mockito.ArgumentMatchers.anyString;
19+
import static org.mockito.Mockito.never;
20+
import static org.mockito.Mockito.verify;
21+
import static org.mockito.Mockito.when;
22+
1823
import com.amazonaws.xray.AWSXRay;
1924
import com.amazonaws.xray.AWSXRayRecorderBuilder;
2025
import com.amazonaws.xray.emitters.Emitter;
@@ -40,10 +45,13 @@
4045
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
4146
import software.amazon.awssdk.core.async.EmptyPublisher;
4247
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
48+
import software.amazon.awssdk.core.interceptor.Context;
49+
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
4350
import software.amazon.awssdk.http.AbortableInputStream;
4451
import software.amazon.awssdk.http.ExecutableHttpRequest;
4552
import software.amazon.awssdk.http.HttpExecuteResponse;
4653
import software.amazon.awssdk.http.SdkHttpClient;
54+
import software.amazon.awssdk.http.SdkHttpRequest;
4755
import software.amazon.awssdk.http.SdkHttpResponse;
4856
import software.amazon.awssdk.http.async.AsyncExecuteRequest;
4957
import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
@@ -86,8 +94,8 @@ private SdkHttpClient mockSdkHttpClient(SdkHttpResponse response, String body) t
8694
ExecutableHttpRequest abortableCallable = Mockito.mock(ExecutableHttpRequest.class);
8795
SdkHttpClient mockClient = Mockito.mock(SdkHttpClient.class);
8896

89-
Mockito.when(mockClient.prepareRequest(Mockito.any())).thenReturn(abortableCallable);
90-
Mockito.when(abortableCallable.call()).thenReturn(HttpExecuteResponse.builder()
97+
when(mockClient.prepareRequest(Mockito.any())).thenReturn(abortableCallable);
98+
when(abortableCallable.call()).thenReturn(HttpExecuteResponse.builder()
9199
.response(response)
92100
.responseBody(AbortableInputStream.create(
93101
new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8))
@@ -99,7 +107,7 @@ private SdkHttpClient mockSdkHttpClient(SdkHttpResponse response, String body) t
99107

100108
private SdkAsyncHttpClient mockSdkAsyncHttpClient(SdkHttpResponse response) {
101109
SdkAsyncHttpClient mockClient = Mockito.mock(SdkAsyncHttpClient.class);
102-
Mockito.when(mockClient.execute(Mockito.any(AsyncExecuteRequest.class)))
110+
when(mockClient.execute(Mockito.any(AsyncExecuteRequest.class)))
103111
.thenAnswer((Answer<CompletableFuture<Void>>) invocationOnMock -> {
104112
AsyncExecuteRequest request = invocationOnMock.getArgument(0);
105113
SdkAsyncHttpResponseHandler handler = request.responseHandler();
@@ -562,5 +570,22 @@ public void testAsync500Exception() {
562570
Assert.assertEquals(true, cause.getExceptions().get(0).isRemote());
563571
}
564572
}
573+
574+
@Test
575+
public void testNoHeaderAddedWhenPropagationOff() {
576+
Subsegment subsegment = Subsegment.noOp(AWSXRay.getGlobalRecorder(), false);
577+
TracingInterceptor interceptor = new TracingInterceptor();
578+
Context.ModifyHttpRequest context = Mockito.mock(Context.ModifyHttpRequest.class);
579+
SdkHttpRequest mockRequest = Mockito.mock(SdkHttpRequest.class);
580+
SdkHttpRequest.Builder mockRequestBuilder = Mockito.mock(SdkHttpRequest.Builder.class);
581+
when(context.httpRequest()).thenReturn(mockRequest);
582+
Mockito.lenient().when(context.httpRequest().toBuilder()).thenReturn(mockRequestBuilder);
583+
ExecutionAttributes attributes = new ExecutionAttributes();
584+
attributes.putAttribute(TracingInterceptor.entityKey, subsegment);
585+
586+
interceptor.modifyHttpRequest(context, attributes);
587+
588+
verify(mockRequest.toBuilder(), never()).appendHeader(anyString(), anyString());
589+
}
565590
}
566591

aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/TracingHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ public void beforeRequest(Request<?> request) {
191191
}
192192
currentSubsegment.setNamespace(Namespace.AWS.toString());
193193

194-
if (recorder.getCurrentSegment() != null) {
194+
if (recorder.getCurrentSegment() != null && recorder.getCurrentSubsegment().shouldPropagate()) {
195195
TraceHeader header =
196196
new TraceHeader(recorder.getCurrentSegment().getTraceId(),
197197
recorder.getCurrentSegment().isSampled() ? currentSubsegment.getId() : null,

aws-xray-recorder-sdk-core/src/main/java/com/amazonaws/xray/AWSXRayRecorder.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,9 @@ public Subsegment beginSubsegment(String name) {
609609
if (context == null) {
610610
// No context available, we return a no-op subsegment so user code does not have to work around this. Based on
611611
// ContextMissingStrategy they will still know about the issue unless they explicitly opt-ed out.
612-
return Subsegment.noOp(this);
612+
// This no-op subsegment is different from unsampled no-op subsegments only in that it should not cause trace
613+
// context to be propagated downstream
614+
return Subsegment.noOp(this, false);
613615
}
614616
return context.beginSubsegment(this, name);
615617
}

aws-xray-recorder-sdk-core/src/main/java/com/amazonaws/xray/contexts/LambdaSegmentContext.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,11 @@ public void endSubsegment(AWSXRayRecorder recorder) {
107107
Entity current = getTraceEntity();
108108
if (current instanceof Subsegment) {
109109
if (logger.isDebugEnabled()) {
110-
logger.debug("Ending subsegment named: " + current.getName());
110+
if (current.getName().isEmpty() && !current.getParentSegment().isSampled()) {
111+
logger.debug("Ending no-op subsegment");
112+
} else {
113+
logger.debug("Ending subsegment named: " + current.getName());
114+
}
111115
}
112116
Subsegment currentSubsegment = (Subsegment) current;
113117

@@ -139,7 +143,8 @@ public void endSubsegment(AWSXRayRecorder recorder) {
139143
}
140144

141145
} else {
142-
throw new SubsegmentNotFoundException("Failed to end a subsegment: subsegment cannot be found.");
146+
recorder.getContextMissingStrategy().contextMissing("Failed to end subsegment: subsegment cannot be found.",
147+
SubsegmentNotFoundException.class);
143148
}
144149
}
145150
}

aws-xray-recorder-sdk-core/src/main/java/com/amazonaws/xray/contexts/ThreadLocalSegmentContext.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public Subsegment beginSubsegment(AWSXRayRecorder recorder, String name) {
3939
if (current == null) {
4040
recorder.getContextMissingStrategy().contextMissing("Failed to begin subsegment named '" + name
4141
+ "': segment cannot be found.", SegmentNotFoundException.class);
42-
return Subsegment.noOp(recorder);
42+
return Subsegment.noOp(recorder, false);
4343
}
4444
if (logger.isDebugEnabled()) {
4545
logger.debug("Beginning subsegment named: " + name);
@@ -65,7 +65,11 @@ public void endSubsegment(AWSXRayRecorder recorder) {
6565
Entity current = getTraceEntity();
6666
if (current instanceof Subsegment) {
6767
if (logger.isDebugEnabled()) {
68-
logger.debug("Ending subsegment named: " + current.getName());
68+
if (current.getName().isEmpty() && !current.getParentSegment().isSampled()) {
69+
logger.debug("Ending no-op subsegment");
70+
} else {
71+
logger.debug("Ending subsegment named: " + current.getName());
72+
}
6973
}
7074
Subsegment currentSubsegment = (Subsegment) current;
7175

aws-xray-recorder-sdk-core/src/main/java/com/amazonaws/xray/entities/DummySubsegment.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
import org.checkerframework.checker.nullness.qual.Nullable;
2828

2929
/**
30-
* @deprecated Use {@link Subsegment#noOp(AWSXRayRecorder)}.
30+
* @deprecated Use {@link Subsegment#noOp(AWSXRayRecorder, boolean)}.
3131
*/
3232
@Deprecated
3333
public class DummySubsegment implements Subsegment {
@@ -340,6 +340,11 @@ public void setPrecursorIds(Set<String> precursorIds) {
340340
public void addPrecursorId(String precursorId) {
341341
}
342342

343+
@Override
344+
public boolean shouldPropagate() {
345+
return false;
346+
}
347+
343348
@Override
344349
public String streamSerialize() {
345350
return "";

aws-xray-recorder-sdk-core/src/main/java/com/amazonaws/xray/entities/NoOpSubSegment.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,18 @@ class NoOpSubSegment implements Subsegment {
2727

2828
private final Segment parentSegment;
2929
private final AWSXRayRecorder creator;
30+
private final boolean shouldPropagate;
3031

3132
private volatile Entity parent;
3233

3334
NoOpSubSegment(Segment parentSegment, AWSXRayRecorder creator) {
35+
this(parentSegment, creator, true);
36+
}
37+
38+
NoOpSubSegment(Segment parentSegment, AWSXRayRecorder creator, boolean shouldPropagate) {
3439
this.parentSegment = parentSegment;
3540
this.creator = creator;
41+
this.shouldPropagate = shouldPropagate;
3642
parent = parentSegment;
3743
}
3844

@@ -347,6 +353,11 @@ public void setPrecursorIds(Set<String> precursorIds) {
347353
public void addPrecursorId(String precursorId) {
348354
}
349355

356+
@Override
357+
public boolean shouldPropagate() {
358+
return this.shouldPropagate;
359+
}
360+
350361
@Override
351362
public String streamSerialize() {
352363
return "";

aws-xray-recorder-sdk-core/src/main/java/com/amazonaws/xray/entities/Subsegment.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121

2222
public interface Subsegment extends Entity {
2323

24-
static Subsegment noOp(AWSXRayRecorder recorder) {
25-
return new NoOpSubSegment(Segment.noOp(TraceID.invalid(), recorder), recorder);
24+
static Subsegment noOp(AWSXRayRecorder recorder, boolean shouldPropagate) {
25+
return new NoOpSubSegment(Segment.noOp(TraceID.invalid(), recorder), recorder, shouldPropagate);
2626
}
2727

2828
static Subsegment noOp(Segment parent, AWSXRayRecorder recorder) {
@@ -78,6 +78,12 @@ static Subsegment noOp(Segment parent, AWSXRayRecorder recorder) {
7878
*/
7979
void addPrecursorId(String precursorId);
8080

81+
/**
82+
* Determines if this subsegment should propagate its trace context downstream
83+
* @return true if its trace context should be propagated downstream, false otherwise
84+
*/
85+
boolean shouldPropagate();
86+
8187
/**
8288
* Serializes the subsegment as a standalone String with enough information for the subsegment to be streamed on its own.
8389
* @return

aws-xray-recorder-sdk-core/src/main/java/com/amazonaws/xray/entities/SubsegmentImpl.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ public class SubsegmentImpl extends EntityImpl implements Subsegment {
3838
@GuardedBy("lock")
3939
private Set<String> precursorIds;
4040

41+
@GuardedBy("lock")
42+
private boolean shouldPropagate;
43+
4144
@SuppressWarnings("nullness")
4245
private SubsegmentImpl() {
4346
super();
@@ -48,6 +51,7 @@ public SubsegmentImpl(AWSXRayRecorder creator, String name, Segment parentSegmen
4851
this.parentSegment = parentSegment;
4952
parentSegment.incrementReferenceCount();
5053
this.precursorIds = new HashSet<>();
54+
this.shouldPropagate = true;
5155
}
5256

5357
@Override
@@ -125,6 +129,13 @@ public void setPrecursorIds(Set<String> precursorIds) {
125129
}
126130
}
127131

132+
@Override
133+
public boolean shouldPropagate() {
134+
synchronized (lock) {
135+
return shouldPropagate;
136+
}
137+
}
138+
128139
private ObjectNode getStreamSerializeObjectNode() {
129140
synchronized (lock) {
130141
ObjectNode obj = (ObjectNode) mapper.valueToTree(this);

aws-xray-recorder-sdk-core/src/main/java/com/amazonaws/xray/javax/servlet/AWSXRayServletAsyncListener.java

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
package com.amazonaws.xray.javax.servlet;
1717

18-
import com.amazonaws.xray.AWSXRay;
1918
import com.amazonaws.xray.AWSXRayRecorder;
2019
import com.amazonaws.xray.entities.Entity;
2120
import java.io.IOException;
@@ -39,15 +38,7 @@ class AWSXRayServletAsyncListener implements AsyncListener {
3938
this.recorder = recorder;
4039
}
4140

42-
private AWSXRayRecorder getRecorder() {
43-
if (recorder == null) {
44-
recorder = AWSXRay.getGlobalRecorder();
45-
}
46-
return recorder;
47-
}
48-
4941
private void processEvent(AsyncEvent event) throws IOException {
50-
AWSXRayRecorder recorder = getRecorder();
5142
Entity entity = (Entity) event.getSuppliedRequest().getAttribute(ENTITY_ATTRIBUTE_KEY);
5243
entity.run(() -> {
5344
if (event.getThrowable() != null) {

aws-xray-recorder-sdk-core/src/test/java/com/amazonaws/xray/AWSXRayRecorderTest.java

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,7 @@ public void testBeginSubsegmentWhenMissingContext() {
505505
// No-op
506506
subsegment.setNamespace("foo");
507507
assertThat(subsegment.getNamespace()).isEmpty();
508+
assertThat(subsegment.shouldPropagate()).isFalse();
508509
}
509510

510511
@Test
@@ -736,8 +737,8 @@ public void testNoOpSegment() throws Exception {
736737
assertThat(segment.getCreator()).isEqualTo(AWSXRay.getGlobalRecorder());
737738
segment.setRuleName("foo");
738739
assertThat(segment.getParentSegment()).isSameAs(segment);
739-
segment.getSubsegments().add(Subsegment.noOp(AWSXRay.getGlobalRecorder()));
740-
segment.addSubsegment(Subsegment.noOp(AWSXRay.getGlobalRecorder()));
740+
segment.getSubsegments().add(Subsegment.noOp(AWSXRay.getGlobalRecorder(), true));
741+
segment.addSubsegment(Subsegment.noOp(AWSXRay.getGlobalRecorder(), true));
741742
assertThat(segment.getSubsegments()).isEmpty();
742743
segment.addException(new IllegalStateException());
743744
assertThat(segment.getReferenceCount()).isZero();
@@ -747,7 +748,7 @@ public void testNoOpSegment() throws Exception {
747748
assertThat(segment.getReferenceCount()).isZero();
748749
segment.decrementReferenceCount();
749750
assertThat(segment.getReferenceCount()).isZero();
750-
segment.removeSubsegment(Subsegment.noOp(AWSXRay.getGlobalRecorder()));
751+
segment.removeSubsegment(Subsegment.noOp(AWSXRay.getGlobalRecorder(), true));
751752
segment.setEmitted(true);
752753
segment.compareAndSetEmitted(false, true);
753754
assertThat(segment.isEmitted()).isFalse();
@@ -788,7 +789,7 @@ public void testAlwaysCreateTraceId() {
788789

789790
@Test
790791
public void testNoOpSubsegment() throws Exception {
791-
Subsegment subsegment = Subsegment.noOp(AWSXRay.getGlobalRecorder());
792+
Subsegment subsegment = Subsegment.noOp(AWSXRay.getGlobalRecorder(), true);
792793

793794
Map<String, Object> map = new HashMap<>();
794795
map.put("dog", "bark");
@@ -854,8 +855,8 @@ public void testNoOpSubsegment() throws Exception {
854855
subsegment.setCreator(AWSXRayRecorderBuilder.standard().build());
855856
assertThat(subsegment.getCreator()).isEqualTo(AWSXRay.getGlobalRecorder());
856857
assertThat(subsegment.getParentSegment().getTraceId()).isEqualTo(TraceID.invalid());
857-
subsegment.getSubsegments().add(Subsegment.noOp(AWSXRay.getGlobalRecorder()));
858-
subsegment.addSubsegment(Subsegment.noOp(AWSXRay.getGlobalRecorder()));
858+
subsegment.getSubsegments().add(Subsegment.noOp(AWSXRay.getGlobalRecorder(), true));
859+
subsegment.addSubsegment(Subsegment.noOp(AWSXRay.getGlobalRecorder(), true));
859860
assertThat(subsegment.getSubsegments()).isEmpty();
860861
subsegment.addException(new IllegalStateException());
861862
assertThat(subsegment.getReferenceCount()).isZero();
@@ -865,7 +866,7 @@ public void testNoOpSubsegment() throws Exception {
865866
assertThat(subsegment.getReferenceCount()).isZero();
866867
subsegment.decrementReferenceCount();
867868
assertThat(subsegment.getReferenceCount()).isZero();
868-
subsegment.removeSubsegment(Subsegment.noOp(AWSXRay.getGlobalRecorder()));
869+
subsegment.removeSubsegment(Subsegment.noOp(AWSXRay.getGlobalRecorder(), true));
869870
subsegment.setEmitted(true);
870871
subsegment.compareAndSetEmitted(false, true);
871872
assertThat(subsegment.isEmitted()).isFalse();
@@ -935,4 +936,17 @@ public void testLogGroupFromEnvironment() {
935936
Set<AWSLogReference> logReferences = (Set<AWSLogReference>) segment.getAws().get("cloudwatch_logs");
936937
assertThat(logReferences).containsOnly(expected);
937938
}
939+
940+
@Test
941+
public void testUnsampledSubsegmentPropagation() {
942+
AWSXRayRecorder recorder = AWSXRayRecorderBuilder.standard()
943+
.withSamplingStrategy(new NoSamplingStrategy())
944+
.build();
945+
946+
Segment segment = recorder.beginSegmentWithSampling("test");
947+
Subsegment subsegment = recorder.beginSubsegment("test");
948+
949+
assertThat(segment.isSampled()).isFalse();
950+
assertThat(subsegment.shouldPropagate()).isTrue();
951+
}
938952
}

0 commit comments

Comments
 (0)