diff --git a/instrumentation/aws-lambda-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambda/v1_0/LambdaParameters.java b/instrumentation/aws-lambda-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambda/v1_0/LambdaParameters.java new file mode 100644 index 000000000000..39b957afdc1b --- /dev/null +++ b/instrumentation/aws-lambda-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambda/v1_0/LambdaParameters.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awslambda.v1_0; + +import com.amazonaws.services.lambda.runtime.Context; +import java.lang.reflect.Method; +import java.util.function.BiFunction; + +final class LambdaParameters { + + static Object[] toArray( + Method targetMethod, T input, Context context, BiFunction mapper) { + Class[] parameterTypes = targetMethod.getParameterTypes(); + Object[] parameters = new Object[parameterTypes.length]; + for (int i = 0; i < parameterTypes.length; i++) { + Class clazz = parameterTypes[i]; + boolean isContext = clazz.equals(Context.class); + if (isContext) { + parameters[i] = context; + } else if (i == 0) { + parameters[0] = (clazz.isInstance(input) ? input : mapper.apply(input, clazz)); + } + } + return parameters; + } + + private LambdaParameters() {} +} diff --git a/instrumentation/aws-lambda-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambda/v1_0/TracingRequestApiGatewayWrapper.java b/instrumentation/aws-lambda-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambda/v1_0/TracingRequestApiGatewayWrapper.java index cd82f6b0f29d..dfd922658d72 100644 --- a/instrumentation/aws-lambda-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambda/v1_0/TracingRequestApiGatewayWrapper.java +++ b/instrumentation/aws-lambda-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambda/v1_0/TracingRequestApiGatewayWrapper.java @@ -5,23 +5,58 @@ package io.opentelemetry.instrumentation.awslambda.v1_0; +import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import com.fasterxml.jackson.core.JsonProcessingException; import io.opentelemetry.sdk.OpenTelemetrySdk; +import java.util.function.BiFunction; /** * Wrapper for {@link TracingRequestHandler}. Allows for wrapping a lambda proxied through API * Gateway, enabling single span tracing and HTTP context propagation. */ public class TracingRequestApiGatewayWrapper - extends TracingRequestWrapperBase { + extends TracingRequestWrapperBase { public TracingRequestApiGatewayWrapper() { - super(); + super(TracingRequestApiGatewayWrapper::map); } // Visible for testing - TracingRequestApiGatewayWrapper(OpenTelemetrySdk openTelemetrySdk, WrappedLambda wrappedLambda) { - super(openTelemetrySdk, wrappedLambda); + TracingRequestApiGatewayWrapper( + OpenTelemetrySdk openTelemetrySdk, + WrappedLambda wrappedLambda, + BiFunction mapper) { + super(openTelemetrySdk, wrappedLambda, mapper); + } + + // Visible for testing + static Object map(APIGatewayProxyRequestEvent event, Class clazz) { + try { + return OBJECT_MAPPER.readValue(event.getBody(), clazz); + } catch (JsonProcessingException e) { + throw new IllegalStateException( + "Could not map API Gateway event body to requested parameter type: " + clazz, e); + } + } + + @Override + protected APIGatewayProxyResponseEvent doHandleRequest( + APIGatewayProxyRequestEvent input, Context context) { + Object result = super.doHandleRequest(input, context); + APIGatewayProxyResponseEvent event = null; + // map to response event if needed + if (result instanceof APIGatewayProxyResponseEvent) { + event = (APIGatewayProxyResponseEvent) result; + } else { + try { + event = new APIGatewayProxyResponseEvent(); + event.setBody(OBJECT_MAPPER.writeValueAsString(result)); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Could not serialize return value.", e); + } + } + return event; } } diff --git a/instrumentation/aws-lambda-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambda/v1_0/TracingRequestWrapper.java b/instrumentation/aws-lambda-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambda/v1_0/TracingRequestWrapper.java index 5498cb044367..6857eea05690 100644 --- a/instrumentation/aws-lambda-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambda/v1_0/TracingRequestWrapper.java +++ b/instrumentation/aws-lambda-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambda/v1_0/TracingRequestWrapper.java @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.awslambda.v1_0; import io.opentelemetry.sdk.OpenTelemetrySdk; +import java.util.function.BiFunction; /** * Wrapper for {@link TracingRequestHandler}. Allows for wrapping a regular lambda, not proxied @@ -13,11 +14,24 @@ */ public class TracingRequestWrapper extends TracingRequestWrapperBase { public TracingRequestWrapper() { - super(); + super(TracingRequestWrapper::map); } // Visible for testing - TracingRequestWrapper(OpenTelemetrySdk openTelemetrySdk, WrappedLambda wrappedLambda) { - super(openTelemetrySdk, wrappedLambda); + TracingRequestWrapper( + OpenTelemetrySdk openTelemetrySdk, + WrappedLambda wrappedLambda, + BiFunction mapper) { + super(openTelemetrySdk, wrappedLambda, mapper); + } + + // Visible for testing + static Object map(Object jsonMap, Class clazz) { + try { + return OBJECT_MAPPER.convertValue(jsonMap, clazz); + } catch (IllegalArgumentException e) { + throw new IllegalStateException( + "Could not map input to requested parameter type: " + clazz, e); + } } } diff --git a/instrumentation/aws-lambda-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambda/v1_0/TracingRequestWrapperBase.java b/instrumentation/aws-lambda-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambda/v1_0/TracingRequestWrapperBase.java index 583babdab716..7d6da5e6aaaa 100644 --- a/instrumentation/aws-lambda-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambda/v1_0/TracingRequestWrapperBase.java +++ b/instrumentation/aws-lambda-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambda/v1_0/TracingRequestWrapperBase.java @@ -6,10 +6,12 @@ package io.opentelemetry.instrumentation.awslambda.v1_0; import com.amazonaws.services.lambda.runtime.Context; +import com.fasterxml.jackson.databind.ObjectMapper; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.OpenTelemetrySdkAutoConfiguration; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.function.BiFunction; /** * Base abstract wrapper for {@link TracingRequestHandler}. Provides: - delegation to a lambda via @@ -17,43 +19,32 @@ */ abstract class TracingRequestWrapperBase extends TracingRequestHandler { + protected static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private final WrappedLambda wrappedLambda; - - protected TracingRequestWrapperBase() { - this(OpenTelemetrySdkAutoConfiguration.initialize(), WrappedLambda.fromConfiguration()); + private final Method targetMethod; + private final BiFunction parameterMapper; + + protected TracingRequestWrapperBase(BiFunction parameterMapper) { + this( + OpenTelemetrySdkAutoConfiguration.initialize(), + WrappedLambda.fromConfiguration(), + parameterMapper); } // Visible for testing - TracingRequestWrapperBase(OpenTelemetrySdk openTelemetrySdk, WrappedLambda wrappedLambda) { + TracingRequestWrapperBase( + OpenTelemetrySdk openTelemetrySdk, + WrappedLambda wrappedLambda, + BiFunction parameterMapper) { super(openTelemetrySdk, WrapperConfiguration.flushTimeout()); this.wrappedLambda = wrappedLambda; - } - - private Object[] createParametersArray(Method targetMethod, I input, Context context) { - Class[] parameterTypes = targetMethod.getParameterTypes(); - Object[] parameters = new Object[parameterTypes.length]; - for (int i = 0; i < parameterTypes.length; i++) { - // loop through to populate each index of parameter - Object parameter = null; - Class clazz = parameterTypes[i]; - boolean isContext = clazz.equals(Context.class); - if (i == 0 && !isContext) { - // first position if it's not context - parameter = input; - } else if (isContext) { - // populate context - parameter = context; - } - parameters[i] = parameter; - } - return parameters; + this.targetMethod = wrappedLambda.getRequestTargetMethod(); + this.parameterMapper = parameterMapper; } @Override protected O doHandleRequest(I input, Context context) { - Method targetMethod = wrappedLambda.getRequestTargetMethod(); - Object[] parameters = createParametersArray(targetMethod, input, context); - + Object[] parameters = LambdaParameters.toArray(targetMethod, input, context, parameterMapper); O result; try { result = (O) targetMethod.invoke(wrappedLambda.getTargetObject(), parameters); diff --git a/instrumentation/aws-lambda-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambda/v1_0/TracingSqsEventWrapper.java b/instrumentation/aws-lambda-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambda/v1_0/TracingSqsEventWrapper.java new file mode 100644 index 000000000000..b898252c9e5c --- /dev/null +++ b/instrumentation/aws-lambda-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambda/v1_0/TracingSqsEventWrapper.java @@ -0,0 +1,45 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awslambda.v1_0; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.OpenTelemetrySdkAutoConfiguration; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class TracingSqsEventWrapper extends TracingSqsEventHandler { + + private final WrappedLambda wrappedLambda; + private final Method targetMethod; + + public TracingSqsEventWrapper() { + this(OpenTelemetrySdkAutoConfiguration.initialize(), WrappedLambda.fromConfiguration()); + } + + // Visible for testing + TracingSqsEventWrapper(OpenTelemetrySdk openTelemetrySdk, WrappedLambda wrappedLambda) { + super(openTelemetrySdk, WrapperConfiguration.flushTimeout()); + this.wrappedLambda = wrappedLambda; + this.targetMethod = wrappedLambda.getRequestTargetMethod(); + } + + @Override + protected void handleEvent(SQSEvent sqsEvent, Context context) { + Object[] parameters = + LambdaParameters.toArray(targetMethod, sqsEvent, context, (event, clazz) -> event); + try { + targetMethod.invoke(wrappedLambda.getTargetObject(), parameters); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Method is inaccessible", e); + } catch (InvocationTargetException e) { + throw (e.getCause() instanceof RuntimeException + ? (RuntimeException) e.getCause() + : new IllegalStateException(e.getTargetException())); + } + } +} diff --git a/instrumentation/aws-lambda-1.0/library/src/test/groovy/io/opentelemetry/instrumentation/awslambda/v1_0/TracingRequestApiGatewayWrapperTest.groovy b/instrumentation/aws-lambda-1.0/library/src/test/groovy/io/opentelemetry/instrumentation/awslambda/v1_0/TracingRequestApiGatewayWrapperTest.groovy index 02ba3f40f9d0..9392a7c44000 100644 --- a/instrumentation/aws-lambda-1.0/library/src/test/groovy/io/opentelemetry/instrumentation/awslambda/v1_0/TracingRequestApiGatewayWrapperTest.groovy +++ b/instrumentation/aws-lambda-1.0/library/src/test/groovy/io/opentelemetry/instrumentation/awslambda/v1_0/TracingRequestApiGatewayWrapperTest.groovy @@ -17,7 +17,7 @@ import static io.opentelemetry.api.trace.SpanKind.SERVER class TracingRequestApiGatewayWrapperTest extends TracingRequestWrapperTestBase { - static class TestApiGatewayHandler implements RequestHandler { + static class TestApiGatewayEventHandler implements RequestHandler { @Override APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { @@ -32,13 +32,50 @@ class TracingRequestApiGatewayWrapperTest extends TracingRequestWrapperTestBase } } + static class TestApiGatewayStringHandler implements RequestHandler { + + @Override + String handleRequest(String input, Context context) { + if (input == "hello") { + return "world" + } + throw new IllegalStateException("bad request") + } + } + + static class TestApiGatewayIntegerHandler implements RequestHandler { + + @Override + String handleRequest(Integer input, Context context) { + if (input == 1) { + return "world" + } + throw new IllegalStateException("bad request") + } + } + + static class CustomType { + String key, value + } + + static class TestApiGatewayCustomTypeHandler implements RequestHandler { + + @Override + String handleRequest(CustomType input, Context context) { + if (input.key == "hello") { + return "Hello "+input.value + } + throw new IllegalStateException("bad request") + } + } + def propagationHeaders() { return Collections.singletonMap("traceparent", "00-4fd0b6131f19f39af59518d127b0cafe-0000000000000456-01") } - def "handler traced with trace propagation"() { + def "event handler traced with trace propagation"() { given: - setLambda(TestApiGatewayHandler.getName() + "::handleRequest", TracingRequestApiGatewayWrapper.metaClass.&invokeConstructor) + setLambda(TestApiGatewayEventHandler.getName() + "::handleRequest", TracingRequestApiGatewayWrapper.metaClass.&invokeConstructor, TracingRequestApiGatewayWrapper.&map) def headers = ImmutableMap.builder() .putAll(propagationHeaders()) @@ -81,9 +118,9 @@ class TracingRequestApiGatewayWrapperTest extends TracingRequestWrapperTestBase } } - def "test empty request & response"() { + def "event handler test empty request & response"() { given: - setLambda(TestApiGatewayHandler.getName() + "::handleRequest", TracingRequestApiGatewayWrapper.metaClass.&invokeConstructor) + setLambda(TestApiGatewayEventHandler.getName() + "::handleRequest", TracingRequestApiGatewayWrapper.metaClass.&invokeConstructor, TracingRequestApiGatewayWrapper.&map) def input = new APIGatewayProxyRequestEvent() .withBody("empty") @@ -108,4 +145,88 @@ class TracingRequestApiGatewayWrapperTest extends TracingRequestWrapperTestBase } } } + + def "string handler test request"() { + given: + setLambda(TestApiGatewayStringHandler.getName() + "::handleRequest", TracingRequestApiGatewayWrapper.metaClass.&invokeConstructor, TracingRequestApiGatewayWrapper.&map) + + def input = new APIGatewayProxyRequestEvent() + .withBody("\"hello\"") + + when: + APIGatewayProxyResponseEvent result = wrapper.handleRequest(input, context) + + then: + result.body == "\"world\"" + assertTraces(1) { + trace(0, 1) { + span(0) { + name("my_function") + kind SERVER + attributes { + "$ResourceAttributes.FAAS_ID.key" "arn:aws:lambda:us-east-1:123456789:function:test" + "$ResourceAttributes.CLOUD_ACCOUNT_ID.key" "123456789" + "$SemanticAttributes.FAAS_EXECUTION.key" "1-22-333" + "$SemanticAttributes.FAAS_TRIGGER.key" "http" + } + } + } + } + } + + def "integer handler test request"() { + given: + setLambda(TestApiGatewayIntegerHandler.getName() + "::handleRequest", TracingRequestApiGatewayWrapper.metaClass.&invokeConstructor, TracingRequestApiGatewayWrapper.&map) + + def input = new APIGatewayProxyRequestEvent() + .withBody("1") + + when: + APIGatewayProxyResponseEvent result = wrapper.handleRequest(input, context) + + then: + result.body == "\"world\"" + assertTraces(1) { + trace(0, 1) { + span(0) { + name("my_function") + kind SERVER + attributes { + "$ResourceAttributes.FAAS_ID.key" "arn:aws:lambda:us-east-1:123456789:function:test" + "$ResourceAttributes.CLOUD_ACCOUNT_ID.key" "123456789" + "$SemanticAttributes.FAAS_EXECUTION.key" "1-22-333" + "$SemanticAttributes.FAAS_TRIGGER.key" "http" + } + } + } + } + } + + def "custom type handler test request"() { + given: + setLambda(TestApiGatewayCustomTypeHandler.getName() + "::handleRequest", TracingRequestApiGatewayWrapper.metaClass.&invokeConstructor, TracingRequestApiGatewayWrapper.&map) + + def input = new APIGatewayProxyRequestEvent() + .withBody("{\"key\":\"hello\", \"value\":\"General Kenobi\"}") + + when: + APIGatewayProxyResponseEvent result = wrapper.handleRequest(input, context) + + then: + result.body == "\"Hello General Kenobi\"" + assertTraces(1) { + trace(0, 1) { + span(0) { + name("my_function") + kind SERVER + attributes { + "$ResourceAttributes.FAAS_ID.key" "arn:aws:lambda:us-east-1:123456789:function:test" + "$ResourceAttributes.CLOUD_ACCOUNT_ID.key" "123456789" + "$SemanticAttributes.FAAS_EXECUTION.key" "1-22-333" + "$SemanticAttributes.FAAS_TRIGGER.key" "http" + } + } + } + } + } } diff --git a/instrumentation/aws-lambda-1.0/library/src/test/groovy/io/opentelemetry/instrumentation/awslambda/v1_0/TracingRequestWrapperTest.groovy b/instrumentation/aws-lambda-1.0/library/src/test/groovy/io/opentelemetry/instrumentation/awslambda/v1_0/TracingRequestWrapperTest.groovy index 16823f6b73a6..c934c5085a6e 100644 --- a/instrumentation/aws-lambda-1.0/library/src/test/groovy/io/opentelemetry/instrumentation/awslambda/v1_0/TracingRequestWrapperTest.groovy +++ b/instrumentation/aws-lambda-1.0/library/src/test/groovy/io/opentelemetry/instrumentation/awslambda/v1_0/TracingRequestWrapperTest.groovy @@ -26,9 +26,35 @@ class TracingRequestWrapperTest extends TracingRequestWrapperTestBase { } } + static class TestRequestHandlerInteger implements RequestHandler { + + @Override + String handleRequest(Integer input, Context context) { + if (input == 1) { + return "world" + } + throw new IllegalArgumentException("bad argument") + } + } + + static class CustomType { + String key, value + } + + static class TestRequestHandlerCustomType implements RequestHandler { + + @Override + String handleRequest(CustomType input, Context context) { + if (input.key == "hello there") { + return input.value + } + throw new IllegalArgumentException("bad argument") + } + } + def "handler string traced"() { given: - setLambda(TestRequestHandlerString.getName() + "::handleRequest", TracingRequestWrapper.metaClass.&invokeConstructor) + setLambda(TestRequestHandlerString.getName() + "::handleRequest", TracingRequestWrapper.metaClass.&invokeConstructor, TracingRequestWrapper.&map) when: def result = wrapper.handleRequest("hello", context) @@ -52,7 +78,7 @@ class TracingRequestWrapperTest extends TracingRequestWrapperTestBase { def "handler with exception"() { given: - setLambda(TestRequestHandlerString.getName() + "::handleRequest", TracingRequestWrapper.metaClass.&invokeConstructor) + setLambda(TestRequestHandlerString.getName() + "::handleRequest", TracingRequestWrapper.metaClass.&invokeConstructor, TracingRequestWrapper.&map) when: def thrown @@ -80,4 +106,55 @@ class TracingRequestWrapperTest extends TracingRequestWrapperTestBase { } } } + + def "handler integer traced"() { + given: + setLambda(TestRequestHandlerInteger.getName() + "::handleRequest", TracingRequestWrapper.metaClass.&invokeConstructor, TracingRequestWrapper.&map) + + when: + def result = wrapper.handleRequest(1, context) + + then: + result == "world" + assertTraces(1) { + trace(0, 1) { + span(0) { + name("my_function") + kind SERVER + attributes { + "$ResourceAttributes.FAAS_ID.key" "arn:aws:lambda:us-east-1:123456789:function:test" + "$ResourceAttributes.CLOUD_ACCOUNT_ID.key" "123456789" + "${SemanticAttributes.FAAS_EXECUTION.key}" "1-22-333" + } + } + } + } + } + + def "handler custom type traced"() { + given: + setLambda(TestRequestHandlerCustomType.getName() + "::handleRequest", TracingRequestWrapper.metaClass.&invokeConstructor, TracingRequestWrapper.&map) + + when: + CustomType ct = new CustomType() + ct.key = "hello there" + ct.value = "General Kenobi" + def result = wrapper.handleRequest(ct, context) + + then: + result == "General Kenobi" + assertTraces(1) { + trace(0, 1) { + span(0) { + name("my_function") + kind SERVER + attributes { + "$ResourceAttributes.FAAS_ID.key" "arn:aws:lambda:us-east-1:123456789:function:test" + "$ResourceAttributes.CLOUD_ACCOUNT_ID.key" "123456789" + "${SemanticAttributes.FAAS_EXECUTION.key}" "1-22-333" + } + } + } + } + } } diff --git a/instrumentation/aws-lambda-1.0/library/src/test/groovy/io/opentelemetry/instrumentation/awslambda/v1_0/TracingRequestWrapperTestBase.groovy b/instrumentation/aws-lambda-1.0/library/src/test/groovy/io/opentelemetry/instrumentation/awslambda/v1_0/TracingRequestWrapperTestBase.groovy index 003759931efc..6bf78611e6b5 100644 --- a/instrumentation/aws-lambda-1.0/library/src/test/groovy/io/opentelemetry/instrumentation/awslambda/v1_0/TracingRequestWrapperTestBase.groovy +++ b/instrumentation/aws-lambda-1.0/library/src/test/groovy/io/opentelemetry/instrumentation/awslambda/v1_0/TracingRequestWrapperTestBase.groovy @@ -7,6 +7,7 @@ package io.opentelemetry.instrumentation.awslambda.v1_0 import com.amazonaws.services.lambda.runtime.Context import io.opentelemetry.instrumentation.test.LibraryInstrumentationSpecification +import java.util.function.BiFunction import org.junit.Rule import org.junit.contrib.java.lang.system.EnvironmentVariables import spock.lang.Shared @@ -29,8 +30,8 @@ class TracingRequestWrapperTestBase extends LibraryInstrumentationSpecification context.getInvokedFunctionArn() >> "arn:aws:lambda:us-east-1:123456789:function:test" } - def setLambda(handler, Closure wrapperConstructor) { + def setLambda(handler, Closure wrapperConstructor, BiFunction mapper) { environmentVariables.set(WrappedLambda.OTEL_LAMBDA_HANDLER_ENV_KEY, handler) - wrapper = wrapperConstructor.call(testRunner().openTelemetrySdk, WrappedLambda.fromConfiguration()) + wrapper = wrapperConstructor.call(testRunner().openTelemetrySdk, WrappedLambda.fromConfiguration(), mapper) } } diff --git a/instrumentation/aws-lambda-1.0/library/src/test/groovy/io/opentelemetry/instrumentation/awslambda/v1_0/TracingSqsEventWrapperTest.groovy b/instrumentation/aws-lambda-1.0/library/src/test/groovy/io/opentelemetry/instrumentation/awslambda/v1_0/TracingSqsEventWrapperTest.groovy new file mode 100644 index 000000000000..67ae3512bf1f --- /dev/null +++ b/instrumentation/aws-lambda-1.0/library/src/test/groovy/io/opentelemetry/instrumentation/awslambda/v1_0/TracingSqsEventWrapperTest.groovy @@ -0,0 +1,85 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awslambda.v1_0 + +import static io.opentelemetry.api.trace.SpanKind.CONSUMER +import static io.opentelemetry.api.trace.SpanKind.SERVER + +import com.amazonaws.services.lambda.runtime.Context +import com.amazonaws.services.lambda.runtime.RequestHandler +import com.amazonaws.services.lambda.runtime.events.SQSEvent +import io.opentelemetry.instrumentation.test.LibraryInstrumentationSpecification +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes +import org.junit.Rule +import org.junit.contrib.java.lang.system.EnvironmentVariables + +class TracingSqsEventWrapperTest extends LibraryInstrumentationSpecification { + + @Rule + public final EnvironmentVariables environmentVariables = new EnvironmentVariables() + + TracingSqsEventWrapper wrapper + Context context + + def setup() { + context = Mock(Context) + context.getFunctionName() >> "my_function" + context.getAwsRequestId() >> "1-22-333" + context.getInvokedFunctionArn() >> "arn:aws:lambda:us-east-1:123456789:function:test" + } + + def setLambda(handler, Closure wrapperConstructor) { + environmentVariables.set(WrappedLambda.OTEL_LAMBDA_HANDLER_ENV_KEY, handler) + wrapper = wrapperConstructor.call(testRunner().openTelemetrySdk, WrappedLambda.fromConfiguration()) + } + + static class TestRequestHandler implements RequestHandler { + + @Override + Void handleRequest(SQSEvent input, Context context) { + return null + } + + } + + + def "handler event traced"() { + given: + setLambda(TestRequestHandler.getName() + "::handleRequest", TracingSqsEventWrapper.metaClass.&invokeConstructor) + + when: + SQSEvent event = new SQSEvent() + SQSEvent.SQSMessage record = new SQSEvent.SQSMessage() + record.setEventSource("otel") + record.setAttributes(Collections.emptyMap()) + event.setRecords(Arrays.asList(record)) + wrapper.handleRequest(event, context) + + then: + assertTraces(1) { + trace(0, 2) { + span(0) { + name("my_function") + kind SERVER + attributes { + "$ResourceAttributes.FAAS_ID.key" "arn:aws:lambda:us-east-1:123456789:function:test" + "$ResourceAttributes.CLOUD_ACCOUNT_ID.key" "123456789" + "${SemanticAttributes.FAAS_EXECUTION.key}" "1-22-333" + } + } + span(1) { + name("otel process") + kind CONSUMER + attributes { + "${SemanticAttributes.MESSAGING_SYSTEM}" "AmazonSQS" + "${SemanticAttributes.MESSAGING_OPERATION}" "process" + } + } + } + } + } +} diff --git a/instrumentation/aws-lambda-1.0/library/src/test/java/io/opentelemetry/instrumentation/awslambda/v1_0/LambdaParametersTest.java b/instrumentation/aws-lambda-1.0/library/src/test/java/io/opentelemetry/instrumentation/awslambda/v1_0/LambdaParametersTest.java new file mode 100644 index 000000000000..ab12e2caf702 --- /dev/null +++ b/instrumentation/aws-lambda-1.0/library/src/test/java/io/opentelemetry/instrumentation/awslambda/v1_0/LambdaParametersTest.java @@ -0,0 +1,47 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awslambda.v1_0; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import com.amazonaws.services.lambda.runtime.Context; +import java.lang.reflect.Method; +import org.junit.jupiter.api.Test; + +class LambdaParametersTest { + + public void onlyContext(Context context) {} + + public void contextOnThird(String one, String two, Context context) {} + + @Test + void shouldSetContextOnFirstPosition() throws NoSuchMethodException { + // given + Context context = mock(Context.class); + Method method = getClass().getMethod("onlyContext", Context.class); + // when + Object[] params = LambdaParameters.toArray(method, "", context, (o, c) -> o); + // then + assertThat(params).hasSize(1); + assertThat(params[0]).isEqualTo(context); + } + + @Test + void shouldSetContextOnTheLastPosition() throws NoSuchMethodException { + // given + Context context = mock(Context.class); + Method method = + getClass().getMethod("contextOnThird", String.class, String.class, Context.class); + // when + Object[] params = LambdaParameters.toArray(method, "", context, (o, c) -> o); + // then + assertThat(params).hasSize(3); + assertThat(params[0]).isEqualTo(""); + assertThat(params[1]).isNull(); + assertThat(params[2]).isEqualTo(context); + } +}