Migrate AWS Lambda tests to Java (#5315)

* Migrate AWS Lambda tests to Java

* Only assert OTLP fields for links
This commit is contained in:
Anuraag Agrawal 2022-02-08 15:27:20 +09:00 committed by GitHub
parent 186c657b4e
commit b9fac11c90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1582 additions and 1216 deletions

View File

@ -110,6 +110,7 @@ val DEPENDENCIES = listOf(
"com.google.code.findbugs:annotations:3.0.1u2",
"com.google.code.findbugs:jsr305:3.0.2",
"org.codehaus.groovy:groovy-all:${groovyVersion}",
"org.junit-pioneer:junit-pioneer:1.5.0",
"org.objenesis:objenesis:3.2",
"org.spockframework:spock-core:2.0-groovy-3.0",
"org.spockframework:spock-junit4:2.0-groovy-3.0",

View File

@ -1,27 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.awslambda.v1_0
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.awslambda.v1_0.AbstractAwsLambdaSqsHandlerTest
import io.opentelemetry.instrumentation.test.AgentTestTrait
class AwsLambdaSqsHandlerTest extends AbstractAwsLambdaSqsHandlerTest implements AgentTestTrait {
static class TestRequestHandler implements RequestHandler<SQSEvent, Void> {
@Override
Void handleRequest(SQSEvent input, Context context) {
return null
}
}
@Override
RequestHandler<SQSEvent, Void> handler() {
return new TestRequestHandler()
}
}

View File

@ -1,31 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.awslambda.v1_0
import com.amazonaws.services.lambda.runtime.Context
import com.amazonaws.services.lambda.runtime.RequestHandler
import io.opentelemetry.instrumentation.awslambda.v1_0.AbstractAwsLambdaRequestHandlerTest
import io.opentelemetry.instrumentation.test.AgentTestTrait
import io.opentelemetry.javaagent.testing.common.AgentTestingExporterAccess
class AwsLambdaTest extends AbstractAwsLambdaRequestHandlerTest implements AgentTestTrait {
def cleanup() {
assert AgentTestingExporterAccess.forceFlushCalled()
}
static class TestRequestHandler implements RequestHandler<String, String> {
@Override
String handleRequest(String input, Context context) {
return doHandleRequest(input, context)
}
}
@Override
RequestHandler<String, String> handler() {
return new TestRequestHandler()
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.awslambda.v1_0;
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.awslambda.v1_0.AbstractAwsLambdaSqsEventHandlerTest;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import org.junit.jupiter.api.extension.RegisterExtension;
public class AwsLambdaSqsEventHandlerTest extends AbstractAwsLambdaSqsEventHandlerTest {
@RegisterExtension
public static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
@Override
protected RequestHandler<SQSEvent, Void> handler() {
return new TestRequestHandler();
}
@Override
protected InstrumentationExtension testing() {
return testing;
}
private static final class TestRequestHandler implements RequestHandler<SQSEvent, Void> {
@Override
public Void handleRequest(SQSEvent input, Context context) {
return null;
}
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.awslambda.v1_0;
import static org.assertj.core.api.Assertions.assertThat;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import io.opentelemetry.instrumentation.awslambda.v1_0.AbstractAwsLambdaTest;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.extension.RegisterExtension;
public class AwsLambdaTest extends AbstractAwsLambdaTest {
@RegisterExtension
public static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
@Override
protected RequestHandler<String, String> handler() {
return new TestRequestHandler();
}
@Override
protected InstrumentationExtension testing() {
return testing;
}
@AfterEach
void tearDown() {
assertThat(testing.forceFlushCalled()).isTrue();
}
private static final class TestRequestHandler implements RequestHandler<String, String> {
@Override
public String handleRequest(String input, Context context) {
return doHandleRequest(input, context);
}
}
}

View File

@ -1,31 +0,0 @@
/*
* 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.RequestHandler
import com.amazonaws.services.lambda.runtime.events.SQSEvent
import io.opentelemetry.instrumentation.test.LibraryTestTrait
import io.opentelemetry.sdk.OpenTelemetrySdk
class AwsLambdaSqsHandlerTest extends AbstractAwsLambdaSqsHandlerTest implements LibraryTestTrait {
static class TestHandler extends TracingSqsEventHandler {
TestHandler(OpenTelemetrySdk openTelemetrySdk) {
super(openTelemetrySdk)
}
@Override
protected void handleEvent(SQSEvent event, Context context) {
}
}
@Override
RequestHandler<SQSEvent, Void> handler() {
return new TestHandler(testRunner().openTelemetrySdk)
}
}

View File

@ -1,102 +0,0 @@
/*
* 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.instrumentation.test.LibraryInstrumentationSpecification
import io.opentelemetry.sdk.OpenTelemetrySdk
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
import static io.opentelemetry.api.trace.SpanKind.CONSUMER
import static io.opentelemetry.api.trace.SpanKind.SERVER
class AwsLambdaSqsMessageHandlerTest extends LibraryInstrumentationSpecification {
private static final String AWS_TRACE_HEADER1 = "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1"
private static final String AWS_TRACE_HEADER2 = "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad9;Sampled=1"
static class TestHandler extends TracingSqsMessageHandler {
TestHandler(OpenTelemetrySdk openTelemetrySdk) {
super(openTelemetrySdk)
}
@Override
protected void handleMessage(SQSEvent.SQSMessage event, Context context) {
}
}
def "messages with process spans"() {
when:
def context = Mock(Context)
context.getFunctionName() >> "my_function"
context.getAwsRequestId() >> "1-22-333"
def message1 = new SQSEvent.SQSMessage()
message1.setAttributes(["AWSTraceHeader": AWS_TRACE_HEADER1])
message1.setMessageId("message1")
message1.setEventSource("queue1")
def message2 = new SQSEvent.SQSMessage()
message2.setAttributes(["AWSTraceHeader": AWS_TRACE_HEADER2])
message2.setMessageId("message2")
message2.setEventSource("queue1")
def event = new SQSEvent()
event.setRecords([message1, message2])
new TestHandler(testRunner().openTelemetrySdk).handleRequest(event, context)
then:
assertTraces(1) {
trace(0, 4) {
span(0) {
name("my_function")
kind SERVER
attributes {
"$SemanticAttributes.FAAS_EXECUTION" "1-22-333"
}
}
span(1) {
name("queue1 process")
kind CONSUMER
parentSpanId(span(0).spanId)
attributes {
"$SemanticAttributes.MESSAGING_SYSTEM" "AmazonSQS"
"$SemanticAttributes.MESSAGING_OPERATION" "process"
}
hasLink("5759e988bd862e3fe1be46a994272793", "53995c3f42cd8ad8")
hasLink("5759e988bd862e3fe1be46a994272793", "53995c3f42cd8ad9")
}
span(2) {
name("queue1 process")
kind CONSUMER
parentSpanId(span(1).spanId)
attributes {
"$SemanticAttributes.MESSAGING_SYSTEM" "AmazonSQS"
"$SemanticAttributes.MESSAGING_OPERATION" "process"
"$SemanticAttributes.MESSAGING_MESSAGE_ID" "message1"
"$SemanticAttributes.MESSAGING_DESTINATION" "queue1"
}
hasLink("5759e988bd862e3fe1be46a994272793", "53995c3f42cd8ad8")
}
span(3) {
name("queue1 process")
kind CONSUMER
parentSpanId(span(1).spanId)
attributes {
"$SemanticAttributes.MESSAGING_SYSTEM" "AmazonSQS"
"$SemanticAttributes.MESSAGING_OPERATION" "process"
"$SemanticAttributes.MESSAGING_MESSAGE_ID" "message2"
"$SemanticAttributes.MESSAGING_DESTINATION" "queue1"
}
hasLink("5759e988bd862e3fe1be46a994272793", "53995c3f42cd8ad9")
}
}
}
}
}

View File

@ -1,35 +0,0 @@
/*
* 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.RequestHandler
import io.opentelemetry.instrumentation.test.LibraryTestTrait
import io.opentelemetry.sdk.OpenTelemetrySdk
class AwsLambdaTest extends AbstractAwsLambdaRequestHandlerTest implements LibraryTestTrait {
def cleanup() {
assert forceFlushCalled()
}
static class TestRequestHandler extends TracingRequestHandler<String, String> {
TestRequestHandler(OpenTelemetrySdk openTelemetrySdk) {
super(openTelemetrySdk)
}
@Override
protected String doHandleRequest(String input, Context context) {
return AbstractAwsLambdaRequestHandlerTest.doHandleRequest(input, context)
}
}
@Override
RequestHandler<String, String> handler() {
return new TestRequestHandler(testRunner().openTelemetrySdk)
}
}

View File

@ -1,232 +0,0 @@
/*
* 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.RequestHandler
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent
import com.google.common.collect.ImmutableMap
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
import static io.opentelemetry.api.trace.SpanKind.SERVER
class TracingRequestApiGatewayWrapperTest extends TracingRequestWrapperTestBase {
static class TestApiGatewayEventHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
@Override
APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) {
if (input.getBody() == "hello") {
return new APIGatewayProxyResponseEvent()
.withStatusCode(200)
.withBody("world")
} else if (input.getBody() == "empty") {
return new APIGatewayProxyResponseEvent()
}
throw new IllegalStateException("bad request")
}
}
static class TestApiGatewayStringHandler implements RequestHandler<String, String> {
@Override
String handleRequest(String input, Context context) {
if (input == "hello") {
return "world"
}
throw new IllegalStateException("bad request")
}
}
static class TestApiGatewayIntegerHandler implements RequestHandler<Integer, String> {
@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<CustomType, String> {
@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 "event handler traced with trace propagation"() {
given:
setLambda(TestApiGatewayEventHandler.getName() + "::handleRequest", TracingRequestApiGatewayWrapper.metaClass.&invokeConstructor, TracingRequestApiGatewayWrapper.&map)
def headers = ImmutableMap.builder()
.putAll(propagationHeaders())
.put("User-Agent", "Test Client")
.put("host", "localhost:123")
.put("X-FORWARDED-PROTO", "http")
.build()
def input = new APIGatewayProxyRequestEvent()
.withHttpMethod("GET")
.withResource("/hello/{param}")
.withPath("/hello/world")
.withBody("hello")
.withQueryStringParamters(["a": "b", "c": "d"])
.withHeaders(headers)
when:
APIGatewayProxyResponseEvent result = wrapper.handleRequest(input, context)
then:
result.getBody() == "world"
assertTraces(1) {
trace(0, 1) {
span(0) {
parentSpanId("0000000000000456")
traceId("4fd0b6131f19f39af59518d127b0cafe")
name("/hello/{param}")
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" "1-22-333"
"$SemanticAttributes.FAAS_TRIGGER" "http"
"$SemanticAttributes.HTTP_METHOD" "GET"
"$SemanticAttributes.HTTP_USER_AGENT" "Test Client"
"$SemanticAttributes.HTTP_URL" "http://localhost:123/hello/world?a=b&c=d"
"$SemanticAttributes.HTTP_STATUS_CODE" 200
}
}
}
}
}
def "event handler test empty request & response"() {
given:
setLambda(TestApiGatewayEventHandler.getName() + "::handleRequest", TracingRequestApiGatewayWrapper.metaClass.&invokeConstructor, TracingRequestApiGatewayWrapper.&map)
def input = new APIGatewayProxyRequestEvent()
.withBody("empty")
when:
APIGatewayProxyResponseEvent result = wrapper.handleRequest(input, context)
then:
result.body == null
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" "1-22-333"
"$SemanticAttributes.FAAS_TRIGGER" "http"
}
}
}
}
}
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" "1-22-333"
"$SemanticAttributes.FAAS_TRIGGER" "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" "1-22-333"
"$SemanticAttributes.FAAS_TRIGGER" "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" "1-22-333"
"$SemanticAttributes.FAAS_TRIGGER" "http"
}
}
}
}
}
}

View File

@ -1,135 +0,0 @@
/*
* 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.RequestStreamHandler
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import io.opentelemetry.instrumentation.test.LibraryInstrumentationSpecification
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
import org.junit.Rule
import org.junit.contrib.java.lang.system.EnvironmentVariables
import spock.lang.Shared
import java.nio.charset.Charset
import static io.opentelemetry.api.trace.SpanKind.SERVER
import static io.opentelemetry.api.trace.StatusCode.ERROR
class TracingRequestStreamWrapperPropagationTest extends LibraryInstrumentationSpecification {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
@Rule
public final EnvironmentVariables environmentVariables = new EnvironmentVariables()
static class TestRequestHandler implements RequestStreamHandler {
@Override
void handleRequest(InputStream input, OutputStream output, Context context) {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output))
JsonNode root = OBJECT_MAPPER.readTree(input)
String body = root.get("body").asText()
if (body == "hello") {
writer.write("world")
writer.flush()
writer.close()
} else {
throw new IllegalArgumentException("bad argument")
}
}
}
@Shared
TracingRequestStreamWrapper wrapper
def setup() {
environmentVariables.set(WrappedLambda.OTEL_LAMBDA_HANDLER_ENV_KEY, "io.opentelemetry.instrumentation.awslambda.v1_0.TracingRequestStreamWrapperPropagationTest\$TestRequestHandler::handleRequest")
wrapper = new TracingRequestStreamWrapper(testRunner().openTelemetrySdk, WrappedLambda.fromConfiguration())
}
def cleanup() {
environmentVariables.clear(WrappedLambda.OTEL_LAMBDA_HANDLER_ENV_KEY)
}
def "handler traced with trace propagation"() {
when:
String content =
"{" +
"\"headers\" : {" +
"\"traceparent\": \"00-4fd0b6131f19f39af59518d127b0cafe-0000000000000456-01\"" +
"}," +
"\"body\" : \"hello\"" +
"}"
def context = Mock(Context)
context.getFunctionName() >> "my_function"
context.getAwsRequestId() >> "1-22-333"
def input = new ByteArrayInputStream(content.getBytes(Charset.defaultCharset()))
def output = new ByteArrayOutputStream()
wrapper.handleRequest(input, output, context)
then:
assertTraces(1) {
trace(0, 1) {
span(0) {
parentSpanId("0000000000000456")
traceId("4fd0b6131f19f39af59518d127b0cafe")
name("my_function")
kind SERVER
attributes {
"$SemanticAttributes.FAAS_EXECUTION" "1-22-333"
}
}
}
}
}
def "handler traced with exception and trace propagation"() {
when:
String content =
"{" +
"\"headers\" : {" +
"\"traceparent\": \"00-4fd0b6131f19f39af59518d127b0cafe-0000000000000456-01\"" +
"}," +
"\"body\" : \"bye\"" +
"}"
def context = Mock(Context)
context.getFunctionName() >> "my_function"
context.getAwsRequestId() >> "1-22-333"
def input = new ByteArrayInputStream(content.getBytes(Charset.defaultCharset()))
def output = new ByteArrayOutputStream()
def thrown
try {
wrapper.handleRequest(input, output, context)
} catch (Throwable t) {
thrown = t
}
then:
thrown != null
assertTraces(1) {
trace(0, 1) {
span(0) {
parentSpanId("0000000000000456")
traceId("4fd0b6131f19f39af59518d127b0cafe")
name("my_function")
kind SERVER
status ERROR
errorEvent(IllegalArgumentException, "bad argument")
attributes {
"$SemanticAttributes.FAAS_EXECUTION" "1-22-333"
}
}
}
}
}
}

View File

@ -1,119 +0,0 @@
/*
* 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.RequestStreamHandler
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
import spock.lang.Shared
import java.nio.charset.Charset
import static io.opentelemetry.api.trace.SpanKind.SERVER
import static io.opentelemetry.api.trace.StatusCode.ERROR
class TracingRequestStreamWrapperTest extends LibraryInstrumentationSpecification {
@Rule
public final EnvironmentVariables environmentVariables = new EnvironmentVariables()
static class TestRequestHandler implements RequestStreamHandler {
@Override
void handleRequest(InputStream input, OutputStream output, Context context) {
BufferedReader reader = new BufferedReader(new InputStreamReader(input))
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output))
String line = reader.readLine()
if (line == "hello") {
writer.write("world")
writer.flush()
writer.close()
} else {
throw new IllegalArgumentException("bad argument")
}
}
}
@Shared
TracingRequestStreamWrapper wrapper
def setup() {
environmentVariables.set(WrappedLambda.OTEL_LAMBDA_HANDLER_ENV_KEY, "io.opentelemetry.instrumentation.awslambda.v1_0.TracingRequestStreamWrapperTest\$TestRequestHandler::handleRequest")
wrapper = new TracingRequestStreamWrapper(testRunner().openTelemetrySdk, WrappedLambda.fromConfiguration())
}
def cleanup() {
environmentVariables.clear(WrappedLambda.OTEL_LAMBDA_HANDLER_ENV_KEY)
}
def "handler traced"() {
when:
def context = Mock(Context)
context.getFunctionName() >> "my_function"
context.getAwsRequestId() >> "1-22-333"
context.getInvokedFunctionArn() >> "arn:aws:lambda:us-east-1:123456789:function:test"
def input = new ByteArrayInputStream("hello\n".getBytes(Charset.defaultCharset()))
def output = new ByteArrayOutputStream()
wrapper.handleRequest(input, output, context)
then:
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" "1-22-333"
}
}
}
}
}
def "handler traced with exception"() {
when:
def context = Mock(Context)
context.getFunctionName() >> "my_function"
context.getAwsRequestId() >> "1-22-333"
context.getInvokedFunctionArn() >> "arn:aws:lambda:us-east-1:123456789:function:test"
def input = new ByteArrayInputStream("bye".getBytes(Charset.defaultCharset()))
def output = new ByteArrayOutputStream()
def thrown
try {
wrapper.handleRequest(input, output, context)
} catch (Throwable t) {
thrown = t
}
then:
thrown != null
assertTraces(1) {
trace(0, 1) {
span(0) {
name("my_function")
kind SERVER
status ERROR
errorEvent(IllegalArgumentException, "bad argument")
attributes {
"$ResourceAttributes.FAAS_ID.key" "arn:aws:lambda:us-east-1:123456789:function:test"
"$ResourceAttributes.CLOUD_ACCOUNT_ID.key" "123456789"
"$SemanticAttributes.FAAS_EXECUTION" "1-22-333"
}
}
}
}
}
}

View File

@ -1,160 +0,0 @@
/*
* 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.RequestHandler
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
import static io.opentelemetry.api.trace.SpanKind.SERVER
import static io.opentelemetry.api.trace.StatusCode.ERROR
class TracingRequestWrapperTest extends TracingRequestWrapperTestBase {
static class TestRequestHandlerString implements RequestHandler<String, String> {
@Override
String handleRequest(String input, Context context) {
if (input == "hello") {
return "world"
}
throw new IllegalArgumentException("bad argument")
}
}
static class TestRequestHandlerInteger implements RequestHandler<Integer, String> {
@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<CustomType, String> {
@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, TracingRequestWrapper.&map)
when:
def result = wrapper.handleRequest("hello", 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" "1-22-333"
}
}
}
}
}
def "handler with exception"() {
given:
setLambda(TestRequestHandlerString.getName() + "::handleRequest", TracingRequestWrapper.metaClass.&invokeConstructor, TracingRequestWrapper.&map)
when:
def thrown
try {
wrapper.handleRequest("goodbye", context)
} catch (Throwable t) {
thrown = t
}
then:
thrown != null
assertTraces(1) {
trace(0, 1) {
span(0) {
name("my_function")
kind SERVER
status ERROR
errorEvent(IllegalArgumentException, "bad argument")
attributes {
"$ResourceAttributes.FAAS_ID.key" "arn:aws:lambda:us-east-1:123456789:function:test"
"$ResourceAttributes.CLOUD_ACCOUNT_ID.key" "123456789"
"$SemanticAttributes.FAAS_EXECUTION" "1-22-333"
}
}
}
}
}
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" "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" "1-22-333"
}
}
}
}
}
}

View File

@ -1,38 +0,0 @@
/*
* 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 io.opentelemetry.instrumentation.test.LibraryInstrumentationSpecification
import org.junit.Rule
import org.junit.contrib.java.lang.system.EnvironmentVariables
import spock.lang.Shared
import java.util.function.BiFunction
class TracingRequestWrapperTestBase extends LibraryInstrumentationSpecification {
@Rule
public final EnvironmentVariables environmentVariables = new EnvironmentVariables()
@Shared
TracingRequestWrapperBase wrapper
@Shared
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<TracingRequestWrapperBase> wrapperConstructor, BiFunction<?, Class, Object> mapper) {
environmentVariables.set(WrappedLambda.OTEL_LAMBDA_HANDLER_ENV_KEY, handler)
wrapper = wrapperConstructor.call(testRunner().openTelemetrySdk, WrappedLambda.fromConfiguration(), mapper)
}
}

View File

@ -1,85 +0,0 @@
/*
* 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.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
import static io.opentelemetry.api.trace.SpanKind.CONSUMER
import static io.opentelemetry.api.trace.SpanKind.SERVER
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<TracingSqsEventWrapper> wrapperConstructor) {
environmentVariables.set(WrappedLambda.OTEL_LAMBDA_HANDLER_ENV_KEY, handler)
wrapper = wrapperConstructor.call(testRunner().openTelemetrySdk, WrappedLambda.fromConfiguration())
}
static class TestRequestHandler implements RequestHandler<SQSEvent, Void> {
@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" "1-22-333"
}
}
span(1) {
name("otel process")
kind CONSUMER
attributes {
"$SemanticAttributes.MESSAGING_SYSTEM" "AmazonSQS"
"$SemanticAttributes.MESSAGING_OPERATION" "process"
}
}
}
}
}
}

View File

@ -0,0 +1,308 @@
/*
* 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.assertj.core.api.Assertions.entry;
import static org.mockito.Mockito.when;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junitpioneer.jupiter.SetEnvironmentVariable;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
public class AwsLambdaApiGatewayWrapperTest {
@RegisterExtension
public static final InstrumentationExtension testing = LibraryInstrumentationExtension.create();
@Mock private Context context;
@BeforeEach
void setUp() {
when(context.getFunctionName()).thenReturn("my_function");
when(context.getAwsRequestId()).thenReturn("1-22-333");
when(context.getInvokedFunctionArn())
.thenReturn("arn:aws:lambda:us-east-1:123456789:function:test");
}
@AfterEach
void tearDown() {
assertThat(testing.forceFlushCalled()).isTrue();
}
@Test
@SetEnvironmentVariable(
key = WrappedLambda.OTEL_LAMBDA_HANDLER_ENV_KEY,
value =
"io.opentelemetry.instrumentation.awslambda.v1_0.AwsLambdaApiGatewayWrapperTest$TestRequestHandlerApiGateway::handleRequest")
void tracedWithHttpPropagation() {
TracingRequestApiGatewayWrapper wrapper =
new TracingRequestApiGatewayWrapper(
testing.getOpenTelemetrySdk(),
WrappedLambda.fromConfiguration(),
TracingRequestApiGatewayWrapper::map);
Map<String, String> headers = new HashMap<>();
headers.put("traceparent", "00-4fd0b6131f19f39af59518d127b0cafe-0000000000000456-01");
headers.put("User-Agent", "Test Client");
headers.put("host", "localhost:123");
headers.put("X-FORWARDED-PROTO", "http");
Map<String, String> query = new HashMap<>();
query.put("a", "b");
query.put("c", "d");
APIGatewayProxyRequestEvent input =
new APIGatewayProxyRequestEvent()
.withHttpMethod("GET")
.withResource("/hello/{param}")
.withPath("/hello/world")
.withBody("hello")
.withQueryStringParameters(query)
.withHeaders(headers);
APIGatewayProxyResponseEvent result =
(APIGatewayProxyResponseEvent) wrapper.handleRequest(input, context);
assertThat(result.getBody()).isEqualTo("world");
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("/hello/{param}")
.hasKind(SpanKind.SERVER)
.hasTraceId("4fd0b6131f19f39af59518d127b0cafe")
.hasParentSpanId("0000000000000456")
.hasAttributesSatisfying(
attrs ->
OpenTelemetryAssertions.assertThat(attrs)
.containsOnly(
entry(
ResourceAttributes.FAAS_ID,
"arn:aws:lambda:us-east-1:123456789:function:test"),
entry(ResourceAttributes.CLOUD_ACCOUNT_ID, "123456789"),
entry(SemanticAttributes.FAAS_EXECUTION, "1-22-333"),
entry(SemanticAttributes.FAAS_TRIGGER, "http"),
entry(SemanticAttributes.HTTP_METHOD, "GET"),
entry(SemanticAttributes.HTTP_USER_AGENT, "Test Client"),
entry(
SemanticAttributes.HTTP_URL,
"http://localhost:123/hello/world?a=b&c=d"),
entry(SemanticAttributes.HTTP_STATUS_CODE, 200L)))));
}
@Test
@SetEnvironmentVariable(
key = WrappedLambda.OTEL_LAMBDA_HANDLER_ENV_KEY,
value =
"io.opentelemetry.instrumentation.awslambda.v1_0.AwsLambdaApiGatewayWrapperTest$TestRequestHandlerApiGateway::handleRequest")
void handlerTraced_empty() {
TracingRequestApiGatewayWrapper wrapper =
new TracingRequestApiGatewayWrapper(
testing.getOpenTelemetrySdk(),
WrappedLambda.fromConfiguration(),
TracingRequestApiGatewayWrapper::map);
APIGatewayProxyResponseEvent result =
(APIGatewayProxyResponseEvent)
wrapper.handleRequest(new APIGatewayProxyRequestEvent().withBody("empty"), context);
assertThat(result.getBody()).isNull();
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("my_function")
.hasKind(SpanKind.SERVER)
.hasAttributesSatisfying(
attrs ->
OpenTelemetryAssertions.assertThat(attrs)
.containsOnly(
entry(
ResourceAttributes.FAAS_ID,
"arn:aws:lambda:us-east-1:123456789:function:test"),
entry(ResourceAttributes.CLOUD_ACCOUNT_ID, "123456789"),
entry(SemanticAttributes.FAAS_EXECUTION, "1-22-333"),
entry(SemanticAttributes.FAAS_TRIGGER, "http")))));
}
@Test
@SetEnvironmentVariable(
key = WrappedLambda.OTEL_LAMBDA_HANDLER_ENV_KEY,
value =
"io.opentelemetry.instrumentation.awslambda.v1_0.AwsLambdaApiGatewayWrapperTest$TestRequestHandlerString::handleRequest")
void handlerTraced_string() {
TracingRequestApiGatewayWrapper wrapper =
new TracingRequestApiGatewayWrapper(
testing.getOpenTelemetrySdk(),
WrappedLambda.fromConfiguration(),
TracingRequestApiGatewayWrapper::map);
APIGatewayProxyResponseEvent result =
(APIGatewayProxyResponseEvent)
wrapper.handleRequest(new APIGatewayProxyRequestEvent().withBody("\"hello\""), context);
assertThat(result.getBody()).isEqualTo("\"world\"");
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("my_function")
.hasKind(SpanKind.SERVER)
.hasAttributesSatisfying(
attrs ->
OpenTelemetryAssertions.assertThat(attrs)
.containsOnly(
entry(
ResourceAttributes.FAAS_ID,
"arn:aws:lambda:us-east-1:123456789:function:test"),
entry(ResourceAttributes.CLOUD_ACCOUNT_ID, "123456789"),
entry(SemanticAttributes.FAAS_EXECUTION, "1-22-333"),
entry(SemanticAttributes.FAAS_TRIGGER, "http")))));
}
@Test
@SetEnvironmentVariable(
key = WrappedLambda.OTEL_LAMBDA_HANDLER_ENV_KEY,
value =
"io.opentelemetry.instrumentation.awslambda.v1_0.AwsLambdaApiGatewayWrapperTest$TestRequestHandlerInteger::handleRequest")
void handlerTraced_integer() {
TracingRequestApiGatewayWrapper wrapper =
new TracingRequestApiGatewayWrapper(
testing.getOpenTelemetrySdk(),
WrappedLambda.fromConfiguration(),
TracingRequestApiGatewayWrapper::map);
APIGatewayProxyResponseEvent result =
(APIGatewayProxyResponseEvent)
wrapper.handleRequest(new APIGatewayProxyRequestEvent().withBody("1"), context);
assertThat(result.getBody()).isEqualTo("\"world\"");
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("my_function")
.hasKind(SpanKind.SERVER)
.hasAttributesSatisfying(
attrs ->
OpenTelemetryAssertions.assertThat(attrs)
.containsOnly(
entry(
ResourceAttributes.FAAS_ID,
"arn:aws:lambda:us-east-1:123456789:function:test"),
entry(ResourceAttributes.CLOUD_ACCOUNT_ID, "123456789"),
entry(SemanticAttributes.FAAS_EXECUTION, "1-22-333"),
entry(SemanticAttributes.FAAS_TRIGGER, "http")))));
}
@Test
@SetEnvironmentVariable(
key = WrappedLambda.OTEL_LAMBDA_HANDLER_ENV_KEY,
value =
"io.opentelemetry.instrumentation.awslambda.v1_0.AwsLambdaApiGatewayWrapperTest$TestRequestHandlerCustomType::handleRequest")
void handlerTraced_customType() {
TracingRequestApiGatewayWrapper wrapper =
new TracingRequestApiGatewayWrapper(
testing.getOpenTelemetrySdk(),
WrappedLambda.fromConfiguration(),
TracingRequestApiGatewayWrapper::map);
APIGatewayProxyResponseEvent result =
(APIGatewayProxyResponseEvent)
wrapper.handleRequest(
new APIGatewayProxyRequestEvent()
.withBody("{\"key\":\"hello\", \"value\":\"General Kenobi\"}"),
context);
assertThat(result.getBody()).isEqualTo("\"General Kenobi\"");
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("my_function")
.hasKind(SpanKind.SERVER)
.hasAttributesSatisfying(
attrs ->
OpenTelemetryAssertions.assertThat(attrs)
.containsOnly(
entry(
ResourceAttributes.FAAS_ID,
"arn:aws:lambda:us-east-1:123456789:function:test"),
entry(ResourceAttributes.CLOUD_ACCOUNT_ID, "123456789"),
entry(SemanticAttributes.FAAS_EXECUTION, "1-22-333"),
entry(SemanticAttributes.FAAS_TRIGGER, "http")))));
}
public static class TestRequestHandlerApiGateway
implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
@Override
public APIGatewayProxyResponseEvent handleRequest(
APIGatewayProxyRequestEvent input, Context context) {
if (input.getBody().equals("hello")) {
return new APIGatewayProxyResponseEvent().withStatusCode(200).withBody("world");
} else if (input.getBody().equals("empty")) {
return new APIGatewayProxyResponseEvent();
}
throw new IllegalStateException("bad request");
}
}
public static final class TestRequestHandlerString implements RequestHandler<String, String> {
@Override
public String handleRequest(String input, Context context) {
if (input.equals("hello")) {
return "world";
}
throw new IllegalArgumentException("bad argument");
}
}
public static final class TestRequestHandlerInteger implements RequestHandler<Integer, String> {
@Override
public String handleRequest(Integer input, Context context) {
if (input == 1) {
return "world";
}
throw new IllegalArgumentException("bad argument");
}
}
public static class CustomType {
public String key;
public String value;
}
public static final class TestRequestHandlerCustomType
implements RequestHandler<CustomType, String> {
@Override
public String handleRequest(CustomType input, Context context) {
if (input.key.equals("hello")) {
return input.value;
}
throw new IllegalArgumentException("bad argument");
}
}
}

View File

@ -0,0 +1,40 @@
/*
* 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.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import org.junit.jupiter.api.extension.RegisterExtension;
public class AwsLambdaSqsEventHandlerTest extends AbstractAwsLambdaSqsEventHandlerTest {
@RegisterExtension
public static final InstrumentationExtension testing = LibraryInstrumentationExtension.create();
@Override
protected RequestHandler<SQSEvent, Void> handler() {
return new TestHandler(testing.getOpenTelemetrySdk());
}
@Override
protected InstrumentationExtension testing() {
return testing;
}
private static final class TestHandler extends TracingSqsEventHandler {
TestHandler(OpenTelemetrySdk openTelemetrySdk) {
super(openTelemetrySdk);
}
@Override
protected void handleEvent(SQSEvent event, Context context) {}
}
}

View File

@ -0,0 +1,112 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.awslambda.v1_0;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
import static org.mockito.Mockito.when;
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.api.trace.SpanKind;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.lang.reflect.Constructor;
import java.util.Collections;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junitpioneer.jupiter.SetEnvironmentVariable;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
@SetEnvironmentVariable(
key = WrappedLambda.OTEL_LAMBDA_HANDLER_ENV_KEY,
value =
"io.opentelemetry.instrumentation.awslambda.v1_0.AwsLambdaSqsEventWrapperTest$TestRequestHandler::handleRequest")
public class AwsLambdaSqsEventWrapperTest {
@RegisterExtension
public static final InstrumentationExtension testing = LibraryInstrumentationExtension.create();
@Mock private Context context;
@BeforeEach
void setUp() {
when(context.getFunctionName()).thenReturn("my_function");
when(context.getAwsRequestId()).thenReturn("1-22-333");
when(context.getInvokedFunctionArn())
.thenReturn("arn:aws:lambda:us-east-1:123456789:function:test");
}
@AfterEach
void tearDown() {
assertThat(testing.forceFlushCalled()).isTrue();
}
@Test
void eventTraced() {
SQSEvent event = new SQSEvent();
SQSEvent.SQSMessage record = newMessage();
record.setEventSource("otel");
record.setAttributes(Collections.emptyMap());
event.setRecords(Collections.singletonList(record));
TracingSqsEventWrapper wrapper =
new TracingSqsEventWrapper(
testing.getOpenTelemetrySdk(), WrappedLambda.fromConfiguration());
wrapper.handleRequest(event, context);
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("my_function")
.hasKind(SpanKind.SERVER)
.hasAttributesSatisfying(
attrs ->
assertThat(attrs)
.containsOnly(
entry(
ResourceAttributes.FAAS_ID,
"arn:aws:lambda:us-east-1:123456789:function:test"),
entry(ResourceAttributes.CLOUD_ACCOUNT_ID, "123456789"),
entry(SemanticAttributes.FAAS_EXECUTION, "1-22-333"))),
span ->
span.hasName("otel process")
.hasKind(SpanKind.CONSUMER)
.hasAttributesSatisfying(
attrs ->
assertThat(attrs)
.containsOnly(
entry(SemanticAttributes.MESSAGING_SYSTEM, "AmazonSQS"),
entry(
SemanticAttributes.MESSAGING_OPERATION, "process")))));
}
public static final class TestRequestHandler implements RequestHandler<SQSEvent, Void> {
@Override
public Void handleRequest(SQSEvent input, Context context) {
return null;
}
}
// Constructor private in early versions.
private static SQSEvent.SQSMessage newMessage() {
try {
Constructor<SQSEvent.SQSMessage> ctor = SQSEvent.SQSMessage.class.getDeclaredConstructor();
return ctor.newInstance();
} catch (Throwable t) {
throw new AssertionError(t);
}
}
}

View File

@ -0,0 +1,168 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.awslambda.v1_0;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
import static org.mockito.Mockito.when;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.TraceFlags;
import io.opentelemetry.api.trace.TraceState;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.trace.data.LinkData;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Collections;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class AwsLambdaSqsMessageHandlerTest {
private static final String AWS_TRACE_HEADER1 =
"Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1";
private static final String AWS_TRACE_HEADER2 =
"Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad9;Sampled=1";
@RegisterExtension
public static final InstrumentationExtension testing = LibraryInstrumentationExtension.create();
@Mock private Context context;
@BeforeEach
void setUp() {
when(context.getFunctionName()).thenReturn("my_function");
when(context.getAwsRequestId()).thenReturn("1-22-333");
}
@AfterEach
void tearDown() {
assertThat(testing.forceFlushCalled()).isTrue();
}
@Test
void processSpans() {
SQSEvent.SQSMessage message1 = newMessage();
message1.setAttributes(Collections.singletonMap("AWSTraceHeader", AWS_TRACE_HEADER1));
message1.setMessageId("message1");
message1.setEventSource("queue1");
SQSEvent.SQSMessage message2 = newMessage();
message2.setAttributes(Collections.singletonMap("AWSTraceHeader", AWS_TRACE_HEADER2));
message2.setMessageId("message2");
message2.setEventSource("queue1");
SQSEvent event = new SQSEvent();
event.setRecords(Arrays.asList(message1, message2));
new TestHandler(testing.getOpenTelemetrySdk()).handleRequest(event, context);
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("my_function")
.hasKind(SpanKind.SERVER)
.hasAttributesSatisfying(
attrs ->
assertThat(attrs)
.containsOnly(
entry(SemanticAttributes.FAAS_EXECUTION, "1-22-333"))),
span ->
span.hasName("queue1 process")
.hasKind(SpanKind.CONSUMER)
.hasParentSpanId(trace.getSpan(0).getSpanId())
.hasAttributesSatisfying(
attrs ->
assertThat(attrs)
.containsOnly(
entry(SemanticAttributes.MESSAGING_SYSTEM, "AmazonSQS"),
entry(SemanticAttributes.MESSAGING_OPERATION, "process")))
.hasLinks(
LinkData.create(
SpanContext.createFromRemoteParent(
"5759e988bd862e3fe1be46a994272793",
"53995c3f42cd8ad8",
TraceFlags.getSampled(),
TraceState.getDefault())),
LinkData.create(
SpanContext.createFromRemoteParent(
"5759e988bd862e3fe1be46a994272793",
"53995c3f42cd8ad9",
TraceFlags.getSampled(),
TraceState.getDefault()))),
span ->
span.hasName("queue1 process")
.hasKind(SpanKind.CONSUMER)
.hasParentSpanId(trace.getSpan(1).getSpanId())
.hasAttributesSatisfying(
attrs ->
assertThat(attrs)
.containsOnly(
entry(SemanticAttributes.MESSAGING_SYSTEM, "AmazonSQS"),
entry(SemanticAttributes.MESSAGING_OPERATION, "process"),
entry(SemanticAttributes.MESSAGING_MESSAGE_ID, "message1"),
entry(SemanticAttributes.MESSAGING_DESTINATION, "queue1")))
.hasLinks(
LinkData.create(
SpanContext.createFromRemoteParent(
"5759e988bd862e3fe1be46a994272793",
"53995c3f42cd8ad8",
TraceFlags.getSampled(),
TraceState.getDefault()))),
span ->
span.hasName("queue1 process")
.hasKind(SpanKind.CONSUMER)
.hasParentSpanId(trace.getSpan(1).getSpanId())
.hasAttributesSatisfying(
attrs ->
assertThat(attrs)
.containsOnly(
entry(SemanticAttributes.MESSAGING_SYSTEM, "AmazonSQS"),
entry(SemanticAttributes.MESSAGING_OPERATION, "process"),
entry(SemanticAttributes.MESSAGING_MESSAGE_ID, "message2"),
entry(SemanticAttributes.MESSAGING_DESTINATION, "queue1")))
.hasLinks(
LinkData.create(
SpanContext.createFromRemoteParent(
"5759e988bd862e3fe1be46a994272793",
"53995c3f42cd8ad9",
TraceFlags.getSampled(),
TraceState.getDefault())))));
}
// Constructor private in early versions.
private static SQSEvent.SQSMessage newMessage() {
try {
Constructor<SQSEvent.SQSMessage> ctor = SQSEvent.SQSMessage.class.getDeclaredConstructor();
return ctor.newInstance();
} catch (Throwable t) {
throw new AssertionError(t);
}
}
private static final class TestHandler extends TracingSqsMessageHandler {
TestHandler(OpenTelemetrySdk openTelemetrySdk) {
super(openTelemetrySdk);
}
@Override
protected void handleMessage(SQSEvent.SQSMessage message, Context context) {}
}
}

View File

@ -0,0 +1,162 @@
/*
* 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.assertj.core.api.Assertions.catchThrowable;
import static org.assertj.core.api.Assertions.entry;
import static org.mockito.Mockito.when;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions;
import io.opentelemetry.sdk.trace.data.StatusData;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junitpioneer.jupiter.SetEnvironmentVariable;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
@SetEnvironmentVariable(
key = WrappedLambda.OTEL_LAMBDA_HANDLER_ENV_KEY,
value =
"io.opentelemetry.instrumentation.awslambda.v1_0.AwsLambdaStreamWrapperHttpPropagationTest$TestRequestHandler::handleRequest")
public class AwsLambdaStreamWrapperHttpPropagationTest {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@RegisterExtension
public static final InstrumentationExtension testing = LibraryInstrumentationExtension.create();
@Mock private Context context;
@BeforeEach
void setUp() {
when(context.getFunctionName()).thenReturn("my_function");
when(context.getAwsRequestId()).thenReturn("1-22-333");
when(context.getInvokedFunctionArn())
.thenReturn("arn:aws:lambda:us-east-1:123456789:function:test");
}
@AfterEach
void tearDown() {
assertThat(testing.forceFlushCalled()).isTrue();
}
@Test
void handlerTraced() throws Exception {
String content =
"{"
+ "\"headers\" : {"
+ "\"traceparent\": \"00-4fd0b6131f19f39af59518d127b0cafe-0000000000000456-01\""
+ "},"
+ "\"body\" : \"hello\""
+ "}";
InputStream input = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
OutputStream output = new ByteArrayOutputStream();
TracingRequestStreamWrapper wrapper =
new TracingRequestStreamWrapper(
testing.getOpenTelemetrySdk(), WrappedLambda.fromConfiguration());
wrapper.handleRequest(input, output, context);
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("my_function")
.hasKind(SpanKind.SERVER)
.hasTraceId("4fd0b6131f19f39af59518d127b0cafe")
.hasParentSpanId("0000000000000456")
.hasAttributesSatisfying(
attrs ->
OpenTelemetryAssertions.assertThat(attrs)
.containsOnly(
entry(
ResourceAttributes.FAAS_ID,
"arn:aws:lambda:us-east-1:123456789:function:test"),
entry(ResourceAttributes.CLOUD_ACCOUNT_ID, "123456789"),
entry(SemanticAttributes.FAAS_EXECUTION, "1-22-333")))));
}
@Test
void handlerTracedWithException() {
String content =
"{"
+ "\"headers\" : {"
+ "\"traceparent\": \"00-4fd0b6131f19f39af59518d127b0cafe-0000000000000456-01\""
+ "},"
+ "\"body\" : \"bye\""
+ "}";
InputStream input = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
OutputStream output = new ByteArrayOutputStream();
TracingRequestStreamWrapper wrapper =
new TracingRequestStreamWrapper(
testing.getOpenTelemetrySdk(), WrappedLambda.fromConfiguration());
Throwable thrown = catchThrowable(() -> wrapper.handleRequest(input, output, context));
assertThat(thrown).isInstanceOf(IllegalArgumentException.class);
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("my_function")
.hasKind(SpanKind.SERVER)
.hasTraceId("4fd0b6131f19f39af59518d127b0cafe")
.hasParentSpanId("0000000000000456")
.hasStatus(StatusData.error())
.hasException(thrown)
.hasAttributesSatisfying(
attrs ->
OpenTelemetryAssertions.assertThat(attrs)
.containsOnly(
entry(
ResourceAttributes.FAAS_ID,
"arn:aws:lambda:us-east-1:123456789:function:test"),
entry(ResourceAttributes.CLOUD_ACCOUNT_ID, "123456789"),
entry(SemanticAttributes.FAAS_EXECUTION, "1-22-333")))));
}
public static final class TestRequestHandler implements RequestStreamHandler {
@Override
public void handleRequest(InputStream input, OutputStream output, Context context)
throws IOException {
JsonNode root = OBJECT_MAPPER.readTree(input);
String body = root.get("body").asText();
BufferedWriter writer =
new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
if (body.equals("hello")) {
writer.write("world");
writer.flush();
writer.close();
} else {
throw new IllegalArgumentException("bad argument");
}
}
}
}

View File

@ -0,0 +1,143 @@
/*
* 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.assertj.core.api.Assertions.catchThrowable;
import static org.assertj.core.api.Assertions.entry;
import static org.mockito.Mockito.when;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions;
import io.opentelemetry.sdk.trace.data.StatusData;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junitpioneer.jupiter.SetEnvironmentVariable;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
@SetEnvironmentVariable(
key = WrappedLambda.OTEL_LAMBDA_HANDLER_ENV_KEY,
value =
"io.opentelemetry.instrumentation.awslambda.v1_0.AwsLambdaStreamWrapperTest$TestRequestHandler::handleRequest")
public class AwsLambdaStreamWrapperTest {
@RegisterExtension
public static final InstrumentationExtension testing = LibraryInstrumentationExtension.create();
@Mock private Context context;
@BeforeEach
void setUp() {
when(context.getFunctionName()).thenReturn("my_function");
when(context.getAwsRequestId()).thenReturn("1-22-333");
when(context.getInvokedFunctionArn())
.thenReturn("arn:aws:lambda:us-east-1:123456789:function:test");
}
@AfterEach
void tearDown() {
assertThat(testing.forceFlushCalled()).isTrue();
}
@Test
void handlerTraced() throws Exception {
InputStream input = new ByteArrayInputStream("hello\n".getBytes(StandardCharsets.UTF_8));
OutputStream output = new ByteArrayOutputStream();
TracingRequestStreamWrapper wrapper =
new TracingRequestStreamWrapper(
testing.getOpenTelemetrySdk(), WrappedLambda.fromConfiguration());
wrapper.handleRequest(input, output, context);
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("my_function")
.hasKind(SpanKind.SERVER)
.hasAttributesSatisfying(
attrs ->
OpenTelemetryAssertions.assertThat(attrs)
.containsOnly(
entry(
ResourceAttributes.FAAS_ID,
"arn:aws:lambda:us-east-1:123456789:function:test"),
entry(ResourceAttributes.CLOUD_ACCOUNT_ID, "123456789"),
entry(SemanticAttributes.FAAS_EXECUTION, "1-22-333")))));
}
@Test
void handlerTracedWithException() {
InputStream input = new ByteArrayInputStream("bye\n".getBytes(StandardCharsets.UTF_8));
OutputStream output = new ByteArrayOutputStream();
TracingRequestStreamWrapper wrapper =
new TracingRequestStreamWrapper(
testing.getOpenTelemetrySdk(), WrappedLambda.fromConfiguration());
Throwable thrown = catchThrowable(() -> wrapper.handleRequest(input, output, context));
assertThat(thrown).isInstanceOf(IllegalArgumentException.class);
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("my_function")
.hasKind(SpanKind.SERVER)
.hasStatus(StatusData.error())
.hasException(thrown)
.hasAttributesSatisfying(
attrs ->
OpenTelemetryAssertions.assertThat(attrs)
.containsOnly(
entry(
ResourceAttributes.FAAS_ID,
"arn:aws:lambda:us-east-1:123456789:function:test"),
entry(ResourceAttributes.CLOUD_ACCOUNT_ID, "123456789"),
entry(SemanticAttributes.FAAS_EXECUTION, "1-22-333")))));
}
public static final class TestRequestHandler implements RequestStreamHandler {
@Override
public void handleRequest(InputStream input, OutputStream output, Context context)
throws IOException {
BufferedReader reader =
new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
BufferedWriter writer =
new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
String line = reader.readLine();
if (line.equals("hello")) {
writer.write("world");
writer.flush();
writer.close();
} else {
throw new IllegalArgumentException("bad argument");
}
}
}
}

View File

@ -0,0 +1,41 @@
/*
* 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.RequestHandler;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import org.junit.jupiter.api.extension.RegisterExtension;
public class AwsLambdaTest extends AbstractAwsLambdaTest {
@RegisterExtension
public static final InstrumentationExtension testing = LibraryInstrumentationExtension.create();
@Override
protected RequestHandler<String, String> handler() {
return new TestRequestHandler(testing.getOpenTelemetrySdk());
}
@Override
protected InstrumentationExtension testing() {
return testing;
}
private static final class TestRequestHandler extends TracingRequestHandler<String, String> {
TestRequestHandler(OpenTelemetrySdk openTelemetrySdk) {
super(openTelemetrySdk);
}
@Override
protected String doHandleRequest(String input, Context context) {
return AbstractAwsLambdaTest.doHandleRequest(input, context);
}
}
}

View File

@ -0,0 +1,219 @@
/*
* 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.assertj.core.api.Assertions.catchThrowable;
import static org.assertj.core.api.Assertions.entry;
import static org.mockito.Mockito.when;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions;
import io.opentelemetry.sdk.trace.data.StatusData;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junitpioneer.jupiter.SetEnvironmentVariable;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class AwsLambdaWrapperTest {
@RegisterExtension
public static final InstrumentationExtension testing = LibraryInstrumentationExtension.create();
@Mock private Context context;
@BeforeEach
void setUp() {
when(context.getFunctionName()).thenReturn("my_function");
when(context.getAwsRequestId()).thenReturn("1-22-333");
when(context.getInvokedFunctionArn())
.thenReturn("arn:aws:lambda:us-east-1:123456789:function:test");
}
@AfterEach
void tearDown() {
assertThat(testing.forceFlushCalled()).isTrue();
}
@Test
@SetEnvironmentVariable(
key = WrappedLambda.OTEL_LAMBDA_HANDLER_ENV_KEY,
value =
"io.opentelemetry.instrumentation.awslambda.v1_0.AwsLambdaWrapperTest$TestRequestHandlerString::handleRequest")
void handlerTraced() {
TracingRequestWrapper wrapper =
new TracingRequestWrapper(
testing.getOpenTelemetrySdk(),
WrappedLambda.fromConfiguration(),
TracingRequestWrapper::map);
Object result = wrapper.handleRequest("hello", context);
assertThat(result).isEqualTo("world");
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("my_function")
.hasKind(SpanKind.SERVER)
.hasAttributesSatisfying(
attrs ->
OpenTelemetryAssertions.assertThat(attrs)
.containsOnly(
entry(
ResourceAttributes.FAAS_ID,
"arn:aws:lambda:us-east-1:123456789:function:test"),
entry(ResourceAttributes.CLOUD_ACCOUNT_ID, "123456789"),
entry(SemanticAttributes.FAAS_EXECUTION, "1-22-333")))));
}
@Test
@SetEnvironmentVariable(
key = WrappedLambda.OTEL_LAMBDA_HANDLER_ENV_KEY,
value =
"io.opentelemetry.instrumentation.awslambda.v1_0.AwsLambdaWrapperTest$TestRequestHandlerString::handleRequest")
void handlerTracedWithException() {
TracingRequestWrapper wrapper =
new TracingRequestWrapper(
testing.getOpenTelemetrySdk(),
WrappedLambda.fromConfiguration(),
TracingRequestWrapper::map);
Throwable thrown = catchThrowable(() -> wrapper.handleRequest("goodbye", context));
assertThat(thrown).isInstanceOf(IllegalArgumentException.class);
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("my_function")
.hasKind(SpanKind.SERVER)
.hasStatus(StatusData.error())
.hasException(thrown)
.hasAttributesSatisfying(
attrs ->
OpenTelemetryAssertions.assertThat(attrs)
.containsOnly(
entry(
ResourceAttributes.FAAS_ID,
"arn:aws:lambda:us-east-1:123456789:function:test"),
entry(ResourceAttributes.CLOUD_ACCOUNT_ID, "123456789"),
entry(SemanticAttributes.FAAS_EXECUTION, "1-22-333")))));
}
@Test
@SetEnvironmentVariable(
key = WrappedLambda.OTEL_LAMBDA_HANDLER_ENV_KEY,
value =
"io.opentelemetry.instrumentation.awslambda.v1_0.AwsLambdaWrapperTest$TestRequestHandlerInteger::handleRequest")
void handlerTraced_integer() {
TracingRequestWrapper wrapper =
new TracingRequestWrapper(
testing.getOpenTelemetrySdk(),
WrappedLambda.fromConfiguration(),
TracingRequestWrapper::map);
Object result = wrapper.handleRequest(1, context);
assertThat(result).isEqualTo("world");
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("my_function")
.hasKind(SpanKind.SERVER)
.hasAttributesSatisfying(
attrs ->
OpenTelemetryAssertions.assertThat(attrs)
.containsOnly(
entry(
ResourceAttributes.FAAS_ID,
"arn:aws:lambda:us-east-1:123456789:function:test"),
entry(ResourceAttributes.CLOUD_ACCOUNT_ID, "123456789"),
entry(SemanticAttributes.FAAS_EXECUTION, "1-22-333")))));
}
@Test
@SetEnvironmentVariable(
key = WrappedLambda.OTEL_LAMBDA_HANDLER_ENV_KEY,
value =
"io.opentelemetry.instrumentation.awslambda.v1_0.AwsLambdaWrapperTest$TestRequestHandlerCustomType::handleRequest")
void handlerTraced_custom() {
TracingRequestWrapper wrapper =
new TracingRequestWrapper(
testing.getOpenTelemetrySdk(),
WrappedLambda.fromConfiguration(),
TracingRequestWrapper::map);
CustomType ct = new CustomType();
ct.key = "hello there";
ct.value = "General Kenobi";
Object result = wrapper.handleRequest(ct, context);
assertThat(result).isEqualTo("General Kenobi");
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("my_function")
.hasKind(SpanKind.SERVER)
.hasAttributesSatisfying(
attrs ->
OpenTelemetryAssertions.assertThat(attrs)
.containsOnly(
entry(
ResourceAttributes.FAAS_ID,
"arn:aws:lambda:us-east-1:123456789:function:test"),
entry(ResourceAttributes.CLOUD_ACCOUNT_ID, "123456789"),
entry(SemanticAttributes.FAAS_EXECUTION, "1-22-333")))));
}
public static final class TestRequestHandlerString implements RequestHandler<String, String> {
@Override
public String handleRequest(String input, Context context) {
if (input.equals("hello")) {
return "world";
}
throw new IllegalArgumentException("bad argument");
}
}
public static final class TestRequestHandlerInteger implements RequestHandler<Integer, String> {
@Override
public String handleRequest(Integer input, Context context) {
if (input == 1) {
return "world";
}
throw new IllegalArgumentException("bad argument");
}
}
private static class CustomType {
String key;
String value;
}
public static final class TestRequestHandlerCustomType
implements RequestHandler<CustomType, String> {
@Override
public String handleRequest(CustomType input, Context context) {
if (input.key.equals("hello there")) {
return input.value;
}
throw new IllegalArgumentException("bad argument");
}
}
}

View File

@ -8,6 +8,9 @@ dependencies {
api("com.amazonaws:aws-lambda-java-core:1.0.0")
api("com.amazonaws:aws-lambda-java-events:2.2.1")
api("org.junit-pioneer:junit-pioneer")
api("org.mockito:mockito-junit-jupiter")
implementation("com.google.guava:guava")
implementation("org.codehaus.groovy:groovy-all")

View File

@ -1,109 +0,0 @@
/*
* 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.RequestHandler
import com.github.stefanbirkner.systemlambda.SystemLambda
import io.opentelemetry.instrumentation.test.InstrumentationSpecification
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
import static io.opentelemetry.api.trace.SpanKind.SERVER
import static io.opentelemetry.api.trace.StatusCode.ERROR
abstract class AbstractAwsLambdaRequestHandlerTest extends InstrumentationSpecification {
protected static String doHandleRequest(String input, Context context) {
if (input == "hello") {
return "world"
}
throw new IllegalArgumentException("bad argument")
}
abstract RequestHandler<String, String> handler()
def "handler traced"() {
when:
def context = Mock(Context)
context.getFunctionName() >> "my_function"
context.getAwsRequestId() >> "1-22-333"
def result = handler().handleRequest("hello", context)
then:
result == "world"
assertTraces(1) {
trace(0, 1) {
span(0) {
name("my_function")
kind SERVER
attributes {
"$SemanticAttributes.FAAS_EXECUTION" "1-22-333"
}
}
}
}
}
def "handler traced with exception"() {
when:
def context = Mock(Context)
context.getFunctionName() >> "my_function"
context.getAwsRequestId() >> "1-22-333"
def thrown
try {
handler().handleRequest("goodbye", context)
} catch (Throwable t) {
thrown = t
}
then:
thrown != null
assertTraces(1) {
trace(0, 1) {
span(0) {
name("my_function")
kind SERVER
status ERROR
errorEvent(IllegalArgumentException, "bad argument")
attributes {
"$SemanticAttributes.FAAS_EXECUTION" "1-22-333"
}
}
}
}
}
def "handler links to lambda trace"() {
when:
def context = Mock(Context)
context.getFunctionName() >> "my_function"
context.getAwsRequestId() >> "1-22-333"
def result
SystemLambda.withEnvironmentVariable("_X_AMZN_TRACE_ID", "Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=0000000000000456;Sampled=1")
.execute({
result = handler().handleRequest("hello", context)
})
then:
result == "world"
assertTraces(1) {
trace(0, 1) {
span(0) {
name("my_function")
kind SERVER
parentSpanId("0000000000000456")
traceId("8a3c60f7d188f8fa79d48a391a778fa6")
attributes {
"$SemanticAttributes.FAAS_EXECUTION" "1-22-333"
}
}
}
}
}
}

View File

@ -1,112 +0,0 @@
/*
* 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.RequestHandler
import com.amazonaws.services.lambda.runtime.events.SQSEvent
import io.opentelemetry.instrumentation.test.InstrumentationSpecification
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
import static io.opentelemetry.api.trace.SpanKind.CONSUMER
import static io.opentelemetry.api.trace.SpanKind.SERVER
abstract class AbstractAwsLambdaSqsHandlerTest extends InstrumentationSpecification {
private static final String AWS_TRACE_HEADER = "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1"
abstract RequestHandler<SQSEvent, Void> handler()
def "messages from same source"() {
when:
def context = Mock(Context)
context.getFunctionName() >> "my_function"
context.getAwsRequestId() >> "1-22-333"
def message1 = new SQSEvent.SQSMessage()
message1.setAttributes(["AWSTraceHeader": AWS_TRACE_HEADER])
message1.setMessageId("message1")
message1.setEventSource("queue1")
def message2 = new SQSEvent.SQSMessage()
message2.setAttributes([:])
message2.setMessageId("message2")
message2.setEventSource("queue1")
def event = new SQSEvent()
event.setRecords([message1, message2])
handler().handleRequest(event, context)
then:
assertTraces(1) {
trace(0, 2) {
span(0) {
name("my_function")
kind SERVER
attributes {
"$SemanticAttributes.FAAS_EXECUTION" "1-22-333"
}
}
span(1) {
name("queue1 process")
kind CONSUMER
parentSpanId(span(0).spanId)
attributes {
"$SemanticAttributes.MESSAGING_SYSTEM" "AmazonSQS"
"$SemanticAttributes.MESSAGING_OPERATION" "process"
}
hasLink("5759e988bd862e3fe1be46a994272793", "53995c3f42cd8ad8")
}
}
}
}
def "messages from different source"() {
when:
def context = Mock(Context)
context.getFunctionName() >> "my_function"
context.getAwsRequestId() >> "1-22-333"
def message1 = new SQSEvent.SQSMessage()
message1.setAttributes(["AWSTraceHeader": AWS_TRACE_HEADER])
message1.setMessageId("message1")
message1.setEventSource("queue1")
def message2 = new SQSEvent.SQSMessage()
message2.setAttributes([:])
message2.setMessageId("message2")
message2.setEventSource("queue2")
def event = new SQSEvent()
event.setRecords([message1, message2])
handler().handleRequest(event, context)
then:
assertTraces(1) {
trace(0, 2) {
span(0) {
name("my_function")
kind SERVER
attributes {
"$SemanticAttributes.FAAS_EXECUTION" "1-22-333"
}
}
span(1) {
name("multiple_sources process")
kind CONSUMER
parentSpanId(span(0).spanId)
attributes {
"$SemanticAttributes.MESSAGING_SYSTEM" "AmazonSQS"
"$SemanticAttributes.MESSAGING_OPERATION" "process"
}
hasLink("5759e988bd862e3fe1be46a994272793", "53995c3f42cd8ad8")
}
}
}
}
}

View File

@ -0,0 +1,167 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.awslambda.v1_0;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
import static org.mockito.Mockito.when;
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.api.trace.SpanKind;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Collections;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public abstract class AbstractAwsLambdaSqsEventHandlerTest {
private static final String AWS_TRACE_HEADER =
"Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1";
protected abstract RequestHandler<SQSEvent, Void> handler();
protected abstract InstrumentationExtension testing();
@Mock private Context context;
@BeforeEach
void setUp() {
when(context.getFunctionName()).thenReturn("my_function");
when(context.getAwsRequestId()).thenReturn("1-22-333");
}
@AfterEach
void tearDown() {
assertThat(testing().forceFlushCalled()).isTrue();
}
@Test
void sameSource() {
SQSEvent.SQSMessage message1 = newMessage();
message1.setAttributes(Collections.singletonMap("AWSTraceHeader", AWS_TRACE_HEADER));
message1.setMessageId("message1");
message1.setEventSource("queue1");
SQSEvent.SQSMessage message2 = newMessage();
message2.setAttributes(Collections.emptyMap());
message2.setMessageId("message2");
message2.setEventSource("queue1");
SQSEvent event = new SQSEvent();
event.setRecords(Arrays.asList(message1, message2));
handler().handleRequest(event, context);
testing()
.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("my_function")
.hasKind(SpanKind.SERVER)
.hasAttributesSatisfying(
attrs ->
assertThat(attrs)
.containsOnly(
entry(SemanticAttributes.FAAS_EXECUTION, "1-22-333"))),
span ->
span.hasName("queue1 process")
.hasKind(SpanKind.CONSUMER)
.hasParentSpanId(trace.getSpan(0).getSpanId())
.hasAttributesSatisfying(
attrs ->
assertThat(attrs)
.containsOnly(
entry(SemanticAttributes.MESSAGING_SYSTEM, "AmazonSQS"),
entry(
SemanticAttributes.MESSAGING_OPERATION, "process")))
.hasLinksSatisfying(
links ->
assertThat(links)
.singleElement()
.satisfies(
link -> {
assertThat(link.getSpanContext().getTraceId())
.isEqualTo("5759e988bd862e3fe1be46a994272793");
assertThat(link.getSpanContext().getSpanId())
.isEqualTo("53995c3f42cd8ad8");
}))));
}
@Test
void differentSource() {
SQSEvent.SQSMessage message1 = newMessage();
message1.setAttributes(Collections.singletonMap("AWSTraceHeader", AWS_TRACE_HEADER));
message1.setMessageId("message1");
message1.setEventSource("queue1");
SQSEvent.SQSMessage message2 = newMessage();
message2.setAttributes(Collections.emptyMap());
message2.setMessageId("message2");
message2.setEventSource("queue2");
SQSEvent event = new SQSEvent();
event.setRecords(Arrays.asList(message1, message2));
handler().handleRequest(event, context);
testing()
.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("my_function")
.hasKind(SpanKind.SERVER)
.hasAttributesSatisfying(
attrs ->
assertThat(attrs)
.containsOnly(
entry(SemanticAttributes.FAAS_EXECUTION, "1-22-333"))),
span ->
span.hasName("multiple_sources process")
.hasKind(SpanKind.CONSUMER)
.hasParentSpanId(trace.getSpan(0).getSpanId())
.hasAttributesSatisfying(
attrs ->
assertThat(attrs)
.containsOnly(
entry(SemanticAttributes.MESSAGING_SYSTEM, "AmazonSQS"),
entry(
SemanticAttributes.MESSAGING_OPERATION, "process")))
.hasLinksSatisfying(
links ->
assertThat(links)
.singleElement()
.satisfies(
link -> {
assertThat(link.getSpanContext().getTraceId())
.isEqualTo("5759e988bd862e3fe1be46a994272793");
assertThat(link.getSpanContext().getSpanId())
.isEqualTo("53995c3f42cd8ad8");
}))));
}
// Constructor private in early versions.
private static SQSEvent.SQSMessage newMessage() {
try {
Constructor<SQSEvent.SQSMessage> ctor = SQSEvent.SQSMessage.class.getDeclaredConstructor();
ctor.setAccessible(true);
return ctor.newInstance();
} catch (Throwable t) {
throw new AssertionError(t);
}
}
}

View File

@ -0,0 +1,120 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.awslambda.v1_0;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
import static org.assertj.core.api.Assertions.entry;
import static org.mockito.Mockito.when;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.sdk.trace.data.StatusData;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junitpioneer.jupiter.SetEnvironmentVariable;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public abstract class AbstractAwsLambdaTest {
protected static String doHandleRequest(String input, Context context) {
if (input.equals("hello")) {
return "world";
}
throw new IllegalArgumentException("bad argument");
}
protected abstract RequestHandler<String, String> handler();
protected abstract InstrumentationExtension testing();
@Mock private Context context;
@BeforeEach
void setUp() {
when(context.getFunctionName()).thenReturn("my_function");
when(context.getAwsRequestId()).thenReturn("1-22-333");
}
@AfterEach
void tearDown() {
assertThat(testing().forceFlushCalled()).isTrue();
}
@Test
void handlerTraced() {
String result = handler().handleRequest("hello", context);
assertThat(result).isEqualTo("world");
testing()
.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("my_function")
.hasKind(SpanKind.SERVER)
.hasAttributesSatisfying(
attrs ->
assertThat(attrs)
.containsOnly(
entry(
SemanticAttributes.FAAS_EXECUTION, "1-22-333")))));
}
@Test
void handlerTracedWithException() {
Throwable thrown = catchThrowable(() -> handler().handleRequest("goodbye", context));
assertThat(thrown).isInstanceOf(IllegalArgumentException.class);
testing()
.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("my_function")
.hasKind(SpanKind.SERVER)
.hasStatus(StatusData.error())
.hasException(thrown)
.hasAttributesSatisfying(
attrs ->
assertThat(attrs)
.containsOnly(
entry(
SemanticAttributes.FAAS_EXECUTION, "1-22-333")))));
}
@Test
@SetEnvironmentVariable(
key = "_X_AMZN_TRACE_ID",
value = "Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=0000000000000456;Sampled=1")
void handlerLinksToInfrastructureTrace() {
String result = handler().handleRequest("hello", context);
assertThat(result).isEqualTo("world");
testing()
.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("my_function")
.hasKind(SpanKind.SERVER)
.hasTraceId("8a3c60f7d188f8fa79d48a391a778fa6")
.hasParentSpanId("0000000000000456")
.hasAttributesSatisfying(
attrs ->
assertThat(attrs)
.containsOnly(
entry(
SemanticAttributes.FAAS_EXECUTION, "1-22-333")))));
}
}

View File

@ -11,9 +11,11 @@ import static org.awaitility.Awaitility.await;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.context.ContextStorage;
import io.opentelemetry.instrumentation.testing.InstrumentationTestRunner;
import io.opentelemetry.instrumentation.testing.LibraryTestRunner;
import io.opentelemetry.instrumentation.testing.util.ContextStorageCloser;
import io.opentelemetry.instrumentation.testing.util.ThrowingRunnable;
import io.opentelemetry.instrumentation.testing.util.ThrowingSupplier;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.metrics.data.MetricData;
import io.opentelemetry.sdk.testing.assertj.TraceAssert;
import io.opentelemetry.sdk.trace.data.SpanData;
@ -108,6 +110,7 @@ public abstract class InstrumentationExtension
}
@SafeVarargs
@SuppressWarnings("varargs")
public final void waitAndAssertTraces(Consumer<TraceAssert>... assertions) {
testRunner.waitAndAssertTraces(assertions);
}
@ -166,6 +169,19 @@ public abstract class InstrumentationExtension
return testRunner.runWithServerSpan(spanName, callback);
}
/** Returns whether forceFlush was called. */
public boolean forceFlushCalled() {
return testRunner.forceFlushCalled();
}
/** Returns the {@link OpenTelemetrySdk} initialied for library tests. */
public OpenTelemetrySdk getOpenTelemetrySdk() {
if (testRunner instanceof LibraryTestRunner) {
return ((LibraryTestRunner) testRunner).getOpenTelemetrySdk();
}
throw new IllegalStateException("Can only be called from library instrumentation tests.");
}
protected InstrumentationTestRunner getTestRunner() {
return testRunner;
}