HTTP client instrumentation cleanup: aws-sdk (#1911)

This commit is contained in:
Trask Stalnaker 2020-12-14 23:06:28 -08:00 committed by GitHub
parent deda1af9c0
commit 49c2a36811
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 69 additions and 87 deletions

View File

@ -11,7 +11,7 @@ import com.amazonaws.Request;
import com.amazonaws.Response; import com.amazonaws.Response;
import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context; import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.TextMapPropagator.Setter; import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.instrumentation.api.tracer.HttpClientTracer; import io.opentelemetry.instrumentation.api.tracer.HttpClientTracer;
import java.net.URI; import java.net.URI;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -31,7 +31,7 @@ public class AwsSdkClientTracer extends HttpClientTracer<Request<?>, Request<?>,
public AwsSdkClientTracer() {} public AwsSdkClientTracer() {}
@Override @Override
public String spanNameForRequest(Request<?> request) { protected String spanNameForRequest(Request<?> request) {
if (request == null) { if (request == null) {
return DEFAULT_SPAN_NAME; return DEFAULT_SPAN_NAME;
} }
@ -111,7 +111,7 @@ public class AwsSdkClientTracer extends HttpClientTracer<Request<?>, Request<?>,
} }
@Override @Override
protected Setter<Request<?>> getSetter() { protected TextMapPropagator.Setter<Request<?>> getSetter() {
return AwsSdkInjectAdapter.INSTANCE; return AwsSdkInjectAdapter.INSTANCE;
} }

View File

@ -12,7 +12,7 @@ public class RequestMeta {
// 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.
public static final HandlerContextKey<ContextScopePair> CONTEXT_SCOPE_PAIR_CONTEXT_KEY = public static final HandlerContextKey<ContextScopePair> CONTEXT_SCOPE_PAIR_CONTEXT_KEY =
new HandlerContextKey<>(RequestMeta.class.getName() + ".ContextSpanPair"); new HandlerContextKey<>(RequestMeta.class.getName() + ".ContextScopePair");
private String bucketName; private String bucketName;
private String queueUrl; private String queueUrl;

View File

@ -30,31 +30,33 @@ public class TracingRequestHandler extends RequestHandler2 {
AmazonWebServiceRequest originalRequest = request.getOriginalRequest(); AmazonWebServiceRequest originalRequest = request.getOriginalRequest();
RequestMeta requestMeta = contextStore.get(originalRequest); RequestMeta requestMeta = contextStore.get(originalRequest);
Context parentContext = Context.current(); Context parentContext = Context.current();
if (tracer().shouldStartSpan(parentContext)) { if (!tracer().shouldStartSpan(parentContext)) {
Context context = tracer().startSpan(parentContext, request, requestMeta); return;
Scope scope = context.makeCurrent();
request.addHandlerContext(
CONTEXT_SCOPE_PAIR_CONTEXT_KEY, new ContextScopePair(context, scope));
} }
Context context = tracer().startSpan(parentContext, request, requestMeta);
Scope scope = context.makeCurrent();
request.addHandlerContext(CONTEXT_SCOPE_PAIR_CONTEXT_KEY, new ContextScopePair(context, scope));
} }
@Override @Override
public void afterResponse(Request<?> request, Response<?> response) { public void afterResponse(Request<?> request, Response<?> response) {
ContextScopePair scope = request.getHandlerContext(CONTEXT_SCOPE_PAIR_CONTEXT_KEY); ContextScopePair scope = request.getHandlerContext(CONTEXT_SCOPE_PAIR_CONTEXT_KEY);
if (scope != null) { if (scope == null) {
request.addHandlerContext(CONTEXT_SCOPE_PAIR_CONTEXT_KEY, null); return;
scope.closeScope();
tracer().end(scope.getContext(), response);
} }
request.addHandlerContext(CONTEXT_SCOPE_PAIR_CONTEXT_KEY, null);
scope.closeScope();
tracer().end(scope.getContext(), response);
} }
@Override @Override
public void afterError(Request<?> request, Response<?> response, Exception e) { public void afterError(Request<?> request, Response<?> response, Exception e) {
ContextScopePair scope = request.getHandlerContext(CONTEXT_SCOPE_PAIR_CONTEXT_KEY); ContextScopePair scope = request.getHandlerContext(CONTEXT_SCOPE_PAIR_CONTEXT_KEY);
if (scope != null) { if (scope == null) {
request.addHandlerContext(CONTEXT_SCOPE_PAIR_CONTEXT_KEY, null); return;
scope.closeScope();
tracer().endExceptionally(scope.getContext(), response, e);
} }
request.addHandlerContext(CONTEXT_SCOPE_PAIR_CONTEXT_KEY, null);
scope.closeScope();
tracer().endExceptionally(scope.getContext(), e);
} }
} }

View File

@ -8,8 +8,6 @@ package io.opentelemetry.instrumentation.awssdk.v2_2;
import static io.opentelemetry.instrumentation.awssdk.v2_2.TracingExecutionInterceptor.CONTEXT_ATTRIBUTE; import static io.opentelemetry.instrumentation.awssdk.v2_2.TracingExecutionInterceptor.CONTEXT_ATTRIBUTE;
import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Span.Kind;
import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context; import io.opentelemetry.context.Context;
import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
@ -40,29 +38,17 @@ public class AwsSdk {
/** /**
* Returns an {@link ExecutionInterceptor} that can be used with an {@link * Returns an {@link ExecutionInterceptor} that can be used with an {@link
* software.amazon.awssdk.http.SdkHttpClient} to trace SDK requests. Spans are created with the * software.amazon.awssdk.http.SdkHttpClient} to trace SDK requests.
* kind {@link Kind#CLIENT}. If you also instrument the HTTP calls made by the SDK, e.g., by
* adding Apache HTTP client or Netty instrumentation, you may want to use {@link
* #newInterceptor(Kind)} with {@link Kind#INTERNAL} instead.
*/ */
public static ExecutionInterceptor newInterceptor() { public static ExecutionInterceptor newInterceptor() {
return newInterceptor(Kind.CLIENT); return new TracingExecutionInterceptor();
} }
/** /**
* Returns an {@link ExecutionInterceptor} that can be used with an {@link * Returns the {@link Context} stored in the {@link ExecutionAttributes}, or {@code null} if there
* software.amazon.awssdk.http.SdkHttpClient} to trace SDK requests. Spans are created with the * is no operation set.
* provided {@link Kind}.
*/ */
public static ExecutionInterceptor newInterceptor(Kind kind) { public static Context getContext(ExecutionAttributes attributes) {
return new TracingExecutionInterceptor(kind);
}
/**
* Returns the {@link Span} stored in the {@link ExecutionAttributes}, or {@code null} if there is
* no span set.
*/
public static Context getContextFromAttributes(ExecutionAttributes attributes) {
return attributes.getAttribute(CONTEXT_ATTRIBUTE); return attributes.getAttribute(CONTEXT_ATTRIBUTE);
} }
} }

View File

@ -5,21 +5,22 @@
package io.opentelemetry.instrumentation.awssdk.v2_2; package io.opentelemetry.instrumentation.awssdk.v2_2;
import static io.opentelemetry.api.trace.Span.Kind.CLIENT;
import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Span.Kind;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.api.trace.attributes.SemanticAttributes;
import io.opentelemetry.context.Context; import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.TextMapPropagator.Setter; import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.instrumentation.api.tracer.BaseTracer; import io.opentelemetry.extension.trace.propagation.AwsXRayPropagator;
import io.opentelemetry.instrumentation.api.tracer.HttpClientTracer; import io.opentelemetry.instrumentation.api.tracer.HttpClientTracer;
import java.net.URI; import java.net.URI;
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute;
import software.amazon.awssdk.http.SdkHttpHeaders; import software.amazon.awssdk.http.SdkHttpHeaders;
import software.amazon.awssdk.http.SdkHttpRequest; import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.SdkHttpResponse; import software.amazon.awssdk.http.SdkHttpResponse;
final class AwsSdkHttpClientTracer final class AwsSdkHttpClientTracer
extends HttpClientTracer<SdkHttpRequest, SdkHttpRequest, SdkHttpResponse> { extends HttpClientTracer<SdkHttpRequest, SdkHttpRequest.Builder, SdkHttpResponse> {
private static final AwsSdkHttpClientTracer TRACER = new AwsSdkHttpClientTracer(); private static final AwsSdkHttpClientTracer TRACER = new AwsSdkHttpClientTracer();
@ -27,10 +28,15 @@ final class AwsSdkHttpClientTracer
return TRACER; return TRACER;
} }
// Certain headers in the request like User-Agent are only available after execution. public Context startSpan(Context parentContext, ExecutionAttributes attributes) {
Span afterExecution(Span span, SdkHttpRequest request) { String spanName = spanName(attributes);
span.setAttribute(SemanticAttributes.HTTP_USER_AGENT, requestHeader(request, USER_AGENT)); Span span =
return span; tracer.spanBuilder(spanName).setSpanKind(CLIENT).setParent(parentContext).startSpan();
return withClientSpan(parentContext, span);
}
public void inject(Context context, SdkHttpRequest.Builder builder) {
AwsXRayPropagator.getInstance().inject(context, builder, getSetter());
} }
@Override @Override
@ -59,8 +65,8 @@ final class AwsSdkHttpClientTracer
} }
@Override @Override
protected Setter<SdkHttpRequest> getSetter() { protected TextMapPropagator.Setter<SdkHttpRequest.Builder> getSetter() {
return null; return AwsSdkInjectAdapter.INSTANCE;
} }
private static String header(SdkHttpHeaders headers, String name) { private static String header(SdkHttpHeaders headers, String name) {
@ -78,9 +84,9 @@ final class AwsSdkHttpClientTracer
super.onRequest(span, sdkHttpRequest); super.onRequest(span, sdkHttpRequest);
} }
public Context startSpan(Context parentContext, String name, Tracer tracer, Kind kind) { private static String spanName(ExecutionAttributes attributes) {
Span clientSpan = String awsServiceName = attributes.getAttribute(SdkExecutionAttribute.SERVICE_NAME);
tracer.spanBuilder(name).setSpanKind(kind).setParent(parentContext).startSpan(); String awsOperation = attributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME);
return parentContext.with(clientSpan).with(BaseTracer.CONTEXT_CLIENT_SPAN_KEY, clientSpan); return awsServiceName + "." + awsOperation;
} }
} }

View File

@ -5,14 +5,13 @@
package io.opentelemetry.instrumentation.awssdk.v2_2; package io.opentelemetry.instrumentation.awssdk.v2_2;
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdk.getContextFromAttributes; import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdk.getContext;
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdkHttpClientTracer.tracer; import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdkHttpClientTracer.tracer;
import static io.opentelemetry.instrumentation.awssdk.v2_2.RequestType.ofSdkRequest; import static io.opentelemetry.instrumentation.awssdk.v2_2.RequestType.ofSdkRequest;
import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Span.Kind; import io.opentelemetry.api.trace.attributes.SemanticAttributes;
import io.opentelemetry.context.Scope; import io.opentelemetry.context.Scope;
import io.opentelemetry.extension.trace.propagation.AwsXRayPropagator;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -61,12 +60,6 @@ final class TracingExecutionInterceptor implements ExecutionInterceptor {
return result; return result;
} }
private final Kind kind;
TracingExecutionInterceptor(Kind kind) {
this.kind = kind;
}
@Nullable @Nullable
private SdkRequestDecorator decorator(ExecutionAttributes executionAttributes) { private SdkRequestDecorator decorator(ExecutionAttributes executionAttributes) {
RequestType type = getTypeFromAttributes(executionAttributes); RequestType type = getTypeFromAttributes(executionAttributes);
@ -80,12 +73,12 @@ final class TracingExecutionInterceptor implements ExecutionInterceptor {
@Override @Override
public void beforeExecution( public void beforeExecution(
Context.BeforeExecution context, ExecutionAttributes executionAttributes) { Context.BeforeExecution context, ExecutionAttributes executionAttributes) {
io.opentelemetry.context.Context parentContext = io.opentelemetry.context.Context.current(); io.opentelemetry.context.Context parentOtelContext = io.opentelemetry.context.Context.current();
if (!tracer().shouldStartSpan(parentContext)) { if (!tracer().shouldStartSpan(parentOtelContext)) {
return; return;
} }
io.opentelemetry.context.Context otelContext = io.opentelemetry.context.Context otelContext =
tracer().startSpan(parentContext, spanName(executionAttributes), AwsSdk.tracer(), kind); tracer().startSpan(parentOtelContext, executionAttributes);
executionAttributes.putAttribute(CONTEXT_ATTRIBUTE, otelContext); executionAttributes.putAttribute(CONTEXT_ATTRIBUTE, otelContext);
RequestType type = ofSdkRequest(context.request()); RequestType type = ofSdkRequest(context.request());
if (type != null) { if (type != null) {
@ -103,20 +96,20 @@ final class TracingExecutionInterceptor implements ExecutionInterceptor {
@Override @Override
public SdkHttpRequest modifyHttpRequest( public SdkHttpRequest modifyHttpRequest(
Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) { Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) {
io.opentelemetry.context.Context otelContext = getContextFromAttributes(executionAttributes); io.opentelemetry.context.Context otelContext = getContext(executionAttributes);
if (otelContext == null) { if (otelContext == null) {
return context.httpRequest(); return context.httpRequest();
} }
SdkHttpRequest.Builder builder = context.httpRequest().toBuilder(); SdkHttpRequest.Builder builder = context.httpRequest().toBuilder();
AwsXRayPropagator.getInstance().inject(otelContext, builder, AwsSdkInjectAdapter.INSTANCE); tracer().inject(otelContext, builder);
return builder.build(); return builder.build();
} }
@Override @Override
public void afterMarshalling( public void afterMarshalling(
Context.AfterMarshalling context, ExecutionAttributes executionAttributes) { Context.AfterMarshalling context, ExecutionAttributes executionAttributes) {
io.opentelemetry.context.Context otelContext = getContextFromAttributes(executionAttributes); io.opentelemetry.context.Context otelContext = getContext(executionAttributes);
if (otelContext == null) { if (otelContext == null) {
return; return;
} }
@ -160,14 +153,18 @@ final class TracingExecutionInterceptor implements ExecutionInterceptor {
if (scope != null) { if (scope != null) {
scope.close(); scope.close();
} }
io.opentelemetry.context.Context otelContext = getContextFromAttributes(executionAttributes); io.opentelemetry.context.Context otelContext = getContext(executionAttributes);
if (otelContext != null) { clearAttributes(executionAttributes);
clearAttributes(executionAttributes); Span span = Span.fromContext(otelContext);
Span span = Span.fromContext(otelContext); onUserAgentHeaderAvailable(span, context.httpRequest());
tracer().afterExecution(span, context.httpRequest()); onSdkResponse(span, context.response());
onSdkResponse(span, context.response()); tracer().end(otelContext, context.httpResponse());
tracer().end(otelContext, context.httpResponse()); }
}
// Certain headers in the request like User-Agent are only available after execution.
private void onUserAgentHeaderAvailable(Span span, SdkHttpRequest request) {
span.setAttribute(
SemanticAttributes.HTTP_USER_AGENT, tracer().requestHeader(request, "User-Agent"));
} }
private void onSdkResponse(Span span, SdkResponse response) { private void onSdkResponse(Span span, SdkResponse response) {
@ -179,22 +176,13 @@ final class TracingExecutionInterceptor implements ExecutionInterceptor {
@Override @Override
public void onExecutionFailure( public void onExecutionFailure(
Context.FailedExecution context, ExecutionAttributes executionAttributes) { Context.FailedExecution context, ExecutionAttributes executionAttributes) {
io.opentelemetry.context.Context otelContext = getContextFromAttributes(executionAttributes); io.opentelemetry.context.Context otelContext = getContext(executionAttributes);
if (otelContext != null) { clearAttributes(executionAttributes);
clearAttributes(executionAttributes); tracer().endExceptionally(otelContext, context.exception());
tracer().endExceptionally(otelContext, context.exception());
}
} }
private void clearAttributes(ExecutionAttributes executionAttributes) { private void clearAttributes(ExecutionAttributes executionAttributes) {
executionAttributes.putAttribute(CONTEXT_ATTRIBUTE, null); executionAttributes.putAttribute(CONTEXT_ATTRIBUTE, null);
executionAttributes.putAttribute(REQUEST_TYPE_ATTRIBUTE, null); executionAttributes.putAttribute(REQUEST_TYPE_ATTRIBUTE, null);
} }
private String spanName(ExecutionAttributes attributes) {
String awsServiceName = attributes.getAttribute(SdkExecutionAttribute.SERVICE_NAME);
String awsOperation = attributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME);
return awsServiceName + "." + awsOperation;
}
} }