Merge pull request #723 from DataDog/tyler/aws-decorators

Migrate AWS SDK instrumentation to decorators
This commit is contained in:
Tyler Benson 2019-02-21 12:37:05 -08:00 committed by GitHub
commit 8f9ea87ef6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 230 additions and 191 deletions

View File

@ -8,7 +8,6 @@ import static net.bytebuddy.matcher.ElementMatchers.named;
import com.amazonaws.handlers.RequestHandler2;
import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import io.opentracing.util.GlobalTracer;
import java.util.List;
import java.util.Map;
import net.bytebuddy.asm.Advice;
@ -36,8 +35,11 @@ public final class AWSClientInstrumentation extends Instrumenter.Default {
@Override
public String[] helperClassNames() {
return new String[] {
"datadog.trace.instrumentation.aws.v0.TracingRequestHandler",
"datadog.trace.instrumentation.aws.v0.SpanDecorator"
"datadog.trace.agent.decorator.BaseDecorator",
"datadog.trace.agent.decorator.ClientDecorator",
"datadog.trace.agent.decorator.HttpClientDecorator",
packageName + ".AwsSdkClientDecorator",
packageName + ".TracingRequestHandler",
};
}
@ -59,7 +61,7 @@ public final class AWSClientInstrumentation extends Instrumenter.Default {
}
}
if (!hasDDHandler) {
handlers.add(new TracingRequestHandler(GlobalTracer.get()));
handlers.add(TracingRequestHandler.INSTANCE);
}
}
}

View File

@ -0,0 +1,102 @@
package datadog.trace.instrumentation.aws.v0;
import com.amazonaws.AmazonWebServiceResponse;
import com.amazonaws.Request;
import com.amazonaws.Response;
import datadog.trace.agent.decorator.HttpClientDecorator;
import datadog.trace.api.DDTags;
import io.opentracing.Span;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
class AwsSdkClientDecorator extends HttpClientDecorator<Request, Response> {
public static final AwsSdkClientDecorator INSTANCE = new AwsSdkClientDecorator();
static final String COMPONENT_NAME = "java-aws-sdk";
private final Map<String, String> serviceNames = new ConcurrentHashMap<>();
private final Map<Class, String> operationNames = new ConcurrentHashMap<>();
@Override
public Span onRequest(final Span span, final Request request) {
// Call super first because we override the resource name below.
super.onRequest(span, request);
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));
return span;
}
@Override
public Span onResponse(final Span span, final Response response) {
if (response.getAwsResponse() instanceof AmazonWebServiceResponse) {
final AmazonWebServiceResponse awsResp = (AmazonWebServiceResponse) response.getAwsResponse();
span.setTag("aws.requestId", awsResp.getRequestId());
}
return super.onResponse(span, response);
}
private String remapServiceName(final String serviceName) {
if (!serviceNames.containsKey(serviceName)) {
serviceNames.put(serviceName, serviceName.replace("Amazon", "").trim());
}
return serviceNames.get(serviceName);
}
private String remapOperationName(final Class<?> awsOperation) {
if (!operationNames.containsKey(awsOperation)) {
operationNames.put(awsOperation, awsOperation.getSimpleName().replace("Request", ""));
}
return operationNames.get(awsOperation);
}
@Override
protected String service() {
return COMPONENT_NAME;
}
@Override
protected String[] instrumentationNames() {
return new String[] {"aws-sdk"};
}
@Override
protected String component() {
return COMPONENT_NAME;
}
@Override
protected String method(final Request request) {
return request.getHttpMethod().name();
}
@Override
protected String url(final Request request) {
return request.getEndpoint().toString();
}
@Override
protected String hostname(final Request request) {
return null;
}
@Override
protected Integer port(final Request request) {
return null;
}
@Override
protected Integer status(final Response response) {
return response.getHttpResponse().getStatusCode();
}
}

View File

@ -1,81 +0,0 @@
/*
* 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.v0;
import static io.opentracing.log.Fields.ERROR_OBJECT;
import com.amazonaws.AmazonWebServiceResponse;
import com.amazonaws.Request;
import com.amazonaws.Response;
import datadog.trace.api.DDSpanTypes;
import datadog.trace.api.DDTags;
import io.opentracing.Span;
import io.opentracing.tag.Tags;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
class SpanDecorator {
static final String COMPONENT_NAME = "java-aws-sdk";
private static final Map<String, String> SERVICE_NAMES = new ConcurrentHashMap<>();
private static final Map<Class, String> 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.SERVICE_NAME, COMPONENT_NAME);
span.setTag(
DDTags.RESOURCE_NAME,
remapServiceName(awsServiceName) + "." + remapOperationName(awsOperation));
span.setTag(DDTags.SPAN_TYPE, DDSpanTypes.HTTP_CLIENT);
}
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(Collections.singletonMap(ERROR_OBJECT, throwable));
}
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);
}
}

View File

@ -19,39 +19,19 @@ import com.amazonaws.Response;
import com.amazonaws.handlers.HandlerContextKey;
import com.amazonaws.handlers.RequestHandler2;
import io.opentracing.Scope;
import io.opentracing.SpanContext;
import io.opentracing.Tracer;
import io.opentracing.propagation.Format;
import io.opentracing.propagation.TextMapInjectAdapter;
import io.opentracing.tag.Tags;
import io.opentracing.util.GlobalTracer;
/** Tracing Request Handler */
public class TracingRequestHandler extends RequestHandler2 {
public static TracingRequestHandler INSTANCE = new TracingRequestHandler();
// Note: aws1.x sdk doesn't have any truly async clients so we can store scope in request context
// safely.
private static final HandlerContextKey<Scope> SCOPE_CONTEXT_KEY =
new HandlerContextKey<>("DatadogScope");
private final SpanContext parentContext; // for Async Client
private final Tracer tracer;
public TracingRequestHandler(final Tracer tracer) {
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;
@ -60,23 +40,17 @@ public class TracingRequestHandler extends RequestHandler2 {
/** {@inheritDoc} */
@Override
public void beforeRequest(final Request<?> request) {
// Note: not setting Component tag here because it is always set by SpanDecorator
final Tracer.SpanBuilder spanBuilder =
tracer.buildSpan("aws.command").withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT);
if (parentContext != null) {
spanBuilder.asChildOf(parentContext);
}
final Scope scope = spanBuilder.startActive(true);
SpanDecorator.onRequest(request, scope.span());
final Scope scope = GlobalTracer.get().buildSpan("aws.command").startActive(true);
AwsSdkClientDecorator.INSTANCE.afterStart(scope.span());
AwsSdkClientDecorator.INSTANCE.onRequest(scope.span(), request);
// We inject headers at aws-client level because aws requests may be signed and adding headers
// on http-client level may break signature.
tracer.inject(
scope.span().context(),
Format.Builtin.HTTP_HEADERS,
new TextMapInjectAdapter(request.getHeaders()));
GlobalTracer.get()
.inject(
scope.span().context(),
Format.Builtin.HTTP_HEADERS,
new TextMapInjectAdapter(request.getHeaders()));
request.addHandlerContext(SCOPE_CONTEXT_KEY, scope);
}
@ -85,7 +59,8 @@ public class TracingRequestHandler extends RequestHandler2 {
@Override
public void afterResponse(final Request<?> request, final Response<?> response) {
final Scope scope = request.getHandlerContext(SCOPE_CONTEXT_KEY);
SpanDecorator.onResponse(response, scope.span());
AwsSdkClientDecorator.INSTANCE.onResponse(scope.span(), response);
AwsSdkClientDecorator.INSTANCE.beforeFinish(scope.span());
scope.close();
}
@ -93,7 +68,8 @@ public class TracingRequestHandler extends RequestHandler2 {
@Override
public void afterError(final Request<?> request, final Response<?> response, final Exception e) {
final Scope scope = request.getHandlerContext(SCOPE_CONTEXT_KEY);
SpanDecorator.onError(e, scope.span());
AwsSdkClientDecorator.INSTANCE.onError(scope.span(), e);
AwsSdkClientDecorator.INSTANCE.beforeFinish(scope.span());
scope.close();
}
}

View File

@ -12,9 +12,12 @@ public abstract class AbstractAwsClientInstrumentation extends Instrumenter.Defa
@Override
public String[] helperClassNames() {
return new String[] {
AwsClientInstrumentation.class.getPackage().getName() + ".TracingExecutionInterceptor",
AwsClientInstrumentation.class.getPackage().getName()
+ ".TracingExecutionInterceptor$InjectAdapter"
"datadog.trace.agent.decorator.BaseDecorator",
"datadog.trace.agent.decorator.ClientDecorator",
"datadog.trace.agent.decorator.HttpClientDecorator",
packageName + ".AwsSdkClientDecorator",
packageName + ".TracingExecutionInterceptor",
packageName + ".TracingExecutionInterceptor$InjectAdapter"
};
}
}

View File

@ -0,0 +1,85 @@
package datadog.trace.instrumentation.aws.v2;
import datadog.trace.agent.decorator.HttpClientDecorator;
import datadog.trace.api.DDTags;
import io.opentracing.Span;
import software.amazon.awssdk.awscore.AwsResponse;
import software.amazon.awssdk.core.SdkResponse;
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute;
import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.SdkHttpResponse;
class AwsSdkClientDecorator extends HttpClientDecorator<SdkHttpRequest, SdkHttpResponse> {
public static final AwsSdkClientDecorator INSTANCE = new AwsSdkClientDecorator();
static final String COMPONENT_NAME = "java-aws-sdk";
public Span onAttributes(final Span span, final ExecutionAttributes attributes) {
final String awsServiceName = attributes.getAttribute(SdkExecutionAttribute.SERVICE_NAME);
final String awsOperation = attributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME);
// Resource Name has to be set after the HTTP_URL because otherwise decorators overwrite it
span.setTag(DDTags.RESOURCE_NAME, awsServiceName + "." + awsOperation);
span.setTag("aws.agent", COMPONENT_NAME);
span.setTag("aws.service", awsServiceName);
span.setTag("aws.operation", awsOperation);
return span;
}
// Not overriding the super. Should call both with each type of response.
public Span onResponse(final Span span, final SdkResponse response) {
if (response instanceof AwsResponse) {
span.setTag("aws.requestId", ((AwsResponse) response).responseMetadata().requestId());
}
return span;
}
@Override
protected String service() {
return COMPONENT_NAME;
}
@Override
protected String[] instrumentationNames() {
return new String[] {"aws-sdk"};
}
@Override
protected String component() {
return COMPONENT_NAME;
}
@Override
protected String method(final SdkHttpRequest request) {
return request.method().name();
}
@Override
protected String url(final SdkHttpRequest request) {
return request.protocol()
+ "://"
+ request.host()
+ ":"
+ request.port()
+ request.encodedPath();
}
@Override
protected String hostname(final SdkHttpRequest request) {
return request.host();
}
@Override
protected Integer port(final SdkHttpRequest request) {
return request.port();
}
@Override
protected Integer status(final SdkHttpResponse response) {
return response.statusCode();
}
}

View File

@ -13,44 +13,31 @@
*/
package datadog.trace.instrumentation.aws.v2;
import static io.opentracing.log.Fields.ERROR_OBJECT;
import datadog.trace.api.DDSpanTypes;
import datadog.trace.api.DDTags;
import datadog.trace.context.TraceScope;
import io.opentracing.Scope;
import io.opentracing.Span;
import io.opentracing.Tracer;
import io.opentracing.propagation.Format;
import io.opentracing.propagation.TextMap;
import io.opentracing.tag.Tags;
import io.opentracing.util.GlobalTracer;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.function.Consumer;
import software.amazon.awssdk.awscore.AwsResponse;
import software.amazon.awssdk.core.SdkResponse;
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
import software.amazon.awssdk.core.interceptor.Context;
import software.amazon.awssdk.core.interceptor.ExecutionAttribute;
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute;
import software.amazon.awssdk.http.SdkHttpRequest;
/** AWS request execution interceptor */
public class TracingExecutionInterceptor implements ExecutionInterceptor {
private static final TracingExecutionInterceptor INSTANCE = new TracingExecutionInterceptor();
// Note: it looks like this lambda doesn't get generated as a separate class file so we do not
// need to inject helper for it.
private static final Consumer<ClientOverrideConfiguration.Builder>
OVERRIDE_CONFIGURATION_CONSUMER = builder -> builder.addExecutionInterceptor(INSTANCE);
static final String COMPONENT_NAME = "java-aws-sdk";
OVERRIDE_CONFIGURATION_CONSUMER =
builder -> builder.addExecutionInterceptor(new TracingExecutionInterceptor());
private static final ExecutionAttribute<Span> SPAN_ATTRIBUTE =
new ExecutionAttribute<>("DatadogSpan");
@ -58,16 +45,9 @@ public class TracingExecutionInterceptor implements ExecutionInterceptor {
@Override
public void beforeExecution(
final Context.BeforeExecution context, final ExecutionAttributes executionAttributes) {
final Tracer tracer = GlobalTracer.get();
final Tracer.SpanBuilder builder =
tracer
.buildSpan("aws.command")
.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)
.withTag(Tags.COMPONENT.getKey(), COMPONENT_NAME)
.withTag(DDTags.SERVICE_NAME, COMPONENT_NAME)
.withTag(DDTags.SPAN_TYPE, DDSpanTypes.HTTP_CLIENT);
executionAttributes.putAttribute(SPAN_ATTRIBUTE, builder.start());
final Span span = GlobalTracer.get().buildSpan("aws.command").start();
AwsSdkClientDecorator.INSTANCE.afterStart(span);
executionAttributes.putAttribute(SPAN_ATTRIBUTE, span);
}
@Override
@ -76,35 +56,8 @@ public class TracingExecutionInterceptor implements ExecutionInterceptor {
final Span span = executionAttributes.getAttribute(SPAN_ATTRIBUTE);
final SdkHttpRequest httpRequest = context.httpRequest();
Tags.HTTP_METHOD.set(span, httpRequest.method().name());
try {
final URI requestUri = httpRequest.getUri();
final String uri =
new URI(
requestUri.getScheme(),
null,
requestUri.getHost(),
requestUri.getPort(),
requestUri.getPath(),
null,
null)
.toString();
Tags.HTTP_URL.set(span, uri);
} catch (final URISyntaxException e) {
Tags.HTTP_URL.set(span, "failed-to-parse");
}
final String awsServiceName =
executionAttributes.getAttribute(SdkExecutionAttribute.SERVICE_NAME);
final String awsOperation =
executionAttributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME);
// Resource Name has to be set after the HTTP_URL because otherwise decorators overwrite it
span.setTag(DDTags.RESOURCE_NAME, awsServiceName + "." + awsOperation);
span.setTag("aws.agent", COMPONENT_NAME);
span.setTag("aws.service", awsServiceName);
span.setTag("aws.operation", awsOperation);
AwsSdkClientDecorator.INSTANCE.onRequest(span, httpRequest);
AwsSdkClientDecorator.INSTANCE.onAttributes(span, executionAttributes);
}
@Override
@ -132,23 +85,18 @@ public class TracingExecutionInterceptor implements ExecutionInterceptor {
public void afterExecution(
final Context.AfterExecution context, final ExecutionAttributes executionAttributes) {
final Span span = executionAttributes.getAttribute(SPAN_ATTRIBUTE);
try {
Tags.HTTP_STATUS.set(span, context.httpResponse().statusCode());
final SdkResponse response = context.response();
if (response instanceof AwsResponse) {
span.setTag("aws.requestId", ((AwsResponse) response).responseMetadata().requestId());
}
} finally {
span.finish();
}
// Call onResponse on both types of responses:
AwsSdkClientDecorator.INSTANCE.onResponse(span, context.response());
AwsSdkClientDecorator.INSTANCE.onResponse(span, context.httpResponse());
AwsSdkClientDecorator.INSTANCE.beforeFinish(span);
span.finish();
}
@Override
public void onExecutionFailure(
final Context.FailedExecution context, final ExecutionAttributes executionAttributes) {
final Span span = executionAttributes.getAttribute(SPAN_ATTRIBUTE);
Tags.ERROR.set(span, Boolean.TRUE);
span.log(Collections.singletonMap(ERROR_OBJECT, context.exception()));
AwsSdkClientDecorator.INSTANCE.onError(span, context.exception());
}
public static Consumer<ClientOverrideConfiguration.Builder> getOverrideConfigurationConsumer() {

View File

@ -76,6 +76,8 @@ class AwsClientTest extends AgentTestRunner {
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" expectedUrl
"$Tags.HTTP_METHOD.key" "$method"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_PORT.key" server.address.port
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_CLIENT
"aws.service" "$service"
@ -167,6 +169,8 @@ class AwsClientTest extends AgentTestRunner {
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" expectedUrl
"$Tags.HTTP_METHOD.key" "$method"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_PORT.key" server.address.port
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_CLIENT
"aws.service" "$service"