Migrate AWS SDK v2 to new library API pattern. (#2487)

This commit is contained in:
Anuraag Agrawal 2021-03-04 19:32:13 +09:00 committed by GitHub
parent 8242a01b3a
commit 926a1fb621
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 135 additions and 79 deletions

View File

@ -5,7 +5,9 @@
package io.opentelemetry.javaagent.instrumentation.awssdk.v2_2;
import io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdk;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdkTracing;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.Optional;
@ -33,7 +35,7 @@ import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.SdkHttpResponse;
/**
* {@link ExecutionInterceptor} that delegates to {@link AwsSdk}, augmenting {@link
* {@link ExecutionInterceptor} that delegates to {@link AwsSdkTracing}, augmenting {@link
* #beforeTransmission(BeforeTransmission, ExecutionAttributes)} to make sure the span is set to the
* current context to allow downstream instrumentation like Netty to pick it up.
*/
@ -42,7 +44,14 @@ public class TracingExecutionInterceptor implements ExecutionInterceptor {
private final ExecutionInterceptor delegate;
public TracingExecutionInterceptor() {
delegate = AwsSdk.newInterceptor();
delegate =
AwsSdkTracing.newBuilder(GlobalOpenTelemetry.get())
.setCaptureExperimentalSpanAttributes(
Config.get()
.getBooleanProperty(
"otel.instrumentation.aws-sdk.experimental-span-attributes", false))
.build()
.newExecutionInterceptor();
}
@Override

View File

@ -1,54 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.awssdk.v2_2;
import static io.opentelemetry.instrumentation.awssdk.v2_2.TracingExecutionInterceptor.CONTEXT_ATTRIBUTE;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context;
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
/**
* Entrypoint to OpenTelemetry instrumentation of the AWS SDK. Register the {@link
* ExecutionInterceptor} returned by {@link #newInterceptor()} with an SDK client to have all
* requests traced.
*
* <pre>{@code
* DynamoDbClient dynamoDb = DynamoDbClient.builder()
* .overrideConfiguration(ClientOverrideConfiguration.builder()
* .addExecutionInterceptor(AwsSdk.newInterceptor())
* .build())
* .build();
* }</pre>
*/
public class AwsSdk {
private static final Tracer tracer =
GlobalOpenTelemetry.getTracer(AwsSdkHttpClientTracer.tracer().getInstrumentationName());
/** Returns the {@link Tracer} used to instrument the AWS SDK. */
public static Tracer tracer() {
return tracer;
}
/**
* Returns an {@link ExecutionInterceptor} that can be used with an {@link
* software.amazon.awssdk.http.SdkHttpClient} to trace SDK requests.
*/
public static ExecutionInterceptor newInterceptor() {
return new TracingExecutionInterceptor();
}
/**
* Returns the {@link Context} stored in the {@link ExecutionAttributes}, or {@code null} if there
* is no operation set.
*/
public static Context getContext(ExecutionAttributes attributes) {
return attributes.getAttribute(CONTEXT_ATTRIBUTE);
}
}

View File

@ -7,6 +7,7 @@ package io.opentelemetry.instrumentation.awssdk.v2_2;
import static io.opentelemetry.api.trace.SpanKind.CLIENT;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.TextMapSetter;
@ -22,10 +23,8 @@ import software.amazon.awssdk.http.SdkHttpResponse;
final class AwsSdkHttpClientTracer
extends HttpClientTracer<SdkHttpRequest, SdkHttpRequest.Builder, SdkHttpResponse> {
private static final AwsSdkHttpClientTracer TRACER = new AwsSdkHttpClientTracer();
static AwsSdkHttpClientTracer tracer() {
return TRACER;
AwsSdkHttpClientTracer(OpenTelemetry openTelemetry) {
super(openTelemetry);
}
public Context startSpan(Context parentContext, ExecutionAttributes attributes) {

View File

@ -0,0 +1,52 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.awssdk.v2_2;
import io.opentelemetry.api.OpenTelemetry;
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
/**
* Entrypoint to OpenTelemetry instrumentation of the AWS SDK. Register the {@link
* ExecutionInterceptor} returned by {@link #newExecutionInterceptor()} with an SDK client to have
* all requests traced.
*
* <pre>{@code
* DynamoDbClient dynamoDb = DynamoDbClient.builder()
* .overrideConfiguration(ClientOverrideConfiguration.builder()
* .addExecutionInterceptor(AwsSdkTracing.create(openTelemetry).newExecutionInterceptor())
* .build())
* .build();
* }</pre>
*/
public class AwsSdkTracing {
/** Returns a new {@link AwsSdkTracing} configured with the given {@link OpenTelemetry}. */
public static AwsSdkTracing create(OpenTelemetry openTelemetry) {
return newBuilder(openTelemetry).build();
}
/** Returns a new {@link AwsSdkTracingBuilder} configured with the given {@link OpenTelemetry}. */
public static AwsSdkTracingBuilder newBuilder(OpenTelemetry openTelemetry) {
return new AwsSdkTracingBuilder(openTelemetry);
}
private final AwsSdkHttpClientTracer tracer;
private final boolean captureExperimentalSpanAttributes;
AwsSdkTracing(OpenTelemetry openTelemetry, boolean captureExperimentalSpanAttributes) {
this.tracer = new AwsSdkHttpClientTracer(openTelemetry);
this.captureExperimentalSpanAttributes = captureExperimentalSpanAttributes;
}
/**
* Returns a new {@link ExecutionInterceptor} that can be used with methods like {@link
* ClientOverrideConfiguration.Builder#addExecutionInterceptor(ExecutionInterceptor)}.
*/
public ExecutionInterceptor newExecutionInterceptor() {
return new TracingExecutionInterceptor(tracer, captureExperimentalSpanAttributes);
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.awssdk.v2_2;
import io.opentelemetry.api.OpenTelemetry;
/** A builder of {@link AwsSdkTracing}. */
public final class AwsSdkTracingBuilder {
private final OpenTelemetry openTelemetry;
private boolean captureExperimentalSpanAttributes;
AwsSdkTracingBuilder(OpenTelemetry openTelemetry) {
this.openTelemetry = openTelemetry;
}
/**
* Sets whether experimental attributes should be set to spans. These attributes may be changed or
* removed in the future, so only enable this if you know you do not require attributes filled by
* this instrumentation to be stable across versions
*/
public AwsSdkTracingBuilder setCaptureExperimentalSpanAttributes(
boolean captureExperimentalSpanAttributes) {
this.captureExperimentalSpanAttributes = captureExperimentalSpanAttributes;
return this;
}
/** Returns a new {@link AwsSdkTracing} with the settings of this {@link AwsSdkTracingBuilder}. */
public AwsSdkTracing build() {
return new AwsSdkTracing(openTelemetry, captureExperimentalSpanAttributes);
}
}

View File

@ -5,13 +5,10 @@
package io.opentelemetry.instrumentation.awssdk.v2_2;
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.AwsSdkRequestType.DynamoDB;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import software.amazon.awssdk.awscore.AwsResponse;
import software.amazon.awssdk.core.ClientType;
@ -27,10 +24,6 @@ import software.amazon.awssdk.http.SdkHttpRequest;
/** AWS request execution interceptor. */
final class TracingExecutionInterceptor implements ExecutionInterceptor {
private static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES =
Config.get()
.getBooleanProperty("otel.instrumentation.aws-sdk.experimental-span-attributes", false);
// the class name is part of the attribute name, so that it will be shaded when used in javaagent
// instrumentation, and won't conflict with usage outside javaagent instrumentation
static final ExecutionAttribute<io.opentelemetry.context.Context> CONTEXT_ATTRIBUTE =
@ -42,17 +35,26 @@ final class TracingExecutionInterceptor implements ExecutionInterceptor {
static final String COMPONENT_NAME = "java-aws-sdk";
private final FieldMapper fieldMapper = new FieldMapper();
private final AwsSdkHttpClientTracer tracer;
private final boolean captureExperimentalSpanAttributes;
private final FieldMapper fieldMapper;
TracingExecutionInterceptor(
AwsSdkHttpClientTracer tracer, boolean captureExperimentalSpanAttributes) {
this.tracer = tracer;
this.captureExperimentalSpanAttributes = captureExperimentalSpanAttributes;
fieldMapper = new FieldMapper();
}
@Override
public void beforeExecution(
Context.BeforeExecution context, ExecutionAttributes executionAttributes) {
io.opentelemetry.context.Context parentOtelContext = io.opentelemetry.context.Context.current();
if (!tracer().shouldStartSpan(parentOtelContext)) {
if (!tracer.shouldStartSpan(parentOtelContext)) {
return;
}
io.opentelemetry.context.Context otelContext =
tracer().startSpan(parentOtelContext, executionAttributes);
tracer.startSpan(parentOtelContext, executionAttributes);
executionAttributes.putAttribute(CONTEXT_ATTRIBUTE, otelContext);
if (executionAttributes
.getAttribute(SdkExecutionAttribute.CLIENT_TYPE)
@ -72,7 +74,7 @@ final class TracingExecutionInterceptor implements ExecutionInterceptor {
}
SdkHttpRequest.Builder builder = context.httpRequest().toBuilder();
tracer().inject(otelContext, builder);
tracer.inject(otelContext, builder);
return builder.build();
}
@ -85,7 +87,7 @@ final class TracingExecutionInterceptor implements ExecutionInterceptor {
}
Span span = Span.fromContext(otelContext);
tracer().onRequest(span, context.httpRequest());
tracer.onRequest(span, context.httpRequest());
AwsSdkRequest awsSdkRequest = AwsSdkRequest.ofSdkRequest(context.request());
if (awsSdkRequest != null) {
@ -113,7 +115,7 @@ final class TracingExecutionInterceptor implements ExecutionInterceptor {
}
private void populateGenericAttributes(Span span, ExecutionAttributes attributes) {
if (CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) {
if (captureExperimentalSpanAttributes) {
String awsServiceName = attributes.getAttribute(SdkExecutionAttribute.SERVICE_NAME);
String awsOperation = attributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME);
@ -135,18 +137,18 @@ final class TracingExecutionInterceptor implements ExecutionInterceptor {
Span span = Span.fromContext(otelContext);
onUserAgentHeaderAvailable(span, context.httpRequest());
onSdkResponse(span, context.response(), executionAttributes);
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"));
SemanticAttributes.HTTP_USER_AGENT, tracer.requestHeader(request, "User-Agent"));
}
private void onSdkResponse(
Span span, SdkResponse response, ExecutionAttributes executionAttributes) {
if (CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) {
if (captureExperimentalSpanAttributes) {
if (response instanceof AwsResponse) {
span.setAttribute("aws.requestId", ((AwsResponse) response).responseMetadata().requestId());
}
@ -162,11 +164,19 @@ final class TracingExecutionInterceptor implements ExecutionInterceptor {
Context.FailedExecution context, ExecutionAttributes executionAttributes) {
io.opentelemetry.context.Context otelContext = getContext(executionAttributes);
clearAttributes(executionAttributes);
tracer().endExceptionally(otelContext, context.exception());
tracer.endExceptionally(otelContext, context.exception());
}
private void clearAttributes(ExecutionAttributes executionAttributes) {
executionAttributes.putAttribute(CONTEXT_ATTRIBUTE, null);
executionAttributes.putAttribute(AWS_SDK_REQUEST_ATTRIBUTE, null);
}
/**
* Returns the {@link Context} stored in the {@link ExecutionAttributes}, or {@code null} if there
* is no operation set.
*/
private static io.opentelemetry.context.Context getContext(ExecutionAttributes attributes) {
return attributes.getAttribute(CONTEXT_ATTRIBUTE);
}
}

View File

@ -13,7 +13,11 @@ class Aws2ClientTest extends AbstractAws2ClientTest implements LibraryTestTrait
@Override
void configureSdkClient(SdkClientBuilder builder) {
builder.overrideConfiguration(ClientOverrideConfiguration.builder()
.addExecutionInterceptor(AwsSdk.newInterceptor())
.addExecutionInterceptor(
AwsSdkTracing.newBuilder(getOpenTelemetry())
.setCaptureExperimentalSpanAttributes(true)
.build()
.newExecutionInterceptor())
.build())
}
}