Added dynamodb instrumenter for aws v1_11 sdk (#12756)

Co-authored-by: Lauri Tulmin <ltulmin@splunk.com>
This commit is contained in:
Alex Kats 2024-11-29 22:18:27 -05:00 committed by GitHub
parent 7c82dc4366
commit 6fa9553674
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 118 additions and 32 deletions

View File

@ -186,6 +186,18 @@ final class AwsSdkInstrumenterFactory {
true); true);
} }
Instrumenter<Request<?>, Response<?>> dynamoDbInstrumenter() {
DynamoDbAttributesExtractor dynamoDbAttributesExtractor = new DynamoDbAttributesExtractor();
return createInstrumenter(
openTelemetry,
spanName,
SpanKindExtractor.alwaysClient(),
attributesExtractors(),
singletonList(dynamoDbAttributesExtractor),
true);
}
private static <REQUEST, RESPONSE> Instrumenter<REQUEST, RESPONSE> createInstrumenter( private static <REQUEST, RESPONSE> Instrumenter<REQUEST, RESPONSE> createInstrumenter(
OpenTelemetry openTelemetry, OpenTelemetry openTelemetry,
SpanNameExtractor<REQUEST> spanNameExtractor, SpanNameExtractor<REQUEST> spanNameExtractor,

View File

@ -49,6 +49,7 @@ public class AwsSdkTelemetry {
private final Instrumenter<SqsReceiveRequest, Response<?>> consumerReceiveInstrumenter; private final Instrumenter<SqsReceiveRequest, Response<?>> consumerReceiveInstrumenter;
private final Instrumenter<SqsProcessRequest, Response<?>> consumerProcessInstrumenter; private final Instrumenter<SqsProcessRequest, Response<?>> consumerProcessInstrumenter;
private final Instrumenter<Request<?>, Response<?>> producerInstrumenter; private final Instrumenter<Request<?>, Response<?>> producerInstrumenter;
private final Instrumenter<Request<?>, Response<?>> dynamoDbInstrumenter;
AwsSdkTelemetry( AwsSdkTelemetry(
OpenTelemetry openTelemetry, OpenTelemetry openTelemetry,
@ -65,6 +66,7 @@ public class AwsSdkTelemetry {
consumerReceiveInstrumenter = instrumenterFactory.consumerReceiveInstrumenter(); consumerReceiveInstrumenter = instrumenterFactory.consumerReceiveInstrumenter();
consumerProcessInstrumenter = instrumenterFactory.consumerProcessInstrumenter(); consumerProcessInstrumenter = instrumenterFactory.consumerProcessInstrumenter();
producerInstrumenter = instrumenterFactory.producerInstrumenter(); producerInstrumenter = instrumenterFactory.producerInstrumenter();
dynamoDbInstrumenter = instrumenterFactory.dynamoDbInstrumenter();
} }
/** /**
@ -76,6 +78,7 @@ public class AwsSdkTelemetry {
requestInstrumenter, requestInstrumenter,
consumerReceiveInstrumenter, consumerReceiveInstrumenter,
consumerProcessInstrumenter, consumerProcessInstrumenter,
producerInstrumenter); producerInstrumenter,
dynamoDbInstrumenter);
} }
} }

View File

@ -0,0 +1,45 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.awssdk.v1_11;
import com.amazonaws.Request;
import com.amazonaws.Response;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.internal.AttributesExtractorUtil;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
public class DynamoDbAttributesExtractor implements AttributesExtractor<Request<?>, Response<?>> {
// copied from DbIncubatingAttributes
private static final AttributeKey<String> DB_SYSTEM = AttributeKey.stringKey("db.system");
// copied from AwsIncubatingAttributes
private static final AttributeKey<List<String>> AWS_DYNAMODB_TABLE_NAMES =
AttributeKey.stringArrayKey("aws.dynamodb.table_names");
// copied from DbIncubatingAttributes.DbSystemIncubatingValues
private static final String DYNAMODB = "dynamodb";
@Override
public void onStart(AttributesBuilder attributes, Context parentContext, Request<?> request) {
AttributesExtractorUtil.internalSet(attributes, DB_SYSTEM, DYNAMODB);
String tableName = RequestAccess.getTableName(request.getOriginalRequest());
AttributesExtractorUtil.internalSet(
attributes, AWS_DYNAMODB_TABLE_NAMES, Collections.singletonList(tableName));
}
@Override
public void onEnd(
AttributesBuilder attributes,
Context context,
Request<?> request,
@Nullable Response<?> response,
@Nullable Throwable error) {}
}

View File

@ -31,21 +31,27 @@ final class TracingRequestHandler extends RequestHandler2 {
ContextKey.named(TracingRequestHandler.class.getName() + ".Timer"); ContextKey.named(TracingRequestHandler.class.getName() + ".Timer");
private static final ContextKey<Boolean> REQUEST_SPAN_SUPPRESSED_KEY = private static final ContextKey<Boolean> REQUEST_SPAN_SUPPRESSED_KEY =
ContextKey.named(TracingRequestHandler.class.getName() + ".RequestSpanSuppressed"); ContextKey.named(TracingRequestHandler.class.getName() + ".RequestSpanSuppressed");
private static final String SEND_MESSAGE_REQUEST_CLASS =
"com.amazonaws.services.sqs.model.SendMessageRequest";
private static final String DYNAMODBV2_CLASS_PREFIX = "com.amazonaws.services.dynamodbv2.model.";
private final Instrumenter<Request<?>, Response<?>> requestInstrumenter; private final Instrumenter<Request<?>, Response<?>> requestInstrumenter;
private final Instrumenter<SqsReceiveRequest, Response<?>> consumerReceiveInstrumenter; private final Instrumenter<SqsReceiveRequest, Response<?>> consumerReceiveInstrumenter;
private final Instrumenter<SqsProcessRequest, Response<?>> consumerProcessInstrumenter; private final Instrumenter<SqsProcessRequest, Response<?>> consumerProcessInstrumenter;
private final Instrumenter<Request<?>, Response<?>> producerInstrumenter; private final Instrumenter<Request<?>, Response<?>> producerInstrumenter;
private final Instrumenter<Request<?>, Response<?>> dynamoDbInstrumenter;
TracingRequestHandler( TracingRequestHandler(
Instrumenter<Request<?>, Response<?>> requestInstrumenter, Instrumenter<Request<?>, Response<?>> requestInstrumenter,
Instrumenter<SqsReceiveRequest, Response<?>> consumerReceiveInstrumenter, Instrumenter<SqsReceiveRequest, Response<?>> consumerReceiveInstrumenter,
Instrumenter<SqsProcessRequest, Response<?>> consumerProcessInstrumenter, Instrumenter<SqsProcessRequest, Response<?>> consumerProcessInstrumenter,
Instrumenter<Request<?>, Response<?>> producerInstrumenter) { Instrumenter<Request<?>, Response<?>> producerInstrumenter,
Instrumenter<Request<?>, Response<?>> dynamoDbInstrumenter) {
this.requestInstrumenter = requestInstrumenter; this.requestInstrumenter = requestInstrumenter;
this.consumerReceiveInstrumenter = consumerReceiveInstrumenter; this.consumerReceiveInstrumenter = consumerReceiveInstrumenter;
this.consumerProcessInstrumenter = consumerProcessInstrumenter; this.consumerProcessInstrumenter = consumerProcessInstrumenter;
this.producerInstrumenter = producerInstrumenter; this.producerInstrumenter = producerInstrumenter;
this.dynamoDbInstrumenter = dynamoDbInstrumenter;
} }
@Override @Override
@ -151,14 +157,17 @@ final class TracingRequestHandler extends RequestHandler2 {
} }
return; return;
} }
instrumenter.end(context, request, response, error); instrumenter.end(context, request, response, error);
} }
private Instrumenter<Request<?>, Response<?>> getInstrumenter(Request<?> request) { private Instrumenter<Request<?>, Response<?>> getInstrumenter(Request<?> request) {
boolean isSqsProducer = String className = request.getOriginalRequest().getClass().getName();
"com.amazonaws.services.sqs.model.SendMessageRequest" if (className.startsWith(DYNAMODBV2_CLASS_PREFIX)) {
.equals(request.getOriginalRequest().getClass().getName()); return dynamoDbInstrumenter;
return isSqsProducer ? producerInstrumenter : requestInstrumenter; }
if (className.equals(SEND_MESSAGE_REQUEST_CLASS)) {
return producerInstrumenter;
}
return requestInstrumenter;
} }
} }

View File

@ -34,7 +34,6 @@ import io.opentelemetry.testing.internal.armeria.testing.junit5.server.mock.Mock
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -75,7 +74,7 @@ public abstract class AbstractBaseAwsClientTest {
String service, String service,
String operation, String operation,
String method, String method,
Map<String, String> additionalAttributes) List<AttributeAssertion> additionalAttributes)
throws Exception { throws Exception {
assertThat(response).isNotNull(); assertThat(response).isNotNull();
@ -113,8 +112,7 @@ public abstract class AbstractBaseAwsClientTest {
stringKey("aws.request_id"), v -> v.isInstanceOf(String.class))); stringKey("aws.request_id"), v -> v.isInstanceOf(String.class)));
} }
additionalAttributes.forEach( attributes.addAll(additionalAttributes);
(k, v) -> attributes.add(equalTo(stringKey(k), v)));
span.hasName(service + "." + operation) span.hasName(service + "." + operation)
.hasKind(operation.equals("SendMessage") ? PRODUCER : CLIENT) .hasKind(operation.equals("SendMessage") ? PRODUCER : CLIENT)

View File

@ -5,13 +5,22 @@
package io.opentelemetry.instrumentation.awssdk.v1_11; package io.opentelemetry.instrumentation.awssdk.v1_11;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
import static io.opentelemetry.semconv.incubating.AwsIncubatingAttributes.AWS_DYNAMODB_TABLE_NAMES;
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM;
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DbSystemIncubatingValues.DYNAMODB;
import static java.util.Collections.singletonList;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder; import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.model.CreateTableRequest; import com.amazonaws.services.dynamodbv2.model.CreateTableRequest;
import com.google.common.collect.ImmutableMap; import io.opentelemetry.sdk.testing.assertj.AttributeAssertion;
import io.opentelemetry.testing.internal.armeria.common.HttpResponse; import io.opentelemetry.testing.internal.armeria.common.HttpResponse;
import io.opentelemetry.testing.internal.armeria.common.HttpStatus; import io.opentelemetry.testing.internal.armeria.common.HttpStatus;
import io.opentelemetry.testing.internal.armeria.common.MediaType; import io.opentelemetry.testing.internal.armeria.common.MediaType;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
public abstract class AbstractDynamoDbClientTest extends AbstractBaseAwsClientTest { public abstract class AbstractDynamoDbClientTest extends AbstractBaseAwsClientTest {
@ -34,13 +43,14 @@ public abstract class AbstractDynamoDbClientTest extends AbstractBaseAwsClientTe
server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, "")); server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, ""));
List<AttributeAssertion> additionalAttributes =
Arrays.asList(
equalTo(stringKey("aws.table.name"), "sometable"),
equalTo(DB_SYSTEM, DYNAMODB),
equalTo(AWS_DYNAMODB_TABLE_NAMES, singletonList("sometable")));
Object response = client.createTable(new CreateTableRequest("sometable", null)); Object response = client.createTable(new CreateTableRequest("sometable", null));
assertRequestWithMockedResponse( assertRequestWithMockedResponse(
response, response, client, "DynamoDBv2", "CreateTable", "POST", additionalAttributes);
client,
"DynamoDBv2",
"CreateTable",
"POST",
ImmutableMap.of("aws.table.name", "sometable"));
} }
} }

View File

@ -41,6 +41,6 @@ public abstract class AbstractEc2ClientTest extends AbstractBaseAwsClientTest {
Object response = client.allocateAddress(); Object response = client.allocateAddress();
assertRequestWithMockedResponse( assertRequestWithMockedResponse(
response, client, "EC2", "AllocateAddress", "POST", Collections.emptyMap()); response, client, "EC2", "AllocateAddress", "POST", Collections.emptyList());
} }
} }

View File

@ -5,14 +5,18 @@
package io.opentelemetry.instrumentation.awssdk.v1_11; package io.opentelemetry.instrumentation.awssdk.v1_11;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
import static java.util.Collections.singletonList;
import com.amazonaws.services.kinesis.AmazonKinesis; import com.amazonaws.services.kinesis.AmazonKinesis;
import com.amazonaws.services.kinesis.AmazonKinesisClientBuilder; import com.amazonaws.services.kinesis.AmazonKinesisClientBuilder;
import com.amazonaws.services.kinesis.model.DeleteStreamRequest; import com.amazonaws.services.kinesis.model.DeleteStreamRequest;
import com.google.common.collect.ImmutableMap; import io.opentelemetry.sdk.testing.assertj.AttributeAssertion;
import io.opentelemetry.testing.internal.armeria.common.HttpResponse; import io.opentelemetry.testing.internal.armeria.common.HttpResponse;
import io.opentelemetry.testing.internal.armeria.common.HttpStatus; import io.opentelemetry.testing.internal.armeria.common.HttpStatus;
import io.opentelemetry.testing.internal.armeria.common.MediaType; import io.opentelemetry.testing.internal.armeria.common.MediaType;
import java.util.Map; import java.util.List;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
@ -42,7 +46,9 @@ public abstract class AbstractKinesisClientTest extends AbstractBaseAwsClientTes
server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, "")); server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, ""));
Map<String, String> additionalAttributes = ImmutableMap.of("aws.stream.name", "somestream"); List<AttributeAssertion> additionalAttributes =
singletonList(equalTo(stringKey("aws.stream.name"), "somestream"));
Object response = call.apply(client); Object response = call.apply(client);
assertRequestWithMockedResponse( assertRequestWithMockedResponse(
response, client, "Kinesis", operation, "POST", additionalAttributes); response, client, "Kinesis", operation, "POST", additionalAttributes);

View File

@ -42,6 +42,6 @@ public abstract class AbstractRdsClientTest extends AbstractBaseAwsClientTest {
Object response = client.deleteOptionGroup(new DeleteOptionGroupRequest()); Object response = client.deleteOptionGroup(new DeleteOptionGroupRequest());
assertRequestWithMockedResponse( assertRequestWithMockedResponse(
response, client, "RDS", "DeleteOptionGroup", "POST", Collections.emptyMap()); response, client, "RDS", "DeleteOptionGroup", "POST", Collections.emptyList());
} }
} }

View File

@ -17,6 +17,7 @@ import static io.opentelemetry.semconv.UrlAttributes.URL_FULL;
import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_METHOD; import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_METHOD;
import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SERVICE; import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SERVICE;
import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SYSTEM; import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SYSTEM;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable; import static org.assertj.core.api.Assertions.catchThrowable;
@ -27,14 +28,14 @@ import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.retry.PredefinedRetryPolicies; import com.amazonaws.retry.PredefinedRetryPolicies;
import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder; import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.google.common.collect.ImmutableMap;
import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.Span;
import io.opentelemetry.sdk.testing.assertj.AttributeAssertion;
import io.opentelemetry.sdk.trace.data.StatusData; import io.opentelemetry.sdk.trace.data.StatusData;
import io.opentelemetry.testing.internal.armeria.common.HttpResponse; import io.opentelemetry.testing.internal.armeria.common.HttpResponse;
import io.opentelemetry.testing.internal.armeria.common.HttpStatus; import io.opentelemetry.testing.internal.armeria.common.HttpStatus;
import io.opentelemetry.testing.internal.armeria.common.MediaType; import io.opentelemetry.testing.internal.armeria.common.MediaType;
import java.time.Duration; import java.time.Duration;
import java.util.Map; import java.util.List;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -60,7 +61,7 @@ public abstract class AbstractS3ClientTest extends AbstractBaseAwsClientTest {
String operation, String operation,
String method, String method,
Function<AmazonS3, Object> call, Function<AmazonS3, Object> call,
Map<String, String> additionalAttributes) List<AttributeAssertion> additionalAttributes)
throws Exception { throws Exception {
AmazonS3 client = AmazonS3 client =
@ -82,12 +83,12 @@ public abstract class AbstractS3ClientTest extends AbstractBaseAwsClientTest {
"CreateBucket", "CreateBucket",
"PUT", "PUT",
(Function<AmazonS3, Object>) c -> c.createBucket("testbucket"), (Function<AmazonS3, Object>) c -> c.createBucket("testbucket"),
ImmutableMap.of("aws.bucket.name", "testbucket")), singletonList(equalTo(stringKey("aws.bucket.name"), "testbucket"))),
Arguments.of( Arguments.of(
"GetObject", "GetObject",
"GET", "GET",
(Function<AmazonS3, Object>) c -> c.getObject("someBucket", "someKey"), (Function<AmazonS3, Object>) c -> c.getObject("someBucket", "someKey"),
ImmutableMap.of("aws.bucket.name", "someBucket"))); singletonList(equalTo(stringKey("aws.bucket.name"), "someBucket"))));
} }
@Test @Test

View File

@ -5,16 +5,18 @@
package io.opentelemetry.instrumentation.awssdk.v1_11; package io.opentelemetry.instrumentation.awssdk.v1_11;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME;
import static java.util.Collections.singletonList;
import com.amazonaws.services.sns.AmazonSNS; import com.amazonaws.services.sns.AmazonSNS;
import com.amazonaws.services.sns.AmazonSNSClientBuilder; import com.amazonaws.services.sns.AmazonSNSClientBuilder;
import com.amazonaws.services.sns.model.PublishRequest; import com.amazonaws.services.sns.model.PublishRequest;
import com.google.common.collect.ImmutableMap; import io.opentelemetry.sdk.testing.assertj.AttributeAssertion;
import io.opentelemetry.testing.internal.armeria.common.HttpResponse; import io.opentelemetry.testing.internal.armeria.common.HttpResponse;
import io.opentelemetry.testing.internal.armeria.common.HttpStatus; import io.opentelemetry.testing.internal.armeria.common.HttpStatus;
import io.opentelemetry.testing.internal.armeria.common.MediaType; import io.opentelemetry.testing.internal.armeria.common.MediaType;
import java.util.Map; import java.util.List;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
@ -52,8 +54,8 @@ public abstract class AbstractSnsClientTest extends AbstractBaseAwsClientTest {
server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, body)); server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, body));
Map<String, String> additionalAttributes = List<AttributeAssertion> additionalAttributes =
ImmutableMap.of(MESSAGING_DESTINATION_NAME.toString(), "somearn"); singletonList(equalTo(MESSAGING_DESTINATION_NAME, "somearn"));
Object response = call.apply(client); Object response = call.apply(client);
assertRequestWithMockedResponse( assertRequestWithMockedResponse(