From f09f07190c6be7992f447a18f743a49253b4fa59 Mon Sep 17 00:00:00 2001 From: Luke Zhang Date: Tue, 19 Aug 2025 10:32:29 -0700 Subject: [PATCH 1/6] fix(dips): update OTel Java Agent to v2.18.1 and OTel Contrib to v1.48.0 This updates upstream dependency OTel Java Agent to v2.18.1 and upgrades upstream OTel Contrib to v1.48.0. It removes the Remote Sampling patch file, as that code has been migrated to upstream OTel Contrib v1.48.0. Tests: - Unit tests pass: ./gradlew build test - Smoke/Contract tests pass: ./gradlew appsignals-tests:contract-tests:contractTests - X-Ray remote sampling service end-to-end manual tests pass - EC2 end-to-end tests with SpringBoot Java app pass - Lambda end-to-end tests with SpringBoot Java app pass --- .../patches/opentelemetry-java-contrib.patch | 237 ------------------ .github/patches/versions | 3 +- .gitignore | 2 + build.gradle.kts | 1 + dependencyManagement/build.gradle.kts | 8 +- lambda-layer/build-layer.sh | 16 -- 6 files changed, 8 insertions(+), 259 deletions(-) delete mode 100644 .github/patches/opentelemetry-java-contrib.patch diff --git a/.github/patches/opentelemetry-java-contrib.patch b/.github/patches/opentelemetry-java-contrib.patch deleted file mode 100644 index 65a6f8b780..0000000000 --- a/.github/patches/opentelemetry-java-contrib.patch +++ /dev/null @@ -1,237 +0,0 @@ -diff --git a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplier.java b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplier.java -index 1ef8abf..ef84f35 100644 ---- a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplier.java -+++ b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplier.java -@@ -35,6 +35,11 @@ final class SamplingRuleApplier { - - private static final Map XRAY_CLOUD_PLATFORM; - -+ private static final AttributeKey URL_PATH = AttributeKey.stringKey("url.path"); -+ private static final AttributeKey URL_FULL = AttributeKey.stringKey("url.full"); -+ private static final AttributeKey HTTP_REQUEST_METHOD = -+ AttributeKey.stringKey("http.request.method"); -+ - static { - Map xrayCloudPlatform = new HashMap<>(); - xrayCloudPlatform.put(ResourceAttributes.CloudPlatformValues.AWS_EC2, "AWS::EC2::Instance"); -@@ -162,11 +167,14 @@ final class SamplingRuleApplier { - String host = null; - - for (Map.Entry, Object> entry : attributes.asMap().entrySet()) { -- if (entry.getKey().equals(SemanticAttributes.HTTP_TARGET)) { -+ if (entry.getKey().equals(SemanticAttributes.HTTP_TARGET) -+ || entry.getKey().equals(URL_PATH)) { - httpTarget = (String) entry.getValue(); -- } else if (entry.getKey().equals(SemanticAttributes.HTTP_URL)) { -+ } else if (entry.getKey().equals(SemanticAttributes.HTTP_URL) -+ || entry.getKey().equals(URL_FULL)) { - httpUrl = (String) entry.getValue(); -- } else if (entry.getKey().equals(SemanticAttributes.HTTP_METHOD)) { -+ } else if (entry.getKey().equals(SemanticAttributes.HTTP_METHOD) -+ || entry.getKey().equals(HTTP_REQUEST_METHOD)) { - httpMethod = (String) entry.getValue(); - } else if (entry.getKey().equals(SemanticAttributes.NET_HOST_NAME)) { - host = (String) entry.getValue(); -diff --git a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplierTest.java b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplierTest.java -index 6bb6e82..55dabbd 100644 ---- a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplierTest.java -+++ b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplierTest.java -@@ -42,6 +42,11 @@ class SamplingRuleApplierTest { - - private static final String CLIENT_ID = "test-client-id"; - -+ private static final AttributeKey URL_PATH = AttributeKey.stringKey("url.path"); -+ private static final AttributeKey URL_FULL = AttributeKey.stringKey("url.full"); -+ private static final AttributeKey HTTP_REQUEST_METHOD = -+ AttributeKey.stringKey("http.request.method"); -+ - @Nested - @SuppressWarnings("ClassCanBeStatic") - class ExactMatch { -@@ -68,6 +73,15 @@ class SamplingRuleApplierTest { - .put(AttributeKey.longKey("speed"), 10) - .build(); - -+ private final Attributes newSemCovAttributes = -+ Attributes.builder() -+ .put(HTTP_REQUEST_METHOD, "GET") -+ .put(SemanticAttributes.NET_HOST_NAME, "opentelemetry.io") -+ .put(URL_PATH, "/instrument-me") -+ .put(AttributeKey.stringKey("animal"), "cat") -+ .put(AttributeKey.longKey("speed"), 10) -+ .build(); -+ - // FixedRate set to 1.0 in rule and no reservoir - @Test - void fixedRateAlwaysSample() { -@@ -116,6 +130,21 @@ class SamplingRuleApplierTest { - .isTrue(); - } - -+ @Test -+ void matchesURLFullNewSemCov() { -+ assertThat(applier.matches(newSemCovAttributes, resource)).isTrue(); -+ -+ // http.url works too -+ assertThat( -+ applier.matches( -+ attributes.toBuilder() -+ .remove(URL_FULL) -+ .put(URL_FULL, "scheme://host:port/instrument-me") -+ .build(), -+ resource)) -+ .isTrue(); -+ } -+ - @Test - void serviceNameNotMatch() { - assertThat( -@@ -137,6 +166,13 @@ class SamplingRuleApplierTest { - assertThat(applier.matches(attributes, resource)).isFalse(); - } - -+ @Test -+ void methodNewSemCovNotMatch() { -+ Attributes attributes = -+ this.newSemCovAttributes.toBuilder().put(HTTP_REQUEST_METHOD, "POST").build(); -+ assertThat(applier.matches(attributes, resource)).isFalse(); -+ } -+ - @Test - void hostNotMatch() { - // Replacing dot with character makes sure we're not accidentally treating dot as regex -@@ -178,6 +214,34 @@ class SamplingRuleApplierTest { - assertThat(applier.matches(attributes, resource)).isFalse(); - } - -+ @Test -+ void pathNewSemCovNotMatch() { -+ Attributes attributes = -+ this.newSemCovAttributes.toBuilder().put(URL_PATH, "/instrument-you").build(); -+ assertThat(applier.matches(attributes, resource)).isFalse(); -+ attributes = -+ this.newSemCovAttributes.toBuilder() -+ .remove(URL_PATH) -+ .put(URL_FULL, "scheme://host:port/instrument-you") -+ .build(); -+ assertThat(applier.matches(attributes, resource)).isFalse(); -+ attributes = -+ this.newSemCovAttributes.toBuilder() -+ .remove(URL_PATH) -+ .put(URL_FULL, "scheme://host:port") -+ .build(); -+ assertThat(applier.matches(attributes, resource)).isFalse(); -+ -+ // Correct path, but we ignore anyways since the URL is malformed per spec, scheme is always -+ // present. -+ attributes = -+ this.newSemCovAttributes.toBuilder() -+ .remove(URL_PATH) -+ .put(URL_FULL, "host:port/instrument-me") -+ .build(); -+ assertThat(applier.matches(attributes, resource)).isFalse(); -+ } -+ - @Test - void attributeNotMatch() { - Attributes attributes = -@@ -243,6 +307,15 @@ class SamplingRuleApplierTest { - .put(AttributeKey.longKey("speed"), 10) - .build(); - -+ private final Attributes newSemCovAttributes = -+ Attributes.builder() -+ .put(HTTP_REQUEST_METHOD, "GET") -+ .put(SemanticAttributes.NET_HOST_NAME, "opentelemetry.io") -+ .put(URL_PATH, "/instrument-me?foo=bar&cat=meow") -+ .put(AttributeKey.stringKey("animal"), "cat") -+ .put(AttributeKey.longKey("speed"), 10) -+ .build(); -+ - // FixedRate set to 0.0 in rule and no reservoir - @Test - void fixedRateNeverSample() { -@@ -329,6 +402,26 @@ class SamplingRuleApplierTest { - assertThat(applier.matches(attributes, resource)).isFalse(); - } - -+ @Test -+ void newSemCovMethodMatches() { -+ Attributes attributes = -+ this.newSemCovAttributes.toBuilder().put(HTTP_REQUEST_METHOD, "BADGETGOOD").build(); -+ assertThat(applier.matches(attributes, resource)).isTrue(); -+ attributes = newSemCovAttributes.toBuilder().put(HTTP_REQUEST_METHOD, "BADGET").build(); -+ assertThat(applier.matches(attributes, resource)).isTrue(); -+ attributes = newSemCovAttributes.toBuilder().put(HTTP_REQUEST_METHOD, "GETGET").build(); -+ assertThat(applier.matches(attributes, resource)).isTrue(); -+ } -+ -+ @Test -+ void newSemCovMethodNotMatch() { -+ Attributes attributes = -+ newSemCovAttributes.toBuilder().put(HTTP_REQUEST_METHOD, "POST").build(); -+ assertThat(applier.matches(attributes, resource)).isFalse(); -+ attributes = removeAttribute(newSemCovAttributes, HTTP_REQUEST_METHOD); -+ assertThat(applier.matches(attributes, resource)).isFalse(); -+ } -+ - @Test - void hostMatches() { - Attributes attributes = -@@ -410,6 +503,29 @@ class SamplingRuleApplierTest { - assertThat(applier.matches(attributes, resource)).isFalse(); - } - -+ @Test -+ void pathNewSemCovMatches() { -+ Attributes attributes = -+ newSemCovAttributes.toBuilder().put(URL_PATH, "/instrument-me?foo=bar&cat=").build(); -+ assertThat(applier.matches(attributes, resource)).isTrue(); -+ // Deceptive question mark, it's actually a wildcard :-) -+ attributes = -+ newSemCovAttributes.toBuilder().put(URL_PATH, "/instrument-meafoo=bar&cat=").build(); -+ assertThat(applier.matches(attributes, resource)).isTrue(); -+ } -+ -+ @Test -+ void pathNewSemCovNotMatch() { -+ Attributes attributes = -+ newSemCovAttributes.toBuilder().put(URL_PATH, "/instrument-mea?foo=bar&cat=").build(); -+ assertThat(applier.matches(attributes, resource)).isFalse(); -+ attributes = -+ newSemCovAttributes.toBuilder().put(URL_PATH, "foo/instrument-meafoo=bar&cat=").build(); -+ assertThat(applier.matches(attributes, resource)).isFalse(); -+ attributes = removeAttribute(newSemCovAttributes, URL_PATH); -+ assertThat(applier.matches(attributes, resource)).isFalse(); -+ } -+ - @Test - void attributeMatches() { - Attributes attributes = -diff --git a/disk-buffering/build.gradle.kts b/disk-buffering/build.gradle.kts -index 041d2e9..e3d60f4 100644 ---- a/disk-buffering/build.gradle.kts -+++ b/disk-buffering/build.gradle.kts -@@ -70,6 +70,10 @@ tasks.named("shadowJar") { - mustRunAfter("jar") - } - -+tasks.withType().configureEach { -+ dependsOn("shadowJar") -+} -+ - // The javadoc from wire's generated classes has errors that make the task that generates the "javadoc" artifact to fail. This - // makes the javadoc task to ignore those generated classes. - tasks.withType(Javadoc::class.java) { -diff --git a/version.gradle.kts b/version.gradle.kts -index acefcee..329b524 100644 ---- a/version.gradle.kts -+++ b/version.gradle.kts -@@ -1,5 +1,5 @@ --val stableVersion = "1.39.0" --val alphaVersion = "1.39.0-alpha" -+val stableVersion = "1.39.0-adot1" -+val alphaVersion = "1.39.0-alpha-adot1" - - allprojects { - if (findProperty("otel.stable") != "true") { diff --git a/.github/patches/versions b/.github/patches/versions index be4e3bbcb6..03f4b3f51f 100644 --- a/.github/patches/versions +++ b/.github/patches/versions @@ -1,2 +1 @@ -OTEL_JAVA_INSTRUMENTATION_VERSION=v2.11.0 -OTEL_JAVA_CONTRIB_VERSION=v1.39.0 \ No newline at end of file +OTEL_JAVA_INSTRUMENTATION_VERSION=v2.18.1 diff --git a/.gitignore b/.gitignore index 150fdce9ee..1f49e40488 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ build/ .DS_Store +.vscode + # Ignore Gradle GUI config gradle-app.setting diff --git a/build.gradle.kts b/build.gradle.kts index 843124bd80..0b3bddd3e7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -108,6 +108,7 @@ allprojects { testImplementation("org.junit.jupiter:junit-jupiter-api") testImplementation("org.junit.jupiter:junit-jupiter-params") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") } spotless { diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 11a6441070..d186406009 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -27,8 +27,8 @@ data class DependencySet(val group: String, val version: String, val modules: Li val testSnapshots = rootProject.findProperty("testUpstreamSnapshots") == "true" // This is the version of the upstream instrumentation BOM -val otelVersion = "2.11.0" -val otelSnapshotVersion = "2.12.0" +val otelVersion = "2.18.1" +val otelSnapshotVersion = "2.19.0" val otelAlphaVersion = if (!testSnapshots) "$otelVersion-alpha" else "$otelSnapshotVersion-alpha-SNAPSHOT" val otelJavaAgentVersion = if (!testSnapshots) otelVersion else "$otelSnapshotVersion-SNAPSHOT" // All versions below are only used in testing and do not affect the released artifact. @@ -76,8 +76,8 @@ val dependencyLists = listOf( "commons-logging:commons-logging:1.2", "com.sparkjava:spark-core:2.9.4", "com.squareup.okhttp3:okhttp:4.12.0", - "io.opentelemetry.contrib:opentelemetry-aws-xray:1.39.0-adot1", - "io.opentelemetry.contrib:opentelemetry-aws-resources:1.39.0-alpha", + "io.opentelemetry.contrib:opentelemetry-aws-xray:1.48.0", + "io.opentelemetry.contrib:opentelemetry-aws-resources:1.48.0-alpha", "io.opentelemetry.proto:opentelemetry-proto:1.0.0-alpha", "io.opentelemetry.javaagent:opentelemetry-javaagent:$otelJavaAgentVersion", "io.opentelemetry:opentelemetry-extension-aws:1.20.1", diff --git a/lambda-layer/build-layer.sh b/lambda-layer/build-layer.sh index 8c944191de..792bb4f685 100755 --- a/lambda-layer/build-layer.sh +++ b/lambda-layer/build-layer.sh @@ -31,22 +31,6 @@ patch -p1 < "$SOURCEDIR"/patches/StreamHandlerInstrumentation.patch popd rm -rf opentelemetry-java-instrumentation -contrib_version=$(awk -F'=v' '/OTEL_JAVA_CONTRIB_VERSION/ {print $2}' "$file") -if [[ -n "$contrib_version" ]]; then - echo "Found OTEL Contrib Version: ${contrib_version}" - ## Clone and Patch the OpenTelemetry Java contrib Repository - echo "Info: Cloning and Patching OpenTelemetry Java contrib Repository" - git clone https://github.com/open-telemetry/opentelemetry-java-contrib.git - pushd opentelemetry-java-contrib - git checkout v${contrib_version} -b tag-v${contrib_version} - - # There is another patch in the .github/patches directory for other changes. We should apply them too for consistency. - patch -p1 < "$SOURCEDIR"/../.github/patches/opentelemetry-java-contrib.patch - - ./gradlew publishToMavenLocal - popd - rm -rf opentelemetry-java-contrib -fi ## Build the ADOT Java from current source echo "Info: Building ADOT Java from current source" From 076d030fd6ca68fb84beb3a4c2178db8100213c0 Mon Sep 17 00:00:00 2001 From: Luke Zhang Date: Tue, 19 Aug 2025 11:48:34 -0700 Subject: [PATCH 2/6] Include changes for Lambda build. --- lambda-layer/build-layer.sh | 2 - .../StreamHandlerInstrumentation.patch | 513 ------------------ .../aws-otel-java-instrumentation.patch | 10 +- .../opentelemetry-java-instrumentation.patch | 12 +- 4 files changed, 11 insertions(+), 526 deletions(-) delete mode 100644 lambda-layer/patches/StreamHandlerInstrumentation.patch diff --git a/lambda-layer/build-layer.sh b/lambda-layer/build-layer.sh index 792bb4f685..b6d362fe9d 100755 --- a/lambda-layer/build-layer.sh +++ b/lambda-layer/build-layer.sh @@ -25,8 +25,6 @@ git checkout v${version} -b tag-v${version} # This patch is for Lambda related context propagation patch -p1 < "$SOURCEDIR"/patches/opentelemetry-java-instrumentation.patch -patch -p1 < "$SOURCEDIR"/patches/StreamHandlerInstrumentation.patch - ./gradlew publishToMavenLocal popd rm -rf opentelemetry-java-instrumentation diff --git a/lambda-layer/patches/StreamHandlerInstrumentation.patch b/lambda-layer/patches/StreamHandlerInstrumentation.patch deleted file mode 100644 index c4d4751c89..0000000000 --- a/lambda-layer/patches/StreamHandlerInstrumentation.patch +++ /dev/null @@ -1,513 +0,0 @@ -diff --git a/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaInstrumentationModule.java b/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaInstrumentationModule.java -index 35d6b70ed6..b6a305178e 100644 ---- a/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaInstrumentationModule.java -+++ b/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaInstrumentationModule.java -@@ -6,17 +6,18 @@ - package io.opentelemetry.javaagent.instrumentation.awslambdacore.v1_0; - - import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; --import static java.util.Collections.singletonList; - import static net.bytebuddy.matcher.ElementMatchers.not; - - import com.google.auto.service.AutoService; - import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; - import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; -+import java.util.Arrays; - import java.util.List; - import net.bytebuddy.matcher.ElementMatcher; - - @AutoService(InstrumentationModule.class) - public class AwsLambdaInstrumentationModule extends InstrumentationModule { -+ - public AwsLambdaInstrumentationModule() { - super("aws-lambda-core", "aws-lambda-core-1.0", "aws-lambda"); - } -@@ -34,6 +35,8 @@ public class AwsLambdaInstrumentationModule extends InstrumentationModule { - - @Override - public List typeInstrumentations() { -- return singletonList(new AwsLambdaRequestHandlerInstrumentation()); -+ return Arrays.asList( -+ new AwsLambdaRequestHandlerInstrumentation(), -+ new AwsLambdaRequestStreamHandlerInstrumentation()); - } - } -diff --git a/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaRequestStreamHandlerInstrumentation.java b/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaRequestStreamHandlerInstrumentation.java -new file mode 100644 -index 0000000000..1c4ef1ac07 ---- /dev/null -+++ b/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaRequestStreamHandlerInstrumentation.java -@@ -0,0 +1,98 @@ -+/* -+ * Copyright The OpenTelemetry Authors -+ * SPDX-License-Identifier: Apache-2.0 -+ */ -+ -+package io.opentelemetry.javaagent.instrumentation.awslambdacore.v1_0; -+ -+import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; -+import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; -+import static io.opentelemetry.javaagent.instrumentation.awslambdacore.v1_0.AwsLambdaInstrumentationHelper.functionInstrumenter; -+import static net.bytebuddy.matcher.ElementMatchers.isMethod; -+import static net.bytebuddy.matcher.ElementMatchers.isPublic; -+import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; -+import static net.bytebuddy.matcher.ElementMatchers.named; -+import static net.bytebuddy.matcher.ElementMatchers.not; -+import static net.bytebuddy.matcher.ElementMatchers.takesArgument; -+ -+import com.amazonaws.services.lambda.runtime.Context; -+import io.opentelemetry.context.Scope; -+import io.opentelemetry.instrumentation.awslambdacore.v1_0.AwsLambdaRequest; -+import io.opentelemetry.javaagent.bootstrap.OpenTelemetrySdkAccess; -+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; -+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; -+import java.io.InputStream; -+import java.util.Collections; -+import java.util.concurrent.TimeUnit; -+import net.bytebuddy.asm.Advice; -+import net.bytebuddy.description.type.TypeDescription; -+import net.bytebuddy.implementation.bytecode.assign.Assigner.Typing; -+import net.bytebuddy.matcher.ElementMatcher; -+ -+public class AwsLambdaRequestStreamHandlerInstrumentation implements TypeInstrumentation { -+ -+ @Override -+ public ElementMatcher classLoaderOptimization() { -+ return hasClassesNamed("com.amazonaws.services.lambda.runtime.RequestStreamHandler"); -+ } -+ -+ @Override -+ public ElementMatcher typeMatcher() { -+ return implementsInterface(named("com.amazonaws.services.lambda.runtime.RequestStreamHandler")) -+ .and(not(nameStartsWith("com.amazonaws.services.lambda.runtime.api.client"))) -+ // In Java 8 and Java 11 runtimes, -+ // AWS Lambda runtime is packaged under `lambdainternal` package. -+ // But it is `com.amazonaws.services.lambda.runtime.api.client` -+ // for new runtime likes Java 17 and Java 21. -+ .and(not(nameStartsWith("lambdainternal"))); -+ } -+ -+ @Override -+ public void transform(TypeTransformer transformer) { -+ transformer.applyAdviceToMethod( -+ isMethod() -+ .and(isPublic()) -+ .and(named("handleRequest")) -+ .and(takesArgument(2, named("com.amazonaws.services.lambda.runtime.Context"))), -+ AwsLambdaRequestStreamHandlerInstrumentation.class.getName() + "$HandleRequestAdvice"); -+ } -+ -+ @SuppressWarnings("unused") -+ public static class HandleRequestAdvice { -+ -+ @Advice.OnMethodEnter(suppress = Throwable.class) -+ public static void onEnter( -+ @Advice.Argument(0) InputStream input, -+ @Advice.Argument(2) Context context, -+ @Advice.Local("otelInput") AwsLambdaRequest otelInput, -+ @Advice.Local("otelContext") io.opentelemetry.context.Context otelContext, -+ @Advice.Local("otelScope") Scope otelScope) { -+ -+ otelInput = AwsLambdaRequest.create(context, input, Collections.emptyMap()); -+ io.opentelemetry.context.Context parentContext = functionInstrumenter().extract(otelInput); -+ -+ if (!functionInstrumenter().shouldStart(parentContext, otelInput)) { -+ return; -+ } -+ -+ otelContext = functionInstrumenter().start(parentContext, otelInput); -+ otelScope = otelContext.makeCurrent(); -+ } -+ -+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) -+ public static void stopSpan( -+ @Advice.Argument(value = 0, typing = Typing.DYNAMIC) Object arg, -+ @Advice.Thrown Throwable throwable, -+ @Advice.Local("otelInput") AwsLambdaRequest input, -+ @Advice.Local("otelContext") io.opentelemetry.context.Context functionContext, -+ @Advice.Local("otelScope") Scope functionScope) { -+ -+ if (functionScope != null) { -+ functionScope.close(); -+ functionInstrumenter().end(functionContext, input, null, throwable); -+ } -+ -+ OpenTelemetrySdkAccess.forceFlush((long)1, TimeUnit.SECONDS); -+ } -+ } -+} -diff --git a/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaStreamHandlerTest.java b/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaStreamHandlerTest.java -new file mode 100644 -index 0000000000..7bed968d77 ---- /dev/null -+++ b/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaStreamHandlerTest.java -@@ -0,0 +1,113 @@ -+/* -+ * Copyright The OpenTelemetry Authors -+ * SPDX-License-Identifier: Apache-2.0 -+ */ -+ -+package io.opentelemetry.javaagent.instrumentation.awslambdacore.v1_0; -+ -+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -+import static org.assertj.core.api.Assertions.assertThat; -+import static org.assertj.core.api.Assertions.catchThrowable; -+import static org.mockito.Mockito.when; -+ -+import com.amazonaws.services.lambda.runtime.Context; -+import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -+import io.opentelemetry.api.trace.SpanKind; -+import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; -+import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -+import io.opentelemetry.sdk.trace.data.StatusData; -+import io.opentelemetry.semconv.SemanticAttributes; -+import java.io.BufferedReader; -+import java.io.BufferedWriter; -+import java.io.ByteArrayInputStream; -+import java.io.ByteArrayOutputStream; -+import java.io.IOException; -+import java.io.InputStream; -+import java.io.InputStreamReader; -+import java.io.OutputStream; -+import java.io.OutputStreamWriter; -+import java.nio.charset.StandardCharsets; -+import org.junit.jupiter.api.AfterEach; -+import org.junit.jupiter.api.BeforeEach; -+import org.junit.jupiter.api.Test; -+import org.junit.jupiter.api.extension.ExtendWith; -+import org.junit.jupiter.api.extension.RegisterExtension; -+import org.mockito.Mock; -+import org.mockito.junit.jupiter.MockitoExtension; -+ -+@ExtendWith(MockitoExtension.class) -+public class AwsLambdaStreamHandlerTest { -+ -+ @RegisterExtension -+ public static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); -+ -+ @Mock private Context context; -+ -+ @BeforeEach -+ void setUp() { -+ when(context.getFunctionName()).thenReturn("my_function"); -+ when(context.getAwsRequestId()).thenReturn("1-22-333"); -+ } -+ -+ @AfterEach -+ void tearDown() { -+ assertThat(testing.forceFlushCalled()).isTrue(); -+ } -+ -+ @Test -+ void handlerTraced() throws Exception { -+ InputStream input = new ByteArrayInputStream("hello\n".getBytes(StandardCharsets.UTF_8)); -+ OutputStream output = new ByteArrayOutputStream(); -+ RequestStreamHandlerTestImpl handler = new RequestStreamHandlerTestImpl(); -+ handler.handleRequest(input, output, context); -+ -+ testing.waitAndAssertTraces( -+ trace -> -+ trace.hasSpansSatisfyingExactly( -+ span -> -+ span.hasName("my_function") -+ .hasKind(SpanKind.SERVER) -+ .hasAttributesSatisfyingExactly( -+ equalTo(SemanticAttributes.FAAS_INVOCATION_ID, "1-22-333")))); -+ } -+ -+ @Test -+ void handlerTracedWithException() { -+ InputStream input = new ByteArrayInputStream("bye\n".getBytes(StandardCharsets.UTF_8)); -+ OutputStream output = new ByteArrayOutputStream(); -+ RequestStreamHandlerTestImpl handler = new RequestStreamHandlerTestImpl(); -+ -+ Throwable thrown = catchThrowable(() -> handler.handleRequest(input, output, context)); -+ assertThat(thrown).isInstanceOf(IllegalArgumentException.class); -+ -+ testing.waitAndAssertTraces( -+ trace -> -+ trace.hasSpansSatisfyingExactly( -+ span -> -+ span.hasName("my_function") -+ .hasKind(SpanKind.SERVER) -+ .hasStatus(StatusData.error()) -+ .hasException(thrown) -+ .hasAttributesSatisfyingExactly( -+ equalTo(SemanticAttributes.FAAS_INVOCATION_ID, "1-22-333")))); -+ } -+ -+ static final class RequestStreamHandlerTestImpl implements RequestStreamHandler { -+ @Override -+ public void handleRequest(InputStream input, OutputStream output, Context context) -+ throws IOException { -+ BufferedReader reader = -+ new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)); -+ BufferedWriter writer = -+ new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8)); -+ String line = reader.readLine(); -+ if (line.equals("hello")) { -+ writer.write("world"); -+ writer.flush(); -+ writer.close(); -+ } else { -+ throw new IllegalArgumentException("bad argument"); -+ } -+ } -+ } -+} -diff --git a/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaInstrumentationModule.java b/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaInstrumentationModule.java -index 9e0e372241..2dd6051c23 100644 ---- a/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaInstrumentationModule.java -+++ b/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaInstrumentationModule.java -@@ -6,11 +6,11 @@ - package io.opentelemetry.javaagent.instrumentation.awslambdaevents.v2_2; - - import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; --import static java.util.Collections.singletonList; - - import com.google.auto.service.AutoService; - import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; - import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; -+import java.util.Arrays; - import java.util.List; - import net.bytebuddy.matcher.ElementMatcher; - -@@ -32,6 +32,8 @@ public class AwsLambdaInstrumentationModule extends InstrumentationModule { - - @Override - public List typeInstrumentations() { -- return singletonList(new AwsLambdaRequestHandlerInstrumentation()); -+ return Arrays.asList( -+ new AwsLambdaRequestHandlerInstrumentation(), -+ new AwsLambdaRequestStreamHandlerInstrumentation()); - } - } -diff --git a/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaRequestStreamHandlerInstrumentation.java b/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaRequestStreamHandlerInstrumentation.java -new file mode 100644 -index 0000000000..f21a4a5526 ---- /dev/null -+++ b/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaRequestStreamHandlerInstrumentation.java -@@ -0,0 +1,104 @@ -+/* -+ * Copyright The OpenTelemetry Authors -+ * SPDX-License-Identifier: Apache-2.0 -+ */ -+ -+package io.opentelemetry.javaagent.instrumentation.awslambdaevents.v2_2; -+ -+import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; -+import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; -+import static net.bytebuddy.matcher.ElementMatchers.isMethod; -+import static net.bytebuddy.matcher.ElementMatchers.isPublic; -+import static net.bytebuddy.matcher.ElementMatchers.named; -+import static net.bytebuddy.matcher.ElementMatchers.takesArgument; -+ -+import com.amazonaws.services.lambda.runtime.Context; -+import com.amazonaws.services.lambda.runtime.events.SQSEvent; -+import io.opentelemetry.context.Scope; -+import io.opentelemetry.instrumentation.awslambdacore.v1_0.AwsLambdaRequest; -+import io.opentelemetry.javaagent.bootstrap.OpenTelemetrySdkAccess; -+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; -+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; -+import java.io.InputStream; -+import java.util.Collections; -+import java.util.concurrent.TimeUnit; -+import net.bytebuddy.asm.Advice; -+import net.bytebuddy.description.type.TypeDescription; -+import net.bytebuddy.implementation.bytecode.assign.Assigner.Typing; -+import net.bytebuddy.matcher.ElementMatcher; -+ -+public class AwsLambdaRequestStreamHandlerInstrumentation implements TypeInstrumentation { -+ -+ @Override -+ public ElementMatcher classLoaderOptimization() { -+ return hasClassesNamed("com.amazonaws.services.lambda.runtime.RequestStreamHandler"); -+ } -+ -+ @Override -+ public ElementMatcher typeMatcher() { -+ return implementsInterface(named("com.amazonaws.services.lambda.runtime.RequestStreamHandler")); -+ } -+ -+ @Override -+ public void transform(TypeTransformer transformer) { -+ transformer.applyAdviceToMethod( -+ isMethod() -+ .and(isPublic()) -+ .and(named("handleRequest")) -+ .and(takesArgument(2, named("com.amazonaws.services.lambda.runtime.Context"))), -+ AwsLambdaRequestStreamHandlerInstrumentation.class.getName() + "$HandleRequestAdvice"); -+ } -+ -+ @SuppressWarnings("unused") -+ public static class HandleRequestAdvice { -+ -+ @Advice.OnMethodEnter(suppress = Throwable.class) -+ public static void onEnter( -+ @Advice.Argument(0) InputStream input, -+ @Advice.Argument(2) Context context, -+ @Advice.Local("otelInput") AwsLambdaRequest otelInput, -+ @Advice.Local("otelFunctionContext") io.opentelemetry.context.Context functionContext, -+ @Advice.Local("otelFunctionScope") Scope functionScope, -+ @Advice.Local("otelMessageContext") io.opentelemetry.context.Context messageContext, -+ @Advice.Local("otelMessageScope") Scope messageScope) { -+ otelInput = AwsLambdaRequest.create(context, input, Collections.emptyMap()); -+ io.opentelemetry.context.Context parentContext = -+ AwsLambdaInstrumentationHelper.functionInstrumenter().extract(otelInput); -+ -+ if (!AwsLambdaInstrumentationHelper.functionInstrumenter() -+ .shouldStart(parentContext, otelInput)) { -+ return; -+ } -+ -+ functionContext = -+ AwsLambdaInstrumentationHelper.functionInstrumenter().start(parentContext, otelInput); -+ -+ functionScope = functionContext.makeCurrent(); -+ } -+ -+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) -+ public static void stopSpan( -+ @Advice.Argument(value = 0, typing = Typing.DYNAMIC) Object arg, -+ @Advice.Thrown Throwable throwable, -+ @Advice.Local("otelInput") AwsLambdaRequest input, -+ @Advice.Local("otelFunctionContext") io.opentelemetry.context.Context functionContext, -+ @Advice.Local("otelFunctionScope") Scope functionScope, -+ @Advice.Local("otelMessageContext") io.opentelemetry.context.Context messageContext, -+ @Advice.Local("otelMessageScope") Scope messageScope) { -+ -+ if (messageScope != null) { -+ messageScope.close(); -+ AwsLambdaInstrumentationHelper.messageInstrumenter() -+ .end(messageContext, (SQSEvent) arg, null, throwable); -+ } -+ -+ if (functionScope != null) { -+ functionScope.close(); -+ AwsLambdaInstrumentationHelper.functionInstrumenter() -+ .end(functionContext, input, null, throwable); -+ } -+ -+ OpenTelemetrySdkAccess.forceFlush((long)1, TimeUnit.SECONDS); -+ } -+ } -+} -diff --git a/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaStreamHandlerTest.java b/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaStreamHandlerTest.java -new file mode 100644 -index 0000000000..e30690418d ---- /dev/null -+++ b/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaStreamHandlerTest.java -@@ -0,0 +1,113 @@ -+/* -+ * Copyright The OpenTelemetry Authors -+ * SPDX-License-Identifier: Apache-2.0 -+ */ -+ -+package io.opentelemetry.javaagent.instrumentation.awslambdaevents.v2_2; -+ -+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -+import static org.assertj.core.api.Assertions.assertThat; -+import static org.assertj.core.api.Assertions.catchThrowable; -+import static org.mockito.Mockito.when; -+ -+import com.amazonaws.services.lambda.runtime.Context; -+import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -+import io.opentelemetry.api.trace.SpanKind; -+import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; -+import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -+import io.opentelemetry.sdk.trace.data.StatusData; -+import io.opentelemetry.semconv.SemanticAttributes; -+import java.io.BufferedReader; -+import java.io.BufferedWriter; -+import java.io.ByteArrayInputStream; -+import java.io.ByteArrayOutputStream; -+import java.io.IOException; -+import java.io.InputStream; -+import java.io.InputStreamReader; -+import java.io.OutputStream; -+import java.io.OutputStreamWriter; -+import java.nio.charset.StandardCharsets; -+import org.junit.jupiter.api.AfterEach; -+import org.junit.jupiter.api.BeforeEach; -+import org.junit.jupiter.api.Test; -+import org.junit.jupiter.api.extension.ExtendWith; -+import org.junit.jupiter.api.extension.RegisterExtension; -+import org.mockito.Mock; -+import org.mockito.junit.jupiter.MockitoExtension; -+ -+@ExtendWith(MockitoExtension.class) -+public class AwsLambdaStreamHandlerTest { -+ -+ @RegisterExtension -+ public static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); -+ -+ @Mock private Context context; -+ -+ @BeforeEach -+ void setUp() { -+ when(context.getFunctionName()).thenReturn("my_function"); -+ when(context.getAwsRequestId()).thenReturn("1-22-333"); -+ } -+ -+ @AfterEach -+ void tearDown() { -+ assertThat(testing.forceFlushCalled()).isTrue(); -+ } -+ -+ @Test -+ void handlerTraced() throws Exception { -+ InputStream input = new ByteArrayInputStream("hello\n".getBytes(StandardCharsets.UTF_8)); -+ OutputStream output = new ByteArrayOutputStream(); -+ RequestStreamHandlerTestImpl handler = new RequestStreamHandlerTestImpl(); -+ handler.handleRequest(input, output, context); -+ -+ testing.waitAndAssertTraces( -+ trace -> -+ trace.hasSpansSatisfyingExactly( -+ span -> -+ span.hasName("my_function") -+ .hasKind(SpanKind.SERVER) -+ .hasAttributesSatisfyingExactly( -+ equalTo(SemanticAttributes.FAAS_INVOCATION_ID, "1-22-333")))); -+ } -+ -+ @Test -+ void handlerTracedWithException() { -+ InputStream input = new ByteArrayInputStream("bye\n".getBytes(StandardCharsets.UTF_8)); -+ OutputStream output = new ByteArrayOutputStream(); -+ RequestStreamHandlerTestImpl handler = new RequestStreamHandlerTestImpl(); -+ -+ Throwable thrown = catchThrowable(() -> handler.handleRequest(input, output, context)); -+ assertThat(thrown).isInstanceOf(IllegalArgumentException.class); -+ -+ testing.waitAndAssertTraces( -+ trace -> -+ trace.hasSpansSatisfyingExactly( -+ span -> -+ span.hasName("my_function") -+ .hasKind(SpanKind.SERVER) -+ .hasStatus(StatusData.error()) -+ .hasException(thrown) -+ .hasAttributesSatisfyingExactly( -+ equalTo(SemanticAttributes.FAAS_INVOCATION_ID, "1-22-333")))); -+ } -+ -+ static final class RequestStreamHandlerTestImpl implements RequestStreamHandler { -+ @Override -+ public void handleRequest(InputStream input, OutputStream output, Context context) -+ throws IOException { -+ BufferedReader reader = -+ new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)); -+ BufferedWriter writer = -+ new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8)); -+ String line = reader.readLine(); -+ if (line.equals("hello")) { -+ writer.write("world"); -+ writer.flush(); -+ writer.close(); -+ } else { -+ throw new IllegalArgumentException("bad argument"); -+ } -+ } -+ } -+} diff --git a/lambda-layer/patches/aws-otel-java-instrumentation.patch b/lambda-layer/patches/aws-otel-java-instrumentation.patch index 6b1f5eb9d5..bbd66b64c1 100644 --- a/lambda-layer/patches/aws-otel-java-instrumentation.patch +++ b/lambda-layer/patches/aws-otel-java-instrumentation.patch @@ -1,13 +1,13 @@ diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts -index 9493189..6090207 100644 +index d186406..91b9386 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -27,7 +27,7 @@ data class DependencySet(val group: String, val version: String, val modules: Li val testSnapshots = rootProject.findProperty("testUpstreamSnapshots") == "true" - + // This is the version of the upstream instrumentation BOM --val otelVersion = "2.11.0" -+val otelVersion = "2.11.0-adot-lambda1" - val otelSnapshotVersion = "2.12.0" +-val otelVersion = "2.18.1" ++val otelVersion = "2.18.1-adot-lambda1" + val otelSnapshotVersion = "2.19.0" val otelAlphaVersion = if (!testSnapshots) "$otelVersion-alpha" else "$otelSnapshotVersion-alpha-SNAPSHOT" val otelJavaAgentVersion = if (!testSnapshots) otelVersion else "$otelSnapshotVersion-SNAPSHOT" diff --git a/lambda-layer/patches/opentelemetry-java-instrumentation.patch b/lambda-layer/patches/opentelemetry-java-instrumentation.patch index a4004e3330..9f4baa1481 100644 --- a/lambda-layer/patches/opentelemetry-java-instrumentation.patch +++ b/lambda-layer/patches/opentelemetry-java-instrumentation.patch @@ -306,14 +306,14 @@ index 4cd11fc0c4..7b7d62755c 100644 } diff --git a/version.gradle.kts b/version.gradle.kts -index 7900c9a4d9..80383d7c22 100644 +index 023d04703c..b267166804 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -1,5 +1,5 @@ --val stableVersion = "2.11.0" --val alphaVersion = "2.11.0-alpha" -+val stableVersion = "2.11.0-adot-lambda1" -+val alphaVersion = "2.11.0-adot-lambda1-alpha" - +-val stableVersion = "2.18.1" +-val alphaVersion = "2.18.1-alpha" ++val stableVersion = "2.18.1-adot-lambda1" ++val alphaVersion = "2.18.1-adot-lambda1-alpha" + allprojects { if (findProperty("otel.stable") != "true") { From 78d9bec63483229d12bd413c5bf5e6f66cb10a13 Mon Sep 17 00:00:00 2001 From: Luke Zhang Date: Tue, 19 Aug 2025 16:30:14 -0700 Subject: [PATCH 3/6] Two minor changes: Move the JUnit dependency to the SPI project. Retain the Contrib patch script. --- build.gradle.kts | 1 - instrumentation/aws-sdk/build.gradle.kts | 2 ++ lambda-layer/build-layer.sh | 16 ++++++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 0b3bddd3e7..843124bd80 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -108,7 +108,6 @@ allprojects { testImplementation("org.junit.jupiter:junit-jupiter-api") testImplementation("org.junit.jupiter:junit-jupiter-params") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") - testRuntimeOnly("org.junit.platform:junit-platform-launcher") } spotless { diff --git a/instrumentation/aws-sdk/build.gradle.kts b/instrumentation/aws-sdk/build.gradle.kts index 101e966a12..58fb6b48d7 100644 --- a/instrumentation/aws-sdk/build.gradle.kts +++ b/instrumentation/aws-sdk/build.gradle.kts @@ -41,4 +41,6 @@ dependencies { testImplementation("com.amazonaws:aws-java-sdk-sns:1.11.106") testImplementation("com.amazonaws:aws-java-sdk-stepfunctions:1.11.230") testImplementation("com.amazonaws:aws-java-sdk-secretsmanager:1.11.309") + + testRuntimeOnly("org.junit.platform:junit-platform-launcher") } diff --git a/lambda-layer/build-layer.sh b/lambda-layer/build-layer.sh index b6d362fe9d..5162915802 100755 --- a/lambda-layer/build-layer.sh +++ b/lambda-layer/build-layer.sh @@ -29,6 +29,22 @@ patch -p1 < "$SOURCEDIR"/patches/opentelemetry-java-instrumentation.patch popd rm -rf opentelemetry-java-instrumentation +contrib_version=$(awk -F'=v' '/OTEL_JAVA_CONTRIB_VERSION/ {print $2}' "$file") +if [[ -n "$contrib_version" ]]; then + echo "Found OTEL Contrib Version: ${contrib_version}" + ## Clone and Patch the OpenTelemetry Java contrib Repository + echo "Info: Cloning and Patching OpenTelemetry Java contrib Repository" + git clone https://github.com/open-telemetry/opentelemetry-java-contrib.git + pushd opentelemetry-java-contrib + git checkout v${contrib_version} -b tag-v${contrib_version} + + # There is another patch in the .github/patches directory for other changes. We should apply them too for consistency. + patch -p1 < "$SOURCEDIR"/../.github/patches/opentelemetry-java-contrib.patch + + ./gradlew publishToMavenLocal + popd + rm -rf opentelemetry-java-contrib +fi ## Build the ADOT Java from current source echo "Info: Building ADOT Java from current source" From 3be8d5b0c80b7ef7c2709759632d3a2d939986fe Mon Sep 17 00:00:00 2001 From: Luke Zhang Date: Wed, 20 Aug 2025 14:13:41 -0700 Subject: [PATCH 4/6] Fix a UT failure unrelated to this PR This unit test failure is not caused by this PR. The same failure occurred in April 2025 and was partially addressed in PR-1049: https://github.com/aws-observability/aws-otel-java-instrumentation/pull/1049. https://github.com/aws-observability/aws-otel-java-instrumentation/blob/release/v2.11.x/exporters/aws-distro-opentelemetry-xray-udp-span-exporter/src/test/java/software/amazon/distro/opentelemetry/exporter/xray/udp/trace/AwsXrayUdpSpanExporterTest.java#L42-L44 PR-1049 fixed the failure in one location but missed another instance. This PR addresses the remaining failure in the test: UdpExporterTest.java --- .../opentelemetry/javaagent/providers/UdpExporterTest.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/UdpExporterTest.java b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/UdpExporterTest.java index 2a1cea7106..b070d53bba 100644 --- a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/UdpExporterTest.java +++ b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/UdpExporterTest.java @@ -17,6 +17,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.*; import io.opentelemetry.api.common.AttributeKey; @@ -36,8 +37,10 @@ public class UdpExporterTest { public void testUdpExporterWithDefaults() { OtlpUdpSpanExporter exporter = new OtlpUdpSpanExporterBuilder().build(); UdpSender sender = exporter.getSender(); - assertThat(sender.getEndpoint().getHostName()) - .isEqualTo("localhost"); // getHostName implicitly converts 127.0.0.1 to localhost + String senderEndpointHostName = sender.getEndpoint().getHostName(); + // getHostName may or may not convert 127.0.0.1 to localhost + assertTrue( + senderEndpointHostName.equals("localhost") || senderEndpointHostName.equals("127.0.0.1")); assertThat(sender.getEndpoint().getPort()).isEqualTo(2000); assertThat(exporter.getPayloadPrefix()).endsWith("T1S"); } From 8541cf9d5defb4ae7462bccb089512c6bfbcdd45 Mon Sep 17 00:00:00 2001 From: Luke Zhang Date: Thu, 21 Aug 2025 11:23:04 -0700 Subject: [PATCH 5/6] Revert change related to StreamHandlerInstrumentation.patch --- lambda-layer/build-layer.sh | 2 + .../StreamHandlerInstrumentation.patch | 513 ++++++++++++++++++ 2 files changed, 515 insertions(+) create mode 100644 lambda-layer/patches/StreamHandlerInstrumentation.patch diff --git a/lambda-layer/build-layer.sh b/lambda-layer/build-layer.sh index ddd144716d..265a367d66 100755 --- a/lambda-layer/build-layer.sh +++ b/lambda-layer/build-layer.sh @@ -25,6 +25,8 @@ git checkout v${version} -b tag-v${version} # This patch is for Lambda related context propagation patch -p1 < "$SOURCEDIR"/patches/opentelemetry-java-instrumentation.patch +patch -p1 < "$SOURCEDIR"/patches/StreamHandlerInstrumentation.patch + ./gradlew publishToMavenLocal popd rm -rf opentelemetry-java-instrumentation diff --git a/lambda-layer/patches/StreamHandlerInstrumentation.patch b/lambda-layer/patches/StreamHandlerInstrumentation.patch new file mode 100644 index 0000000000..c4d4751c89 --- /dev/null +++ b/lambda-layer/patches/StreamHandlerInstrumentation.patch @@ -0,0 +1,513 @@ +diff --git a/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaInstrumentationModule.java b/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaInstrumentationModule.java +index 35d6b70ed6..b6a305178e 100644 +--- a/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaInstrumentationModule.java ++++ b/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaInstrumentationModule.java +@@ -6,17 +6,18 @@ + package io.opentelemetry.javaagent.instrumentation.awslambdacore.v1_0; + + import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +-import static java.util.Collections.singletonList; + import static net.bytebuddy.matcher.ElementMatchers.not; + + import com.google.auto.service.AutoService; + import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; + import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; ++import java.util.Arrays; + import java.util.List; + import net.bytebuddy.matcher.ElementMatcher; + + @AutoService(InstrumentationModule.class) + public class AwsLambdaInstrumentationModule extends InstrumentationModule { ++ + public AwsLambdaInstrumentationModule() { + super("aws-lambda-core", "aws-lambda-core-1.0", "aws-lambda"); + } +@@ -34,6 +35,8 @@ public class AwsLambdaInstrumentationModule extends InstrumentationModule { + + @Override + public List typeInstrumentations() { +- return singletonList(new AwsLambdaRequestHandlerInstrumentation()); ++ return Arrays.asList( ++ new AwsLambdaRequestHandlerInstrumentation(), ++ new AwsLambdaRequestStreamHandlerInstrumentation()); + } + } +diff --git a/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaRequestStreamHandlerInstrumentation.java b/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaRequestStreamHandlerInstrumentation.java +new file mode 100644 +index 0000000000..1c4ef1ac07 +--- /dev/null ++++ b/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaRequestStreamHandlerInstrumentation.java +@@ -0,0 +1,98 @@ ++/* ++ * Copyright The OpenTelemetry Authors ++ * SPDX-License-Identifier: Apache-2.0 ++ */ ++ ++package io.opentelemetry.javaagent.instrumentation.awslambdacore.v1_0; ++ ++import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; ++import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; ++import static io.opentelemetry.javaagent.instrumentation.awslambdacore.v1_0.AwsLambdaInstrumentationHelper.functionInstrumenter; ++import static net.bytebuddy.matcher.ElementMatchers.isMethod; ++import static net.bytebuddy.matcher.ElementMatchers.isPublic; ++import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; ++import static net.bytebuddy.matcher.ElementMatchers.named; ++import static net.bytebuddy.matcher.ElementMatchers.not; ++import static net.bytebuddy.matcher.ElementMatchers.takesArgument; ++ ++import com.amazonaws.services.lambda.runtime.Context; ++import io.opentelemetry.context.Scope; ++import io.opentelemetry.instrumentation.awslambdacore.v1_0.AwsLambdaRequest; ++import io.opentelemetry.javaagent.bootstrap.OpenTelemetrySdkAccess; ++import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; ++import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; ++import java.io.InputStream; ++import java.util.Collections; ++import java.util.concurrent.TimeUnit; ++import net.bytebuddy.asm.Advice; ++import net.bytebuddy.description.type.TypeDescription; ++import net.bytebuddy.implementation.bytecode.assign.Assigner.Typing; ++import net.bytebuddy.matcher.ElementMatcher; ++ ++public class AwsLambdaRequestStreamHandlerInstrumentation implements TypeInstrumentation { ++ ++ @Override ++ public ElementMatcher classLoaderOptimization() { ++ return hasClassesNamed("com.amazonaws.services.lambda.runtime.RequestStreamHandler"); ++ } ++ ++ @Override ++ public ElementMatcher typeMatcher() { ++ return implementsInterface(named("com.amazonaws.services.lambda.runtime.RequestStreamHandler")) ++ .and(not(nameStartsWith("com.amazonaws.services.lambda.runtime.api.client"))) ++ // In Java 8 and Java 11 runtimes, ++ // AWS Lambda runtime is packaged under `lambdainternal` package. ++ // But it is `com.amazonaws.services.lambda.runtime.api.client` ++ // for new runtime likes Java 17 and Java 21. ++ .and(not(nameStartsWith("lambdainternal"))); ++ } ++ ++ @Override ++ public void transform(TypeTransformer transformer) { ++ transformer.applyAdviceToMethod( ++ isMethod() ++ .and(isPublic()) ++ .and(named("handleRequest")) ++ .and(takesArgument(2, named("com.amazonaws.services.lambda.runtime.Context"))), ++ AwsLambdaRequestStreamHandlerInstrumentation.class.getName() + "$HandleRequestAdvice"); ++ } ++ ++ @SuppressWarnings("unused") ++ public static class HandleRequestAdvice { ++ ++ @Advice.OnMethodEnter(suppress = Throwable.class) ++ public static void onEnter( ++ @Advice.Argument(0) InputStream input, ++ @Advice.Argument(2) Context context, ++ @Advice.Local("otelInput") AwsLambdaRequest otelInput, ++ @Advice.Local("otelContext") io.opentelemetry.context.Context otelContext, ++ @Advice.Local("otelScope") Scope otelScope) { ++ ++ otelInput = AwsLambdaRequest.create(context, input, Collections.emptyMap()); ++ io.opentelemetry.context.Context parentContext = functionInstrumenter().extract(otelInput); ++ ++ if (!functionInstrumenter().shouldStart(parentContext, otelInput)) { ++ return; ++ } ++ ++ otelContext = functionInstrumenter().start(parentContext, otelInput); ++ otelScope = otelContext.makeCurrent(); ++ } ++ ++ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) ++ public static void stopSpan( ++ @Advice.Argument(value = 0, typing = Typing.DYNAMIC) Object arg, ++ @Advice.Thrown Throwable throwable, ++ @Advice.Local("otelInput") AwsLambdaRequest input, ++ @Advice.Local("otelContext") io.opentelemetry.context.Context functionContext, ++ @Advice.Local("otelScope") Scope functionScope) { ++ ++ if (functionScope != null) { ++ functionScope.close(); ++ functionInstrumenter().end(functionContext, input, null, throwable); ++ } ++ ++ OpenTelemetrySdkAccess.forceFlush((long)1, TimeUnit.SECONDS); ++ } ++ } ++} +diff --git a/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaStreamHandlerTest.java b/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaStreamHandlerTest.java +new file mode 100644 +index 0000000000..7bed968d77 +--- /dev/null ++++ b/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaStreamHandlerTest.java +@@ -0,0 +1,113 @@ ++/* ++ * Copyright The OpenTelemetry Authors ++ * SPDX-License-Identifier: Apache-2.0 ++ */ ++ ++package io.opentelemetry.javaagent.instrumentation.awslambdacore.v1_0; ++ ++import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; ++import static org.assertj.core.api.Assertions.assertThat; ++import static org.assertj.core.api.Assertions.catchThrowable; ++import static org.mockito.Mockito.when; ++ ++import com.amazonaws.services.lambda.runtime.Context; ++import com.amazonaws.services.lambda.runtime.RequestStreamHandler; ++import io.opentelemetry.api.trace.SpanKind; ++import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; ++import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; ++import io.opentelemetry.sdk.trace.data.StatusData; ++import io.opentelemetry.semconv.SemanticAttributes; ++import java.io.BufferedReader; ++import java.io.BufferedWriter; ++import java.io.ByteArrayInputStream; ++import java.io.ByteArrayOutputStream; ++import java.io.IOException; ++import java.io.InputStream; ++import java.io.InputStreamReader; ++import java.io.OutputStream; ++import java.io.OutputStreamWriter; ++import java.nio.charset.StandardCharsets; ++import org.junit.jupiter.api.AfterEach; ++import org.junit.jupiter.api.BeforeEach; ++import org.junit.jupiter.api.Test; ++import org.junit.jupiter.api.extension.ExtendWith; ++import org.junit.jupiter.api.extension.RegisterExtension; ++import org.mockito.Mock; ++import org.mockito.junit.jupiter.MockitoExtension; ++ ++@ExtendWith(MockitoExtension.class) ++public class AwsLambdaStreamHandlerTest { ++ ++ @RegisterExtension ++ public static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); ++ ++ @Mock private Context context; ++ ++ @BeforeEach ++ void setUp() { ++ when(context.getFunctionName()).thenReturn("my_function"); ++ when(context.getAwsRequestId()).thenReturn("1-22-333"); ++ } ++ ++ @AfterEach ++ void tearDown() { ++ assertThat(testing.forceFlushCalled()).isTrue(); ++ } ++ ++ @Test ++ void handlerTraced() throws Exception { ++ InputStream input = new ByteArrayInputStream("hello\n".getBytes(StandardCharsets.UTF_8)); ++ OutputStream output = new ByteArrayOutputStream(); ++ RequestStreamHandlerTestImpl handler = new RequestStreamHandlerTestImpl(); ++ handler.handleRequest(input, output, context); ++ ++ testing.waitAndAssertTraces( ++ trace -> ++ trace.hasSpansSatisfyingExactly( ++ span -> ++ span.hasName("my_function") ++ .hasKind(SpanKind.SERVER) ++ .hasAttributesSatisfyingExactly( ++ equalTo(SemanticAttributes.FAAS_INVOCATION_ID, "1-22-333")))); ++ } ++ ++ @Test ++ void handlerTracedWithException() { ++ InputStream input = new ByteArrayInputStream("bye\n".getBytes(StandardCharsets.UTF_8)); ++ OutputStream output = new ByteArrayOutputStream(); ++ RequestStreamHandlerTestImpl handler = new RequestStreamHandlerTestImpl(); ++ ++ Throwable thrown = catchThrowable(() -> handler.handleRequest(input, output, context)); ++ assertThat(thrown).isInstanceOf(IllegalArgumentException.class); ++ ++ testing.waitAndAssertTraces( ++ trace -> ++ trace.hasSpansSatisfyingExactly( ++ span -> ++ span.hasName("my_function") ++ .hasKind(SpanKind.SERVER) ++ .hasStatus(StatusData.error()) ++ .hasException(thrown) ++ .hasAttributesSatisfyingExactly( ++ equalTo(SemanticAttributes.FAAS_INVOCATION_ID, "1-22-333")))); ++ } ++ ++ static final class RequestStreamHandlerTestImpl implements RequestStreamHandler { ++ @Override ++ public void handleRequest(InputStream input, OutputStream output, Context context) ++ throws IOException { ++ BufferedReader reader = ++ new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)); ++ BufferedWriter writer = ++ new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8)); ++ String line = reader.readLine(); ++ if (line.equals("hello")) { ++ writer.write("world"); ++ writer.flush(); ++ writer.close(); ++ } else { ++ throw new IllegalArgumentException("bad argument"); ++ } ++ } ++ } ++} +diff --git a/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaInstrumentationModule.java b/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaInstrumentationModule.java +index 9e0e372241..2dd6051c23 100644 +--- a/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaInstrumentationModule.java ++++ b/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaInstrumentationModule.java +@@ -6,11 +6,11 @@ + package io.opentelemetry.javaagent.instrumentation.awslambdaevents.v2_2; + + import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +-import static java.util.Collections.singletonList; + + import com.google.auto.service.AutoService; + import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; + import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; ++import java.util.Arrays; + import java.util.List; + import net.bytebuddy.matcher.ElementMatcher; + +@@ -32,6 +32,8 @@ public class AwsLambdaInstrumentationModule extends InstrumentationModule { + + @Override + public List typeInstrumentations() { +- return singletonList(new AwsLambdaRequestHandlerInstrumentation()); ++ return Arrays.asList( ++ new AwsLambdaRequestHandlerInstrumentation(), ++ new AwsLambdaRequestStreamHandlerInstrumentation()); + } + } +diff --git a/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaRequestStreamHandlerInstrumentation.java b/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaRequestStreamHandlerInstrumentation.java +new file mode 100644 +index 0000000000..f21a4a5526 +--- /dev/null ++++ b/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaRequestStreamHandlerInstrumentation.java +@@ -0,0 +1,104 @@ ++/* ++ * Copyright The OpenTelemetry Authors ++ * SPDX-License-Identifier: Apache-2.0 ++ */ ++ ++package io.opentelemetry.javaagent.instrumentation.awslambdaevents.v2_2; ++ ++import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; ++import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; ++import static net.bytebuddy.matcher.ElementMatchers.isMethod; ++import static net.bytebuddy.matcher.ElementMatchers.isPublic; ++import static net.bytebuddy.matcher.ElementMatchers.named; ++import static net.bytebuddy.matcher.ElementMatchers.takesArgument; ++ ++import com.amazonaws.services.lambda.runtime.Context; ++import com.amazonaws.services.lambda.runtime.events.SQSEvent; ++import io.opentelemetry.context.Scope; ++import io.opentelemetry.instrumentation.awslambdacore.v1_0.AwsLambdaRequest; ++import io.opentelemetry.javaagent.bootstrap.OpenTelemetrySdkAccess; ++import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; ++import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; ++import java.io.InputStream; ++import java.util.Collections; ++import java.util.concurrent.TimeUnit; ++import net.bytebuddy.asm.Advice; ++import net.bytebuddy.description.type.TypeDescription; ++import net.bytebuddy.implementation.bytecode.assign.Assigner.Typing; ++import net.bytebuddy.matcher.ElementMatcher; ++ ++public class AwsLambdaRequestStreamHandlerInstrumentation implements TypeInstrumentation { ++ ++ @Override ++ public ElementMatcher classLoaderOptimization() { ++ return hasClassesNamed("com.amazonaws.services.lambda.runtime.RequestStreamHandler"); ++ } ++ ++ @Override ++ public ElementMatcher typeMatcher() { ++ return implementsInterface(named("com.amazonaws.services.lambda.runtime.RequestStreamHandler")); ++ } ++ ++ @Override ++ public void transform(TypeTransformer transformer) { ++ transformer.applyAdviceToMethod( ++ isMethod() ++ .and(isPublic()) ++ .and(named("handleRequest")) ++ .and(takesArgument(2, named("com.amazonaws.services.lambda.runtime.Context"))), ++ AwsLambdaRequestStreamHandlerInstrumentation.class.getName() + "$HandleRequestAdvice"); ++ } ++ ++ @SuppressWarnings("unused") ++ public static class HandleRequestAdvice { ++ ++ @Advice.OnMethodEnter(suppress = Throwable.class) ++ public static void onEnter( ++ @Advice.Argument(0) InputStream input, ++ @Advice.Argument(2) Context context, ++ @Advice.Local("otelInput") AwsLambdaRequest otelInput, ++ @Advice.Local("otelFunctionContext") io.opentelemetry.context.Context functionContext, ++ @Advice.Local("otelFunctionScope") Scope functionScope, ++ @Advice.Local("otelMessageContext") io.opentelemetry.context.Context messageContext, ++ @Advice.Local("otelMessageScope") Scope messageScope) { ++ otelInput = AwsLambdaRequest.create(context, input, Collections.emptyMap()); ++ io.opentelemetry.context.Context parentContext = ++ AwsLambdaInstrumentationHelper.functionInstrumenter().extract(otelInput); ++ ++ if (!AwsLambdaInstrumentationHelper.functionInstrumenter() ++ .shouldStart(parentContext, otelInput)) { ++ return; ++ } ++ ++ functionContext = ++ AwsLambdaInstrumentationHelper.functionInstrumenter().start(parentContext, otelInput); ++ ++ functionScope = functionContext.makeCurrent(); ++ } ++ ++ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) ++ public static void stopSpan( ++ @Advice.Argument(value = 0, typing = Typing.DYNAMIC) Object arg, ++ @Advice.Thrown Throwable throwable, ++ @Advice.Local("otelInput") AwsLambdaRequest input, ++ @Advice.Local("otelFunctionContext") io.opentelemetry.context.Context functionContext, ++ @Advice.Local("otelFunctionScope") Scope functionScope, ++ @Advice.Local("otelMessageContext") io.opentelemetry.context.Context messageContext, ++ @Advice.Local("otelMessageScope") Scope messageScope) { ++ ++ if (messageScope != null) { ++ messageScope.close(); ++ AwsLambdaInstrumentationHelper.messageInstrumenter() ++ .end(messageContext, (SQSEvent) arg, null, throwable); ++ } ++ ++ if (functionScope != null) { ++ functionScope.close(); ++ AwsLambdaInstrumentationHelper.functionInstrumenter() ++ .end(functionContext, input, null, throwable); ++ } ++ ++ OpenTelemetrySdkAccess.forceFlush((long)1, TimeUnit.SECONDS); ++ } ++ } ++} +diff --git a/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaStreamHandlerTest.java b/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaStreamHandlerTest.java +new file mode 100644 +index 0000000000..e30690418d +--- /dev/null ++++ b/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaStreamHandlerTest.java +@@ -0,0 +1,113 @@ ++/* ++ * Copyright The OpenTelemetry Authors ++ * SPDX-License-Identifier: Apache-2.0 ++ */ ++ ++package io.opentelemetry.javaagent.instrumentation.awslambdaevents.v2_2; ++ ++import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; ++import static org.assertj.core.api.Assertions.assertThat; ++import static org.assertj.core.api.Assertions.catchThrowable; ++import static org.mockito.Mockito.when; ++ ++import com.amazonaws.services.lambda.runtime.Context; ++import com.amazonaws.services.lambda.runtime.RequestStreamHandler; ++import io.opentelemetry.api.trace.SpanKind; ++import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; ++import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; ++import io.opentelemetry.sdk.trace.data.StatusData; ++import io.opentelemetry.semconv.SemanticAttributes; ++import java.io.BufferedReader; ++import java.io.BufferedWriter; ++import java.io.ByteArrayInputStream; ++import java.io.ByteArrayOutputStream; ++import java.io.IOException; ++import java.io.InputStream; ++import java.io.InputStreamReader; ++import java.io.OutputStream; ++import java.io.OutputStreamWriter; ++import java.nio.charset.StandardCharsets; ++import org.junit.jupiter.api.AfterEach; ++import org.junit.jupiter.api.BeforeEach; ++import org.junit.jupiter.api.Test; ++import org.junit.jupiter.api.extension.ExtendWith; ++import org.junit.jupiter.api.extension.RegisterExtension; ++import org.mockito.Mock; ++import org.mockito.junit.jupiter.MockitoExtension; ++ ++@ExtendWith(MockitoExtension.class) ++public class AwsLambdaStreamHandlerTest { ++ ++ @RegisterExtension ++ public static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); ++ ++ @Mock private Context context; ++ ++ @BeforeEach ++ void setUp() { ++ when(context.getFunctionName()).thenReturn("my_function"); ++ when(context.getAwsRequestId()).thenReturn("1-22-333"); ++ } ++ ++ @AfterEach ++ void tearDown() { ++ assertThat(testing.forceFlushCalled()).isTrue(); ++ } ++ ++ @Test ++ void handlerTraced() throws Exception { ++ InputStream input = new ByteArrayInputStream("hello\n".getBytes(StandardCharsets.UTF_8)); ++ OutputStream output = new ByteArrayOutputStream(); ++ RequestStreamHandlerTestImpl handler = new RequestStreamHandlerTestImpl(); ++ handler.handleRequest(input, output, context); ++ ++ testing.waitAndAssertTraces( ++ trace -> ++ trace.hasSpansSatisfyingExactly( ++ span -> ++ span.hasName("my_function") ++ .hasKind(SpanKind.SERVER) ++ .hasAttributesSatisfyingExactly( ++ equalTo(SemanticAttributes.FAAS_INVOCATION_ID, "1-22-333")))); ++ } ++ ++ @Test ++ void handlerTracedWithException() { ++ InputStream input = new ByteArrayInputStream("bye\n".getBytes(StandardCharsets.UTF_8)); ++ OutputStream output = new ByteArrayOutputStream(); ++ RequestStreamHandlerTestImpl handler = new RequestStreamHandlerTestImpl(); ++ ++ Throwable thrown = catchThrowable(() -> handler.handleRequest(input, output, context)); ++ assertThat(thrown).isInstanceOf(IllegalArgumentException.class); ++ ++ testing.waitAndAssertTraces( ++ trace -> ++ trace.hasSpansSatisfyingExactly( ++ span -> ++ span.hasName("my_function") ++ .hasKind(SpanKind.SERVER) ++ .hasStatus(StatusData.error()) ++ .hasException(thrown) ++ .hasAttributesSatisfyingExactly( ++ equalTo(SemanticAttributes.FAAS_INVOCATION_ID, "1-22-333")))); ++ } ++ ++ static final class RequestStreamHandlerTestImpl implements RequestStreamHandler { ++ @Override ++ public void handleRequest(InputStream input, OutputStream output, Context context) ++ throws IOException { ++ BufferedReader reader = ++ new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)); ++ BufferedWriter writer = ++ new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8)); ++ String line = reader.readLine(); ++ if (line.equals("hello")) { ++ writer.write("world"); ++ writer.flush(); ++ writer.close(); ++ } else { ++ throw new IllegalArgumentException("bad argument"); ++ } ++ } ++ } ++} From 6fa5cc666f077eeb059fec9e267eba5d6029a7cf Mon Sep 17 00:00:00 2001 From: Luke Zhang Date: Thu, 21 Aug 2025 12:33:33 -0700 Subject: [PATCH 6/6] revert previous commit. upstream code is correct. --- lambda-layer/build-layer.sh | 2 - .../StreamHandlerInstrumentation.patch | 513 ------------------ 2 files changed, 515 deletions(-) delete mode 100644 lambda-layer/patches/StreamHandlerInstrumentation.patch diff --git a/lambda-layer/build-layer.sh b/lambda-layer/build-layer.sh index 265a367d66..ddd144716d 100755 --- a/lambda-layer/build-layer.sh +++ b/lambda-layer/build-layer.sh @@ -25,8 +25,6 @@ git checkout v${version} -b tag-v${version} # This patch is for Lambda related context propagation patch -p1 < "$SOURCEDIR"/patches/opentelemetry-java-instrumentation.patch -patch -p1 < "$SOURCEDIR"/patches/StreamHandlerInstrumentation.patch - ./gradlew publishToMavenLocal popd rm -rf opentelemetry-java-instrumentation diff --git a/lambda-layer/patches/StreamHandlerInstrumentation.patch b/lambda-layer/patches/StreamHandlerInstrumentation.patch deleted file mode 100644 index c4d4751c89..0000000000 --- a/lambda-layer/patches/StreamHandlerInstrumentation.patch +++ /dev/null @@ -1,513 +0,0 @@ -diff --git a/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaInstrumentationModule.java b/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaInstrumentationModule.java -index 35d6b70ed6..b6a305178e 100644 ---- a/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaInstrumentationModule.java -+++ b/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaInstrumentationModule.java -@@ -6,17 +6,18 @@ - package io.opentelemetry.javaagent.instrumentation.awslambdacore.v1_0; - - import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; --import static java.util.Collections.singletonList; - import static net.bytebuddy.matcher.ElementMatchers.not; - - import com.google.auto.service.AutoService; - import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; - import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; -+import java.util.Arrays; - import java.util.List; - import net.bytebuddy.matcher.ElementMatcher; - - @AutoService(InstrumentationModule.class) - public class AwsLambdaInstrumentationModule extends InstrumentationModule { -+ - public AwsLambdaInstrumentationModule() { - super("aws-lambda-core", "aws-lambda-core-1.0", "aws-lambda"); - } -@@ -34,6 +35,8 @@ public class AwsLambdaInstrumentationModule extends InstrumentationModule { - - @Override - public List typeInstrumentations() { -- return singletonList(new AwsLambdaRequestHandlerInstrumentation()); -+ return Arrays.asList( -+ new AwsLambdaRequestHandlerInstrumentation(), -+ new AwsLambdaRequestStreamHandlerInstrumentation()); - } - } -diff --git a/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaRequestStreamHandlerInstrumentation.java b/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaRequestStreamHandlerInstrumentation.java -new file mode 100644 -index 0000000000..1c4ef1ac07 ---- /dev/null -+++ b/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaRequestStreamHandlerInstrumentation.java -@@ -0,0 +1,98 @@ -+/* -+ * Copyright The OpenTelemetry Authors -+ * SPDX-License-Identifier: Apache-2.0 -+ */ -+ -+package io.opentelemetry.javaagent.instrumentation.awslambdacore.v1_0; -+ -+import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; -+import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; -+import static io.opentelemetry.javaagent.instrumentation.awslambdacore.v1_0.AwsLambdaInstrumentationHelper.functionInstrumenter; -+import static net.bytebuddy.matcher.ElementMatchers.isMethod; -+import static net.bytebuddy.matcher.ElementMatchers.isPublic; -+import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; -+import static net.bytebuddy.matcher.ElementMatchers.named; -+import static net.bytebuddy.matcher.ElementMatchers.not; -+import static net.bytebuddy.matcher.ElementMatchers.takesArgument; -+ -+import com.amazonaws.services.lambda.runtime.Context; -+import io.opentelemetry.context.Scope; -+import io.opentelemetry.instrumentation.awslambdacore.v1_0.AwsLambdaRequest; -+import io.opentelemetry.javaagent.bootstrap.OpenTelemetrySdkAccess; -+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; -+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; -+import java.io.InputStream; -+import java.util.Collections; -+import java.util.concurrent.TimeUnit; -+import net.bytebuddy.asm.Advice; -+import net.bytebuddy.description.type.TypeDescription; -+import net.bytebuddy.implementation.bytecode.assign.Assigner.Typing; -+import net.bytebuddy.matcher.ElementMatcher; -+ -+public class AwsLambdaRequestStreamHandlerInstrumentation implements TypeInstrumentation { -+ -+ @Override -+ public ElementMatcher classLoaderOptimization() { -+ return hasClassesNamed("com.amazonaws.services.lambda.runtime.RequestStreamHandler"); -+ } -+ -+ @Override -+ public ElementMatcher typeMatcher() { -+ return implementsInterface(named("com.amazonaws.services.lambda.runtime.RequestStreamHandler")) -+ .and(not(nameStartsWith("com.amazonaws.services.lambda.runtime.api.client"))) -+ // In Java 8 and Java 11 runtimes, -+ // AWS Lambda runtime is packaged under `lambdainternal` package. -+ // But it is `com.amazonaws.services.lambda.runtime.api.client` -+ // for new runtime likes Java 17 and Java 21. -+ .and(not(nameStartsWith("lambdainternal"))); -+ } -+ -+ @Override -+ public void transform(TypeTransformer transformer) { -+ transformer.applyAdviceToMethod( -+ isMethod() -+ .and(isPublic()) -+ .and(named("handleRequest")) -+ .and(takesArgument(2, named("com.amazonaws.services.lambda.runtime.Context"))), -+ AwsLambdaRequestStreamHandlerInstrumentation.class.getName() + "$HandleRequestAdvice"); -+ } -+ -+ @SuppressWarnings("unused") -+ public static class HandleRequestAdvice { -+ -+ @Advice.OnMethodEnter(suppress = Throwable.class) -+ public static void onEnter( -+ @Advice.Argument(0) InputStream input, -+ @Advice.Argument(2) Context context, -+ @Advice.Local("otelInput") AwsLambdaRequest otelInput, -+ @Advice.Local("otelContext") io.opentelemetry.context.Context otelContext, -+ @Advice.Local("otelScope") Scope otelScope) { -+ -+ otelInput = AwsLambdaRequest.create(context, input, Collections.emptyMap()); -+ io.opentelemetry.context.Context parentContext = functionInstrumenter().extract(otelInput); -+ -+ if (!functionInstrumenter().shouldStart(parentContext, otelInput)) { -+ return; -+ } -+ -+ otelContext = functionInstrumenter().start(parentContext, otelInput); -+ otelScope = otelContext.makeCurrent(); -+ } -+ -+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) -+ public static void stopSpan( -+ @Advice.Argument(value = 0, typing = Typing.DYNAMIC) Object arg, -+ @Advice.Thrown Throwable throwable, -+ @Advice.Local("otelInput") AwsLambdaRequest input, -+ @Advice.Local("otelContext") io.opentelemetry.context.Context functionContext, -+ @Advice.Local("otelScope") Scope functionScope) { -+ -+ if (functionScope != null) { -+ functionScope.close(); -+ functionInstrumenter().end(functionContext, input, null, throwable); -+ } -+ -+ OpenTelemetrySdkAccess.forceFlush((long)1, TimeUnit.SECONDS); -+ } -+ } -+} -diff --git a/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaStreamHandlerTest.java b/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaStreamHandlerTest.java -new file mode 100644 -index 0000000000..7bed968d77 ---- /dev/null -+++ b/instrumentation/aws-lambda/aws-lambda-core-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awslambdacore/v1_0/AwsLambdaStreamHandlerTest.java -@@ -0,0 +1,113 @@ -+/* -+ * Copyright The OpenTelemetry Authors -+ * SPDX-License-Identifier: Apache-2.0 -+ */ -+ -+package io.opentelemetry.javaagent.instrumentation.awslambdacore.v1_0; -+ -+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -+import static org.assertj.core.api.Assertions.assertThat; -+import static org.assertj.core.api.Assertions.catchThrowable; -+import static org.mockito.Mockito.when; -+ -+import com.amazonaws.services.lambda.runtime.Context; -+import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -+import io.opentelemetry.api.trace.SpanKind; -+import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; -+import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -+import io.opentelemetry.sdk.trace.data.StatusData; -+import io.opentelemetry.semconv.SemanticAttributes; -+import java.io.BufferedReader; -+import java.io.BufferedWriter; -+import java.io.ByteArrayInputStream; -+import java.io.ByteArrayOutputStream; -+import java.io.IOException; -+import java.io.InputStream; -+import java.io.InputStreamReader; -+import java.io.OutputStream; -+import java.io.OutputStreamWriter; -+import java.nio.charset.StandardCharsets; -+import org.junit.jupiter.api.AfterEach; -+import org.junit.jupiter.api.BeforeEach; -+import org.junit.jupiter.api.Test; -+import org.junit.jupiter.api.extension.ExtendWith; -+import org.junit.jupiter.api.extension.RegisterExtension; -+import org.mockito.Mock; -+import org.mockito.junit.jupiter.MockitoExtension; -+ -+@ExtendWith(MockitoExtension.class) -+public class AwsLambdaStreamHandlerTest { -+ -+ @RegisterExtension -+ public static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); -+ -+ @Mock private Context context; -+ -+ @BeforeEach -+ void setUp() { -+ when(context.getFunctionName()).thenReturn("my_function"); -+ when(context.getAwsRequestId()).thenReturn("1-22-333"); -+ } -+ -+ @AfterEach -+ void tearDown() { -+ assertThat(testing.forceFlushCalled()).isTrue(); -+ } -+ -+ @Test -+ void handlerTraced() throws Exception { -+ InputStream input = new ByteArrayInputStream("hello\n".getBytes(StandardCharsets.UTF_8)); -+ OutputStream output = new ByteArrayOutputStream(); -+ RequestStreamHandlerTestImpl handler = new RequestStreamHandlerTestImpl(); -+ handler.handleRequest(input, output, context); -+ -+ testing.waitAndAssertTraces( -+ trace -> -+ trace.hasSpansSatisfyingExactly( -+ span -> -+ span.hasName("my_function") -+ .hasKind(SpanKind.SERVER) -+ .hasAttributesSatisfyingExactly( -+ equalTo(SemanticAttributes.FAAS_INVOCATION_ID, "1-22-333")))); -+ } -+ -+ @Test -+ void handlerTracedWithException() { -+ InputStream input = new ByteArrayInputStream("bye\n".getBytes(StandardCharsets.UTF_8)); -+ OutputStream output = new ByteArrayOutputStream(); -+ RequestStreamHandlerTestImpl handler = new RequestStreamHandlerTestImpl(); -+ -+ Throwable thrown = catchThrowable(() -> handler.handleRequest(input, output, context)); -+ assertThat(thrown).isInstanceOf(IllegalArgumentException.class); -+ -+ testing.waitAndAssertTraces( -+ trace -> -+ trace.hasSpansSatisfyingExactly( -+ span -> -+ span.hasName("my_function") -+ .hasKind(SpanKind.SERVER) -+ .hasStatus(StatusData.error()) -+ .hasException(thrown) -+ .hasAttributesSatisfyingExactly( -+ equalTo(SemanticAttributes.FAAS_INVOCATION_ID, "1-22-333")))); -+ } -+ -+ static final class RequestStreamHandlerTestImpl implements RequestStreamHandler { -+ @Override -+ public void handleRequest(InputStream input, OutputStream output, Context context) -+ throws IOException { -+ BufferedReader reader = -+ new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)); -+ BufferedWriter writer = -+ new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8)); -+ String line = reader.readLine(); -+ if (line.equals("hello")) { -+ writer.write("world"); -+ writer.flush(); -+ writer.close(); -+ } else { -+ throw new IllegalArgumentException("bad argument"); -+ } -+ } -+ } -+} -diff --git a/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaInstrumentationModule.java b/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaInstrumentationModule.java -index 9e0e372241..2dd6051c23 100644 ---- a/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaInstrumentationModule.java -+++ b/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaInstrumentationModule.java -@@ -6,11 +6,11 @@ - package io.opentelemetry.javaagent.instrumentation.awslambdaevents.v2_2; - - import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; --import static java.util.Collections.singletonList; - - import com.google.auto.service.AutoService; - import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; - import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; -+import java.util.Arrays; - import java.util.List; - import net.bytebuddy.matcher.ElementMatcher; - -@@ -32,6 +32,8 @@ public class AwsLambdaInstrumentationModule extends InstrumentationModule { - - @Override - public List typeInstrumentations() { -- return singletonList(new AwsLambdaRequestHandlerInstrumentation()); -+ return Arrays.asList( -+ new AwsLambdaRequestHandlerInstrumentation(), -+ new AwsLambdaRequestStreamHandlerInstrumentation()); - } - } -diff --git a/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaRequestStreamHandlerInstrumentation.java b/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaRequestStreamHandlerInstrumentation.java -new file mode 100644 -index 0000000000..f21a4a5526 ---- /dev/null -+++ b/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaRequestStreamHandlerInstrumentation.java -@@ -0,0 +1,104 @@ -+/* -+ * Copyright The OpenTelemetry Authors -+ * SPDX-License-Identifier: Apache-2.0 -+ */ -+ -+package io.opentelemetry.javaagent.instrumentation.awslambdaevents.v2_2; -+ -+import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; -+import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; -+import static net.bytebuddy.matcher.ElementMatchers.isMethod; -+import static net.bytebuddy.matcher.ElementMatchers.isPublic; -+import static net.bytebuddy.matcher.ElementMatchers.named; -+import static net.bytebuddy.matcher.ElementMatchers.takesArgument; -+ -+import com.amazonaws.services.lambda.runtime.Context; -+import com.amazonaws.services.lambda.runtime.events.SQSEvent; -+import io.opentelemetry.context.Scope; -+import io.opentelemetry.instrumentation.awslambdacore.v1_0.AwsLambdaRequest; -+import io.opentelemetry.javaagent.bootstrap.OpenTelemetrySdkAccess; -+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; -+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; -+import java.io.InputStream; -+import java.util.Collections; -+import java.util.concurrent.TimeUnit; -+import net.bytebuddy.asm.Advice; -+import net.bytebuddy.description.type.TypeDescription; -+import net.bytebuddy.implementation.bytecode.assign.Assigner.Typing; -+import net.bytebuddy.matcher.ElementMatcher; -+ -+public class AwsLambdaRequestStreamHandlerInstrumentation implements TypeInstrumentation { -+ -+ @Override -+ public ElementMatcher classLoaderOptimization() { -+ return hasClassesNamed("com.amazonaws.services.lambda.runtime.RequestStreamHandler"); -+ } -+ -+ @Override -+ public ElementMatcher typeMatcher() { -+ return implementsInterface(named("com.amazonaws.services.lambda.runtime.RequestStreamHandler")); -+ } -+ -+ @Override -+ public void transform(TypeTransformer transformer) { -+ transformer.applyAdviceToMethod( -+ isMethod() -+ .and(isPublic()) -+ .and(named("handleRequest")) -+ .and(takesArgument(2, named("com.amazonaws.services.lambda.runtime.Context"))), -+ AwsLambdaRequestStreamHandlerInstrumentation.class.getName() + "$HandleRequestAdvice"); -+ } -+ -+ @SuppressWarnings("unused") -+ public static class HandleRequestAdvice { -+ -+ @Advice.OnMethodEnter(suppress = Throwable.class) -+ public static void onEnter( -+ @Advice.Argument(0) InputStream input, -+ @Advice.Argument(2) Context context, -+ @Advice.Local("otelInput") AwsLambdaRequest otelInput, -+ @Advice.Local("otelFunctionContext") io.opentelemetry.context.Context functionContext, -+ @Advice.Local("otelFunctionScope") Scope functionScope, -+ @Advice.Local("otelMessageContext") io.opentelemetry.context.Context messageContext, -+ @Advice.Local("otelMessageScope") Scope messageScope) { -+ otelInput = AwsLambdaRequest.create(context, input, Collections.emptyMap()); -+ io.opentelemetry.context.Context parentContext = -+ AwsLambdaInstrumentationHelper.functionInstrumenter().extract(otelInput); -+ -+ if (!AwsLambdaInstrumentationHelper.functionInstrumenter() -+ .shouldStart(parentContext, otelInput)) { -+ return; -+ } -+ -+ functionContext = -+ AwsLambdaInstrumentationHelper.functionInstrumenter().start(parentContext, otelInput); -+ -+ functionScope = functionContext.makeCurrent(); -+ } -+ -+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) -+ public static void stopSpan( -+ @Advice.Argument(value = 0, typing = Typing.DYNAMIC) Object arg, -+ @Advice.Thrown Throwable throwable, -+ @Advice.Local("otelInput") AwsLambdaRequest input, -+ @Advice.Local("otelFunctionContext") io.opentelemetry.context.Context functionContext, -+ @Advice.Local("otelFunctionScope") Scope functionScope, -+ @Advice.Local("otelMessageContext") io.opentelemetry.context.Context messageContext, -+ @Advice.Local("otelMessageScope") Scope messageScope) { -+ -+ if (messageScope != null) { -+ messageScope.close(); -+ AwsLambdaInstrumentationHelper.messageInstrumenter() -+ .end(messageContext, (SQSEvent) arg, null, throwable); -+ } -+ -+ if (functionScope != null) { -+ functionScope.close(); -+ AwsLambdaInstrumentationHelper.functionInstrumenter() -+ .end(functionContext, input, null, throwable); -+ } -+ -+ OpenTelemetrySdkAccess.forceFlush((long)1, TimeUnit.SECONDS); -+ } -+ } -+} -diff --git a/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaStreamHandlerTest.java b/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaStreamHandlerTest.java -new file mode 100644 -index 0000000000..e30690418d ---- /dev/null -+++ b/instrumentation/aws-lambda/aws-lambda-events-2.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awslambdaevents/v2_2/AwsLambdaStreamHandlerTest.java -@@ -0,0 +1,113 @@ -+/* -+ * Copyright The OpenTelemetry Authors -+ * SPDX-License-Identifier: Apache-2.0 -+ */ -+ -+package io.opentelemetry.javaagent.instrumentation.awslambdaevents.v2_2; -+ -+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -+import static org.assertj.core.api.Assertions.assertThat; -+import static org.assertj.core.api.Assertions.catchThrowable; -+import static org.mockito.Mockito.when; -+ -+import com.amazonaws.services.lambda.runtime.Context; -+import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -+import io.opentelemetry.api.trace.SpanKind; -+import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; -+import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -+import io.opentelemetry.sdk.trace.data.StatusData; -+import io.opentelemetry.semconv.SemanticAttributes; -+import java.io.BufferedReader; -+import java.io.BufferedWriter; -+import java.io.ByteArrayInputStream; -+import java.io.ByteArrayOutputStream; -+import java.io.IOException; -+import java.io.InputStream; -+import java.io.InputStreamReader; -+import java.io.OutputStream; -+import java.io.OutputStreamWriter; -+import java.nio.charset.StandardCharsets; -+import org.junit.jupiter.api.AfterEach; -+import org.junit.jupiter.api.BeforeEach; -+import org.junit.jupiter.api.Test; -+import org.junit.jupiter.api.extension.ExtendWith; -+import org.junit.jupiter.api.extension.RegisterExtension; -+import org.mockito.Mock; -+import org.mockito.junit.jupiter.MockitoExtension; -+ -+@ExtendWith(MockitoExtension.class) -+public class AwsLambdaStreamHandlerTest { -+ -+ @RegisterExtension -+ public static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); -+ -+ @Mock private Context context; -+ -+ @BeforeEach -+ void setUp() { -+ when(context.getFunctionName()).thenReturn("my_function"); -+ when(context.getAwsRequestId()).thenReturn("1-22-333"); -+ } -+ -+ @AfterEach -+ void tearDown() { -+ assertThat(testing.forceFlushCalled()).isTrue(); -+ } -+ -+ @Test -+ void handlerTraced() throws Exception { -+ InputStream input = new ByteArrayInputStream("hello\n".getBytes(StandardCharsets.UTF_8)); -+ OutputStream output = new ByteArrayOutputStream(); -+ RequestStreamHandlerTestImpl handler = new RequestStreamHandlerTestImpl(); -+ handler.handleRequest(input, output, context); -+ -+ testing.waitAndAssertTraces( -+ trace -> -+ trace.hasSpansSatisfyingExactly( -+ span -> -+ span.hasName("my_function") -+ .hasKind(SpanKind.SERVER) -+ .hasAttributesSatisfyingExactly( -+ equalTo(SemanticAttributes.FAAS_INVOCATION_ID, "1-22-333")))); -+ } -+ -+ @Test -+ void handlerTracedWithException() { -+ InputStream input = new ByteArrayInputStream("bye\n".getBytes(StandardCharsets.UTF_8)); -+ OutputStream output = new ByteArrayOutputStream(); -+ RequestStreamHandlerTestImpl handler = new RequestStreamHandlerTestImpl(); -+ -+ Throwable thrown = catchThrowable(() -> handler.handleRequest(input, output, context)); -+ assertThat(thrown).isInstanceOf(IllegalArgumentException.class); -+ -+ testing.waitAndAssertTraces( -+ trace -> -+ trace.hasSpansSatisfyingExactly( -+ span -> -+ span.hasName("my_function") -+ .hasKind(SpanKind.SERVER) -+ .hasStatus(StatusData.error()) -+ .hasException(thrown) -+ .hasAttributesSatisfyingExactly( -+ equalTo(SemanticAttributes.FAAS_INVOCATION_ID, "1-22-333")))); -+ } -+ -+ static final class RequestStreamHandlerTestImpl implements RequestStreamHandler { -+ @Override -+ public void handleRequest(InputStream input, OutputStream output, Context context) -+ throws IOException { -+ BufferedReader reader = -+ new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)); -+ BufferedWriter writer = -+ new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8)); -+ String line = reader.readLine(); -+ if (line.equals("hello")) { -+ writer.write("world"); -+ writer.flush(); -+ writer.close(); -+ } else { -+ throw new IllegalArgumentException("bad argument"); -+ } -+ } -+ } -+}