Skip to content

Commit

Permalink
aws lambda instrumentation - http headers propagation fix (#2222)
Browse files Browse the repository at this point in the history
* aws lambda instrumentation - http headers propagation fix

* documented lambda propagation behaviour

* spotless apply
  • Loading branch information
kuba-wu committed Feb 10, 2021
1 parent ecf64c2 commit 8592d79
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 43 deletions.
32 changes: 4 additions & 28 deletions instrumentation/aws-lambda-1.0/library/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,36 +87,12 @@ public class MyBatchHandler extends TracingSQSEventHandler {

## Trace propagation

### X-Ray propagation
This instrumentation supports propagating traces using the `X-Amzn-Trace-Id` format for both normal
requests and SQS requests. To enable this propagation, in your code as early as possible,
configure the `AwsXrayPropagator` along with any other propagators you use. If in doubt, you can
configure X-Ray along with the default W3C propagator like this in a static block of your handler.

```java
class MyRequestHandler extends TracingRequestHandler<String, String> {

static {
OpenTelemetry.setGlobalPropagators(
DefaultContextPropagators.builder()
.addTextMapPropagator(HttpTraceContext.getInstance())
.addTextMapPropagator(AwsXrayPropagator.getInstance())
.build());
}
Context propagation for this instrumentation can be done either with X-Ray propagation or regular HTTP propagation. If X-Ray is enabled for instrumented lambda, it will be preferred. If X-Ray is disabled, HTTP propagation will be tried (that is HTTP headers will be read to check for a valid trace context).

@Override
protected String doHandleRequest(String input, Context context) {
// logic
}
}
```

If you are using this instrumentation with SQS, you should always enable the `AwsXrayPropagator` to
allow linking between messages in a backend-agnostic way.

Otherwise, only enable the above if you are using AWS X-Ray as your tracing backend. You should not
enable the X-Ray propagator if you are not using X-Ray as it will cause the spans in Lambda to not
have the correct parent/child connection between client and server spans.
### X-Ray propagation
This instrumentation supports propagating traces using the `X-Amzn-Trace-Id` format for both normal
requests and SQS requests. X-Ray propagation is always enabled, there is no need to configure it explicitely.

### HTTP headers based propagation
For API Gateway (HTTP) requests instrumented by using one of following methods:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,25 @@ static Context extract(Map<String, String> headers) {
Context parentContext = null;
String parentTraceHeader = System.getenv(AWS_TRACE_HEADER_ENV_KEY);
if (parentTraceHeader != null) {
parentContext = ParentContextExtractor.fromXRayHeader(parentTraceHeader);
parentContext = fromXRayHeader(parentTraceHeader);
}
if (!isValid(parentContext)) {
if (!isValidAndSampled(parentContext)) {
// try http
parentContext = ParentContextExtractor.fromHttpHeaders(headers);
parentContext = fromHttpHeaders(headers);
}
return parentContext;
}

private static boolean isValid(Context context) {
private static boolean isValidAndSampled(Context context) {
if (context == null) {
return false;
}
Span parentSpan = Span.fromContext(context);
SpanContext parentSpanContext = parentSpan.getSpanContext();
return parentSpanContext.isValid();
return (parentSpanContext.isValid() && parentSpanContext.isSampled());
}

static Context fromHttpHeaders(Map<String, String> headers) {
private static Context fromHttpHeaders(Map<String, String> headers) {
return BaseTracer.extractWithGlobalPropagators(lowercaseMap(headers), MapGetter.INSTANCE);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,75 @@
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.extension.trace.propagation.B3Propagator;
import java.util.Map;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.EnvironmentVariables;
import org.junit.contrib.java.lang.system.RestoreSystemProperties;

class ParentContextExtractorTest {
public class ParentContextExtractorTest {

@AfterEach
void resetOpenTelemetry() {
GlobalOpenTelemetry.resetForTest();
@Rule
public final RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();

@Rule public final EnvironmentVariables environmentVariables = new EnvironmentVariables();

@BeforeClass
public static void setUp() {
GlobalOpenTelemetry.set(
OpenTelemetry.getPropagating(ContextPropagators.create(B3Propagator.getInstance())));
}

@Test
public void shouldUseHttpIfAwsParentNotSampled() {
// given
Map<String, String> headers =
ImmutableMap.of(
"X-b3-traceId",
"4fd0b6131f19f39af59518d127b0cafe",
"x-b3-spanid",
"0000000000000123",
"X-B3-Sampled",
"true");
environmentVariables.set(
"_X_AMZN_TRACE_ID",
"Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=0000000000000456;Sampled=0");

// when
Context context = ParentContextExtractor.extract(headers);
// then
Span span = Span.fromContext(context);
SpanContext spanContext = span.getSpanContext();
assertThat(spanContext.isValid()).isTrue();
assertThat(spanContext.isValid()).isTrue();
assertThat(spanContext.getSpanId()).isEqualTo("0000000000000123");
assertThat(spanContext.getTraceId()).isEqualTo("4fd0b6131f19f39af59518d127b0cafe");
}

@Test
public void shouldPreferAwsParentHeaderIfValidAndSampled() {
// given
Map<String, String> headers =
ImmutableMap.of(
"X-b3-traceId",
"4fd0b6131f19f39af59518d127b0cafe",
"x-b3-spanid",
"0000000000000456",
"X-B3-Sampled",
"true");
environmentVariables.set(
"_X_AMZN_TRACE_ID",
"Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=0000000000000456;Sampled=1");

// when
Context context = ParentContextExtractor.extract(headers);
// then
Span span = Span.fromContext(context);
SpanContext spanContext = span.getSpanContext();
assertThat(spanContext.isValid()).isTrue();
assertThat(spanContext.isValid()).isTrue();
assertThat(spanContext.getSpanId()).isEqualTo("0000000000000456");
assertThat(spanContext.getTraceId()).isEqualTo("8a3c60f7d188f8fa79d48a391a778fa6");
}

@Test
Expand All @@ -37,11 +98,9 @@ public void shouldExtractCaseInsensitiveHeaders() {
"0000000000000456",
"X-B3-Sampled",
"true");
GlobalOpenTelemetry.set(
OpenTelemetry.getPropagating(ContextPropagators.create(B3Propagator.getInstance())));

// when
Context context = ParentContextExtractor.fromHttpHeaders(headers);
Context context = ParentContextExtractor.extract(headers);
// then
Span span = Span.fromContext(context);
SpanContext spanContext = span.getSpanContext();
Expand Down

0 comments on commit 8592d79

Please sign in to comment.