Merge pull request #723 from DataDog/tyler/aws-decorators
Migrate AWS SDK instrumentation to decorators
This commit is contained in:
commit
8f9ea87ef6
|
@ -8,7 +8,6 @@ import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||||
import com.amazonaws.handlers.RequestHandler2;
|
import com.amazonaws.handlers.RequestHandler2;
|
||||||
import com.google.auto.service.AutoService;
|
import com.google.auto.service.AutoService;
|
||||||
import datadog.trace.agent.tooling.Instrumenter;
|
import datadog.trace.agent.tooling.Instrumenter;
|
||||||
import io.opentracing.util.GlobalTracer;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import net.bytebuddy.asm.Advice;
|
import net.bytebuddy.asm.Advice;
|
||||||
|
@ -36,8 +35,11 @@ public final class AWSClientInstrumentation extends Instrumenter.Default {
|
||||||
@Override
|
@Override
|
||||||
public String[] helperClassNames() {
|
public String[] helperClassNames() {
|
||||||
return new String[] {
|
return new String[] {
|
||||||
"datadog.trace.instrumentation.aws.v0.TracingRequestHandler",
|
"datadog.trace.agent.decorator.BaseDecorator",
|
||||||
"datadog.trace.instrumentation.aws.v0.SpanDecorator"
|
"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) {
|
if (!hasDDHandler) {
|
||||||
handlers.add(new TracingRequestHandler(GlobalTracer.get()));
|
handlers.add(TracingRequestHandler.INSTANCE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -19,39 +19,19 @@ import com.amazonaws.Response;
|
||||||
import com.amazonaws.handlers.HandlerContextKey;
|
import com.amazonaws.handlers.HandlerContextKey;
|
||||||
import com.amazonaws.handlers.RequestHandler2;
|
import com.amazonaws.handlers.RequestHandler2;
|
||||||
import io.opentracing.Scope;
|
import io.opentracing.Scope;
|
||||||
import io.opentracing.SpanContext;
|
|
||||||
import io.opentracing.Tracer;
|
|
||||||
import io.opentracing.propagation.Format;
|
import io.opentracing.propagation.Format;
|
||||||
import io.opentracing.propagation.TextMapInjectAdapter;
|
import io.opentracing.propagation.TextMapInjectAdapter;
|
||||||
import io.opentracing.tag.Tags;
|
import io.opentracing.util.GlobalTracer;
|
||||||
|
|
||||||
/** Tracing Request Handler */
|
/** Tracing Request Handler */
|
||||||
public class TracingRequestHandler extends RequestHandler2 {
|
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
|
// Note: aws1.x sdk doesn't have any truly async clients so we can store scope in request context
|
||||||
// safely.
|
// safely.
|
||||||
private static final HandlerContextKey<Scope> SCOPE_CONTEXT_KEY =
|
private static final HandlerContextKey<Scope> SCOPE_CONTEXT_KEY =
|
||||||
new HandlerContextKey<>("DatadogScope");
|
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
|
@Override
|
||||||
public AmazonWebServiceRequest beforeMarshalling(final AmazonWebServiceRequest request) {
|
public AmazonWebServiceRequest beforeMarshalling(final AmazonWebServiceRequest request) {
|
||||||
return request;
|
return request;
|
||||||
|
@ -60,23 +40,17 @@ public class TracingRequestHandler extends RequestHandler2 {
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
@Override
|
@Override
|
||||||
public void beforeRequest(final Request<?> request) {
|
public void beforeRequest(final Request<?> request) {
|
||||||
// Note: not setting Component tag here because it is always set by SpanDecorator
|
final Scope scope = GlobalTracer.get().buildSpan("aws.command").startActive(true);
|
||||||
final Tracer.SpanBuilder spanBuilder =
|
AwsSdkClientDecorator.INSTANCE.afterStart(scope.span());
|
||||||
tracer.buildSpan("aws.command").withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT);
|
AwsSdkClientDecorator.INSTANCE.onRequest(scope.span(), request);
|
||||||
|
|
||||||
if (parentContext != null) {
|
|
||||||
spanBuilder.asChildOf(parentContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Scope scope = spanBuilder.startActive(true);
|
|
||||||
SpanDecorator.onRequest(request, scope.span());
|
|
||||||
|
|
||||||
// We inject headers at aws-client level because aws requests may be signed and adding headers
|
// We inject headers at aws-client level because aws requests may be signed and adding headers
|
||||||
// on http-client level may break signature.
|
// on http-client level may break signature.
|
||||||
tracer.inject(
|
GlobalTracer.get()
|
||||||
scope.span().context(),
|
.inject(
|
||||||
Format.Builtin.HTTP_HEADERS,
|
scope.span().context(),
|
||||||
new TextMapInjectAdapter(request.getHeaders()));
|
Format.Builtin.HTTP_HEADERS,
|
||||||
|
new TextMapInjectAdapter(request.getHeaders()));
|
||||||
|
|
||||||
request.addHandlerContext(SCOPE_CONTEXT_KEY, scope);
|
request.addHandlerContext(SCOPE_CONTEXT_KEY, scope);
|
||||||
}
|
}
|
||||||
|
@ -85,7 +59,8 @@ public class TracingRequestHandler extends RequestHandler2 {
|
||||||
@Override
|
@Override
|
||||||
public void afterResponse(final Request<?> request, final Response<?> response) {
|
public void afterResponse(final Request<?> request, final Response<?> response) {
|
||||||
final Scope scope = request.getHandlerContext(SCOPE_CONTEXT_KEY);
|
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();
|
scope.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +68,8 @@ public class TracingRequestHandler extends RequestHandler2 {
|
||||||
@Override
|
@Override
|
||||||
public void afterError(final Request<?> request, final Response<?> response, final Exception e) {
|
public void afterError(final Request<?> request, final Response<?> response, final Exception e) {
|
||||||
final Scope scope = request.getHandlerContext(SCOPE_CONTEXT_KEY);
|
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();
|
scope.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,12 @@ public abstract class AbstractAwsClientInstrumentation extends Instrumenter.Defa
|
||||||
@Override
|
@Override
|
||||||
public String[] helperClassNames() {
|
public String[] helperClassNames() {
|
||||||
return new String[] {
|
return new String[] {
|
||||||
AwsClientInstrumentation.class.getPackage().getName() + ".TracingExecutionInterceptor",
|
"datadog.trace.agent.decorator.BaseDecorator",
|
||||||
AwsClientInstrumentation.class.getPackage().getName()
|
"datadog.trace.agent.decorator.ClientDecorator",
|
||||||
+ ".TracingExecutionInterceptor$InjectAdapter"
|
"datadog.trace.agent.decorator.HttpClientDecorator",
|
||||||
|
packageName + ".AwsSdkClientDecorator",
|
||||||
|
packageName + ".TracingExecutionInterceptor",
|
||||||
|
packageName + ".TracingExecutionInterceptor$InjectAdapter"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,44 +13,31 @@
|
||||||
*/
|
*/
|
||||||
package datadog.trace.instrumentation.aws.v2;
|
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 datadog.trace.context.TraceScope;
|
||||||
import io.opentracing.Scope;
|
import io.opentracing.Scope;
|
||||||
import io.opentracing.Span;
|
import io.opentracing.Span;
|
||||||
import io.opentracing.Tracer;
|
import io.opentracing.Tracer;
|
||||||
import io.opentracing.propagation.Format;
|
import io.opentracing.propagation.Format;
|
||||||
import io.opentracing.propagation.TextMap;
|
import io.opentracing.propagation.TextMap;
|
||||||
import io.opentracing.tag.Tags;
|
|
||||||
import io.opentracing.util.GlobalTracer;
|
import io.opentracing.util.GlobalTracer;
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
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.client.config.ClientOverrideConfiguration;
|
||||||
import software.amazon.awssdk.core.interceptor.Context;
|
import software.amazon.awssdk.core.interceptor.Context;
|
||||||
import software.amazon.awssdk.core.interceptor.ExecutionAttribute;
|
import software.amazon.awssdk.core.interceptor.ExecutionAttribute;
|
||||||
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
|
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
|
||||||
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
|
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
|
||||||
import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute;
|
|
||||||
import software.amazon.awssdk.http.SdkHttpRequest;
|
import software.amazon.awssdk.http.SdkHttpRequest;
|
||||||
|
|
||||||
/** AWS request execution interceptor */
|
/** AWS request execution interceptor */
|
||||||
public class TracingExecutionInterceptor implements ExecutionInterceptor {
|
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
|
// 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.
|
// need to inject helper for it.
|
||||||
private static final Consumer<ClientOverrideConfiguration.Builder>
|
private static final Consumer<ClientOverrideConfiguration.Builder>
|
||||||
OVERRIDE_CONFIGURATION_CONSUMER = builder -> builder.addExecutionInterceptor(INSTANCE);
|
OVERRIDE_CONFIGURATION_CONSUMER =
|
||||||
|
builder -> builder.addExecutionInterceptor(new TracingExecutionInterceptor());
|
||||||
static final String COMPONENT_NAME = "java-aws-sdk";
|
|
||||||
|
|
||||||
private static final ExecutionAttribute<Span> SPAN_ATTRIBUTE =
|
private static final ExecutionAttribute<Span> SPAN_ATTRIBUTE =
|
||||||
new ExecutionAttribute<>("DatadogSpan");
|
new ExecutionAttribute<>("DatadogSpan");
|
||||||
|
@ -58,16 +45,9 @@ public class TracingExecutionInterceptor implements ExecutionInterceptor {
|
||||||
@Override
|
@Override
|
||||||
public void beforeExecution(
|
public void beforeExecution(
|
||||||
final Context.BeforeExecution context, final ExecutionAttributes executionAttributes) {
|
final Context.BeforeExecution context, final ExecutionAttributes executionAttributes) {
|
||||||
final Tracer tracer = GlobalTracer.get();
|
final Span span = GlobalTracer.get().buildSpan("aws.command").start();
|
||||||
|
AwsSdkClientDecorator.INSTANCE.afterStart(span);
|
||||||
final Tracer.SpanBuilder builder =
|
executionAttributes.putAttribute(SPAN_ATTRIBUTE, span);
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -76,35 +56,8 @@ public class TracingExecutionInterceptor implements ExecutionInterceptor {
|
||||||
final Span span = executionAttributes.getAttribute(SPAN_ATTRIBUTE);
|
final Span span = executionAttributes.getAttribute(SPAN_ATTRIBUTE);
|
||||||
final SdkHttpRequest httpRequest = context.httpRequest();
|
final SdkHttpRequest httpRequest = context.httpRequest();
|
||||||
|
|
||||||
Tags.HTTP_METHOD.set(span, httpRequest.method().name());
|
AwsSdkClientDecorator.INSTANCE.onRequest(span, httpRequest);
|
||||||
|
AwsSdkClientDecorator.INSTANCE.onAttributes(span, executionAttributes);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -132,23 +85,18 @@ public class TracingExecutionInterceptor implements ExecutionInterceptor {
|
||||||
public void afterExecution(
|
public void afterExecution(
|
||||||
final Context.AfterExecution context, final ExecutionAttributes executionAttributes) {
|
final Context.AfterExecution context, final ExecutionAttributes executionAttributes) {
|
||||||
final Span span = executionAttributes.getAttribute(SPAN_ATTRIBUTE);
|
final Span span = executionAttributes.getAttribute(SPAN_ATTRIBUTE);
|
||||||
try {
|
// Call onResponse on both types of responses:
|
||||||
Tags.HTTP_STATUS.set(span, context.httpResponse().statusCode());
|
AwsSdkClientDecorator.INSTANCE.onResponse(span, context.response());
|
||||||
final SdkResponse response = context.response();
|
AwsSdkClientDecorator.INSTANCE.onResponse(span, context.httpResponse());
|
||||||
if (response instanceof AwsResponse) {
|
AwsSdkClientDecorator.INSTANCE.beforeFinish(span);
|
||||||
span.setTag("aws.requestId", ((AwsResponse) response).responseMetadata().requestId());
|
span.finish();
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
span.finish();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExecutionFailure(
|
public void onExecutionFailure(
|
||||||
final Context.FailedExecution context, final ExecutionAttributes executionAttributes) {
|
final Context.FailedExecution context, final ExecutionAttributes executionAttributes) {
|
||||||
final Span span = executionAttributes.getAttribute(SPAN_ATTRIBUTE);
|
final Span span = executionAttributes.getAttribute(SPAN_ATTRIBUTE);
|
||||||
Tags.ERROR.set(span, Boolean.TRUE);
|
AwsSdkClientDecorator.INSTANCE.onError(span, context.exception());
|
||||||
span.log(Collections.singletonMap(ERROR_OBJECT, context.exception()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Consumer<ClientOverrideConfiguration.Builder> getOverrideConfigurationConsumer() {
|
public static Consumer<ClientOverrideConfiguration.Builder> getOverrideConfigurationConsumer() {
|
||||||
|
|
|
@ -76,6 +76,8 @@ class AwsClientTest extends AgentTestRunner {
|
||||||
"$Tags.HTTP_STATUS.key" 200
|
"$Tags.HTTP_STATUS.key" 200
|
||||||
"$Tags.HTTP_URL.key" expectedUrl
|
"$Tags.HTTP_URL.key" expectedUrl
|
||||||
"$Tags.HTTP_METHOD.key" "$method"
|
"$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
|
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||||
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_CLIENT
|
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_CLIENT
|
||||||
"aws.service" "$service"
|
"aws.service" "$service"
|
||||||
|
@ -167,6 +169,8 @@ class AwsClientTest extends AgentTestRunner {
|
||||||
"$Tags.HTTP_STATUS.key" 200
|
"$Tags.HTTP_STATUS.key" 200
|
||||||
"$Tags.HTTP_URL.key" expectedUrl
|
"$Tags.HTTP_URL.key" expectedUrl
|
||||||
"$Tags.HTTP_METHOD.key" "$method"
|
"$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
|
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||||
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_CLIENT
|
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_CLIENT
|
||||||
"aws.service" "$service"
|
"aws.service" "$service"
|
||||||
|
|
Loading…
Reference in New Issue