Skip to content

Commit c8bce79

Browse files
authored
[Bug fixes] Lambda - duplicate lambda spans + appsignals from unsampled spans (#1000)
### Issue 1 - Duplicate lambda root spans The instrumentation produces 2 lambda root spans upon each invocation of the function. This is a known issue in OTel: open-telemetry/opentelemetry-java-instrumentation#7808 #### Fix - Otel has fixed it in [this PR](open-telemetry/opentelemetry-java-instrumentation#10736) but the change wasn't ported to the 1.x versions. So I took the diff from the upstream commit and made it into a patch that we apply when building the ADOT Java Lambda Layer for v1.33.x. ### Issue 2 - Unsampled spans do not produce Application Signals metrics On lambda environment, we export 100% of the spans to X-Ray to ensure we are able to provide 100% Application Signals metrics. However, currently only the sampled spans show up on the "Services" page and the unsampled spans do not. #### Fix - Upon comparing the sampled vs unsampled spans, I noticed that the unsampled spans are missing the attributes like `aws.local.service` and `aws.local.operation` which are required to generate Application Signals metrics. - The fix is to wrap the `OtlpUdpSpanExporter` instance for unsampled spans with the `AwsMetricAttributesSpanExporter` so that the exported spans have the desired attributes. #### Testing - After creating a layer with the fix, I set the `OTEL_TRACES_SAMPLER` to `always_off`. Then I invoked the function once. - The metrics appeared on the Application Signals console. - See the following screenshots <img width="1722" alt="image" src="https://github.com/user-attachments/assets/fa10e09f-ae24-4ab3-989f-838aacfb7e50" /> <img width="1722" alt="image" src="https://github.com/user-attachments/assets/d777304f-2cd9-4348-8e29-74f4a8b6b917" /> *Description of changes:* By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent a7d3e00 commit c8bce79

File tree

2 files changed

+106
-2
lines changed

2 files changed

+106
-2
lines changed

awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java

+19-1
Original file line numberDiff line numberDiff line change
@@ -206,9 +206,27 @@ private SdkTracerProviderBuilder customizeTracerProviderBuilder(
206206

207207
// If running on Lambda, we just need to export 100% spans and skip generating any Application
208208
// Signals metrics.
209-
if (isLambdaEnvironment()) {
209+
if (isLambdaEnvironment()
210+
&& System.getenv(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT_CONFIG) == null) {
211+
String tracesEndpoint =
212+
Optional.ofNullable(System.getenv(AWS_XRAY_DAEMON_ADDRESS_CONFIG))
213+
.orElse(DEFAULT_UDP_ENDPOINT);
214+
SpanExporter spanExporter =
215+
new OtlpUdpSpanExporterBuilder()
216+
.setPayloadSampleDecision(TracePayloadSampleDecision.UNSAMPLED)
217+
.setEndpoint(tracesEndpoint)
218+
.build();
219+
220+
// Wrap the udp exporter with the AwsMetricsAttributesSpanExporter to add Application
221+
// Signals attributes to unsampled spans too
222+
SpanExporter appSignalsSpanExporter =
223+
AwsMetricAttributesSpanExporterBuilder.create(
224+
spanExporter, ResourceHolder.getResource())
225+
.build();
226+
210227
tracerProviderBuilder.addSpanProcessor(
211228
AwsUnsampledOnlySpanProcessorBuilder.create()
229+
.setSpanExporter(appSignalsSpanExporter)
212230
.setMaxExportBatchSize(LAMBDA_SPAN_EXPORT_BATCH_SIZE)
213231
.build());
214232
return tracerProviderBuilder;

lambda-layer/patches/opentelemetry-java-instrumentation.patch

+87-1
Original file line numberDiff line numberDiff line change
@@ -642,4 +642,90 @@ index cc1414c0bf..db8a59b046 100644
642642
+val alphaVersion = "1.33.6-adot-lambda1-alpha"
643643

644644
allprojects {
645-
if (findProperty("otel.stable") != "true") {
645+
if (findProperty("otel.stable") != "true") {
646+
diff --git a/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaRequestHandlerInstrumentation.java b/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaRequestHandlerInstrumentation.java
647+
index 2332f24a8f..0743cdea75 100644
648+
--- a/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaRequestHandlerInstrumentation.java
649+
+++ b/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaRequestHandlerInstrumentation.java
650+
@@ -10,7 +10,9 @@ import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.
651+
import static io.opentelemetry.javaagent.instrumentation.awslambdacore.v1_0.AwsLambdaInstrumentationHelper.functionInstrumenter;
652+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
653+
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
654+
+import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
655+
import static net.bytebuddy.matcher.ElementMatchers.named;
656+
+import static net.bytebuddy.matcher.ElementMatchers.not;
657+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
658+
659+
import com.amazonaws.services.lambda.runtime.Context;
660+
@@ -35,7 +37,8 @@ public class AwsLambdaRequestHandlerInstrumentation implements TypeInstrumentati
661+
662+
@Override
663+
public ElementMatcher<TypeDescription> typeMatcher() {
664+
- return implementsInterface(named("com.amazonaws.services.lambda.runtime.RequestHandler"));
665+
+ return implementsInterface(named("com.amazonaws.services.lambda.runtime.RequestHandler"))
666+
+ .and(not(nameStartsWith("com.amazonaws.services.lambda.runtime.api.client")));
667+
}
668+
669+
@Override
670+
diff --git a/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaTest.java b/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaTest.java
671+
index af939fcb6d..8b8398950a 100644
672+
--- a/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaTest.java
673+
+++ b/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaTest.java
674+
@@ -5,14 +5,19 @@
675+
676+
package io.opentelemetry.javaagent.instrumentation.awslambdacore.v1_0;
677+
678+
+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
679+
import static org.assertj.core.api.Assertions.assertThat;
680+
681+
import com.amazonaws.services.lambda.runtime.Context;
682+
import com.amazonaws.services.lambda.runtime.RequestHandler;
683+
+import com.amazonaws.services.lambda.runtime.api.client.AwsLambdaInternalRequestHandler;
684+
+import io.opentelemetry.api.trace.SpanKind;
685+
import io.opentelemetry.instrumentation.awslambdacore.v1_0.AbstractAwsLambdaTest;
686+
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
687+
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
688+
+import io.opentelemetry.semconv.SemanticAttributes;
689+
import org.junit.jupiter.api.AfterEach;
690+
+import org.junit.jupiter.api.Test;
691+
import org.junit.jupiter.api.extension.RegisterExtension;
692+
693+
public class AwsLambdaTest extends AbstractAwsLambdaTest {
694+
@@ -35,6 +40,22 @@ public class AwsLambdaTest extends AbstractAwsLambdaTest {
695+
assertThat(testing.forceFlushCalled()).isTrue();
696+
}
697+
698+
+ @Test
699+
+ void awsLambdaInternalHandlerIgnoredAndUserHandlerTraced() {
700+
+ RequestHandler<String, String> handler = new AwsLambdaInternalRequestHandler(handler());
701+
+ String result = handler.handleRequest("hello", context());
702+
+ assertThat(result).isEqualTo("world");
703+
+ testing()
704+
+ .waitAndAssertTraces(
705+
+ trace ->
706+
+ trace.hasSpansSatisfyingExactly(
707+
+ span ->
708+
+ span.hasName("my_function")
709+
+ .hasKind(SpanKind.SERVER)
710+
+ .hasAttributesSatisfyingExactly(
711+
+ equalTo(SemanticAttributes.FAAS_INVOCATION_ID, "1-22-333"))));
712+
+ }
713+
+
714+
private static final class TestRequestHandler implements RequestHandler<String, String> {
715+
716+
@Override
717+
diff --git a/instrumentation/aws-lambda/aws-lambda-core-1.0/testing/src/main/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/AbstractAwsLambdaTest.java b/instrumentation/aws-lambda/aws-lambda-core-1.0/testing/src/main/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/AbstractAwsLambdaTest.java
718+
index 94a85244e2..25a32896aa 100644
719+
--- a/instrumentation/aws-lambda/aws-lambda-core-1.0/testing/src/main/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/AbstractAwsLambdaTest.java
720+
+++ b/instrumentation/aws-lambda/aws-lambda-core-1.0/testing/src/main/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/AbstractAwsLambdaTest.java
721+
@@ -53,6 +53,10 @@ public abstract class AbstractAwsLambdaTest {
722+
assertThat(testing().forceFlushCalled()).isTrue();
723+
}
724+
725+
+ protected Context context() {
726+
+ return context;
727+
+ }
728+
+
729+
@Test
730+
void handlerTraced() {
731+
String result = handler().handleRequest("hello", context);

0 commit comments

Comments
 (0)