diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/aws-sdk-1.11-javaagent.gradle b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/aws-sdk-1.11-javaagent.gradle index 9b11b52ad9a9..66df7a45cbea 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/aws-sdk-1.11-javaagent.gradle +++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/aws-sdk-1.11-javaagent.gradle @@ -33,7 +33,8 @@ testSets { } // We test SQS separately since we have special logic for it and want to make sure the presence of - // SQS on the classpath doesn't conflict with tests for usage of the core SDK. + // SQS on the classpath doesn't conflict with tests for usage of the core SDK. This only affects + // the agent. testSqs } @@ -52,6 +53,8 @@ configurations { dependencies { compileOnly deps.opentelemetryExtAws + implementation project(':instrumentation:aws-sdk:aws-sdk-1.11:library') + library group: 'com.amazonaws', name: 'aws-java-sdk-core', version: '1.11.0' testLibrary group: 'com.amazonaws', name: 'aws-java-sdk-s3', version: '1.11.106' @@ -61,9 +64,9 @@ dependencies { testLibrary group: 'com.amazonaws', name: 'aws-java-sdk-dynamodb', version: '1.11.106' testLibrary group: 'com.amazonaws', name: 'aws-java-sdk-sns', version: '1.11.106' + testImplementation project(':instrumentation:aws-sdk:aws-sdk-1.11:testing') + testSqsImplementation group: 'com.amazonaws', name: 'aws-java-sdk-sqs', version: '1.11.106' - // needed for SQS - using emq directly as localstack references emq v0.15.7 ie WITHOUT AWS trace header propagation - testSqsImplementation group: 'org.elasticmq', name: 'elasticmq-rest-sqs_2.12', version: '1.0.0' // Include httpclient instrumentation for testing because it is a dependency for aws-sdk. testInstrumentation project(':instrumentation:apache-httpclient:apache-httpclient-4.0:javaagent') diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AwsHttpClientInstrumentation.java b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AwsHttpClientInstrumentation.java index 0a8e19e82291..d4289d0e8678 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AwsHttpClientInstrumentation.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AwsHttpClientInstrumentation.java @@ -5,17 +5,19 @@ package io.opentelemetry.javaagent.instrumentation.awssdk.v1_11; -import static io.opentelemetry.javaagent.instrumentation.awssdk.v1_11.AwsSdkClientTracer.CONTEXT_SCOPE_PAIR_CONTEXT_KEY; -import static io.opentelemetry.javaagent.instrumentation.awssdk.v1_11.AwsSdkClientTracer.tracer; import static java.util.Collections.singletonMap; import static net.bytebuddy.matcher.ElementMatchers.isAbstract; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.not; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import com.amazonaws.AmazonClientException; import com.amazonaws.Request; +import com.amazonaws.Response; import com.amazonaws.handlers.RequestHandler2; +import io.opentelemetry.context.Scope; import io.opentelemetry.javaagent.tooling.TypeInstrumentation; import java.util.Map; import net.bytebuddy.asm.Advice; @@ -38,22 +40,26 @@ public ElementMatcher typeMatcher() { @Override public Map, String> transformers() { return singletonMap( - isMethod().and(not(isAbstract())).and(named("doExecute")), + isMethod() + .and(not(isAbstract())) + .and(named("doExecute")) + .and(takesArgument(0, named("com.amazonaws.Request"))) + .and(returns(named("com.amazonaws.Response"))), AwsHttpClientInstrumentation.class.getName() + "$HttpClientAdvice"); } public static class HttpClientAdvice { @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void methodExit( - @Advice.Argument(value = 0, optional = true) Request request, + @Advice.Argument(value = 0) Request request, + @Advice.Return Response response, @Advice.Thrown Throwable throwable) { - if (throwable != null) { - ContextScopePair scope = request.getHandlerContext(CONTEXT_SCOPE_PAIR_CONTEXT_KEY); - if (scope != null) { - request.addHandlerContext(CONTEXT_SCOPE_PAIR_CONTEXT_KEY, null); - tracer().endExceptionally(scope.getContext(), throwable); - scope.closeScope(); - } + if (throwable instanceof Exception) { + TracingRequestHandler.tracingHandler.afterError(request, response, (Exception) throwable); + } + Scope scope = request.getHandlerContext(TracingRequestHandler.SCOPE); + if (scope != null) { + scope.close(); } } } diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/ContextScopePair.java b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/ContextScopePair.java deleted file mode 100644 index c10cccf1c335..000000000000 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/ContextScopePair.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.awssdk.v1_11; - -import io.opentelemetry.context.Context; -import io.opentelemetry.context.Scope; - -public class ContextScopePair { - private final Context context; - private final Scope scope; - - public ContextScopePair(Context context, Scope scope) { - this.context = context; - this.scope = scope; - } - - public Context getContext() { - return context; - } - - public void closeScope() { - scope.close(); - } -} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/RequestExecutorInstrumentation.java b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/RequestExecutorInstrumentation.java index 21c95041c120..d6207407d2c2 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/RequestExecutorInstrumentation.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/RequestExecutorInstrumentation.java @@ -5,15 +5,16 @@ package io.opentelemetry.javaagent.instrumentation.awssdk.v1_11; -import static io.opentelemetry.javaagent.instrumentation.awssdk.v1_11.AwsSdkClientTracer.CONTEXT_SCOPE_PAIR_CONTEXT_KEY; -import static io.opentelemetry.javaagent.instrumentation.awssdk.v1_11.AwsSdkClientTracer.tracer; import static java.util.Collections.singletonMap; import static net.bytebuddy.matcher.ElementMatchers.isAbstract; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.not; +import static net.bytebuddy.matcher.ElementMatchers.returns; import com.amazonaws.Request; +import com.amazonaws.Response; +import io.opentelemetry.context.Scope; import io.opentelemetry.javaagent.tooling.TypeInstrumentation; import java.util.Map; import net.bytebuddy.asm.Advice; @@ -35,21 +36,25 @@ public ElementMatcher typeMatcher() { @Override public Map, String> transformers() { return singletonMap( - isMethod().and(not(isAbstract())).and(named("doExecute")), + isMethod() + .and(not(isAbstract())) + .and(named("doExecute")) + .and(returns(named("com.amazonaws.Response"))), RequestExecutorInstrumentation.class.getName() + "$RequestExecutorAdvice"); } public static class RequestExecutorAdvice { @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void methodExit( - @Advice.FieldValue("request") Request request, @Advice.Thrown Throwable throwable) { - if (throwable != null) { - ContextScopePair scope = request.getHandlerContext(CONTEXT_SCOPE_PAIR_CONTEXT_KEY); - if (scope != null) { - request.addHandlerContext(CONTEXT_SCOPE_PAIR_CONTEXT_KEY, null); - tracer().endExceptionally(scope.getContext(), throwable); - scope.closeScope(); - } + @Advice.FieldValue("request") Request request, + @Advice.Return Response response, + @Advice.Thrown Throwable throwable) { + if (throwable instanceof Exception) { + TracingRequestHandler.tracingHandler.afterError(request, response, (Exception) throwable); + } + Scope scope = request.getHandlerContext(TracingRequestHandler.SCOPE); + if (scope != null) { + scope.close(); } } } diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/TracingRequestHandler.java b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/TracingRequestHandler.java index dada59fcea45..8958b01ddfda 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/TracingRequestHandler.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/TracingRequestHandler.java @@ -5,94 +5,72 @@ package io.opentelemetry.javaagent.instrumentation.awssdk.v1_11; -import static io.opentelemetry.javaagent.instrumentation.awssdk.v1_11.AwsSdkClientTracer.CONTEXT_SCOPE_PAIR_CONTEXT_KEY; -import static io.opentelemetry.javaagent.instrumentation.awssdk.v1_11.AwsSdkClientTracer.tracer; - import com.amazonaws.AmazonWebServiceRequest; import com.amazonaws.Request; import com.amazonaws.Response; +import com.amazonaws.handlers.HandlerContextKey; import com.amazonaws.handlers.RequestHandler2; -import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; -import java.util.List; +import io.opentelemetry.instrumentation.api.config.Config; +import io.opentelemetry.instrumentation.awssdk.v1_11.AwsSdkTracing; -/** Tracing Request Handler. */ +/** + * A {@link RequestHandler2} for use in the agent. Unlike library instrumentation, the agent will + * also instrument the underlying HTTP client, and we must set the context as current to be able to + * suppress it. Also unlike library instrumentation, we are able to instrument the SDK's internal + * classes to handle buggy behavior related to exceptions that can cause scopes to never be closed + * otherwise which would be disastrous. We hope there won't be anymore significant changes to this + * legacy SDK that would cause these workarounds to break in the future. + */ +// NB: If the error-handling workarounds stop working, we should consider introducing the same +// x-amzn-request-id header check in Apache instrumentation for suppressing spans that we have in +// Netty instrumentation. public class TracingRequestHandler extends RequestHandler2 { - @Override - public void beforeRequest(Request request) { + public static final HandlerContextKey SCOPE = + new HandlerContextKey<>(Scope.class.getName()); - AmazonWebServiceRequest originalRequest = request.getOriginalRequest(); - SpanKind kind = (isSqsProducer(originalRequest) ? SpanKind.PRODUCER : SpanKind.CLIENT); + public static final RequestHandler2 tracingHandler = + AwsSdkTracing.newBuilder(GlobalOpenTelemetry.get()) + .setCaptureExperimentalSpanAttributes( + Config.get() + .getBooleanProperty( + "otel.instrumentation.aws-sdk.experimental-span-attributes", false)) + .build() + .newRequestHandler(); - Context parentContext = Context.current(); - if (!tracer().shouldStartSpan(parentContext)) { - return; - } - Context context = tracer().startSpan(kind, parentContext, request); + @Override + public void beforeRequest(Request request) { + tracingHandler.beforeRequest(request); + Context context = AwsSdkTracing.getOpenTelemetryContext(request); Scope scope = context.makeCurrent(); - request.addHandlerContext(CONTEXT_SCOPE_PAIR_CONTEXT_KEY, new ContextScopePair(context, scope)); - } - - private boolean isSqsProducer(AmazonWebServiceRequest request) { - return request - .getClass() - .getName() - .equals("com.amazonaws.services.sqs.model.SendMessageRequest"); + request.addHandlerContext(SCOPE, scope); } @Override public AmazonWebServiceRequest beforeMarshalling(AmazonWebServiceRequest request) { - if (SqsReceiveMessageRequestAccess.isInstance(request)) { - if (!SqsReceiveMessageRequestAccess.getAttributeNames(request) - .contains(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE)) { - SqsReceiveMessageRequestAccess.withAttributeNames( - request, SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE); - } - } - return request; + return tracingHandler.beforeMarshalling(request); } @Override public void afterResponse(Request request, Response response) { - if (SqsReceiveMessageRequestAccess.isInstance(request.getOriginalRequest())) { - afterConsumerResponse(request, response); - } - // close outstanding "client" span - ContextScopePair scope = request.getHandlerContext(CONTEXT_SCOPE_PAIR_CONTEXT_KEY); - if (scope == null) { - return; - } - request.addHandlerContext(CONTEXT_SCOPE_PAIR_CONTEXT_KEY, null); - scope.closeScope(); - tracer().end(scope.getContext(), response); - } - - /** Create and close CONSUMER span for each message consumed. */ - private void afterConsumerResponse(Request request, Response response) { - Object receiveMessageResult = response.getAwsResponse(); - List messages = SqsReceiveMessageResultAccess.getMessages(receiveMessageResult); - for (Object message : messages) { - createConsumerSpan(message, request, response); - } - } - - private void createConsumerSpan(Object message, Request request, Response response) { - Context parentContext = - SqsParentContext.ofSystemAttributes(SqsMessageAccess.getAttributes(message)); - Context context = tracer().startSpan(SpanKind.CONSUMER, parentContext, request); - tracer().end(context, response); + tracingHandler.afterResponse(request, response); } @Override public void afterError(Request request, Response response, Exception e) { - ContextScopePair scope = request.getHandlerContext(CONTEXT_SCOPE_PAIR_CONTEXT_KEY); + tracingHandler.afterError(request, response, e); + finish(request); + } + + private static void finish(Request request) { + Scope scope = request.getHandlerContext(SCOPE); if (scope == null) { return; } - request.addHandlerContext(CONTEXT_SCOPE_PAIR_CONTEXT_KEY, null); - scope.closeScope(); - tracer().endExceptionally(scope.getContext(), e); + scope.close(); + request.addHandlerContext(SCOPE, null); } } diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/groovy/SnsTracingTest.groovy b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/groovy/SnsTracingTest.groovy index a8c28a06dbbc..7c4331d3b52e 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/groovy/SnsTracingTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/groovy/SnsTracingTest.groovy @@ -19,6 +19,7 @@ import org.testcontainers.utility.DockerImageName import spock.lang.Ignore import spock.lang.Shared +@Ignore("Requires https://github.com/localstack/localstack/issues/3669 to work with localstack") class SnsTracingTest extends AgentInstrumentationSpecification { @Shared @@ -76,7 +77,6 @@ class SnsTracingTest extends AgentInstrumentationSpecification { return ctr.getTopicArn() } - @Ignore("Requires https://github.com/localstack/localstack/issues/3669 to work with localstack") def "simple SNS producer - SQS consumer services"() { setup: String queueName = "snsToSqsTestQueue" diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/Aws1ClientTest.groovy b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/Aws1ClientTest.groovy new file mode 100644 index 000000000000..af24eca55052 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/Aws1ClientTest.groovy @@ -0,0 +1,112 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.awssdk.v1_11 + +import com.amazonaws.AmazonWebServiceClient +import com.amazonaws.Request +import com.amazonaws.auth.BasicAWSCredentials +import com.amazonaws.handlers.RequestHandler2 +import com.amazonaws.regions.Regions +import com.amazonaws.services.s3.AmazonS3Client +import com.amazonaws.services.s3.AmazonS3ClientBuilder +import io.opentelemetry.api.trace.Span +import io.opentelemetry.api.trace.SpanKind +import io.opentelemetry.instrumentation.awssdk.v1_11.AbstractAws1ClientTest +import io.opentelemetry.instrumentation.test.AgentTestTrait +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes + +class Aws1ClientTest extends AbstractAws1ClientTest implements AgentTestTrait { + @Override + def configureClient(def client) { + return client + } + + // Verify agent instruments old and new construction patterns. + + def "request handler is hooked up with builder"() { + setup: + def builder = AmazonS3ClientBuilder.standard() + .withRegion(Regions.US_EAST_1) + if (addHandler) { + builder.withRequestHandlers(new RequestHandler2() {}) + } + AmazonWebServiceClient client = builder.build() + + expect: + client.requestHandler2s != null + client.requestHandler2s.size() == size + client.requestHandler2s.get(position).getClass().getSimpleName() == "TracingRequestHandler" + + where: + addHandler | size | position + true | 2 | 1 + false | 1 | 0 + } + + def "request handler is hooked up with constructor"() { + setup: + String accessKey = "asdf" + String secretKey = "qwerty" + def credentials = new BasicAWSCredentials(accessKey, secretKey) + def client = new AmazonS3Client(credentials) + if (addHandler) { + client.addRequestHandler(new RequestHandler2() {}) + } + + expect: + client.requestHandler2s != null + client.requestHandler2s.size() == size + client.requestHandler2s.get(0).getClass().getSimpleName() == "TracingRequestHandler" + + where: + addHandler | size + true | 2 + false | 1 + } + + // Test cases that require workarounds using bytecode instrumentation + + def "naughty request handler doesn't break the trace"() { + setup: + def client = new AmazonS3Client(CREDENTIALS_PROVIDER_CHAIN) + client.addRequestHandler(new RequestHandler2() { + void beforeRequest(Request request) { + throw new RuntimeException("bad handler") + } + }) + + when: + client.getObject("someBucket", "someKey") + + then: + !Span.current().getSpanContext().isValid() + thrown RuntimeException + + assertTraces(1) { + trace(0, 1) { + span(0) { + name "S3.HeadBucket" + kind SpanKind.CLIENT + errored true + errorEvent RuntimeException, "bad handler" + hasNoParent() + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.HTTP_URL.key}" "https://s3.amazonaws.com" + "${SemanticAttributes.HTTP_METHOD.key}" "HEAD" + "${SemanticAttributes.HTTP_FLAVOR.key}" "1.1" + "${SemanticAttributes.NET_PEER_NAME.key}" "s3.amazonaws.com" + "aws.service" "Amazon S3" + "aws.endpoint" "https://s3.amazonaws.com" + "aws.operation" "HeadBucket" + "aws.agent" "java-aws-sdk" + "aws.bucket.name" "someBucket" + } + } + } + } + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/testSqs/groovy/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SqsTracingTest.groovy b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/testSqs/groovy/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SqsTracingTest.groovy new file mode 100644 index 000000000000..80b19b8f0f67 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/testSqs/groovy/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SqsTracingTest.groovy @@ -0,0 +1,17 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.awssdk.v1_11 + +import com.amazonaws.services.sqs.AmazonSQSAsyncClientBuilder +import io.opentelemetry.instrumentation.awssdk.v1_11.AbstractSqsTracingTest +import io.opentelemetry.instrumentation.test.AgentTestTrait + +class SqsTracingTest extends AbstractSqsTracingTest implements AgentTestTrait { + @Override + AmazonSQSAsyncClientBuilder configureClient(AmazonSQSAsyncClientBuilder client) { + return client + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/aws-sdk-1.11-library.gradle b/instrumentation/aws-sdk/aws-sdk-1.11/library/aws-sdk-1.11-library.gradle new file mode 100644 index 000000000000..e68d00a82247 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/aws-sdk-1.11-library.gradle @@ -0,0 +1,17 @@ +apply from: "$rootDir/gradle/instrumentation-library.gradle" + +dependencies { + implementation deps.opentelemetryExtAws + + library group: 'com.amazonaws', name: 'aws-java-sdk-core', version: '1.11.0' + + testImplementation project(':instrumentation:aws-sdk:aws-sdk-1.11:testing') + + testLibrary group: 'com.amazonaws', name: 'aws-java-sdk-s3', version: '1.11.106' + testLibrary group: 'com.amazonaws', name: 'aws-java-sdk-rds', version: '1.11.106' + testLibrary group: 'com.amazonaws', name: 'aws-java-sdk-ec2', version: '1.11.106' + testLibrary group: 'com.amazonaws', name: 'aws-java-sdk-kinesis', version: '1.11.106' + testLibrary group: 'com.amazonaws', name: 'aws-java-sdk-dynamodb', version: '1.11.106' + testLibrary group: 'com.amazonaws', name: 'aws-java-sdk-sns', version: '1.11.106' + testLibrary group: 'com.amazonaws', name: 'aws-java-sdk-sqs', version: '1.11.106' +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AwsSdkClientTracer.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkClientTracer.java similarity index 83% rename from instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AwsSdkClientTracer.java rename to instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkClientTracer.java index 94de45de6adc..e6eb3cc61d93 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AwsSdkClientTracer.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkClientTracer.java @@ -3,32 +3,22 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.awssdk.v1_11; +package io.opentelemetry.instrumentation.awssdk.v1_11; import com.amazonaws.AmazonWebServiceResponse; import com.amazonaws.Request; import com.amazonaws.Response; -import com.amazonaws.handlers.HandlerContextKey; +import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.context.Context; import io.opentelemetry.context.propagation.TextMapSetter; import io.opentelemetry.extension.aws.AwsXrayPropagator; -import io.opentelemetry.instrumentation.api.config.Config; import io.opentelemetry.instrumentation.api.tracer.HttpClientTracer; import java.net.URI; import java.util.concurrent.ConcurrentHashMap; -public class AwsSdkClientTracer extends HttpClientTracer, Request, Response> { - - // Note: aws1.x sdk doesn't have any truly async clients so we can store scope in request context - // safely. - public static final HandlerContextKey CONTEXT_SCOPE_PAIR_CONTEXT_KEY = - new HandlerContextKey<>(AwsSdkClientTracer.class.getName() + ".ContextScopePair"); - - private static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES = - Config.get() - .getBooleanProperty("otel.instrumentation.aws-sdk.experimental-span-attributes", false); +final class AwsSdkClientTracer extends HttpClientTracer, Request, Response> { private static final ClassValue OPERATION_NAME = new ClassValue() { @@ -42,15 +32,14 @@ protected String computeValue(Class type) { static final String COMPONENT_NAME = "java-aws-sdk"; - private static final AwsSdkClientTracer TRACER = new AwsSdkClientTracer(); - - public static AwsSdkClientTracer tracer() { - return TRACER; - } - private final NamesCache namesCache = new NamesCache(); - public AwsSdkClientTracer() {} + private final boolean captureExperimentalSpanAttributes; + + AwsSdkClientTracer(OpenTelemetry openTelemetry, boolean captureExperimentalSpanAttributes) { + super(openTelemetry); + this.captureExperimentalSpanAttributes = captureExperimentalSpanAttributes; + } @Override protected void inject(Context context, Request request) { @@ -73,7 +62,7 @@ public Context startSpan(SpanKind kind, Context parentContext, Request reques String awsServiceName = request.getServiceName(); - if (CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) { + if (captureExperimentalSpanAttributes) { span.setAttribute("aws.agent", COMPONENT_NAME); span.setAttribute("aws.service", awsServiceName); span.setAttribute("aws.operation", extractOperationName(request)); @@ -106,7 +95,7 @@ public Context startSpan(SpanKind kind, Context parentContext, Request reques @Override public void onResponse(Span span, Response response) { - if (CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES + if (captureExperimentalSpanAttributes && response != null && response.getAwsResponse() instanceof AmazonWebServiceResponse) { AmazonWebServiceResponse awsResp = (AmazonWebServiceResponse) response.getAwsResponse(); diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AwsSdkInjectAdapter.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkInjectAdapter.java similarity index 87% rename from instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AwsSdkInjectAdapter.java rename to instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkInjectAdapter.java index f6156508f15a..08b8c034e422 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AwsSdkInjectAdapter.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkInjectAdapter.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.awssdk.v1_11; +package io.opentelemetry.instrumentation.awssdk.v1_11; import com.amazonaws.Request; import io.opentelemetry.context.propagation.TextMapSetter; diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkTracing.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkTracing.java new file mode 100644 index 000000000000..fd8fcb771cd2 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkTracing.java @@ -0,0 +1,56 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v1_11; + +import com.amazonaws.Request; +import com.amazonaws.handlers.RequestHandler2; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.context.Context; + +/** + * Entrypoint for tracing AWS SDK v1 clients. + * + *

AWS SDK v1 is quite old and has some known bugs that are not fixed due to possible backwards + * compatibility issues. Notably, if a {@link RequestHandler2} throws an exception in a callback, + * this exception will leak up the chain and prevent other handlers from being executed. You must + * ensure you do not register any problematic {@link RequestHandler2}s on your clients or you will + * witness broken traces. + */ +public class AwsSdkTracing { + + /** + * Returns the OpenTelemetry {@link Context} stored in the {@link Request}, or {@code null} if + * there is no {@link Context}. This is generally not needed unless you are implementing your own + * instrumentation that delegates to this one. + */ + public static Context getOpenTelemetryContext(Request request) { + return request.getHandlerContext(TracingRequestHandler.CONTEXT); + } + + /** Returns a new {@link AwsSdkTracing} configured with the given {@link OpenTelemetry}. */ + public static AwsSdkTracing create(OpenTelemetry openTelemetry) { + return newBuilder(openTelemetry).build(); + } + + /** Returns a new {@link AwsSdkTracingBuilder} configured with the given {@link OpenTelemetry}. */ + public static AwsSdkTracingBuilder newBuilder(OpenTelemetry openTelemetry) { + return new AwsSdkTracingBuilder(openTelemetry); + } + + private final AwsSdkClientTracer tracer; + + AwsSdkTracing(OpenTelemetry openTelemetry, boolean captureExperimentalSpanAttributes) { + tracer = new AwsSdkClientTracer(openTelemetry, captureExperimentalSpanAttributes); + } + + /** + * Returns a {@link RequestHandler2} for registration to AWS SDK client builders using {@code + * withRequestHandlers}. + */ + public RequestHandler2 newRequestHandler() { + return new TracingRequestHandler(tracer); + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkTracingBuilder.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkTracingBuilder.java new file mode 100644 index 000000000000..e031928025b4 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkTracingBuilder.java @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v1_11; + +import io.opentelemetry.api.OpenTelemetry; + +/** A builder of {@link AwsSdkTracing}. */ +public class AwsSdkTracingBuilder { + + private final OpenTelemetry openTelemetry; + + private boolean captureExperimentalSpanAttributes; + + AwsSdkTracingBuilder(OpenTelemetry openTelemetry) { + this.openTelemetry = openTelemetry; + } + + /** + * Sets whether experimental attributes should be set to spans. These attributes may be changed or + * removed in the future, so only enable this if you know you do not require attributes filled by + * this instrumentation to be stable across versions + */ + public AwsSdkTracingBuilder setCaptureExperimentalSpanAttributes( + boolean captureExperimentalSpanAttributes) { + this.captureExperimentalSpanAttributes = captureExperimentalSpanAttributes; + return this; + } + + /** Returns a new {@link AwsSdkTracing} with the settings of this {@link AwsSdkTracingBuilder}. */ + public AwsSdkTracing build() { + return new AwsSdkTracing(openTelemetry, captureExperimentalSpanAttributes); + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/RequestAccess.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/RequestAccess.java similarity index 97% rename from instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/RequestAccess.java rename to instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/RequestAccess.java index 0f9b8c803a81..d7732341aae3 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/RequestAccess.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/RequestAccess.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.awssdk.v1_11; +package io.opentelemetry.instrumentation.awssdk.v1_11; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SqsMessageAccess.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsMessageAccess.java similarity index 94% rename from instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SqsMessageAccess.java rename to instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsMessageAccess.java index 920ebff7a6cb..e090178dbbf6 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SqsMessageAccess.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsMessageAccess.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.awssdk.v1_11; +package io.opentelemetry.instrumentation.awssdk.v1_11; import static java.lang.invoke.MethodType.methodType; @@ -11,7 +11,7 @@ import java.lang.invoke.MethodHandles; import java.util.Collections; import java.util.Map; -import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Reflective access to aws-sdk-java-sqs class Message. diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SqsParentContext.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsParentContext.java similarity index 90% rename from instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SqsParentContext.java rename to instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsParentContext.java index 34194fe5fd9d..928933109beb 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SqsParentContext.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsParentContext.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.awssdk.v1_11; +package io.opentelemetry.instrumentation.awssdk.v1_11; import io.opentelemetry.context.Context; import io.opentelemetry.context.propagation.TextMapGetter; @@ -11,7 +11,7 @@ import java.util.Collections; import java.util.Map; -class SqsParentContext { +final class SqsParentContext { private static class MapGetter implements TextMapGetter> { @@ -38,4 +38,6 @@ static Context ofSystemAttributes(Map systemAttributes) { Collections.singletonMap("X-Amzn-Trace-Id", traceHeader), MapGetter.INSTANCE); } + + private SqsParentContext() {} } diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SqsReceiveMessageRequestAccess.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsReceiveMessageRequestAccess.java similarity index 96% rename from instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SqsReceiveMessageRequestAccess.java rename to instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsReceiveMessageRequestAccess.java index f38dad4404c6..7122134a8029 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SqsReceiveMessageRequestAccess.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsReceiveMessageRequestAccess.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.awssdk.v1_11; +package io.opentelemetry.instrumentation.awssdk.v1_11; import static java.lang.invoke.MethodType.methodType; @@ -12,7 +12,7 @@ import java.lang.invoke.MethodHandles; import java.util.Collections; import java.util.List; -import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Reflective access to aws-sdk-java-sqs class ReceiveMessageRequest. diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SqsReceiveMessageResultAccess.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsReceiveMessageResultAccess.java similarity index 94% rename from instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SqsReceiveMessageResultAccess.java rename to instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsReceiveMessageResultAccess.java index 08a17c0e5c41..5a0084adf99b 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SqsReceiveMessageResultAccess.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsReceiveMessageResultAccess.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.awssdk.v1_11; +package io.opentelemetry.instrumentation.awssdk.v1_11; import static java.lang.invoke.MethodType.methodType; @@ -11,7 +11,7 @@ import java.lang.invoke.MethodHandles; import java.util.Collections; import java.util.List; -import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Reflective access to aws-sdk-java-sqs class ReceiveMessageResult. diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/TracingRequestHandler.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/TracingRequestHandler.java new file mode 100644 index 000000000000..b989278dd918 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/TracingRequestHandler.java @@ -0,0 +1,104 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v1_11; + +import com.amazonaws.AmazonWebServiceRequest; +import com.amazonaws.Request; +import com.amazonaws.Response; +import com.amazonaws.handlers.HandlerContextKey; +import com.amazonaws.handlers.RequestHandler2; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.context.Context; +import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** Tracing Request Handler. */ +final class TracingRequestHandler extends RequestHandler2 { + + static final HandlerContextKey CONTEXT = + new HandlerContextKey<>(Context.class.getName()); + + private final AwsSdkClientTracer tracer; + + TracingRequestHandler(AwsSdkClientTracer tracer) { + this.tracer = tracer; + } + + @Override + public void beforeRequest(Request request) { + AmazonWebServiceRequest originalRequest = request.getOriginalRequest(); + SpanKind kind = (isSqsProducer(originalRequest) ? SpanKind.PRODUCER : SpanKind.CLIENT); + + Context parentContext = Context.current(); + if (!tracer.shouldStartSpan(parentContext)) { + return; + } + Context context = tracer.startSpan(kind, parentContext, request); + request.addHandlerContext(CONTEXT, context); + } + + private boolean isSqsProducer(AmazonWebServiceRequest request) { + return request + .getClass() + .getName() + .equals("com.amazonaws.services.sqs.model.SendMessageRequest"); + } + + @Override + public AmazonWebServiceRequest beforeMarshalling(AmazonWebServiceRequest request) { + if (SqsReceiveMessageRequestAccess.isInstance(request)) { + if (!SqsReceiveMessageRequestAccess.getAttributeNames(request) + .contains(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE)) { + SqsReceiveMessageRequestAccess.withAttributeNames( + request, SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE); + } + } + return request; + } + + @Override + public void afterResponse(Request request, Response response) { + if (SqsReceiveMessageRequestAccess.isInstance(request.getOriginalRequest())) { + afterConsumerResponse(request, response); + } + finish(request, response, null); + } + + /** Create and close CONSUMER span for each message consumed. */ + private void afterConsumerResponse(Request request, Response response) { + Object receiveMessageResult = response.getAwsResponse(); + List messages = SqsReceiveMessageResultAccess.getMessages(receiveMessageResult); + for (Object message : messages) { + createConsumerSpan(message, request, response); + } + } + + private void createConsumerSpan(Object message, Request request, Response response) { + Context parentContext = + SqsParentContext.ofSystemAttributes(SqsMessageAccess.getAttributes(message)); + Context context = tracer.startSpan(SpanKind.CONSUMER, parentContext, request); + tracer.end(context, response); + } + + @Override + public void afterError(Request request, Response response, Exception e) { + finish(request, response, e); + } + + private void finish(Request request, Response response, @Nullable Throwable error) { + // close outstanding "client" span + Context context = request.getHandlerContext(CONTEXT); + if (context == null) { + return; + } + request.addHandlerContext(CONTEXT, null); + if (error == null) { + tracer.end(context, response); + } else { + tracer.endExceptionally(context, response, error); + } + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v1_11/Aws1ClientTest.groovy b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v1_11/Aws1ClientTest.groovy new file mode 100644 index 000000000000..d865eb64b91d --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v1_11/Aws1ClientTest.groovy @@ -0,0 +1,19 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v1_11 + +import io.opentelemetry.instrumentation.test.LibraryTestTrait + +class Aws1ClientTest extends AbstractAws1ClientTest implements LibraryTestTrait { + @Override + def configureClient(def client) { + client.withRequestHandlers( + AwsSdkTracing.newBuilder(getOpenTelemetry()) + .setCaptureExperimentalSpanAttributes(true) + .build() + .newRequestHandler()) + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v1_11/SqsTracingTest.groovy b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v1_11/SqsTracingTest.groovy new file mode 100644 index 000000000000..d9538f148fbe --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v1_11/SqsTracingTest.groovy @@ -0,0 +1,20 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v1_11 + +import com.amazonaws.services.sqs.AmazonSQSAsyncClientBuilder +import io.opentelemetry.instrumentation.test.LibraryTestTrait + +class SqsTracingTest extends AbstractSqsTracingTest implements LibraryTestTrait { + @Override + AmazonSQSAsyncClientBuilder configureClient(AmazonSQSAsyncClientBuilder client) { + return client.withRequestHandlers( + AwsSdkTracing.newBuilder(getOpenTelemetry()) + .setCaptureExperimentalSpanAttributes(true) + .build() + .newRequestHandler()) + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/testing/aws-sdk-1.11-testing.gradle b/instrumentation/aws-sdk/aws-sdk-1.11/testing/aws-sdk-1.11-testing.gradle new file mode 100644 index 000000000000..1a996c3c23ea --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-1.11/testing/aws-sdk-1.11-testing.gradle @@ -0,0 +1,24 @@ +apply from: "$rootDir/gradle/java.gradle" + +dependencies { + api project(':testing-common') + + api group: 'com.amazonaws', name: 'aws-java-sdk-core', version: '1.11.0' + + compileOnly group: 'com.amazonaws', name: 'aws-java-sdk-s3', version: '1.11.106' + compileOnly group: 'com.amazonaws', name: 'aws-java-sdk-rds', version: '1.11.106' + compileOnly group: 'com.amazonaws', name: 'aws-java-sdk-ec2', version: '1.11.106' + compileOnly group: 'com.amazonaws', name: 'aws-java-sdk-kinesis', version: '1.11.106' + compileOnly group: 'com.amazonaws', name: 'aws-java-sdk-dynamodb', version: '1.11.106' + compileOnly group: 'com.amazonaws', name: 'aws-java-sdk-sns', version: '1.11.106' + compileOnly group: 'com.amazonaws', name: 'aws-java-sdk-sqs', version: '1.11.106' + + // needed for SQS - using emq directly as localstack references emq v0.15.7 ie WITHOUT AWS trace header propagation + implementation group: 'org.elasticmq', name: 'elasticmq-rest-sqs_2.12', version: '1.0.0' + + implementation deps.guava + + implementation deps.groovy + implementation deps.opentelemetryApi + implementation deps.spock +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/groovy/Aws1ClientTest.groovy b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractAws1ClientTest.groovy similarity index 62% rename from instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/groovy/Aws1ClientTest.groovy rename to instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractAws1ClientTest.groovy index 9d9313b59785..4889b1a293e6 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/groovy/Aws1ClientTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractAws1ClientTest.groovy @@ -3,28 +3,25 @@ * SPDX-License-Identifier: Apache-2.0 */ +package io.opentelemetry.instrumentation.awssdk.v1_11 + import static io.opentelemetry.api.trace.SpanKind.CLIENT import static io.opentelemetry.api.trace.SpanKind.PRODUCER import static io.opentelemetry.instrumentation.test.server.http.TestHttpServer.httpServer import static io.opentelemetry.instrumentation.test.utils.PortUtils.UNUSABLE_PORT import com.amazonaws.AmazonClientException -import com.amazonaws.AmazonWebServiceClient import com.amazonaws.ClientConfiguration -import com.amazonaws.Request import com.amazonaws.SDKGlobalConfiguration import com.amazonaws.SdkClientException import com.amazonaws.auth.AWSCredentialsProviderChain import com.amazonaws.auth.AWSStaticCredentialsProvider import com.amazonaws.auth.AnonymousAWSCredentials -import com.amazonaws.auth.BasicAWSCredentials import com.amazonaws.auth.EnvironmentVariableCredentialsProvider import com.amazonaws.auth.InstanceProfileCredentialsProvider import com.amazonaws.auth.SystemPropertiesCredentialsProvider import com.amazonaws.auth.profile.ProfileCredentialsProvider import com.amazonaws.client.builder.AwsClientBuilder -import com.amazonaws.handlers.RequestHandler2 -import com.amazonaws.regions.Regions import com.amazonaws.retry.PredefinedRetryPolicies import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder import com.amazonaws.services.dynamodbv2.model.CreateTableRequest @@ -36,15 +33,17 @@ import com.amazonaws.services.rds.model.DeleteOptionGroupRequest import com.amazonaws.services.s3.AmazonS3Client import com.amazonaws.services.s3.AmazonS3ClientBuilder import io.opentelemetry.api.trace.Span -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification +import io.opentelemetry.instrumentation.test.InstrumentationSpecification import io.opentelemetry.semconv.trace.attributes.SemanticAttributes import java.util.concurrent.atomic.AtomicReference import spock.lang.AutoCleanup import spock.lang.Shared -class Aws1ClientTest extends AgentInstrumentationSpecification { +abstract class AbstractAws1ClientTest extends InstrumentationSpecification { + + abstract T configureClient(T client) - private static final CREDENTIALS_PROVIDER_CHAIN = new AWSCredentialsProviderChain( + static final CREDENTIALS_PROVIDER_CHAIN = new AWSCredentialsProviderChain( new EnvironmentVariableCredentialsProvider(), new SystemPropertiesCredentialsProvider(), new ProfileCredentialsProvider(), @@ -76,52 +75,12 @@ class Aws1ClientTest extends AgentInstrumentationSpecification { @Shared def endpoint = new AwsClientBuilder.EndpointConfiguration("http://localhost:$server.address.port", "us-west-2") - def "request handler is hooked up with builder"() { - setup: - def builder = AmazonS3ClientBuilder.standard() - .withRegion(Regions.US_EAST_1) - if (addHandler) { - builder.withRequestHandlers(new RequestHandler2() {}) - } - AmazonWebServiceClient client = builder.build() - - expect: - client.requestHandler2s != null - client.requestHandler2s.size() == size - client.requestHandler2s.get(position).getClass().getSimpleName() == "TracingRequestHandler" - - where: - addHandler | size | position - true | 2 | 1 - false | 1 | 0 - } - - def "request handler is hooked up with constructor"() { - setup: - String accessKey = "asdf" - String secretKey = "qwerty" - def credentials = new BasicAWSCredentials(accessKey, secretKey) - def client = new AmazonS3Client(credentials) - if (addHandler) { - client.addRequestHandler(new RequestHandler2() {}) - } - - expect: - client.requestHandler2s != null - client.requestHandler2s.size() == size - client.requestHandler2s.get(0).getClass().getSimpleName() == "TracingRequestHandler" - - where: - addHandler | size - true | 2 - false | 1 - } - def "send #operation request with mocked response"() { setup: responseBody.set(body) when: + def client = configureClient(clientBuilder).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() def response = call.call(client) then: @@ -161,19 +120,19 @@ class Aws1ClientTest extends AgentInstrumentationSpecification { server.lastRequest.headers.get("traceparent") == null where: - service | operation | method | path | handlerCount | client | call | additionalAttributes | body - "S3" | "CreateBucket" | "PUT" | "/testbucket/" | 1 | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { client -> client.createBucket("testbucket") } | ["aws.bucket.name": "testbucket"] | "" - "S3" | "GetObject" | "GET" | "/someBucket/someKey" | 1 | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { client -> client.getObject("someBucket", "someKey") } | ["aws.bucket.name": "someBucket"] | "" - "DynamoDBv2" | "CreateTable" | "POST" | "/" | 1 | AmazonDynamoDBClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createTable(new CreateTableRequest("sometable", null)) } | ["aws.table.name": "sometable"] | "" - "Kinesis" | "DeleteStream" | "POST" | "/" | 1 | AmazonKinesisClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.deleteStream(new DeleteStreamRequest().withStreamName("somestream")) } | ["aws.stream.name": "somestream"] | "" - "EC2" | "AllocateAddress" | "POST" | "/" | 4 | AmazonEC2ClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { client -> client.allocateAddress() } | [:] | """ + service | operation | method | path | handlerCount | clientBuilder | call | additionalAttributes | body + "S3" | "CreateBucket" | "PUT" | "/testbucket/" | 1 | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true) | { c -> c.createBucket("testbucket") } | ["aws.bucket.name": "testbucket"] | "" + "S3" | "GetObject" | "GET" | "/someBucket/someKey" | 1 | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true) | { c -> c.getObject("someBucket", "someKey") } | ["aws.bucket.name": "someBucket"] | "" + "DynamoDBv2" | "CreateTable" | "POST" | "/" | 1 | AmazonDynamoDBClientBuilder.standard() | { c -> c.createTable(new CreateTableRequest("sometable", null)) } | ["aws.table.name": "sometable"] | "" + "Kinesis" | "DeleteStream" | "POST" | "/" | 1 | AmazonKinesisClientBuilder.standard() | { c -> c.deleteStream(new DeleteStreamRequest().withStreamName("somestream")) } | ["aws.stream.name": "somestream"] | "" + "EC2" | "AllocateAddress" | "POST" | "/" | 4 | AmazonEC2ClientBuilder.standard() | { c -> c.allocateAddress() } | [:] | """ 59dbff89-35bd-4eac-99ed-be587EXAMPLE 192.0.2.1 standard """ - "RDS" | "DeleteOptionGroup" | "POST" | "/" | (Boolean.getBoolean("testLatestDeps") ? 6 : 5) | AmazonRDSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { client -> client.deleteOptionGroup(new DeleteOptionGroupRequest()) } | [:] | """ + "RDS" | "DeleteOptionGroup" | "POST" | "/" | (Boolean.getBoolean("testLatestDeps") ? 6 : 5) | AmazonRDSClientBuilder.standard() | { c -> c.deleteOptionGroup(new DeleteOptionGroupRequest()) } | [:] | """ 0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99 @@ -187,6 +146,11 @@ class Aws1ClientTest extends AgentInstrumentationSpecification { responseBody.set(body) when: + def client = configureClient(clientBuilder) + .withCredentials(CREDENTIALS_PROVIDER_CHAIN) + .withClientConfiguration(new ClientConfiguration().withRetryPolicy(PredefinedRetryPolicies.getDefaultRetryPolicyWithCustomMaxRetries(0))) + .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration("http://localhost:${UNUSABLE_PORT}", "us-east-1")) + .build() call.call(client) then: @@ -220,49 +184,8 @@ class Aws1ClientTest extends AgentInstrumentationSpecification { } where: - service | operation | method | url | call | additionalAttributes | body | client - "S3" | "GetObject" | "GET" | "someBucket/someKey" | { client -> client.getObject("someBucket", "someKey") } | ["aws.bucket.name": "someBucket"] | "" | new AmazonS3Client(CREDENTIALS_PROVIDER_CHAIN, new ClientConfiguration().withRetryPolicy(PredefinedRetryPolicies.getDefaultRetryPolicyWithCustomMaxRetries(0))).withEndpoint("http://localhost:${UNUSABLE_PORT}") - } - - def "naughty request handler doesn't break the trace"() { - setup: - def client = new AmazonS3Client(CREDENTIALS_PROVIDER_CHAIN) - client.addRequestHandler(new RequestHandler2() { - void beforeRequest(Request request) { - throw new RuntimeException("bad handler") - } - }) - - when: - client.getObject("someBucket", "someKey") - - then: - !Span.current().getSpanContext().isValid() - thrown RuntimeException - - assertTraces(1) { - trace(0, 1) { - span(0) { - name "S3.HeadBucket" - kind CLIENT - errored true - errorEvent RuntimeException, "bad handler" - hasNoParent() - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.HTTP_URL.key}" "https://s3.amazonaws.com" - "${SemanticAttributes.HTTP_METHOD.key}" "HEAD" - "${SemanticAttributes.HTTP_FLAVOR.key}" "1.1" - "${SemanticAttributes.NET_PEER_NAME.key}" "s3.amazonaws.com" - "aws.service" "Amazon S3" - "aws.endpoint" "https://s3.amazonaws.com" - "aws.operation" "HeadBucket" - "aws.agent" "java-aws-sdk" - "aws.bucket.name" "someBucket" - } - } - } - } + service | operation | method | url | call | additionalAttributes | body | clientBuilder + "S3" | "GetObject" | "GET" | "someBucket/someKey" | { c -> c.getObject("someBucket", "someKey") } | ["aws.bucket.name": "someBucket"] | "" | AmazonS3ClientBuilder.standard() } // TODO(anuraaga): Add events for retries. @@ -276,8 +199,10 @@ class Aws1ClientTest extends AgentInstrumentationSpecification { } } } - AmazonS3Client client = new AmazonS3Client(new ClientConfiguration().withRequestTimeout(50 /* ms */)) - .withEndpoint("http://localhost:$server.address.port") + AmazonS3Client client = configureClient(AmazonS3ClientBuilder.standard()) + .withClientConfiguration(new ClientConfiguration().withRequestTimeout(50 /* ms */)) + .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration("http://localhost:$server.address.port", "us-east-1")) + .build() when: client.getObject("someBucket", "someKey") diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/testSqs/groovy/SqsTracingTest.groovy b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractSqsTracingTest.groovy similarity index 91% rename from instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/testSqs/groovy/SqsTracingTest.groovy rename to instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractSqsTracingTest.groovy index 5a00c22d0330..3f6cd1cd0771 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/testSqs/groovy/SqsTracingTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractSqsTracingTest.groovy @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +package io.opentelemetry.instrumentation.awssdk.v1_11 + import static io.opentelemetry.api.trace.SpanKind.CLIENT import static io.opentelemetry.api.trace.SpanKind.CONSUMER import static io.opentelemetry.api.trace.SpanKind.PRODUCER @@ -11,14 +13,17 @@ import com.amazonaws.auth.AWSStaticCredentialsProvider import com.amazonaws.auth.BasicAWSCredentials import com.amazonaws.client.builder.AwsClientBuilder import com.amazonaws.services.sqs.AmazonSQSAsyncClient +import com.amazonaws.services.sqs.AmazonSQSAsyncClientBuilder import com.amazonaws.services.sqs.model.ReceiveMessageRequest import com.amazonaws.services.sqs.model.SendMessageRequest -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification +import io.opentelemetry.instrumentation.test.InstrumentationSpecification import io.opentelemetry.instrumentation.test.utils.PortUtils import org.elasticmq.rest.sqs.SQSRestServerBuilder import spock.lang.Shared -class SqsTracingTest extends AgentInstrumentationSpecification { +abstract class AbstractSqsTracingTest extends InstrumentationSpecification { + + abstract AmazonSQSAsyncClientBuilder configureClient(AmazonSQSAsyncClientBuilder client) @Shared def sqs @@ -35,7 +40,7 @@ class SqsTracingTest extends AgentInstrumentationSpecification { def credentials = new AWSStaticCredentialsProvider(new BasicAWSCredentials("x", "x")) def endpointConfiguration = new AwsClientBuilder.EndpointConfiguration("http://localhost:"+sqsPort, "elasticmq") - client = AmazonSQSAsyncClient.asyncBuilder().withCredentials(credentials).withEndpointConfiguration(endpointConfiguration).build() + client = configureClient(AmazonSQSAsyncClient.asyncBuilder()).withCredentials(credentials).withEndpointConfiguration(endpointConfiguration).build() } def cleanupSpec() { diff --git a/settings.gradle b/settings.gradle index 78e006855b7a..9b72ab7f0de1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -73,6 +73,8 @@ include ':instrumentation:aws-lambda-1.0:javaagent' include ':instrumentation:aws-lambda-1.0:library' include ':instrumentation:aws-lambda-1.0:testing' include ':instrumentation:aws-sdk:aws-sdk-1.11:javaagent' +include ':instrumentation:aws-sdk:aws-sdk-1.11:library' +include ':instrumentation:aws-sdk:aws-sdk-1.11:testing' include ':instrumentation:aws-sdk:aws-sdk-2.2:javaagent' include ':instrumentation:aws-sdk:aws-sdk-2.2:library' include ':instrumentation:aws-sdk:aws-sdk-2.2:testing'