From a3a1b08fa96844827ebf80015bc54d1b1b66d138 Mon Sep 17 00:00:00 2001 From: Tyler Benson Date: Tue, 22 May 2018 15:13:12 +1000 Subject: [PATCH] Fix AWS version ranges --- .../aws-java-sdk-1.11.0.gradle | 72 ++++++++ .../aws/v0/AWSClientInstrumentation.java | 71 +++++++ .../aws/v0}/SpanDecorator.java | 2 +- .../aws/v0}/TracingRequestHandler.java | 7 +- .../src/test/groovy/AWSClientTest.groovy | 173 ++++++++++++++++++ .../aws-java-sdk-1.11.106.gradle} | 20 +- .../aws/v106}/AWSClientInstrumentation.java | 18 +- .../aws/v106/SpanDecorator.java | 124 +++++++++++++ .../aws/v106/TracingRequestHandler.java | 92 ++++++++++ .../src/test/groovy/AWSClientTest.groovy | 11 ++ settings.gradle | 3 +- 11 files changed, 566 insertions(+), 27 deletions(-) create mode 100644 dd-java-agent/instrumentation/aws-java-sdk-1.11.0/aws-java-sdk-1.11.0.gradle create mode 100644 dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/main/java/datadog/trace/instrumentation/aws/v0/AWSClientInstrumentation.java rename dd-java-agent/instrumentation/{aws-sdk/src/main/java/datadog/trace/instrumentation/aws => aws-java-sdk-1.11.0/src/main/java/datadog/trace/instrumentation/aws/v0}/SpanDecorator.java (99%) rename dd-java-agent/instrumentation/{aws-sdk/src/main/java/datadog/trace/instrumentation/aws => aws-java-sdk-1.11.0/src/main/java/datadog/trace/instrumentation/aws/v0}/TracingRequestHandler.java (94%) create mode 100644 dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/test/groovy/AWSClientTest.groovy rename dd-java-agent/instrumentation/{aws-sdk/aws-sdk.gradle => aws-java-sdk-1.11.106/aws-java-sdk-1.11.106.gradle} (70%) rename dd-java-agent/instrumentation/{aws-sdk/src/main/java/datadog/trace/instrumentation/aws => aws-java-sdk-1.11.106/src/main/java/datadog/trace/instrumentation/aws/v106}/AWSClientInstrumentation.java (78%) create mode 100644 dd-java-agent/instrumentation/aws-java-sdk-1.11.106/src/main/java/datadog/trace/instrumentation/aws/v106/SpanDecorator.java create mode 100644 dd-java-agent/instrumentation/aws-java-sdk-1.11.106/src/main/java/datadog/trace/instrumentation/aws/v106/TracingRequestHandler.java rename dd-java-agent/instrumentation/{aws-sdk => aws-java-sdk-1.11.106}/src/test/groovy/AWSClientTest.groovy (94%) diff --git a/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/aws-java-sdk-1.11.0.gradle b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/aws-java-sdk-1.11.0.gradle new file mode 100644 index 0000000000..f7e4f1fee2 --- /dev/null +++ b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/aws-java-sdk-1.11.0.gradle @@ -0,0 +1,72 @@ +apply plugin: 'version-scan' + +// Commented out because version scan doesn't catch the combination of tests. +// HttpClientFactory is only present starting in 1.11.0 +// HandlerContextAware is added in 1.11.106 +// The combination of the two allow us to filter the ranges. +// + +//versionScan { +// group = "com.amazonaws" +// module = "aws-java-sdk-core" +// versions = "[1.11.0,1.11.106)" +// verifyPresent = [ +// "com.amazonaws.http.client.HttpClientFactory": null, +// ] +// verifyMissing = [ +// "com.amazonaws.HandlerContextAware", +// ] +//} + +//versionScan { +// group = "com.amazonaws" +// module = "aws-java-sdk-core" +// versions = "[1.11.0,)" +// verifyPresent = [ +// "com.amazonaws.http.client.HttpClientFactory": null, +// ] +//} + +versionScan { + group = "com.amazonaws" + module = "aws-java-sdk-core" + versions = "[,1.11.106)" + verifyMissing = [ + "com.amazonaws.HandlerContextAware", + ] +} + +apply from: "${rootDir}/gradle/java.gradle" + +apply plugin: 'org.unbroken-dome.test-sets' + +testSets { + latestDepTest { + dirName = 'test' + } +} + +// These classes use Ratpack which requires Java 8. (Currently also incompatible with Java 9.) +testJava8Only += '**/AWSClientTest.class' + +dependencies { + compileOnly group: 'com.amazonaws', name: 'aws-java-sdk-core', version: '1.11.0' + + compile project(':dd-java-agent:agent-tooling') + + compile deps.bytebuddy + compile deps.opentracing + annotationProcessor deps.autoservice + implementation deps.autoservice + + testCompile project(':dd-java-agent:testing') + // Include httpclient instrumentation for testing because it is a dependency for aws-sdk. + testCompile project(':dd-java-agent:instrumentation:apache-httpclient-4.3') + testCompile group: 'com.amazonaws', name: 'aws-java-sdk', version: '1.11.0' +} + +configurations.latestDepTestCompile { + resolutionStrategy { + force group: 'com.amazonaws', name: 'aws-java-sdk', version: '1.11.105' + } +} diff --git a/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/main/java/datadog/trace/instrumentation/aws/v0/AWSClientInstrumentation.java b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/main/java/datadog/trace/instrumentation/aws/v0/AWSClientInstrumentation.java new file mode 100644 index 0000000000..226e9ff246 --- /dev/null +++ b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/main/java/datadog/trace/instrumentation/aws/v0/AWSClientInstrumentation.java @@ -0,0 +1,71 @@ +package datadog.trace.instrumentation.aws.v0; + +import static datadog.trace.agent.tooling.ClassLoaderMatcher.classLoaderHasClasses; +import static net.bytebuddy.matcher.ElementMatchers.declaresField; +import static net.bytebuddy.matcher.ElementMatchers.isAbstract; +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; + +import com.amazonaws.handlers.RequestHandler2; +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.DDAdvice; +import datadog.trace.agent.tooling.DDTransformers; +import datadog.trace.agent.tooling.HelperInjector; +import datadog.trace.agent.tooling.Instrumenter; +import io.opentracing.util.GlobalTracer; +import java.util.List; +import net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.asm.Advice; + +/** + * This instrumentation might work with versions before 1.11.0, but this was the first version that + * is tested. It could possibly be extended earlier. + */ +@AutoService(Instrumenter.class) +public final class AWSClientInstrumentation extends Instrumenter.Configurable { + + public AWSClientInstrumentation() { + super("aws-sdk"); + } + + @Override + public AgentBuilder apply(final AgentBuilder agentBuilder) { + return agentBuilder + .type( + isAbstract() + .and( + named("com.amazonaws.AmazonWebServiceClient") + .and(declaresField(named("requestHandler2s")))), + classLoaderHasClasses("com.amazonaws.http.client.HttpClientFactory") + .and( + not( + classLoaderHasClasses( + "com.amazonaws.client.builder.AwsClientBuilder$EndpointConfiguration")))) + .transform( + new HelperInjector( + "datadog.trace.instrumentation.aws.v0.TracingRequestHandler", + "datadog.trace.instrumentation.aws.v0.SpanDecorator")) + .transform(DDTransformers.defaultTransformers()) + .transform(DDAdvice.create().advice(isConstructor(), AWSClientAdvice.class.getName())) + .asDecorator(); + } + + public static class AWSClientAdvice { + // Since we're instrumenting the constructor, we can't add onThrowable. + @Advice.OnMethodExit(suppress = Throwable.class) + public static void addHandler( + @Advice.FieldValue("requestHandler2s") final List handlers) { + boolean hasDDHandler = false; + for (final RequestHandler2 handler : handlers) { + if (handler instanceof TracingRequestHandler) { + hasDDHandler = true; + break; + } + } + if (!hasDDHandler) { + handlers.add(new TracingRequestHandler(GlobalTracer.get())); + } + } + } +} diff --git a/dd-java-agent/instrumentation/aws-sdk/src/main/java/datadog/trace/instrumentation/aws/SpanDecorator.java b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/main/java/datadog/trace/instrumentation/aws/v0/SpanDecorator.java similarity index 99% rename from dd-java-agent/instrumentation/aws-sdk/src/main/java/datadog/trace/instrumentation/aws/SpanDecorator.java rename to dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/main/java/datadog/trace/instrumentation/aws/v0/SpanDecorator.java index 89c6a6fdb9..b61316d47d 100644 --- a/dd-java-agent/instrumentation/aws-sdk/src/main/java/datadog/trace/instrumentation/aws/SpanDecorator.java +++ b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/main/java/datadog/trace/instrumentation/aws/v0/SpanDecorator.java @@ -11,7 +11,7 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ -package datadog.trace.instrumentation.aws; +package datadog.trace.instrumentation.aws.v0; import com.amazonaws.AmazonWebServiceResponse; import com.amazonaws.Request; diff --git a/dd-java-agent/instrumentation/aws-sdk/src/main/java/datadog/trace/instrumentation/aws/TracingRequestHandler.java b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/main/java/datadog/trace/instrumentation/aws/v0/TracingRequestHandler.java similarity index 94% rename from dd-java-agent/instrumentation/aws-sdk/src/main/java/datadog/trace/instrumentation/aws/TracingRequestHandler.java rename to dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/main/java/datadog/trace/instrumentation/aws/v0/TracingRequestHandler.java index f9c727ee73..ff352de1d3 100644 --- a/dd-java-agent/instrumentation/aws-sdk/src/main/java/datadog/trace/instrumentation/aws/TracingRequestHandler.java +++ b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/main/java/datadog/trace/instrumentation/aws/v0/TracingRequestHandler.java @@ -11,7 +11,7 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ -package datadog.trace.instrumentation.aws; +package datadog.trace.instrumentation.aws.v0; import com.amazonaws.AmazonWebServiceRequest; import com.amazonaws.Request; @@ -48,11 +48,6 @@ public class TracingRequestHandler extends RequestHandler2 { this.tracer = tracer; } - @Override - public AmazonWebServiceRequest beforeExecution(final AmazonWebServiceRequest request) { - return request; - } - @Override public AmazonWebServiceRequest beforeMarshalling(final AmazonWebServiceRequest request) { return request; diff --git a/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/test/groovy/AWSClientTest.groovy b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/test/groovy/AWSClientTest.groovy new file mode 100644 index 0000000000..eea79aa12c --- /dev/null +++ b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/test/groovy/AWSClientTest.groovy @@ -0,0 +1,173 @@ +import com.amazonaws.SDKGlobalConfiguration +import com.amazonaws.auth.BasicAWSCredentials +import com.amazonaws.handlers.RequestHandler2 +import com.amazonaws.services.ec2.AmazonEC2Client +import com.amazonaws.services.rds.AmazonRDSClient +import com.amazonaws.services.rds.model.DeleteOptionGroupRequest +import com.amazonaws.services.s3.AmazonS3Client +import com.amazonaws.services.s3.S3ClientOptions +import datadog.trace.agent.test.AgentTestRunner +import datadog.trace.api.DDTags +import io.opentracing.tag.Tags +import ratpack.http.Headers +import spock.lang.Shared + +import java.util.concurrent.atomic.AtomicReference + +import static ratpack.groovy.test.embed.GroovyEmbeddedApp.ratpack + +class AWSClientTest extends AgentTestRunner { + def setupSpec() { + System.setProperty(SDKGlobalConfiguration.ACCESS_KEY_SYSTEM_PROPERTY, "my-access-key") + System.setProperty(SDKGlobalConfiguration.SECRET_KEY_SYSTEM_PROPERTY, "my-secret-key") + } + + def cleanupSpec() { + System.clearProperty(SDKGlobalConfiguration.ACCESS_KEY_SYSTEM_PROPERTY) + System.clearProperty(SDKGlobalConfiguration.SECRET_KEY_SYSTEM_PROPERTY) + } + + @Shared + def receivedHeaders = new AtomicReference() + @Shared + def responseBody = new AtomicReference() + @Shared + def server = ratpack { + handlers { + all { + receivedHeaders.set(request.headers) + response.status(200).send(responseBody.get()) + } + } + } + + 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) + def response = call.call(client) + + expect: + response != null + + client.requestHandler2s != null + client.requestHandler2s.size() == handlerCount + client.requestHandler2s.get(0).getClass().getSimpleName() == "TracingRequestHandler" + + TEST_WRITER.size() == 2 + + def trace = TEST_WRITER.get(0) + trace.size() == 2 + + and: // span 0 - from apache-httpclient instrumentation + def span1 = trace[0] + + span1.context().operationName == "apache.http" + span1.serviceName == "unnamed-java-app" + span1.resourceName == "apache.http" + span1.type == null + !span1.context().getErrorFlag() + span1.context().parentId == 0 + + + def tags1 = span1.context().tags + tags1["component"] == "apache-httpclient" + tags1["thread.name"] != null + tags1["thread.id"] != null + tags1.size() == 3 + + and: // span 1 - from apache-httpclient instrumentation + def span2 = trace[1] + + span2.context().operationName == "http.request" + span2.serviceName == "unnamed-java-app" + span2.resourceName == "$method /$url" + span2.type == "http" + !span2.context().getErrorFlag() + span2.context().parentId == span1.spanId + + + def tags2 = span2.context().tags + tags2[Tags.SPAN_KIND.key] == Tags.SPAN_KIND_CLIENT + tags2[Tags.HTTP_METHOD.key] == "$method" + tags2[Tags.HTTP_URL.key] == "http://localhost:$server.address.port/$url" + tags2[Tags.PEER_HOSTNAME.key] == "localhost" + tags2[Tags.PEER_PORT.key] == server.address.port + tags2[DDTags.THREAD_NAME] != null + tags2[DDTags.THREAD_ID] != null + tags2.size() == 9 + + and: + + def trace2 = TEST_WRITER.get(1) + trace2.size() == 1 + + and: // span 0 - from aws instrumentation + def span = trace2[0] + + span.context().operationName == "aws.http" + span.serviceName == "java-aws-sdk" + span.resourceName == "$service.$operation" + span.type == "web" + !span.context().getErrorFlag() + span.context().parentId == 0 + + def tags = span.context().tags + tags[Tags.COMPONENT.key] == "java-aws-sdk" + tags[Tags.SPAN_KIND.key] == Tags.SPAN_KIND_CLIENT + tags[Tags.HTTP_METHOD.key] == "$method" + tags[Tags.HTTP_URL.key] == "http://localhost:$server.address.port" + tags[Tags.HTTP_STATUS.key] == 200 + tags["aws.service"] == "Amazon $service" || tags["aws.service"] == "Amazon$service" + tags["aws.endpoint"] == "http://localhost:$server.address.port" + tags["aws.operation"] == "${operation}Request" + tags["aws.agent"] == "java-aws-sdk" + tags["params"] == params + tags["span.type"] == "web" + tags["thread.name"] != null + tags["thread.id"] != null + tags.size() == 13 + + receivedHeaders.get().get("x-datadog-trace-id") == "$span.traceId" + receivedHeaders.get().get("x-datadog-parent-id") == "$span.spanId" + + where: + service | operation | method | url | handlerCount | call | body | params | client + "S3" | "CreateBucket" | "PUT" | "testbucket/" | 1 | { client -> client.setS3ClientOptions(S3ClientOptions.builder().setPathStyleAccess(true).build()); client.createBucket("testbucket") } | "" | "{}" | new AmazonS3Client().withEndpoint("http://localhost:$server.address.port") + "S3" | "GetObject" | "GET" | "someBucket/someKey" | 1 | { client -> client.getObject("someBucket", "someKey") } | "" | "{}" | new AmazonS3Client().withEndpoint("http://localhost:$server.address.port") + "EC2" | "AllocateAddress" | "POST" | "" | 4 | { client -> client.allocateAddress() } | """ + + 59dbff89-35bd-4eac-99ed-be587EXAMPLE + 192.0.2.1 + standard + + """ | "{Action=[AllocateAddress],Version=[2015-10-01]}" | new AmazonEC2Client().withEndpoint("http://localhost:$server.address.port") + "RDS" | "DeleteOptionGroup" | "POST" | "" | 1 | { client -> client.deleteOptionGroup(new DeleteOptionGroupRequest()) } | """ + + + 0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99 + + + """ | "{Action=[DeleteOptionGroup],Version=[2014-10-31]}" | new AmazonRDSClient().withEndpoint("http://localhost:$server.address.port") + } +} diff --git a/dd-java-agent/instrumentation/aws-sdk/aws-sdk.gradle b/dd-java-agent/instrumentation/aws-java-sdk-1.11.106/aws-java-sdk-1.11.106.gradle similarity index 70% rename from dd-java-agent/instrumentation/aws-sdk/aws-sdk.gradle rename to dd-java-agent/instrumentation/aws-java-sdk-1.11.106/aws-java-sdk-1.11.106.gradle index 68c8a5973a..4f83b7a8f2 100644 --- a/dd-java-agent/instrumentation/aws-sdk/aws-sdk.gradle +++ b/dd-java-agent/instrumentation/aws-java-sdk-1.11.106/aws-java-sdk-1.11.106.gradle @@ -1,15 +1,11 @@ -// This results in download a LOT of modules. commenting out for now... - apply plugin: 'version-scan' versionScan { group = "com.amazonaws" module = "aws-java-sdk-core" - versions = "[1.11.0,)" + versions = "[1.11.106,)" verifyPresent = [ - "com.amazonaws.http.client.HttpClientFactory" : null, - "com.amazonaws.http.apache.utils.ApacheUtils" : null, - "com.amazonaws.http.request.HttpRequestFactory": null, + "com.amazonaws.HandlerContextAware": null, ] } @@ -27,7 +23,7 @@ testSets { testJava8Only += '**/AWSClientTest.class' dependencies { - compileOnly group: 'com.amazonaws', name: 'aws-java-sdk-core', version: '1.11.119' + compileOnly group: 'com.amazonaws', name: 'aws-java-sdk-core', version: '1.11.106' compile project(':dd-java-agent:agent-tooling') @@ -39,7 +35,11 @@ dependencies { testCompile project(':dd-java-agent:testing') // Include httpclient instrumentation for testing because it is a dependency for aws-sdk. testCompile project(':dd-java-agent:instrumentation:apache-httpclient-4.3') - testCompile group: 'com.amazonaws', name: 'aws-java-sdk', version: '1.11.119' - - latestDepTestCompile group: 'com.amazonaws', name: 'aws-java-sdk', version: '+' + testCompile group: 'com.amazonaws', name: 'aws-java-sdk', version: '1.11.106' +} + +configurations.latestDepTestCompile { + resolutionStrategy { + force group: 'com.amazonaws', name: 'aws-java-sdk', version: '+' + } } diff --git a/dd-java-agent/instrumentation/aws-sdk/src/main/java/datadog/trace/instrumentation/aws/AWSClientInstrumentation.java b/dd-java-agent/instrumentation/aws-java-sdk-1.11.106/src/main/java/datadog/trace/instrumentation/aws/v106/AWSClientInstrumentation.java similarity index 78% rename from dd-java-agent/instrumentation/aws-sdk/src/main/java/datadog/trace/instrumentation/aws/AWSClientInstrumentation.java rename to dd-java-agent/instrumentation/aws-java-sdk-1.11.106/src/main/java/datadog/trace/instrumentation/aws/v106/AWSClientInstrumentation.java index a3ae8dd0c1..b5effae293 100644 --- a/dd-java-agent/instrumentation/aws-sdk/src/main/java/datadog/trace/instrumentation/aws/AWSClientInstrumentation.java +++ b/dd-java-agent/instrumentation/aws-java-sdk-1.11.106/src/main/java/datadog/trace/instrumentation/aws/v106/AWSClientInstrumentation.java @@ -1,4 +1,4 @@ -package datadog.trace.instrumentation.aws; +package datadog.trace.instrumentation.aws.v106; import static datadog.trace.agent.tooling.ClassLoaderMatcher.classLoaderHasClasses; import static net.bytebuddy.matcher.ElementMatchers.declaresField; @@ -17,6 +17,11 @@ import java.util.List; import net.bytebuddy.agent.builder.AgentBuilder; import net.bytebuddy.asm.Advice; +/** + * The interface for com.amazonaws.Request changed in 106. The method addHandlerContext which is + * used in TracingRequestHandler moved to a parent interface, which makes it source, but not + * bytecode compatible. The instrumentation is the same, but the compiled output is different. + */ @AutoService(Instrumenter.class) public final class AWSClientInstrumentation extends Instrumenter.Configurable { @@ -32,16 +37,11 @@ public final class AWSClientInstrumentation extends Instrumenter.Configurable { .and( named("com.amazonaws.AmazonWebServiceClient") .and(declaresField(named("requestHandler2s")))), - classLoaderHasClasses( - // aws classes used by opentracing contrib helpers - "com.amazonaws.handlers.RequestHandler2", - "com.amazonaws.Request", - "com.amazonaws.Response", - "com.amazonaws.handlers.HandlerContextKey")) + classLoaderHasClasses("com.amazonaws.HandlerContextAware")) .transform( new HelperInjector( - "datadog.trace.instrumentation.aws.TracingRequestHandler", - "datadog.trace.instrumentation.aws.SpanDecorator")) + "datadog.trace.instrumentation.aws.v106.TracingRequestHandler", + "datadog.trace.instrumentation.aws.v106.SpanDecorator")) .transform(DDTransformers.defaultTransformers()) .transform(DDAdvice.create().advice(isConstructor(), AWSClientAdvice.class.getName())) .asDecorator(); diff --git a/dd-java-agent/instrumentation/aws-java-sdk-1.11.106/src/main/java/datadog/trace/instrumentation/aws/v106/SpanDecorator.java b/dd-java-agent/instrumentation/aws-java-sdk-1.11.106/src/main/java/datadog/trace/instrumentation/aws/v106/SpanDecorator.java new file mode 100644 index 0000000000..1074a96876 --- /dev/null +++ b/dd-java-agent/instrumentation/aws-java-sdk-1.11.106/src/main/java/datadog/trace/instrumentation/aws/v106/SpanDecorator.java @@ -0,0 +1,124 @@ +/* + * Copyright 2017-2018 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package datadog.trace.instrumentation.aws.v106; + +import com.amazonaws.AmazonWebServiceResponse; +import com.amazonaws.Request; +import com.amazonaws.Response; +import datadog.trace.api.DDTags; +import io.opentracing.Span; +import io.opentracing.tag.Tags; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +class SpanDecorator { + static final String COMPONENT_NAME = "java-aws-sdk"; + + private static final Map SERVICE_NAMES = new ConcurrentHashMap<>(); + private static final Map OPERATION_NAMES = new ConcurrentHashMap<>(); + + static void onRequest(final Request request, final Span span) { + Tags.COMPONENT.set(span, COMPONENT_NAME); + Tags.HTTP_METHOD.set(span, request.getHttpMethod().name()); + Tags.HTTP_URL.set(span, request.getEndpoint().toString()); + + final String awsServiceName = request.getServiceName(); + final Class awsOperation = request.getOriginalRequest().getClass(); + + span.setTag("aws.agent", COMPONENT_NAME); + span.setTag("aws.service", awsServiceName); + span.setTag("aws.operation", awsOperation.getSimpleName()); + span.setTag("aws.endpoint", request.getEndpoint().toString()); + + span.setTag( + DDTags.RESOURCE_NAME, + remapServiceName(awsServiceName) + "." + remapOperationName(awsOperation)); + + try { + final StringBuilder params = new StringBuilder("{"); + final Map> requestParams = request.getParameters(); + boolean firstKey = true; + for (final Entry> entry : requestParams.entrySet()) { + if (!firstKey) { + params.append(","); + } + params.append(entry.getKey()).append("=["); + for (int i = 0; i < entry.getValue().size(); ++i) { + if (i > 0) { + params.append(","); + } + params.append(entry.getValue().get(i)); + } + params.append("]"); + firstKey = false; + } + params.append("}"); + span.setTag("params", params.toString()); + } catch (final Exception e) { + try { + org.slf4j.LoggerFactory.getLogger(SpanDecorator.class) + .debug("Failed to decorate aws span", e); + } catch (final Exception e2) { + // can't reach logger. Silently eat excetpion. + } + } + } + + static void onResponse(final Response response, final Span span) { + Tags.HTTP_STATUS.set(span, response.getHttpResponse().getStatusCode()); + if (response.getAwsResponse() instanceof AmazonWebServiceResponse) { + final AmazonWebServiceResponse awsResp = (AmazonWebServiceResponse) response.getAwsResponse(); + span.setTag("aws.requestId", awsResp.getRequestId()); + } + } + + static void onError(final Throwable throwable, final Span span) { + Tags.ERROR.set(span, Boolean.TRUE); + span.log(errorLogs(throwable)); + } + + private static Map errorLogs(final Throwable throwable) { + final Map errorLogs = new HashMap<>(4); + errorLogs.put("event", Tags.ERROR.getKey()); + errorLogs.put("error.kind", throwable.getClass().getName()); + errorLogs.put("error.object", throwable); + + errorLogs.put("message", throwable.getMessage()); + + final StringWriter sw = new StringWriter(); + throwable.printStackTrace(new PrintWriter(sw)); + errorLogs.put("stack", sw.toString()); + + return errorLogs; + } + + private static String remapServiceName(final String serviceName) { + if (!SERVICE_NAMES.containsKey(serviceName)) { + SERVICE_NAMES.put(serviceName, serviceName.replace("Amazon", "").trim()); + } + return SERVICE_NAMES.get(serviceName); + } + + private static String remapOperationName(final Class awsOperation) { + if (!OPERATION_NAMES.containsKey(awsOperation)) { + OPERATION_NAMES.put(awsOperation, awsOperation.getSimpleName().replace("Request", "")); + } + return OPERATION_NAMES.get(awsOperation); + } +} diff --git a/dd-java-agent/instrumentation/aws-java-sdk-1.11.106/src/main/java/datadog/trace/instrumentation/aws/v106/TracingRequestHandler.java b/dd-java-agent/instrumentation/aws-java-sdk-1.11.106/src/main/java/datadog/trace/instrumentation/aws/v106/TracingRequestHandler.java new file mode 100644 index 0000000000..7cdc002262 --- /dev/null +++ b/dd-java-agent/instrumentation/aws-java-sdk-1.11.106/src/main/java/datadog/trace/instrumentation/aws/v106/TracingRequestHandler.java @@ -0,0 +1,92 @@ +/* + * Copyright 2017-2018 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package datadog.trace.instrumentation.aws.v106; + +import com.amazonaws.AmazonWebServiceRequest; +import com.amazonaws.Request; +import com.amazonaws.Response; +import com.amazonaws.handlers.HandlerContextKey; +import com.amazonaws.handlers.RequestHandler2; +import io.opentracing.Span; +import io.opentracing.SpanContext; +import io.opentracing.Tracer; +import io.opentracing.propagation.Format; +import io.opentracing.propagation.TextMapInjectAdapter; +import io.opentracing.tag.Tags; + +/** Tracing Request Handler */ +public class TracingRequestHandler extends RequestHandler2 { + + private final HandlerContextKey contextKey = new HandlerContextKey<>("span"); + private final SpanContext parentContext; // for Async Client + private final Tracer tracer; + + public TracingRequestHandler(final Tracer tracer) { + this.parentContext = null; + this.tracer = tracer; + } + + /** + * In case of Async Client: beforeRequest runs in separate thread therefore we need to inject + * parent context to build chain + * + * @param parentContext parent context + */ + public TracingRequestHandler(final SpanContext parentContext, final Tracer tracer) { + this.parentContext = parentContext; + this.tracer = tracer; + } + + @Override + public AmazonWebServiceRequest beforeMarshalling(final AmazonWebServiceRequest request) { + return request; + } + + /** {@inheritDoc} */ + @Override + public void beforeRequest(final Request request) { + final Tracer.SpanBuilder spanBuilder = + tracer.buildSpan("aws.command").withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT); + + if (parentContext != null) { + spanBuilder.asChildOf(parentContext); + } + + final Span span = spanBuilder.start(); + SpanDecorator.onRequest(request, span); + + tracer.inject( + span.context(), + Format.Builtin.HTTP_HEADERS, + new TextMapInjectAdapter(request.getHeaders())); + + request.addHandlerContext(contextKey, span); + } + + /** {@inheritDoc} */ + @Override + public void afterResponse(final Request request, final Response response) { + final Span span = request.getHandlerContext(contextKey); + SpanDecorator.onResponse(response, span); + span.finish(); + } + + /** {@inheritDoc} */ + @Override + public void afterError(final Request request, final Response response, final Exception e) { + final Span span = request.getHandlerContext(contextKey); + SpanDecorator.onError(e, span); + span.finish(); + } +} diff --git a/dd-java-agent/instrumentation/aws-sdk/src/test/groovy/AWSClientTest.groovy b/dd-java-agent/instrumentation/aws-java-sdk-1.11.106/src/test/groovy/AWSClientTest.groovy similarity index 94% rename from dd-java-agent/instrumentation/aws-sdk/src/test/groovy/AWSClientTest.groovy rename to dd-java-agent/instrumentation/aws-java-sdk-1.11.106/src/test/groovy/AWSClientTest.groovy index 1303c148ad..81db986447 100644 --- a/dd-java-agent/instrumentation/aws-sdk/src/test/groovy/AWSClientTest.groovy +++ b/dd-java-agent/instrumentation/aws-java-sdk-1.11.106/src/test/groovy/AWSClientTest.groovy @@ -1,4 +1,5 @@ import com.amazonaws.AmazonWebServiceClient +import com.amazonaws.SDKGlobalConfiguration import com.amazonaws.auth.AWSStaticCredentialsProvider import com.amazonaws.auth.AnonymousAWSCredentials import com.amazonaws.auth.BasicAWSCredentials @@ -21,6 +22,16 @@ import java.util.concurrent.atomic.AtomicReference import static ratpack.groovy.test.embed.GroovyEmbeddedApp.ratpack class AWSClientTest extends AgentTestRunner { + def setupSpec() { + System.setProperty(SDKGlobalConfiguration.ACCESS_KEY_SYSTEM_PROPERTY, "my-access-key") + System.setProperty(SDKGlobalConfiguration.SECRET_KEY_SYSTEM_PROPERTY, "my-secret-key") + } + + def cleanupSpec() { + System.clearProperty(SDKGlobalConfiguration.ACCESS_KEY_SYSTEM_PROPERTY) + System.clearProperty(SDKGlobalConfiguration.SECRET_KEY_SYSTEM_PROPERTY) + } + @Shared def credentialsProvider = new AWSStaticCredentialsProvider(new AnonymousAWSCredentials()) @Shared diff --git a/settings.gradle b/settings.gradle index 4f6cd4510e..805ee94880 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,7 +10,8 @@ include ':dd-trace-api' // instrumentation: include ':dd-java-agent:instrumentation:apache-httpclient-4.3' -include ':dd-java-agent:instrumentation:aws-sdk' +include ':dd-java-agent:instrumentation:aws-java-sdk-1.11.0' +include ':dd-java-agent:instrumentation:aws-java-sdk-1.11.106' include ':dd-java-agent:instrumentation:classloaders' include ':dd-java-agent:instrumentation:datastax-cassandra-3.2' include ':dd-java-agent:instrumentation:hystrix-1.4'