Skip to content

Commit d9d17ec

Browse files
willarmirosAnuraag Agrawal
andauthored
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

14 files changed

Lines changed: 123 additions & 33 deletions

File tree

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

0 commit comments

Comments
 (0)