Convert AwsSdk2 to Instrumenter API (#4341)

* Convert AwsSdk2 to Instrumenter API
This commit is contained in:
Nikita Salnikov-Tarnovski 2021-10-12 15:58:00 +03:00 committed by GitHub
parent 55f987613a
commit 3a90673c99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 274 additions and 147 deletions

View File

@ -0,0 +1,83 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.awssdk.v2_2;
import static java.util.Collections.emptyList;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.util.List;
import org.checkerframework.checker.nullness.qual.Nullable;
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.SdkHttpResponse;
class AwsSdkAttributesExtractor
extends HttpClientAttributesExtractor<ExecutionAttributes, SdkHttpResponse> {
@Override
protected String url(ExecutionAttributes request) {
SdkHttpRequest httpRequest =
request.getAttribute(TracingExecutionInterceptor.SDK_HTTP_REQUEST_ATTRIBUTE);
return httpRequest.getUri().toString();
}
@Override
protected @Nullable String flavor(
ExecutionAttributes request, @Nullable SdkHttpResponse response) {
return SemanticAttributes.HttpFlavorValues.HTTP_1_1;
}
@Override
protected String method(ExecutionAttributes request) {
SdkHttpRequest httpRequest =
request.getAttribute(TracingExecutionInterceptor.SDK_HTTP_REQUEST_ATTRIBUTE);
return httpRequest.method().name();
}
@Override
protected List<String> requestHeader(ExecutionAttributes request, String name) {
SdkHttpRequest httpRequest =
request.getAttribute(TracingExecutionInterceptor.SDK_HTTP_REQUEST_ATTRIBUTE);
List<String> value = httpRequest.headers().get(name);
return value == null ? emptyList() : value;
}
@Override
protected @Nullable Long requestContentLength(
ExecutionAttributes request, @Nullable SdkHttpResponse response) {
return null;
}
@Override
protected @Nullable Long requestContentLengthUncompressed(
ExecutionAttributes request, @Nullable SdkHttpResponse response) {
return null;
}
@Override
protected Integer statusCode(ExecutionAttributes request, SdkHttpResponse response) {
return response.statusCode();
}
@Override
protected @Nullable Long responseContentLength(
ExecutionAttributes request, @Nullable SdkHttpResponse response) {
return null;
}
@Override
protected @Nullable Long responseContentLengthUncompressed(
ExecutionAttributes request, @Nullable SdkHttpResponse response) {
return null;
}
@Override
protected List<String> responseHeader(
ExecutionAttributes request, SdkHttpResponse response, String name) {
List<String> value = response.headers().get(name);
return value == null ? emptyList() : value;
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.awssdk.v2_2;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import org.checkerframework.checker.nullness.qual.Nullable;
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute;
import software.amazon.awssdk.http.SdkHttpResponse;
class AwsSdkExperimentalAttributesExtractor
extends AttributesExtractor<ExecutionAttributes, SdkHttpResponse> {
private static final String COMPONENT_NAME = "java-aws-sdk";
private static final AttributeKey<String> AWS_AGENT = AttributeKey.stringKey("aws.agent");
private static final AttributeKey<String> AWS_SERVICE = AttributeKey.stringKey("aws.service");
private static final AttributeKey<String> AWS_OPERATION = AttributeKey.stringKey("aws.operation");
@Override
protected void onStart(AttributesBuilder attributes, ExecutionAttributes executionAttributes) {
String awsServiceName = executionAttributes.getAttribute(SdkExecutionAttribute.SERVICE_NAME);
String awsOperation = executionAttributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME);
attributes.put(AWS_AGENT, COMPONENT_NAME);
attributes.put(AWS_SERVICE, awsServiceName);
attributes.put(AWS_OPERATION, awsOperation);
}
@Override
protected void onEnd(
AttributesBuilder attributes,
ExecutionAttributes executionAttributes,
@Nullable SdkHttpResponse sdkHttpResponse,
@Nullable Throwable error) {}
}

View File

@ -1,92 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
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;
import io.opentelemetry.extension.aws.AwsXrayPropagator;
import io.opentelemetry.instrumentation.api.tracer.HttpClientTracer;
import io.opentelemetry.instrumentation.api.tracer.net.NetPeerAttributes;
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.SdkHttpRequest;
import software.amazon.awssdk.http.SdkHttpResponse;
final class AwsSdkHttpClientTracer
extends HttpClientTracer<SdkHttpRequest, SdkHttpRequest.Builder, SdkHttpResponse> {
AwsSdkHttpClientTracer(OpenTelemetry openTelemetry) {
super(openTelemetry, new NetPeerAttributes());
}
public Context startSpan(Context parentContext, ExecutionAttributes attributes) {
String spanName = spanName(attributes);
Span span = spanBuilder(parentContext, spanName, CLIENT).startSpan();
return withClientSpan(parentContext, span);
}
@Override
public void inject(Context context, SdkHttpRequest.Builder builder) {
AwsXrayPropagator.getInstance().inject(context, builder, getSetter());
}
@Override
protected String method(SdkHttpRequest request) {
return request.method().name();
}
@Override
protected URI url(SdkHttpRequest request) {
return request.getUri();
}
@Override
protected Integer status(SdkHttpResponse response) {
return response.statusCode();
}
@Override
protected String requestHeader(SdkHttpRequest sdkHttpRequest, String name) {
return header(sdkHttpRequest, name);
}
@Override
protected String responseHeader(SdkHttpResponse sdkHttpResponse, String name) {
return header(sdkHttpResponse, name);
}
@Override
protected TextMapSetter<SdkHttpRequest.Builder> getSetter() {
return AwsSdkInjectAdapter.INSTANCE;
}
private static String header(SdkHttpHeaders headers, String name) {
return headers.firstMatchingHeader(name).orElse(null);
}
@Override
protected String getInstrumentationName() {
return "io.opentelemetry.aws-sdk-2.2";
}
/** This method is overridden to allow other classes in this package to call it. */
@Override
protected void onRequest(Span span, SdkHttpRequest sdkHttpRequest) {
super.onRequest(span, sdkHttpRequest);
}
private static String spanName(ExecutionAttributes attributes) {
String awsServiceName = attributes.getAttribute(SdkExecutionAttribute.SERVICE_NAME);
String awsOperation = attributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME);
return awsServiceName + "." + awsOperation;
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.awssdk.v2_2;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
import java.util.Arrays;
import java.util.List;
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute;
import software.amazon.awssdk.http.SdkHttpResponse;
final class AwsSdkInstrumenterFactory {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.aws-sdk-2.2";
static final AwsSdkAttributesExtractor attributesExtractor = new AwsSdkAttributesExtractor();
private static final AwsSdkNetAttributesExtractor netAttributesExtractor =
new AwsSdkNetAttributesExtractor();
private static final AwsSdkExperimentalAttributesExtractor experimentalAttributesExtractor =
new AwsSdkExperimentalAttributesExtractor();
private static final List<AttributesExtractor<ExecutionAttributes, SdkHttpResponse>>
defaultAttributesExtractors = Arrays.asList(attributesExtractor, netAttributesExtractor);
private static final List<AttributesExtractor<ExecutionAttributes, SdkHttpResponse>>
extendedAttributesExtractors =
Arrays.asList(
attributesExtractor, netAttributesExtractor, experimentalAttributesExtractor);
static Instrumenter<ExecutionAttributes, SdkHttpResponse> createInstrumenter(
OpenTelemetry openTelemetry, boolean captureExperimentalSpanAttributes) {
return Instrumenter.<ExecutionAttributes, SdkHttpResponse>newBuilder(
openTelemetry, INSTRUMENTATION_NAME, AwsSdkInstrumenterFactory::spanName)
.addAttributesExtractors(
captureExperimentalSpanAttributes
? extendedAttributesExtractors
: defaultAttributesExtractors)
.newInstrumenter(SpanKindExtractor.alwaysClient());
}
private static String spanName(ExecutionAttributes attributes) {
String awsServiceName = attributes.getAttribute(SdkExecutionAttribute.SERVICE_NAME);
String awsOperation = attributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME);
return awsServiceName + "." + awsOperation;
}
private AwsSdkInstrumenterFactory() {}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.awssdk.v2_2;
import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesExtractor;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import org.checkerframework.checker.nullness.qual.Nullable;
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.SdkHttpResponse;
class AwsSdkNetAttributesExtractor
extends NetClientAttributesExtractor<ExecutionAttributes, SdkHttpResponse> {
@Override
public String transport(ExecutionAttributes request, @Nullable SdkHttpResponse response) {
return SemanticAttributes.NetTransportValues.IP_TCP;
}
@Override
public @Nullable String peerName(
ExecutionAttributes request, @Nullable SdkHttpResponse response) {
SdkHttpRequest httpRequest =
request.getAttribute(TracingExecutionInterceptor.SDK_HTTP_REQUEST_ATTRIBUTE);
return httpRequest.host();
}
@Override
public Integer peerPort(ExecutionAttributes request, @Nullable SdkHttpResponse response) {
SdkHttpRequest httpRequest =
request.getAttribute(TracingExecutionInterceptor.SDK_HTTP_REQUEST_ATTRIBUTE);
return httpRequest.port();
}
@Override
public @Nullable String peerIp(ExecutionAttributes request, @Nullable SdkHttpResponse response) {
return null;
}
}

View File

@ -6,8 +6,11 @@
package io.opentelemetry.instrumentation.awssdk.v2_2;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
import software.amazon.awssdk.http.SdkHttpResponse;
/**
* Entrypoint to OpenTelemetry instrumentation of the AWS SDK. Register the {@link
@ -34,11 +37,13 @@ public class AwsSdkTracing {
return new AwsSdkTracingBuilder(openTelemetry);
}
private final AwsSdkHttpClientTracer tracer;
private final Instrumenter<ExecutionAttributes, SdkHttpResponse> tracer;
private final boolean captureExperimentalSpanAttributes;
AwsSdkTracing(OpenTelemetry openTelemetry, boolean captureExperimentalSpanAttributes) {
this.tracer = new AwsSdkHttpClientTracer(openTelemetry);
this.tracer =
AwsSdkInstrumenterFactory.createInstrumenter(
openTelemetry, captureExperimentalSpanAttributes);
this.captureExperimentalSpanAttributes = captureExperimentalSpanAttributes;
}

View File

@ -9,7 +9,10 @@ import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdkRequestType.Dyn
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Scope;
import io.opentelemetry.extension.aws.AwsXrayPropagator;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.util.List;
import software.amazon.awssdk.awscore.AwsResponse;
import software.amazon.awssdk.core.ClientType;
import software.amazon.awssdk.core.SdkRequest;
@ -20,6 +23,7 @@ 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;
import software.amazon.awssdk.http.SdkHttpResponse;
/** AWS request execution interceptor. */
final class TracingExecutionInterceptor implements ExecutionInterceptor {
@ -32,29 +36,36 @@ final class TracingExecutionInterceptor implements ExecutionInterceptor {
new ExecutionAttribute<>(TracingExecutionInterceptor.class.getName() + ".Scope");
static final ExecutionAttribute<AwsSdkRequest> AWS_SDK_REQUEST_ATTRIBUTE =
new ExecutionAttribute<>(TracingExecutionInterceptor.class.getName() + ".AwsSdkRequest");
static final ExecutionAttribute<SdkHttpRequest> SDK_HTTP_REQUEST_ATTRIBUTE =
new ExecutionAttribute<>(TracingExecutionInterceptor.class.getName() + ".SdkHttpRequest");
static final String COMPONENT_NAME = "java-aws-sdk";
private final AwsSdkHttpClientTracer tracer;
private final Instrumenter<ExecutionAttributes, SdkHttpResponse> instrumenter;
private final boolean captureExperimentalSpanAttributes;
private final FieldMapper fieldMapper;
TracingExecutionInterceptor(
AwsSdkHttpClientTracer tracer, boolean captureExperimentalSpanAttributes) {
this.tracer = tracer;
Instrumenter<ExecutionAttributes, SdkHttpResponse> instrumenter,
boolean captureExperimentalSpanAttributes) {
this.instrumenter = instrumenter;
this.captureExperimentalSpanAttributes = captureExperimentalSpanAttributes;
fieldMapper = new FieldMapper();
this.fieldMapper = new FieldMapper();
}
@Override
public void beforeExecution(
Context.BeforeExecution context, ExecutionAttributes executionAttributes) {
public void afterMarshalling(
Context.AfterMarshalling context, ExecutionAttributes executionAttributes) {
io.opentelemetry.context.Context parentOtelContext = io.opentelemetry.context.Context.current();
if (!tracer.shouldStartSpan(parentOtelContext)) {
if (!instrumenter.shouldStart(parentOtelContext, executionAttributes)) {
return;
}
SdkHttpRequest httpRequest = context.httpRequest();
executionAttributes.putAttribute(SDK_HTTP_REQUEST_ATTRIBUTE, httpRequest);
io.opentelemetry.context.Context otelContext =
tracer.startSpan(parentOtelContext, executionAttributes);
instrumenter.start(parentOtelContext, executionAttributes);
executionAttributes.putAttribute(CONTEXT_ATTRIBUTE, otelContext);
if (executionAttributes
.getAttribute(SdkExecutionAttribute.CLIENT_TYPE)
@ -63,38 +74,29 @@ final class TracingExecutionInterceptor implements ExecutionInterceptor {
// instrumentation like Apache to know about the SDK span.
executionAttributes.putAttribute(SCOPE_ATTRIBUTE, otelContext.makeCurrent());
}
}
@Override
public SdkHttpRequest modifyHttpRequest(
Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) {
io.opentelemetry.context.Context otelContext = getContext(executionAttributes);
if (otelContext == null) {
return context.httpRequest();
}
SdkHttpRequest.Builder builder = context.httpRequest().toBuilder();
tracer.inject(otelContext, builder);
return builder.build();
}
@Override
public void afterMarshalling(
Context.AfterMarshalling context, ExecutionAttributes executionAttributes) {
io.opentelemetry.context.Context otelContext = getContext(executionAttributes);
if (otelContext == null) {
return;
}
Span span = Span.fromContext(otelContext);
tracer.onRequest(span, context.httpRequest());
AwsSdkRequest awsSdkRequest = AwsSdkRequest.ofSdkRequest(context.request());
if (awsSdkRequest != null) {
executionAttributes.putAttribute(AWS_SDK_REQUEST_ATTRIBUTE, awsSdkRequest);
populateRequestAttributes(span, awsSdkRequest, context.request(), executionAttributes);
}
populateGenericAttributes(span, executionAttributes);
}
@Override
public SdkHttpRequest modifyHttpRequest(
Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) {
SdkHttpRequest httpRequest = context.httpRequest();
io.opentelemetry.context.Context otelContext = getContext(executionAttributes);
if (otelContext == null) {
return httpRequest;
}
SdkHttpRequest.Builder builder = httpRequest.toBuilder();
AwsXrayPropagator.getInstance().inject(otelContext, builder, AwsSdkInjectAdapter.INSTANCE);
return builder.build();
}
private void populateRequestAttributes(
@ -114,32 +116,26 @@ final class TracingExecutionInterceptor implements ExecutionInterceptor {
}
}
private void populateGenericAttributes(Span span, ExecutionAttributes attributes) {
if (captureExperimentalSpanAttributes) {
String awsServiceName = attributes.getAttribute(SdkExecutionAttribute.SERVICE_NAME);
String awsOperation = attributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME);
span.setAttribute("aws.agent", COMPONENT_NAME);
span.setAttribute("aws.service", awsServiceName);
span.setAttribute("aws.operation", awsOperation);
}
}
@Override
public void afterExecution(
Context.AfterExecution context, ExecutionAttributes executionAttributes) {
// http request has been changed
executionAttributes.putAttribute(SDK_HTTP_REQUEST_ATTRIBUTE, context.httpRequest());
io.opentelemetry.context.Context otelContext = getContext(executionAttributes);
clearAttributes(executionAttributes);
Span span = Span.fromContext(otelContext);
onUserAgentHeaderAvailable(span, context.httpRequest());
onUserAgentHeaderAvailable(span, executionAttributes);
onSdkResponse(span, context.response(), executionAttributes);
tracer.end(otelContext, context.httpResponse());
instrumenter.end(otelContext, executionAttributes, context.httpResponse(), null);
clearAttributes(executionAttributes);
}
// 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 static void onUserAgentHeaderAvailable(Span span, ExecutionAttributes request) {
List<String> userAgent =
AwsSdkInstrumenterFactory.attributesExtractor.requestHeader(request, "User-Agent");
if (!userAgent.isEmpty()) {
span.setAttribute(SemanticAttributes.HTTP_USER_AGENT, userAgent.get(0));
}
}
private void onSdkResponse(
@ -159,8 +155,8 @@ final class TracingExecutionInterceptor implements ExecutionInterceptor {
public void onExecutionFailure(
Context.FailedExecution context, ExecutionAttributes executionAttributes) {
io.opentelemetry.context.Context otelContext = getContext(executionAttributes);
instrumenter.end(otelContext, executionAttributes, null, context.exception());
clearAttributes(executionAttributes);
tracer.endExceptionally(otelContext, context.exception());
}
private static void clearAttributes(ExecutionAttributes executionAttributes) {
@ -170,13 +166,14 @@ final class TracingExecutionInterceptor implements ExecutionInterceptor {
}
executionAttributes.putAttribute(CONTEXT_ATTRIBUTE, null);
executionAttributes.putAttribute(AWS_SDK_REQUEST_ATTRIBUTE, null);
executionAttributes.putAttribute(SDK_HTTP_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) {
static io.opentelemetry.context.Context getContext(ExecutionAttributes attributes) {
return attributes.getAttribute(CONTEXT_ATTRIBUTE);
}
}