From 97286cf818d7489aee07c5348a80948f241b821e Mon Sep 17 00:00:00 2001 From: John Bley Date: Mon, 3 Jun 2024 12:44:13 -0400 Subject: [PATCH 01/19] Look for propagated context in custom lambda context in addition to http headers --- .../AwsLambdaFunctionInstrumenter.java | 14 ++++++- .../internal/InstrumenterExtractionTest.java | 38 +++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/test/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/internal/InstrumenterExtractionTest.java diff --git a/instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/internal/AwsLambdaFunctionInstrumenter.java b/instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/internal/AwsLambdaFunctionInstrumenter.java index 4136e7bed954..71070cf2b317 100644 --- a/instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/internal/AwsLambdaFunctionInstrumenter.java +++ b/instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/internal/AwsLambdaFunctionInstrumenter.java @@ -11,6 +11,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.internal.ContextPropagationDebug; import io.opentelemetry.instrumentation.awslambdacore.v1_0.AwsLambdaRequest; +import java.util.HashMap; import java.util.Locale; import java.util.Map; import javax.annotation.Nullable; @@ -48,11 +49,20 @@ public void end( public Context extract(AwsLambdaRequest input) { ContextPropagationDebug.debugContextLeakIfEnabled(); + // Look in both the http headers and the custom client context + Map headers = input.getHeaders(); + if (input.getAwsContext() != null && input.getAwsContext().getClientContext() != null) { + Map customContext = input.getAwsContext().getClientContext().getCustom(); + if (customContext != null) { + headers = new HashMap<>(headers); + headers.putAll(customContext); + } + } - return openTelemetry + return openTelemetry .getPropagators() .getTextMapPropagator() - .extract(Context.root(), input.getHeaders(), MapGetter.INSTANCE); + .extract(Context.root(), headers, MapGetter.INSTANCE); } private enum MapGetter implements TextMapGetter> { diff --git a/instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/test/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/internal/InstrumenterExtractionTest.java b/instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/test/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/internal/InstrumenterExtractionTest.java new file mode 100644 index 000000000000..5790784cd9ce --- /dev/null +++ b/instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/test/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/internal/InstrumenterExtractionTest.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awslambdacore.v1_0.internal; +import com.amazonaws.services.lambda.runtime.ClientContext; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.instrumentation.awslambdacore.v1_0.AwsLambdaRequest; +import org.junit.jupiter.api.Test; +import java.util.HashMap; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.assertj.core.api.Assertions.assertThat; + + +class InstrumenterExtractionTest { + @Test + public void useCustomContext() { + AwsLambdaFunctionInstrumenter instr = AwsLambdaFunctionInstrumenterFactory.createInstrumenter(OpenTelemetry.propagating( + ContextPropagators.create(W3CTraceContextPropagator.getInstance()))); + com.amazonaws.services.lambda.runtime.Context awsContext = mock(com.amazonaws.services.lambda.runtime.Context.class); + ClientContext clientContext = mock(ClientContext.class); + when(awsContext.getClientContext()).thenReturn(clientContext); + HashMap customMap = new HashMap<>(); + customMap.put("traceparent", "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"); + when(clientContext.getCustom()).thenReturn(customMap); + + AwsLambdaRequest input = AwsLambdaRequest.create(awsContext, new HashMap<>(), new HashMap<>()); + + Context extracted = instr.extract(input); + assertThat(extracted.toString().contains("3577")).isTrue(); + } +} From c396628bf9e573f5bbae84e2c5833ebe524ffa0f Mon Sep 17 00:00:00 2001 From: John Bley Date: Wed, 26 Jun 2024 10:50:59 -0400 Subject: [PATCH 02/19] Working code to propagate through X-Amx-Client-Context --- .../aws-sdk-2.2/library/build.gradle.kts | 1 + .../awssdk/v2_2/DirectLambdaAccess.java | 22 ++++ .../awssdk/v2_2/DirectLambdaImpl.java | 94 ++++++++++++++++ .../v2_2/TracingExecutionInterceptor.java | 4 + .../awssdk/v2_2/DirectLambdaTest.java | 100 ++++++++++++++++++ 5 files changed, 221 insertions(+) create mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaAccess.java create mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java create mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts index 862df156ae14..0eaab056689f 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts @@ -7,6 +7,7 @@ dependencies { library("software.amazon.awssdk:aws-core:2.2.0") library("software.amazon.awssdk:sqs:2.2.0") + library("software.amazon.awssdk:lambda:2.2.0") library("software.amazon.awssdk:sns:2.2.0") library("software.amazon.awssdk:aws-json-protocol:2.2.0") compileOnly(project(":muzzle")) // For @NoMuzzle diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaAccess.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaAccess.java new file mode 100644 index 000000000000..26d98de91f8f --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaAccess.java @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2; + +import io.opentelemetry.context.Context; +import io.opentelemetry.javaagent.tooling.muzzle.NoMuzzle; +import software.amazon.awssdk.core.SdkRequest; + +final class DirectLambdaAccess { + private DirectLambdaAccess() {} + + private static final boolean enabled = PluginImplUtil.isImplPresent("DirectLambdaImpl"); + + @NoMuzzle + public static SdkRequest modifyRequest( + SdkRequest request, Context otelContext) { + return enabled ? DirectLambdaImpl.modifyRequest(request, otelContext) : null; + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java new file mode 100644 index 000000000000..931c53dd1896 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java @@ -0,0 +1,94 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.opentelemetry.api.GlobalOpenTelemetry; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.services.lambda.model.InvokeRequest; +import javax.annotation.Nullable; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +// this class is only used from DirectLambdaAccess from method with @NoMuzzle annotation + +// Direct lambda invocations (e.g., not through an api gateway) currently strip +// away the otel propagation headers (but leave x-ray ones intact). Use the +// custom client context header as an additional propagation mechanism for this +// very specific scenario. For reference, the header is named "X-Amz-Client-Context" but the api to manipulate +// it abstracts that away. The client context field is documented in https://docs.aws.amazon.com/lambda/latest/api/API_Invoke.html#API_Invoke_RequestParameters + +final class DirectLambdaImpl { + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + public static final String CLIENT_CONTEXT_CUSTOM_FIELDS_KEY = "custom"; + public static final int MAX_CLIENT_CONTEXT_LENGTH = 3583; + + private DirectLambdaImpl() {} + + @Nullable + static SdkRequest modifyRequest( + SdkRequest request, + io.opentelemetry.context.Context otelContext) { + if (isDirectLambdaInvocation(request)) { + try { + return modifyOrAddCustomContextHeader((InvokeRequest) request, otelContext); + } catch (Exception e) { + System.out.println("JBLEY addHeader faile: "+e); + e.printStackTrace(); + return null; + } + + } + return null; + } + static boolean isDirectLambdaInvocation(SdkRequest request) { + return request instanceof InvokeRequest; + } + + @SuppressWarnings("unchecked") + static SdkRequest modifyOrAddCustomContextHeader( + InvokeRequest request, + io.opentelemetry.context.Context otelContext) throws Exception{ + System.out.println("JBLEY addHeader start"); + InvokeRequest.Builder builder = request.toBuilder(); + // Unfortunately the value of this thing is a base64-encoded json with a character limit; also + // therefore not comma-composable like many http headers + String clientContextString = request.clientContext(); + String clientContextJsonString = "{}"; + if (clientContextString != null && !clientContextString.isEmpty()) { + clientContextJsonString = new String(Base64.getDecoder().decode(clientContextString), StandardCharsets.UTF_8); + } + Map parsedJson = (Map) OBJECT_MAPPER.readValue(clientContextJsonString, new TypeReference() {}); + Map customFields = (Map) parsedJson.getOrDefault( + CLIENT_CONTEXT_CUSTOM_FIELDS_KEY, new HashMap()); + + GlobalOpenTelemetry.getPropagators().getTextMapPropagator().inject(otelContext, customFields, Map::put); + // FIXME if no headers added, bail out + System.out.println("JBLEY addHeader added headers: "+customFields); + + parsedJson.put(CLIENT_CONTEXT_CUSTOM_FIELDS_KEY, customFields); + + // turn it back into a string (json encode) + String newJson = OBJECT_MAPPER.writeValueAsString(parsedJson); + System.out.println("JBLEY addHeader newJson: "+newJson); + // turn it back into a base64 string + String newJson64 = Base64.getEncoder().encodeToString(newJson.getBytes(StandardCharsets.UTF_8)); + System.out.println("JBLEY addHeader newJson64: "+newJson64); + // check it for length (err on the safe side with >=) + if (newJson64.length() >= MAX_CLIENT_CONTEXT_LENGTH) { + //log("Skipping propagation, resulting custom context length would be too long"); + System.out.println("JBLEY addHeader too long??"); + return null; + } + builder.clientContext(newJson64); + System.out.println("JBLEY addHeader almost done"); + return builder.build(); + } + +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java index 4b718e0abc40..3f875db3045c 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java @@ -212,6 +212,10 @@ public SdkRequest modifyRequest( if (modifiedRequest != null) { return modifiedRequest; } + modifiedRequest = DirectLambdaAccess.modifyRequest(request, otelContext); + if (modifiedRequest != null) { + return modifiedRequest; + } // Insert other special handling here, following the same pattern as SQS and SNS. diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java new file mode 100644 index 000000000000..4908505d56ee --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java @@ -0,0 +1,100 @@ +package io.opentelemetry.instrumentation.awssdk.v2_2; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import software.amazon.awssdk.services.lambda.model.InvokeRequest; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +public class DirectLambdaTest{ + private Span parent; + @Before + public void setup() { + System.out.println("BEFORE"); + OpenTelemetrySdk.builder() + .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) + .buildAndRegisterGlobal(); + + parent = GlobalOpenTelemetry.getTracer("test").spanBuilder("parentSpan").setSpanKind(SpanKind.SERVER).startSpan(); + parent.storeInContext(Context.current()).makeCurrent(); // why is this necessary? + } + + @After + public void cleanup() { + System.out.println("AFTER"); + if (parent != null) { + parent.end(); + } + GlobalOpenTelemetry.resetForTest(); + } + + private static String base64ify(String json) { + return Base64.getEncoder().encodeToString(json.getBytes(StandardCharsets.UTF_8)); + } + + + + @Test + public void noExistingClientContext() throws Exception { + InvokeRequest r = InvokeRequest.builder().build(); + Context ctx = Context.current(); + assertThat(ctx.toString().equals("{}")).isFalse(); + + InvokeRequest newR = (InvokeRequest) DirectLambdaImpl.modifyOrAddCustomContextHeader(r, ctx); + + String newCC = newR.clientContext(); + newCC = new String(Base64.getDecoder().decode(newCC), StandardCharsets.UTF_8); + assertThat(newCC.contains("traceparent")).isTrue(); + } + + @Test + public void withExistingClientContext() throws Exception { + String clientContext = base64ify("{\"otherStuff\": \"otherValue\", \"custom\": {\"preExisting\": \"somevalue\"} }"); + InvokeRequest r = InvokeRequest.builder().clientContext(clientContext).build(); + + InvokeRequest newR = (InvokeRequest) DirectLambdaImpl.modifyOrAddCustomContextHeader(r, Context.current()); + + String newCC = newR.clientContext(); + newCC = new String(Base64.getDecoder().decode(newCC), StandardCharsets.UTF_8); + assertThat(newCC.contains("traceparent")).isTrue(); + assertThat(newCC.contains("preExisting")).isTrue(); + assertThat(newCC.contains("otherStuff")).isTrue(); + } + + @Test + public void exceedingMaximumLengthDoesNotModify() throws Exception { + // awkward way to build a valid json that is almost but not quite too long + boolean continueLengthingInput = true; + StringBuffer x = new StringBuffer("x"); + String base64edCC = ""; + while(continueLengthingInput) { + x.append("x"); + String newCC = base64ify("{\""+x+"\": \""+x+"\"}"); + if (newCC.length() >= DirectLambdaImpl.MAX_CLIENT_CONTEXT_LENGTH) { + continueLengthingInput = false; + break; + } + base64edCC = newCC; + continueLengthingInput = base64edCC.length() < DirectLambdaImpl.MAX_CLIENT_CONTEXT_LENGTH; + } + + InvokeRequest r = InvokeRequest.builder().clientContext(base64edCC).build(); + assertThat(r.clientContext().equals(base64edCC)).isTrue(); + + InvokeRequest newR = (InvokeRequest) DirectLambdaImpl.modifyOrAddCustomContextHeader(r, Context.current()); + assertThat(newR == null).isTrue(); // null return means no modification performed + } + + +} From 3bae213930e0ff0ef601f0ffa563ef7b3fe41234 Mon Sep 17 00:00:00 2001 From: John Bley Date: Wed, 26 Jun 2024 10:52:31 -0400 Subject: [PATCH 03/19] Missed copyright on test file --- .../instrumentation/awssdk/v2_2/DirectLambdaTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java index 4908505d56ee..7e7d334c3d5b 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java @@ -1,3 +1,8 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.instrumentation.awssdk.v2_2; import static org.assertj.core.api.Assertions.assertThat; From 1c98310e27cfe5628b598dfa851aee993387bc00 Mon Sep 17 00:00:00 2001 From: John Bley Date: Wed, 26 Jun 2024 10:56:06 -0400 Subject: [PATCH 04/19] Clean up some debug comments, add code to handle no propagation happening --- .../awssdk/v2_2/DirectLambdaImpl.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java index 931c53dd1896..f366288441e5 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java @@ -39,8 +39,6 @@ static SdkRequest modifyRequest( try { return modifyOrAddCustomContextHeader((InvokeRequest) request, otelContext); } catch (Exception e) { - System.out.println("JBLEY addHeader faile: "+e); - e.printStackTrace(); return null; } @@ -55,7 +53,6 @@ static boolean isDirectLambdaInvocation(SdkRequest request) { static SdkRequest modifyOrAddCustomContextHeader( InvokeRequest request, io.opentelemetry.context.Context otelContext) throws Exception{ - System.out.println("JBLEY addHeader start"); InvokeRequest.Builder builder = request.toBuilder(); // Unfortunately the value of this thing is a base64-encoded json with a character limit; also // therefore not comma-composable like many http headers @@ -68,8 +65,12 @@ static SdkRequest modifyOrAddCustomContextHeader( Map customFields = (Map) parsedJson.getOrDefault( CLIENT_CONTEXT_CUSTOM_FIELDS_KEY, new HashMap()); + int numCustomFields = customFields.size(); GlobalOpenTelemetry.getPropagators().getTextMapPropagator().inject(otelContext, customFields, Map::put); - // FIXME if no headers added, bail out + if (numCustomFields == customFields.size()) { + return null; // no modifications made + } + System.out.println("JBLEY addHeader added headers: "+customFields); parsedJson.put(CLIENT_CONTEXT_CUSTOM_FIELDS_KEY, customFields); @@ -79,15 +80,11 @@ static SdkRequest modifyOrAddCustomContextHeader( System.out.println("JBLEY addHeader newJson: "+newJson); // turn it back into a base64 string String newJson64 = Base64.getEncoder().encodeToString(newJson.getBytes(StandardCharsets.UTF_8)); - System.out.println("JBLEY addHeader newJson64: "+newJson64); // check it for length (err on the safe side with >=) if (newJson64.length() >= MAX_CLIENT_CONTEXT_LENGTH) { - //log("Skipping propagation, resulting custom context length would be too long"); - System.out.println("JBLEY addHeader too long??"); return null; } builder.clientContext(newJson64); - System.out.println("JBLEY addHeader almost done"); return builder.build(); } From fafb4ac526a99a0cb115b8c0a2f75759952e6aa7 Mon Sep 17 00:00:00 2001 From: John Bley Date: Wed, 26 Jun 2024 14:37:16 -0400 Subject: [PATCH 05/19] Remove more debug output --- .../instrumentation/awssdk/v2_2/DirectLambdaImpl.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java index f366288441e5..931dee5232ba 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java @@ -71,13 +71,10 @@ static SdkRequest modifyOrAddCustomContextHeader( return null; // no modifications made } - System.out.println("JBLEY addHeader added headers: "+customFields); - parsedJson.put(CLIENT_CONTEXT_CUSTOM_FIELDS_KEY, customFields); // turn it back into a string (json encode) String newJson = OBJECT_MAPPER.writeValueAsString(parsedJson); - System.out.println("JBLEY addHeader newJson: "+newJson); // turn it back into a base64 string String newJson64 = Base64.getEncoder().encodeToString(newJson.getBytes(StandardCharsets.UTF_8)); // check it for length (err on the safe side with >=) From d2092d539732f443f8d93670ca601da2c40c5b8f Mon Sep 17 00:00:00 2001 From: John Bley Date: Wed, 26 Jun 2024 14:46:11 -0400 Subject: [PATCH 06/19] Remove yet more debugging printlns --- .../instrumentation/awssdk/v2_2/DirectLambdaTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java index 7e7d334c3d5b..8a6994f01a9b 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java @@ -26,7 +26,6 @@ public class DirectLambdaTest{ private Span parent; @Before public void setup() { - System.out.println("BEFORE"); OpenTelemetrySdk.builder() .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) .buildAndRegisterGlobal(); @@ -37,7 +36,6 @@ public void setup() { @After public void cleanup() { - System.out.println("AFTER"); if (parent != null) { parent.end(); } From 7038146b8b3f1859d4b12dc6cef78db4afc22750 Mon Sep 17 00:00:00 2001 From: John Bley Date: Wed, 26 Jun 2024 15:14:29 -0400 Subject: [PATCH 07/19] Clean up context handling for tests --- .../awssdk/v2_2/DirectLambdaTest.java | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java index 8a6994f01a9b..72a2afb91657 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java @@ -23,22 +23,21 @@ import java.util.Base64; public class DirectLambdaTest{ - private Span parent; + private Context context; @Before public void setup() { OpenTelemetrySdk.builder() .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) .buildAndRegisterGlobal(); - parent = GlobalOpenTelemetry.getTracer("test").spanBuilder("parentSpan").setSpanKind(SpanKind.SERVER).startSpan(); - parent.storeInContext(Context.current()).makeCurrent(); // why is this necessary? + Span parent = GlobalOpenTelemetry.getTracer("test").spanBuilder("parentSpan").setSpanKind(SpanKind.SERVER).startSpan(); + context = parent.storeInContext(Context.current()); + assertThat(context.toString().equals("{}")).isFalse(); + parent.end(); } @After public void cleanup() { - if (parent != null) { - parent.end(); - } GlobalOpenTelemetry.resetForTest(); } @@ -51,10 +50,8 @@ private static String base64ify(String json) { @Test public void noExistingClientContext() throws Exception { InvokeRequest r = InvokeRequest.builder().build(); - Context ctx = Context.current(); - assertThat(ctx.toString().equals("{}")).isFalse(); - InvokeRequest newR = (InvokeRequest) DirectLambdaImpl.modifyOrAddCustomContextHeader(r, ctx); + InvokeRequest newR = (InvokeRequest) DirectLambdaImpl.modifyOrAddCustomContextHeader(r, context); String newCC = newR.clientContext(); newCC = new String(Base64.getDecoder().decode(newCC), StandardCharsets.UTF_8); @@ -66,7 +63,7 @@ public void withExistingClientContext() throws Exception { String clientContext = base64ify("{\"otherStuff\": \"otherValue\", \"custom\": {\"preExisting\": \"somevalue\"} }"); InvokeRequest r = InvokeRequest.builder().clientContext(clientContext).build(); - InvokeRequest newR = (InvokeRequest) DirectLambdaImpl.modifyOrAddCustomContextHeader(r, Context.current()); + InvokeRequest newR = (InvokeRequest) DirectLambdaImpl.modifyOrAddCustomContextHeader(r, context); String newCC = newR.clientContext(); newCC = new String(Base64.getDecoder().decode(newCC), StandardCharsets.UTF_8); @@ -95,7 +92,7 @@ public void exceedingMaximumLengthDoesNotModify() throws Exception { InvokeRequest r = InvokeRequest.builder().clientContext(base64edCC).build(); assertThat(r.clientContext().equals(base64edCC)).isTrue(); - InvokeRequest newR = (InvokeRequest) DirectLambdaImpl.modifyOrAddCustomContextHeader(r, Context.current()); + InvokeRequest newR = (InvokeRequest) DirectLambdaImpl.modifyOrAddCustomContextHeader(r, context); assertThat(newR == null).isTrue(); // null return means no modification performed } From 411cb6e50502abc1b3f22e0cae08890121c3acbf Mon Sep 17 00:00:00 2001 From: John Bley Date: Wed, 26 Jun 2024 15:34:57 -0400 Subject: [PATCH 08/19] spotlessApply --- .../AwsLambdaFunctionInstrumenter.java | 4 +- .../internal/InstrumenterExtractionTest.java | 21 +++++---- .../awssdk/v2_2/DirectLambdaAccess.java | 3 +- .../awssdk/v2_2/DirectLambdaImpl.java | 38 +++++++++------- .../awssdk/v2_2/DirectLambdaTest.java | 43 +++++++++++-------- 5 files changed, 61 insertions(+), 48 deletions(-) diff --git a/instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/internal/AwsLambdaFunctionInstrumenter.java b/instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/internal/AwsLambdaFunctionInstrumenter.java index 71070cf2b317..9341bf6f79dc 100644 --- a/instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/internal/AwsLambdaFunctionInstrumenter.java +++ b/instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/internal/AwsLambdaFunctionInstrumenter.java @@ -52,14 +52,14 @@ public Context extract(AwsLambdaRequest input) { // Look in both the http headers and the custom client context Map headers = input.getHeaders(); if (input.getAwsContext() != null && input.getAwsContext().getClientContext() != null) { - Map customContext = input.getAwsContext().getClientContext().getCustom(); + Map customContext = input.getAwsContext().getClientContext().getCustom(); if (customContext != null) { headers = new HashMap<>(headers); headers.putAll(customContext); } } - return openTelemetry + return openTelemetry .getPropagators() .getTextMapPropagator() .extract(Context.root(), headers, MapGetter.INSTANCE); diff --git a/instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/test/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/internal/InstrumenterExtractionTest.java b/instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/test/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/internal/InstrumenterExtractionTest.java index 5790784cd9ce..1fda2462018d 100644 --- a/instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/test/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/internal/InstrumenterExtractionTest.java +++ b/instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/test/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/internal/InstrumenterExtractionTest.java @@ -4,26 +4,29 @@ */ package io.opentelemetry.instrumentation.awslambdacore.v1_0.internal; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.amazonaws.services.lambda.runtime.ClientContext; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; import io.opentelemetry.context.Context; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.instrumentation.awslambdacore.v1_0.AwsLambdaRequest; -import org.junit.jupiter.api.Test; import java.util.HashMap; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.assertj.core.api.Assertions.assertThat; - +import org.junit.jupiter.api.Test; class InstrumenterExtractionTest { @Test public void useCustomContext() { - AwsLambdaFunctionInstrumenter instr = AwsLambdaFunctionInstrumenterFactory.createInstrumenter(OpenTelemetry.propagating( - ContextPropagators.create(W3CTraceContextPropagator.getInstance()))); - com.amazonaws.services.lambda.runtime.Context awsContext = mock(com.amazonaws.services.lambda.runtime.Context.class); + AwsLambdaFunctionInstrumenter instr = + AwsLambdaFunctionInstrumenterFactory.createInstrumenter( + OpenTelemetry.propagating( + ContextPropagators.create(W3CTraceContextPropagator.getInstance()))); + com.amazonaws.services.lambda.runtime.Context awsContext = + mock(com.amazonaws.services.lambda.runtime.Context.class); ClientContext clientContext = mock(ClientContext.class); when(awsContext.getClientContext()).thenReturn(clientContext); HashMap customMap = new HashMap<>(); diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaAccess.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaAccess.java index 26d98de91f8f..026549c0a25b 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaAccess.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaAccess.java @@ -15,8 +15,7 @@ private DirectLambdaAccess() {} private static final boolean enabled = PluginImplUtil.isImplPresent("DirectLambdaImpl"); @NoMuzzle - public static SdkRequest modifyRequest( - SdkRequest request, Context otelContext) { + public static SdkRequest modifyRequest(SdkRequest request, Context otelContext) { return enabled ? DirectLambdaImpl.modifyRequest(request, otelContext) : null; } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java index 931dee5232ba..892559996cca 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java @@ -8,21 +8,23 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import io.opentelemetry.api.GlobalOpenTelemetry; -import software.amazon.awssdk.core.SdkRequest; -import software.amazon.awssdk.services.lambda.model.InvokeRequest; -import javax.annotation.Nullable; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.HashMap; import java.util.Map; +import javax.annotation.Nullable; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.services.lambda.model.InvokeRequest; // this class is only used from DirectLambdaAccess from method with @NoMuzzle annotation // Direct lambda invocations (e.g., not through an api gateway) currently strip // away the otel propagation headers (but leave x-ray ones intact). Use the // custom client context header as an additional propagation mechanism for this -// very specific scenario. For reference, the header is named "X-Amz-Client-Context" but the api to manipulate -// it abstracts that away. The client context field is documented in https://docs.aws.amazon.com/lambda/latest/api/API_Invoke.html#API_Invoke_RequestParameters +// very specific scenario. For reference, the header is named "X-Amz-Client-Context" but the api to +// manipulate +// it abstracts that away. The client context field is documented in +// https://docs.aws.amazon.com/lambda/latest/api/API_Invoke.html#API_Invoke_RequestParameters final class DirectLambdaImpl { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); @@ -33,40 +35,45 @@ private DirectLambdaImpl() {} @Nullable static SdkRequest modifyRequest( - SdkRequest request, - io.opentelemetry.context.Context otelContext) { + SdkRequest request, io.opentelemetry.context.Context otelContext) { if (isDirectLambdaInvocation(request)) { try { return modifyOrAddCustomContextHeader((InvokeRequest) request, otelContext); } catch (Exception e) { return null; } - } return null; } + static boolean isDirectLambdaInvocation(SdkRequest request) { return request instanceof InvokeRequest; } @SuppressWarnings("unchecked") static SdkRequest modifyOrAddCustomContextHeader( - InvokeRequest request, - io.opentelemetry.context.Context otelContext) throws Exception{ + InvokeRequest request, io.opentelemetry.context.Context otelContext) throws Exception { InvokeRequest.Builder builder = request.toBuilder(); // Unfortunately the value of this thing is a base64-encoded json with a character limit; also // therefore not comma-composable like many http headers String clientContextString = request.clientContext(); String clientContextJsonString = "{}"; if (clientContextString != null && !clientContextString.isEmpty()) { - clientContextJsonString = new String(Base64.getDecoder().decode(clientContextString), StandardCharsets.UTF_8); + clientContextJsonString = + new String(Base64.getDecoder().decode(clientContextString), StandardCharsets.UTF_8); } - Map parsedJson = (Map) OBJECT_MAPPER.readValue(clientContextJsonString, new TypeReference() {}); - Map customFields = (Map) parsedJson.getOrDefault( - CLIENT_CONTEXT_CUSTOM_FIELDS_KEY, new HashMap()); + Map parsedJson = + (Map) + OBJECT_MAPPER.readValue(clientContextJsonString, new TypeReference() {}); + Map customFields = + (Map) + parsedJson.getOrDefault( + CLIENT_CONTEXT_CUSTOM_FIELDS_KEY, new HashMap()); int numCustomFields = customFields.size(); - GlobalOpenTelemetry.getPropagators().getTextMapPropagator().inject(otelContext, customFields, Map::put); + GlobalOpenTelemetry.getPropagators() + .getTextMapPropagator() + .inject(otelContext, customFields, Map::put); if (numCustomFields == customFields.size()) { return null; // no modifications made } @@ -84,5 +91,4 @@ static SdkRequest modifyOrAddCustomContextHeader( builder.clientContext(newJson64); return builder.build(); } - } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java index 72a2afb91657..d3a4fd812299 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java @@ -14,24 +14,28 @@ import io.opentelemetry.context.Context; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.sdk.OpenTelemetrySdk; +import java.nio.charset.StandardCharsets; +import java.util.Base64; import org.junit.After; import org.junit.Before; import org.junit.Test; - import software.amazon.awssdk.services.lambda.model.InvokeRequest; -import java.nio.charset.StandardCharsets; -import java.util.Base64; -public class DirectLambdaTest{ +public class DirectLambdaTest { private Context context; + @Before public void setup() { OpenTelemetrySdk.builder() - .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) - .buildAndRegisterGlobal(); - - Span parent = GlobalOpenTelemetry.getTracer("test").spanBuilder("parentSpan").setSpanKind(SpanKind.SERVER).startSpan(); - context = parent.storeInContext(Context.current()); + .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) + .buildAndRegisterGlobal(); + + Span parent = + GlobalOpenTelemetry.getTracer("test") + .spanBuilder("parentSpan") + .setSpanKind(SpanKind.SERVER) + .startSpan(); + context = parent.storeInContext(Context.current()); assertThat(context.toString().equals("{}")).isFalse(); parent.end(); } @@ -45,13 +49,12 @@ private static String base64ify(String json) { return Base64.getEncoder().encodeToString(json.getBytes(StandardCharsets.UTF_8)); } - - @Test public void noExistingClientContext() throws Exception { InvokeRequest r = InvokeRequest.builder().build(); - InvokeRequest newR = (InvokeRequest) DirectLambdaImpl.modifyOrAddCustomContextHeader(r, context); + InvokeRequest newR = + (InvokeRequest) DirectLambdaImpl.modifyOrAddCustomContextHeader(r, context); String newCC = newR.clientContext(); newCC = new String(Base64.getDecoder().decode(newCC), StandardCharsets.UTF_8); @@ -60,10 +63,13 @@ public void noExistingClientContext() throws Exception { @Test public void withExistingClientContext() throws Exception { - String clientContext = base64ify("{\"otherStuff\": \"otherValue\", \"custom\": {\"preExisting\": \"somevalue\"} }"); + String clientContext = + base64ify( + "{\"otherStuff\": \"otherValue\", \"custom\": {\"preExisting\": \"somevalue\"} }"); InvokeRequest r = InvokeRequest.builder().clientContext(clientContext).build(); - InvokeRequest newR = (InvokeRequest) DirectLambdaImpl.modifyOrAddCustomContextHeader(r, context); + InvokeRequest newR = + (InvokeRequest) DirectLambdaImpl.modifyOrAddCustomContextHeader(r, context); String newCC = newR.clientContext(); newCC = new String(Base64.getDecoder().decode(newCC), StandardCharsets.UTF_8); @@ -78,9 +84,9 @@ public void exceedingMaximumLengthDoesNotModify() throws Exception { boolean continueLengthingInput = true; StringBuffer x = new StringBuffer("x"); String base64edCC = ""; - while(continueLengthingInput) { + while (continueLengthingInput) { x.append("x"); - String newCC = base64ify("{\""+x+"\": \""+x+"\"}"); + String newCC = base64ify("{\"" + x + "\": \"" + x + "\"}"); if (newCC.length() >= DirectLambdaImpl.MAX_CLIENT_CONTEXT_LENGTH) { continueLengthingInput = false; break; @@ -92,9 +98,8 @@ public void exceedingMaximumLengthDoesNotModify() throws Exception { InvokeRequest r = InvokeRequest.builder().clientContext(base64edCC).build(); assertThat(r.clientContext().equals(base64edCC)).isTrue(); - InvokeRequest newR = (InvokeRequest) DirectLambdaImpl.modifyOrAddCustomContextHeader(r, context); + InvokeRequest newR = + (InvokeRequest) DirectLambdaImpl.modifyOrAddCustomContextHeader(r, context); assertThat(newR == null).isTrue(); // null return means no modification performed } - - } From c0ff0128264e06b33f4e766a80eafec87ada4560 Mon Sep 17 00:00:00 2001 From: John Bley Date: Wed, 26 Jun 2024 15:48:03 -0400 Subject: [PATCH 09/19] Properly reset test --- .../instrumentation/awssdk/v2_2/DirectLambdaTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java index d3a4fd812299..2ab9cd77b388 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java @@ -26,6 +26,8 @@ public class DirectLambdaTest { @Before public void setup() { + GlobalOpenTelemetry.resetForTest(); + ; OpenTelemetrySdk.builder() .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) .buildAndRegisterGlobal(); From 2309f77b2321d0df21e87894d56f8bd60eb90b19 Mon Sep 17 00:00:00 2001 From: John Bley Date: Wed, 26 Jun 2024 16:09:12 -0400 Subject: [PATCH 10/19] checkstyle fixes --- .../awssdk/v2_2/DirectLambdaTest.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java index 2ab9cd77b388..58f720fced0d 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java @@ -58,9 +58,9 @@ public void noExistingClientContext() throws Exception { InvokeRequest newR = (InvokeRequest) DirectLambdaImpl.modifyOrAddCustomContextHeader(r, context); - String newCC = newR.clientContext(); - newCC = new String(Base64.getDecoder().decode(newCC), StandardCharsets.UTF_8); - assertThat(newCC.contains("traceparent")).isTrue(); + String newClientContext = newR.clientContext(); + newClientContext = new String(Base64.getDecoder().decode(newClientContext), StandardCharsets.UTF_8); + assertThat(newClientContext.contains("traceparent")).isTrue(); } @Test @@ -73,11 +73,11 @@ public void withExistingClientContext() throws Exception { InvokeRequest newR = (InvokeRequest) DirectLambdaImpl.modifyOrAddCustomContextHeader(r, context); - String newCC = newR.clientContext(); - newCC = new String(Base64.getDecoder().decode(newCC), StandardCharsets.UTF_8); - assertThat(newCC.contains("traceparent")).isTrue(); - assertThat(newCC.contains("preExisting")).isTrue(); - assertThat(newCC.contains("otherStuff")).isTrue(); + String newClientContext = newR.clientContext(); + newClientContext = new String(Base64.getDecoder().decode(newClientContext), StandardCharsets.UTF_8); + assertThat(newClientContext.contains("traceparent")).isTrue(); + assertThat(newClientContext.contains("preExisting")).isTrue(); + assertThat(newClientContext.contains("otherStuff")).isTrue(); } @Test @@ -85,20 +85,20 @@ public void exceedingMaximumLengthDoesNotModify() throws Exception { // awkward way to build a valid json that is almost but not quite too long boolean continueLengthingInput = true; StringBuffer x = new StringBuffer("x"); - String base64edCC = ""; + String long64edClientContext = ""; while (continueLengthingInput) { x.append("x"); - String newCC = base64ify("{\"" + x + "\": \"" + x + "\"}"); - if (newCC.length() >= DirectLambdaImpl.MAX_CLIENT_CONTEXT_LENGTH) { + String newClientContext = base64ify("{\"" + x + "\": \"" + x + "\"}"); + if (newClientContext.length() >= DirectLambdaImpl.MAX_CLIENT_CONTEXT_LENGTH) { continueLengthingInput = false; break; } - base64edCC = newCC; - continueLengthingInput = base64edCC.length() < DirectLambdaImpl.MAX_CLIENT_CONTEXT_LENGTH; + long64edClientContext = newClientContext; + continueLengthingInput = long64edClientContext.length() < DirectLambdaImpl.MAX_CLIENT_CONTEXT_LENGTH; } - InvokeRequest r = InvokeRequest.builder().clientContext(base64edCC).build(); - assertThat(r.clientContext().equals(base64edCC)).isTrue(); + InvokeRequest r = InvokeRequest.builder().clientContext(long64edClientContext).build(); + assertThat(r.clientContext().equals(long64edClientContext)).isTrue(); InvokeRequest newR = (InvokeRequest) DirectLambdaImpl.modifyOrAddCustomContextHeader(r, context); From 30462c9e47c93582d35f39757b03fc1e60da4397 Mon Sep 17 00:00:00 2001 From: John Bley Date: Wed, 26 Jun 2024 16:17:39 -0400 Subject: [PATCH 11/19] spotlessApply again --- .../instrumentation/awssdk/v2_2/DirectLambdaTest.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java index 58f720fced0d..350c5da1f498 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java @@ -59,7 +59,8 @@ public void noExistingClientContext() throws Exception { (InvokeRequest) DirectLambdaImpl.modifyOrAddCustomContextHeader(r, context); String newClientContext = newR.clientContext(); - newClientContext = new String(Base64.getDecoder().decode(newClientContext), StandardCharsets.UTF_8); + newClientContext = + new String(Base64.getDecoder().decode(newClientContext), StandardCharsets.UTF_8); assertThat(newClientContext.contains("traceparent")).isTrue(); } @@ -74,7 +75,8 @@ public void withExistingClientContext() throws Exception { (InvokeRequest) DirectLambdaImpl.modifyOrAddCustomContextHeader(r, context); String newClientContext = newR.clientContext(); - newClientContext = new String(Base64.getDecoder().decode(newClientContext), StandardCharsets.UTF_8); + newClientContext = + new String(Base64.getDecoder().decode(newClientContext), StandardCharsets.UTF_8); assertThat(newClientContext.contains("traceparent")).isTrue(); assertThat(newClientContext.contains("preExisting")).isTrue(); assertThat(newClientContext.contains("otherStuff")).isTrue(); @@ -94,7 +96,8 @@ public void exceedingMaximumLengthDoesNotModify() throws Exception { break; } long64edClientContext = newClientContext; - continueLengthingInput = long64edClientContext.length() < DirectLambdaImpl.MAX_CLIENT_CONTEXT_LENGTH; + continueLengthingInput = + long64edClientContext.length() < DirectLambdaImpl.MAX_CLIENT_CONTEXT_LENGTH; } InvokeRequest r = InvokeRequest.builder().clientContext(long64edClientContext).build(); From 64d57280fdb4056d08ef37c19099a5423bdfda0a Mon Sep 17 00:00:00 2001 From: John Bley Date: Thu, 27 Jun 2024 09:00:10 -0400 Subject: [PATCH 12/19] Properly depend on jackson for json manipulation --- instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts index 0eaab056689f..6761e21b1563 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts @@ -4,6 +4,7 @@ plugins { dependencies { implementation("io.opentelemetry.contrib:opentelemetry-aws-xray-propagator") + implementation("com.fasterxml.jackson.core:jackson-databind") library("software.amazon.awssdk:aws-core:2.2.0") library("software.amazon.awssdk:sqs:2.2.0") From 76ba8c2fe5438884ab1f87fce72dc803566b4d73 Mon Sep 17 00:00:00 2001 From: John Bley Date: Thu, 27 Jun 2024 09:33:07 -0400 Subject: [PATCH 13/19] Handful of cleanups from review feedback --- .../v1_0/internal/InstrumenterExtractionTest.java | 6 +++++- .../instrumentation/awssdk/v2_2/DirectLambdaImpl.java | 4 ++-- .../instrumentation/awssdk/v2_2/DirectLambdaTest.java | 1 - 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/test/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/internal/InstrumenterExtractionTest.java b/instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/test/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/internal/InstrumenterExtractionTest.java index 1fda2462018d..cb19d1e56879 100644 --- a/instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/test/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/internal/InstrumenterExtractionTest.java +++ b/instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/test/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/internal/InstrumenterExtractionTest.java @@ -11,6 +11,8 @@ import com.amazonaws.services.lambda.runtime.ClientContext; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; import io.opentelemetry.context.Context; import io.opentelemetry.context.propagation.ContextPropagators; @@ -36,6 +38,8 @@ public void useCustomContext() { AwsLambdaRequest input = AwsLambdaRequest.create(awsContext, new HashMap<>(), new HashMap<>()); Context extracted = instr.extract(input); - assertThat(extracted.toString().contains("3577")).isTrue(); + SpanContext spanContext = Span.fromContext(extracted).getSpanContext(); + assertThat(spanContext.getTraceId()).isEqualTo("4bf92f3577b34da6a3ce929d0e0e4736"); + assertThat(spanContext.getSpanId()).isEqualTo("00f067aa0ba902b7"); } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java index 892559996cca..54a4d04475fc 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java @@ -28,8 +28,8 @@ final class DirectLambdaImpl { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - public static final String CLIENT_CONTEXT_CUSTOM_FIELDS_KEY = "custom"; - public static final int MAX_CLIENT_CONTEXT_LENGTH = 3583; + private static final String CLIENT_CONTEXT_CUSTOM_FIELDS_KEY = "custom"; + static final int MAX_CLIENT_CONTEXT_LENGTH = 3583; // visible for testing private DirectLambdaImpl() {} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java index 350c5da1f498..cc938fc16ddc 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java @@ -27,7 +27,6 @@ public class DirectLambdaTest { @Before public void setup() { GlobalOpenTelemetry.resetForTest(); - ; OpenTelemetrySdk.builder() .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) .buildAndRegisterGlobal(); From 416e3a68cf1ef5e9bfd54995daff635544358472 Mon Sep 17 00:00:00 2001 From: John Bley Date: Thu, 27 Jun 2024 09:38:59 -0400 Subject: [PATCH 14/19] Clean up test setup per review feedback --- .../awssdk/v2_2/DirectLambdaTest.java | 35 ++++++------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java index cc938fc16ddc..87468d0c8a62 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java @@ -7,43 +7,28 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; import io.opentelemetry.context.Context; -import io.opentelemetry.context.propagation.ContextPropagators; -import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; import java.nio.charset.StandardCharsets; import java.util.Base64; -import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.junit.jupiter.api.extension.RegisterExtension; import software.amazon.awssdk.services.lambda.model.InvokeRequest; public class DirectLambdaTest { + @RegisterExtension + private static final LibraryInstrumentationExtension testing = + LibraryInstrumentationExtension.create(); + private Context context; @Before public void setup() { - GlobalOpenTelemetry.resetForTest(); - OpenTelemetrySdk.builder() - .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) - .buildAndRegisterGlobal(); - - Span parent = - GlobalOpenTelemetry.getTracer("test") - .spanBuilder("parentSpan") - .setSpanKind(SpanKind.SERVER) - .startSpan(); - context = parent.storeInContext(Context.current()); - assertThat(context.toString().equals("{}")).isFalse(); - parent.end(); - } - - @After - public void cleanup() { - GlobalOpenTelemetry.resetForTest(); + testing.runWithHttpServerSpan( + () -> { + context = Context.current(); + }); } private static String base64ify(String json) { From 150c0bc01081debdf93495ff88414b823d86e8dd Mon Sep 17 00:00:00 2001 From: John Bley Date: Thu, 27 Jun 2024 10:27:45 -0400 Subject: [PATCH 15/19] Adding lambda dependency to test as well; not sure why this is needed... --- instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts index 6761e21b1563..65103b983e4d 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts @@ -40,10 +40,12 @@ testing { implementation("software.amazon.awssdk:aws-core:+") implementation("software.amazon.awssdk:aws-json-protocol:+") implementation("software.amazon.awssdk:dynamodb:+") + implementation("software.amazon.awssdk:lambda:+") } else { implementation("software.amazon.awssdk:aws-core:2.2.0") implementation("software.amazon.awssdk:aws-json-protocol:2.2.0") implementation("software.amazon.awssdk:dynamodb:2.2.0") + implementation("software.amazon.awssdk:lambda:2.2.0") } } } From fb525c6cfd617091f244a714171234bdc5f50765 Mon Sep 17 00:00:00 2001 From: John Bley Date: Thu, 27 Jun 2024 11:17:17 -0400 Subject: [PATCH 16/19] Another missing test dependency --- .../aws-sdk/aws-sdk-2.2/library-autoconfigure/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure/build.gradle.kts index 6e0ea36a64bd..7f621f4977d2 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure/build.gradle.kts @@ -17,6 +17,7 @@ dependencies { testLibrary("software.amazon.awssdk:dynamodb:2.2.0") testLibrary("software.amazon.awssdk:ec2:2.2.0") testLibrary("software.amazon.awssdk:kinesis:2.2.0") + testLibrary("software.amazon.awssdk:lambda:2.2.0") testLibrary("software.amazon.awssdk:rds:2.2.0") testLibrary("software.amazon.awssdk:s3:2.2.0") testLibrary("software.amazon.awssdk:sqs:2.2.0") From 5217e3be078c52639d6ee0d1dc5d92d950524e74 Mon Sep 17 00:00:00 2001 From: John Bley Date: Thu, 27 Jun 2024 12:19:59 -0400 Subject: [PATCH 17/19] And one more check for plugin/classloader safety --- .../instrumentation/awssdk/v2_2/DirectLambdaImpl.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java index 54a4d04475fc..1bb32623a938 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java @@ -27,6 +27,14 @@ // https://docs.aws.amazon.com/lambda/latest/api/API_Invoke.html#API_Invoke_RequestParameters final class DirectLambdaImpl { + static { + // Force loading of InvokeRequest; this ensures that an exception is thrown at this point when + // the Lambda library is not present, which will cause DirectLambdaAccess to have + // enabled=false in library mode. + @SuppressWarnings("unused") + String ensureLoadedDummy = InvokeRequest.class.getName(); + } + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static final String CLIENT_CONTEXT_CUSTOM_FIELDS_KEY = "custom"; static final int MAX_CLIENT_CONTEXT_LENGTH = 3583; // visible for testing From 5b814070fb76f2de6c2b50779e7262e57915eaf3 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Fri, 5 Jul 2024 13:14:52 +0300 Subject: [PATCH 18/19] Replace jackson databind with json parser from aws sdk --- .../aws-sdk-2.2/javaagent/build.gradle.kts | 1 + .../awssdk/v2_2/LambdaAdviceBridge.java | 15 +++ .../v2_2/LambdaInstrumentationModule.java | 47 ++++++++ .../awssdk/v2_2/Aws2LambdaTest.java | 35 ++++++ .../aws-sdk-2.2/library/build.gradle.kts | 15 ++- ...ectLambdaAccess.java => LambdaAccess.java} | 8 +- ...{DirectLambdaImpl.java => LambdaImpl.java} | 68 +++++------ .../v2_2/TracingExecutionInterceptor.java | 2 +- .../awssdk/v2_2/DirectLambdaTest.java | 94 --------------- .../awssdk/v2_2/Aws2LambdaTest.java | 107 ++++++++++++++++++ .../aws-sdk-2.2/testing/build.gradle.kts | 1 + .../awssdk/v2_2/AbstractAws2LambdaTest.java | 78 +++++++++++++ 12 files changed, 337 insertions(+), 134 deletions(-) create mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/LambdaAdviceBridge.java create mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/LambdaInstrumentationModule.java create mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/Aws2LambdaTest.java rename instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/{DirectLambdaAccess.java => LambdaAccess.java} (72%) rename instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/{DirectLambdaImpl.java => LambdaImpl.java} (63%) delete mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java create mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/library/src/testLambda/java/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2LambdaTest.java create mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2LambdaTest.java diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts index 4cfaae6135b2..5be1df51b89b 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts @@ -81,6 +81,7 @@ dependencies { testLibrary("software.amazon.awssdk:dynamodb:2.2.0") testLibrary("software.amazon.awssdk:ec2:2.2.0") testLibrary("software.amazon.awssdk:kinesis:2.2.0") + testLibrary("software.amazon.awssdk:lambda:2.2.0") testLibrary("software.amazon.awssdk:rds:2.2.0") testLibrary("software.amazon.awssdk:s3:2.2.0") testLibrary("software.amazon.awssdk:sqs:2.2.0") diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/LambdaAdviceBridge.java b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/LambdaAdviceBridge.java new file mode 100644 index 000000000000..3aaa17525137 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/LambdaAdviceBridge.java @@ -0,0 +1,15 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2; + +public final class LambdaAdviceBridge { + private LambdaAdviceBridge() {} + + public static void referenceForMuzzleOnly() { + throw new UnsupportedOperationException( + LambdaImpl.class.getName() + " referencing for muzzle, should never be actually called"); + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/LambdaInstrumentationModule.java b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/LambdaInstrumentationModule.java new file mode 100644 index 000000000000..575bb5ec7fbc --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/LambdaInstrumentationModule.java @@ -0,0 +1,47 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.awssdk.v2_2; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static net.bytebuddy.matcher.ElementMatchers.none; + +import com.google.auto.service.AutoService; +import io.opentelemetry.instrumentation.awssdk.v2_2.LambdaAdviceBridge; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(InstrumentationModule.class) +public class LambdaInstrumentationModule extends AbstractAwsSdkInstrumentationModule { + + public LambdaInstrumentationModule() { + super("aws-sdk-2.2-lambda"); + } + + @Override + public ElementMatcher.Junction classLoaderMatcher() { + return hasClassesNamed( + "software.amazon.awssdk.services.lambda.model.InvokeRequest", + "software.amazon.awssdk.protocols.jsoncore.JsonNode"); + } + + @Override + public void doTransform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + none(), LambdaInstrumentationModule.class.getName() + "$RegisterAdvice"); + } + + @SuppressWarnings("unused") + public static class RegisterAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit() { + // (indirectly) using LambdaImpl class here to make sure it is available from LambdaAccess + // (injected into app classloader) and checked by Muzzle + LambdaAdviceBridge.referenceForMuzzleOnly(); + } + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/Aws2LambdaTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/Aws2LambdaTest.java new file mode 100644 index 000000000000..0991e804956e --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/Aws2LambdaTest.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.awssdk.v2_2; + +import io.opentelemetry.instrumentation.awssdk.v2_2.AbstractAws2LambdaTest; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; + +class Aws2LambdaTest extends AbstractAws2LambdaTest { + + @RegisterExtension + private static final AgentInstrumentationExtension testing = + AgentInstrumentationExtension.create(); + + @Override + protected InstrumentationExtension getTesting() { + return testing; + } + + @Override + protected boolean canTestLambdaInvoke() { + // only supported since 2.17.0 + return Boolean.getBoolean("testLatestDeps"); + } + + @Override + protected ClientOverrideConfiguration.Builder createOverrideConfigurationBuilder() { + return ClientOverrideConfiguration.builder(); + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts index 65103b983e4d..e4be5bdfe30a 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts @@ -4,13 +4,14 @@ plugins { dependencies { implementation("io.opentelemetry.contrib:opentelemetry-aws-xray-propagator") - implementation("com.fasterxml.jackson.core:jackson-databind") library("software.amazon.awssdk:aws-core:2.2.0") library("software.amazon.awssdk:sqs:2.2.0") library("software.amazon.awssdk:lambda:2.2.0") library("software.amazon.awssdk:sns:2.2.0") library("software.amazon.awssdk:aws-json-protocol:2.2.0") + // json-utils was added in 2.17.0 + compileOnly("software.amazon.awssdk:json-utils:2.17.0") compileOnly(project(":muzzle")) // For @NoMuzzle testImplementation(project(":instrumentation:aws-sdk:aws-sdk-2.2:testing")) @@ -49,6 +50,18 @@ testing { } } } + + val testLambda by registering(JvmTestSuite::class) { + dependencies { + implementation(project()) + implementation(project(":instrumentation:aws-sdk:aws-sdk-2.2:testing")) + if (findProperty("testLatestDeps") as Boolean) { + implementation("software.amazon.awssdk:lambda:+") + } else { + implementation("software.amazon.awssdk:lambda:2.17.0") + } + } + } } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaAccess.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/LambdaAccess.java similarity index 72% rename from instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaAccess.java rename to instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/LambdaAccess.java index 026549c0a25b..33d792c0c826 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaAccess.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/LambdaAccess.java @@ -9,13 +9,13 @@ import io.opentelemetry.javaagent.tooling.muzzle.NoMuzzle; import software.amazon.awssdk.core.SdkRequest; -final class DirectLambdaAccess { - private DirectLambdaAccess() {} +final class LambdaAccess { + private LambdaAccess() {} - private static final boolean enabled = PluginImplUtil.isImplPresent("DirectLambdaImpl"); + private static final boolean enabled = PluginImplUtil.isImplPresent("LambdaImpl"); @NoMuzzle public static SdkRequest modifyRequest(SdkRequest request, Context otelContext) { - return enabled ? DirectLambdaImpl.modifyRequest(request, otelContext) : null; + return enabled ? LambdaImpl.modifyRequest(request, otelContext) : null; } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/LambdaImpl.java similarity index 63% rename from instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java rename to instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/LambdaImpl.java index 1bb32623a938..fae5edf5e12a 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaImpl.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/LambdaImpl.java @@ -5,51 +5,49 @@ package io.opentelemetry.instrumentation.awssdk.v2_2; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; import io.opentelemetry.api.GlobalOpenTelemetry; import java.nio.charset.StandardCharsets; import java.util.Base64; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import javax.annotation.Nullable; import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.protocols.jsoncore.JsonNode; +import software.amazon.awssdk.protocols.jsoncore.internal.ObjectJsonNode; +import software.amazon.awssdk.protocols.jsoncore.internal.StringJsonNode; import software.amazon.awssdk.services.lambda.model.InvokeRequest; -// this class is only used from DirectLambdaAccess from method with @NoMuzzle annotation +// this class is only used from LambdaAccess from method with @NoMuzzle annotation // Direct lambda invocations (e.g., not through an api gateway) currently strip -// away the otel propagation headers (but leave x-ray ones intact). Use the +// away the otel propagation headers (but leave x-ray ones intact). Use the // custom client context header as an additional propagation mechanism for this -// very specific scenario. For reference, the header is named "X-Amz-Client-Context" but the api to -// manipulate -// it abstracts that away. The client context field is documented in +// very specific scenario. For reference, the header is named "X-Amz-Client-Context" but the api to +// manipulate it abstracts that away. The client context field is documented in // https://docs.aws.amazon.com/lambda/latest/api/API_Invoke.html#API_Invoke_RequestParameters -final class DirectLambdaImpl { +final class LambdaImpl { static { // Force loading of InvokeRequest; this ensures that an exception is thrown at this point when // the Lambda library is not present, which will cause DirectLambdaAccess to have // enabled=false in library mode. @SuppressWarnings("unused") - String ensureLoadedDummy = InvokeRequest.class.getName(); + String invokeRequestName = InvokeRequest.class.getName(); + // was added in 2.17.0 + @SuppressWarnings("unused") + String jsonNodeName = JsonNode.class.getName(); } - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static final String CLIENT_CONTEXT_CUSTOM_FIELDS_KEY = "custom"; static final int MAX_CLIENT_CONTEXT_LENGTH = 3583; // visible for testing - private DirectLambdaImpl() {} + private LambdaImpl() {} @Nullable static SdkRequest modifyRequest( SdkRequest request, io.opentelemetry.context.Context otelContext) { if (isDirectLambdaInvocation(request)) { - try { - return modifyOrAddCustomContextHeader((InvokeRequest) request, otelContext); - } catch (Exception e) { - return null; - } + return modifyOrAddCustomContextHeader((InvokeRequest) request, otelContext); } return null; } @@ -58,9 +56,8 @@ static boolean isDirectLambdaInvocation(SdkRequest request) { return request instanceof InvokeRequest; } - @SuppressWarnings("unchecked") static SdkRequest modifyOrAddCustomContextHeader( - InvokeRequest request, io.opentelemetry.context.Context otelContext) throws Exception { + InvokeRequest request, io.opentelemetry.context.Context otelContext) { InvokeRequest.Builder builder = request.toBuilder(); // Unfortunately the value of this thing is a base64-encoded json with a character limit; also // therefore not comma-composable like many http headers @@ -70,26 +67,29 @@ static SdkRequest modifyOrAddCustomContextHeader( clientContextJsonString = new String(Base64.getDecoder().decode(clientContextString), StandardCharsets.UTF_8); } - Map parsedJson = - (Map) - OBJECT_MAPPER.readValue(clientContextJsonString, new TypeReference() {}); - Map customFields = - (Map) - parsedJson.getOrDefault( - CLIENT_CONTEXT_CUSTOM_FIELDS_KEY, new HashMap()); - - int numCustomFields = customFields.size(); + JsonNode jsonNode = JsonNode.parser().parse(clientContextJsonString); + if (!jsonNode.isObject()) { + return null; + } + JsonNode customNode = + jsonNode + .asObject() + .computeIfAbsent( + CLIENT_CONTEXT_CUSTOM_FIELDS_KEY, (k) -> new ObjectJsonNode(new LinkedHashMap<>())); + if (!customNode.isObject()) { + return null; + } + Map map = customNode.asObject(); GlobalOpenTelemetry.getPropagators() .getTextMapPropagator() - .inject(otelContext, customFields, Map::put); - if (numCustomFields == customFields.size()) { - return null; // no modifications made + .inject(otelContext, map, (nodes, key, value) -> nodes.put(key, new StringJsonNode(value))); + if (map.isEmpty()) { + return null; } - parsedJson.put(CLIENT_CONTEXT_CUSTOM_FIELDS_KEY, customFields); - // turn it back into a string (json encode) - String newJson = OBJECT_MAPPER.writeValueAsString(parsedJson); + String newJson = jsonNode.toString(); + // turn it back into a base64 string String newJson64 = Base64.getEncoder().encodeToString(newJson.getBytes(StandardCharsets.UTF_8)); // check it for length (err on the safe side with >=) diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java index 3f875db3045c..ada21457d45b 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java @@ -212,7 +212,7 @@ public SdkRequest modifyRequest( if (modifiedRequest != null) { return modifiedRequest; } - modifiedRequest = DirectLambdaAccess.modifyRequest(request, otelContext); + modifiedRequest = LambdaAccess.modifyRequest(request, otelContext); if (modifiedRequest != null) { return modifiedRequest; } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java deleted file mode 100644 index 87468d0c8a62..000000000000 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/DirectLambdaTest.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.awssdk.v2_2; - -import static org.assertj.core.api.Assertions.assertThat; - -import io.opentelemetry.context.Context; -import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import org.junit.Before; -import org.junit.Test; -import org.junit.jupiter.api.extension.RegisterExtension; -import software.amazon.awssdk.services.lambda.model.InvokeRequest; - -public class DirectLambdaTest { - @RegisterExtension - private static final LibraryInstrumentationExtension testing = - LibraryInstrumentationExtension.create(); - - private Context context; - - @Before - public void setup() { - testing.runWithHttpServerSpan( - () -> { - context = Context.current(); - }); - } - - private static String base64ify(String json) { - return Base64.getEncoder().encodeToString(json.getBytes(StandardCharsets.UTF_8)); - } - - @Test - public void noExistingClientContext() throws Exception { - InvokeRequest r = InvokeRequest.builder().build(); - - InvokeRequest newR = - (InvokeRequest) DirectLambdaImpl.modifyOrAddCustomContextHeader(r, context); - - String newClientContext = newR.clientContext(); - newClientContext = - new String(Base64.getDecoder().decode(newClientContext), StandardCharsets.UTF_8); - assertThat(newClientContext.contains("traceparent")).isTrue(); - } - - @Test - public void withExistingClientContext() throws Exception { - String clientContext = - base64ify( - "{\"otherStuff\": \"otherValue\", \"custom\": {\"preExisting\": \"somevalue\"} }"); - InvokeRequest r = InvokeRequest.builder().clientContext(clientContext).build(); - - InvokeRequest newR = - (InvokeRequest) DirectLambdaImpl.modifyOrAddCustomContextHeader(r, context); - - String newClientContext = newR.clientContext(); - newClientContext = - new String(Base64.getDecoder().decode(newClientContext), StandardCharsets.UTF_8); - assertThat(newClientContext.contains("traceparent")).isTrue(); - assertThat(newClientContext.contains("preExisting")).isTrue(); - assertThat(newClientContext.contains("otherStuff")).isTrue(); - } - - @Test - public void exceedingMaximumLengthDoesNotModify() throws Exception { - // awkward way to build a valid json that is almost but not quite too long - boolean continueLengthingInput = true; - StringBuffer x = new StringBuffer("x"); - String long64edClientContext = ""; - while (continueLengthingInput) { - x.append("x"); - String newClientContext = base64ify("{\"" + x + "\": \"" + x + "\"}"); - if (newClientContext.length() >= DirectLambdaImpl.MAX_CLIENT_CONTEXT_LENGTH) { - continueLengthingInput = false; - break; - } - long64edClientContext = newClientContext; - continueLengthingInput = - long64edClientContext.length() < DirectLambdaImpl.MAX_CLIENT_CONTEXT_LENGTH; - } - - InvokeRequest r = InvokeRequest.builder().clientContext(long64edClientContext).build(); - assertThat(r.clientContext().equals(long64edClientContext)).isTrue(); - - InvokeRequest newR = - (InvokeRequest) DirectLambdaImpl.modifyOrAddCustomContextHeader(r, context); - assertThat(newR == null).isTrue(); // null return means no modification performed - } -} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/testLambda/java/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2LambdaTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/testLambda/java/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2LambdaTest.java new file mode 100644 index 000000000000..3bfe7788e066 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/testLambda/java/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2LambdaTest.java @@ -0,0 +1,107 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.services.lambda.model.InvokeRequest; + +class Aws2LambdaTest extends AbstractAws2LambdaTest { + + @RegisterExtension + private static final LibraryInstrumentationExtension testing = + LibraryInstrumentationExtension.create(); + + private static Context context; + private static AwsSdkTelemetry telemetry; + + @BeforeAll + static void setup() { + testing.runWithHttpServerSpan( + () -> { + context = Context.current(); + }); + + telemetry = AwsSdkTelemetry.create(testing.getOpenTelemetry()); + } + + @Override + protected InstrumentationExtension getTesting() { + return testing; + } + + @Override + protected ClientOverrideConfiguration.Builder createOverrideConfigurationBuilder() { + return ClientOverrideConfiguration.builder() + .addExecutionInterceptor(telemetry.newExecutionInterceptor()); + } + + private static String base64ify(String json) { + return Base64.getEncoder().encodeToString(json.getBytes(StandardCharsets.UTF_8)); + } + + @Test + void noExistingClientContext() { + InvokeRequest request = InvokeRequest.builder().build(); + + InvokeRequest newRequest = + (InvokeRequest) LambdaImpl.modifyOrAddCustomContextHeader(request, context); + + String newClientContext = newRequest.clientContext(); + newClientContext = + new String(Base64.getDecoder().decode(newClientContext), StandardCharsets.UTF_8); + assertThat(newClientContext.contains("traceparent")).isTrue(); + } + + @Test + void withExistingClientContext() { + String clientContext = + base64ify( + "{\"otherStuff\": \"otherValue\", \"custom\": {\"preExisting\": \"somevalue\"} }"); + InvokeRequest request = InvokeRequest.builder().clientContext(clientContext).build(); + + InvokeRequest newRequest = + (InvokeRequest) LambdaImpl.modifyOrAddCustomContextHeader(request, context); + + String newClientContext = newRequest.clientContext(); + newClientContext = + new String(Base64.getDecoder().decode(newClientContext), StandardCharsets.UTF_8); + assertThat(newClientContext.contains("traceparent")).isTrue(); + assertThat(newClientContext.contains("preExisting")).isTrue(); + assertThat(newClientContext.contains("otherStuff")).isTrue(); + } + + @Test + void exceedingMaximumLengthDoesNotModify() { + // awkward way to build a valid json that is almost but not quite too long + StringBuilder buffer = new StringBuilder("x"); + String long64edClientContext = ""; + while (true) { + buffer.append("x"); + String newClientContext = base64ify("{\"" + buffer + "\": \"" + buffer + "\"}"); + if (newClientContext.length() >= LambdaImpl.MAX_CLIENT_CONTEXT_LENGTH) { + break; + } + long64edClientContext = newClientContext; + } + + InvokeRequest request = InvokeRequest.builder().clientContext(long64edClientContext).build(); + assertThat(request.clientContext().equals(long64edClientContext)).isTrue(); + + InvokeRequest newRequest = + (InvokeRequest) LambdaImpl.modifyOrAddCustomContextHeader(request, context); + assertThat(newRequest).isNull(); // null return means no modification performed + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts index 9981aa9a1965..f9c0791bd205 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts @@ -14,6 +14,7 @@ dependencies { compileOnly("software.amazon.awssdk:dynamodb:2.2.0") compileOnly("software.amazon.awssdk:ec2:2.2.0") compileOnly("software.amazon.awssdk:kinesis:2.2.0") + compileOnly("software.amazon.awssdk:lambda:2.2.0") compileOnly("software.amazon.awssdk:rds:2.2.0") compileOnly("software.amazon.awssdk:s3:2.2.0") compileOnly("software.amazon.awssdk:sqs:2.2.0") diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2LambdaTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2LambdaTest.java new file mode 100644 index 000000000000..64c8a48a6c77 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2LambdaTest.java @@ -0,0 +1,78 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.testing.internal.armeria.common.HttpResponse; +import io.opentelemetry.testing.internal.armeria.common.HttpStatus; +import io.opentelemetry.testing.internal.armeria.common.MediaType; +import io.opentelemetry.testing.internal.armeria.testing.junit5.server.mock.MockWebServerExtension; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.lambda.LambdaClient; +import software.amazon.awssdk.services.lambda.LambdaClientBuilder; +import software.amazon.awssdk.services.lambda.model.InvokeRequest; +import software.amazon.awssdk.services.lambda.model.InvokeResponse; + +public abstract class AbstractAws2LambdaTest { + + @RegisterExtension + private static final MockWebServerExtension server = new MockWebServerExtension(); + + private static final StaticCredentialsProvider CREDENTIALS_PROVIDER = + StaticCredentialsProvider.create( + AwsBasicCredentials.create("my-access-key", "my-secret-key")); + + protected abstract InstrumentationExtension getTesting(); + + protected abstract ClientOverrideConfiguration.Builder createOverrideConfigurationBuilder(); + + protected boolean canTestLambdaInvoke() { + return true; + } + + @Test + void testInvokeLambda() { + Assumptions.assumeTrue(canTestLambdaInvoke()); + + LambdaClientBuilder builder = LambdaClient.builder(); + builder + .overrideConfiguration(createOverrideConfigurationBuilder().build()) + .endpointOverride(server.httpUri()); + builder.region(Region.AP_NORTHEAST_1).credentialsProvider(CREDENTIALS_PROVIDER); + LambdaClient client = builder.build(); + + server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, "ok")); + + InvokeRequest request = InvokeRequest.builder().functionName("test").build(); + InvokeResponse response = client.invoke(request); + assertThat(response.statusCode()).isEqualTo(200); + assertThat(response.payload().asUtf8String()).isEqualTo("ok"); + + String clientContextHeader = + server.takeRequest().request().headers().get("x-amz-client-context"); + assertThat(clientContextHeader).isNotEmpty(); + String clientContextJson = + new String(Base64.getDecoder().decode(clientContextHeader), StandardCharsets.UTF_8); + assertThat(clientContextJson).contains("traceparent"); + + getTesting() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("Lambda.Invoke").hasKind(SpanKind.CLIENT).hasNoParent())); + } +} From 40d1264d2a588d09870f411d98f7fb1df58caeee Mon Sep 17 00:00:00 2001 From: John Bley Date: Sun, 7 Jul 2024 07:17:06 -0400 Subject: [PATCH 19/19] Add muzzle build settings for new lambda call instrumentation --- .../aws-sdk-2.2/javaagent/build.gradle.kts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts index 5be1df51b89b..9f83480f5511 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts @@ -13,6 +13,7 @@ muzzle { excludeInstrumentationName("aws-sdk-2.2-sqs") excludeInstrumentationName("aws-sdk-2.2-sns") + excludeInstrumentationName("aws-sdk-2.2-lambda") // several software.amazon.awssdk artifacts are missing for this version skip("2.17.200") @@ -43,6 +44,7 @@ muzzle { extraDependency("software.amazon.awssdk:protocol-core") excludeInstrumentationName("aws-sdk-2.2-sns") + excludeInstrumentationName("aws-sdk-2.2-lambda") // several software.amazon.awssdk artifacts are missing for this version skip("2.17.200") @@ -57,6 +59,21 @@ muzzle { extraDependency("software.amazon.awssdk:protocol-core") excludeInstrumentationName("aws-sdk-2.2-sqs") + excludeInstrumentationName("aws-sdk-2.2-lambda") + + // several software.amazon.awssdk artifacts are missing for this version + skip("2.17.200") + } + pass { + group.set("software.amazon.awssdk") + module.set("lambda") + versions.set("[2.17.0,)") + // Used by all SDK services, the only case it isn't is an SDK extension such as a custom HTTP + // client, which is not target of instrumentation anyways. + extraDependency("software.amazon.awssdk:protocol-core") + + excludeInstrumentationName("aws-sdk-2.2-sqs") + excludeInstrumentationName("aws-sdk-2.2-sns") // several software.amazon.awssdk artifacts are missing for this version skip("2.17.200")