Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AWS SDK instrumentation - DynamoDB attributes #2262

Merged
merged 7 commits into from Feb 22, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
AWS SDK instrumentation - DynamoDB attributes
  • Loading branch information
kuba-wu committed Feb 18, 2021
commit fc1248532579fcb49036db2a1a887232649d9609
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ dependencies {

library group: 'software.amazon.awssdk', name: 'aws-core', version: '2.2.0'

implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.2'
This conversation was marked as resolved.
Show resolved Hide resolved
testImplementation project(':instrumentation:aws-sdk:aws-sdk-2.2:testing')
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,29 @@

package io.opentelemetry.instrumentation.awssdk.v2_2;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.lang.reflect.Method;
import java.util.List;
import java.util.stream.Collectors;
import org.checkerframework.checker.nullness.qual.Nullable;
import software.amazon.awssdk.core.SdkPojo;
import software.amazon.awssdk.core.SdkRequest;
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute;

final class DbRequestDecorator implements SdkRequestDecorator {

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

static {
OBJECT_MAPPER.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
}

@Override
public void decorate(Span span, SdkRequest sdkRequest, ExecutionAttributes attributes) {

Expand All @@ -26,5 +41,58 @@ public void decorate(Span span, SdkRequest sdkRequest, ExecutionAttributes attri
if (operation != null) {
span.setAttribute(SemanticAttributes.DB_OPERATION, operation);
}

DynamoDbRequest request = DynamoDbRequest.ofSdkRequest(sdkRequest);
if (request != null) {
mapRequestFields(request, sdkRequest, span);
}
}

private final void mapRequestFields(DynamoDbRequest request, SdkRequest sdkRequest, Span span) {
for (FieldMapping fieldMapping : request.fields()) {
mapRequestField(fieldMapping, sdkRequest, span);
}
}

private final void mapRequestField(FieldMapping fieldMapping, SdkRequest sdkRequest, Span span) {
// traverse path
String[] path = fieldMapping.getFields();
Object target = sdkRequest;
for (int i = 0; i < path.length && target != null; i++) {
target = next(target, path[i]);
}
if (target != null) {
String value = serialize(target);
if (value != null && !value.isEmpty()) {
span.setAttribute(fieldMapping.getAttribute(), value);
}
}
}

@Nullable
private String serialize(Object target) {
if (target instanceof SdkPojo) {
try {
return OBJECT_MAPPER.writeValueAsString(target);
} catch (JsonProcessingException e) {
return null;
}
}
if (target instanceof List) {
return ((List<Object>) target).stream().map(this::serialize).collect(Collectors.joining());
}
// simple type
return target.toString();
}

@Nullable
private Object next(Object current, String fieldName) {
This conversation was marked as resolved.
Show resolved Hide resolved
try {
Method method = current.getClass().getMethod(fieldName);
return method.invoke(current);
} catch (Exception e) {
// ignore
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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.FieldMapping.of;

import org.checkerframework.checker.nullness.qual.Nullable;
import software.amazon.awssdk.core.SdkRequest;

public enum DynamoDbRequest {
This conversation was marked as resolved.
Show resolved Hide resolved
CreateTable(
"CreateTableRequest",
of("awssdk.global_secondary_indexes", "globalSecondaryIndexes"),
of("awssdk.local_secondary_indexes", "localSecondaryIndexes"),
of(
"awssdk.provisioned_throughput.read_capacity_units",
"provisionedThroughput.readCapacityUnits"),
of(
"awssdk.provisioned_throughput.write_capacity_units",
"provisionedThroughput.writeCapacityUnits"));

private final String requestClass;
private final FieldMapping[] fields;

DynamoDbRequest(String requestClass, FieldMapping... fields) {
this.requestClass = requestClass;
this.fields = fields;
}

@Nullable
static DynamoDbRequest ofSdkRequest(SdkRequest request) {
// exact request class should be 1st level child of request type
String typeName = request.getClass().getSimpleName();
for (DynamoDbRequest type : values()) {
if (type.requestClass.equals(typeName)) {
return type;
}
}
return null;
}

public FieldMapping[] fields() {
return fields;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.awssdk.v2_2;

class FieldMapping {
static FieldMapping of(String attribute, String fieldPath) {
return new FieldMapping(attribute, fieldPath);
}

FieldMapping(String attribute, String fieldPath) {
this.attribute = attribute;
this.fieldPath = fieldPath;
this.fields = fieldPath.split("\\.");
mateuszrzeszutek marked this conversation as resolved.
Show resolved Hide resolved
}

String getAttribute() {
return attribute;
}

String[] getFields() {
return fields;
}
This conversation was marked as resolved.
Show resolved Hide resolved

private final String attribute;
private final String fieldPath;
This conversation was marked as resolved.
Show resolved Hide resolved
private final String[] fields;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import static com.google.common.collect.ImmutableMap.of
import static io.opentelemetry.api.trace.SpanKind.CLIENT
import static io.opentelemetry.instrumentation.test.server.http.TestHttpServer.httpServer

import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
import io.opentelemetry.instrumentation.test.InstrumentationSpecification
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
import java.time.Duration
import java.util.concurrent.Future
import java.util.concurrent.atomic.AtomicReference
Expand All @@ -29,6 +29,9 @@ import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest
import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest
import software.amazon.awssdk.services.dynamodb.model.DeleteTableRequest
import software.amazon.awssdk.services.dynamodb.model.GetItemRequest
import software.amazon.awssdk.services.dynamodb.model.GlobalSecondaryIndex
import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement
import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest
import software.amazon.awssdk.services.dynamodb.model.QueryRequest
import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest
Expand Down Expand Up @@ -97,8 +100,11 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification {
expect:
response != null
response.class.simpleName.startsWith(operation)
assertDynamoDbRequest(service, operation, path, method, requestId)

if (operation == "CreateTable") {
assertCreateTableRequest(path, method, requestId)
} else {
assertDynamoDbRequest(service, operation, path, method, requestId)
}
where:
[service, operation, method, path, requestId, builder, call] << dynamoDbRequestDataTable(DynamoDbClient.builder())
}
Expand All @@ -120,12 +126,52 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification {

expect:
response != null
assertDynamoDbRequest(service, operation, path, method, requestId)
if (operation == "CreateTable") {
assertCreateTableRequest(path, method, requestId)
} else {
assertDynamoDbRequest(service, operation, path, method, requestId)
}

where:
[service, operation, method, path, requestId, builder, call] << dynamoDbRequestDataTable(DynamoDbAsyncClient.builder())
}

def assertCreateTableRequest(path, method, requestId) {
assertTraces(1) {
trace(0, 1) {
span(0) {
name "DynamoDb.CreateTable"
This conversation was marked as resolved.
Show resolved Hide resolved
kind CLIENT
errored false
hasNoParent()
attributes {
"${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP"
"${SemanticAttributes.NET_PEER_NAME.key}" "localhost"
"${SemanticAttributes.NET_PEER_PORT.key}" server.address.port
"${SemanticAttributes.HTTP_URL.key}" { it.startsWith("${server.address}${path}") }
"${SemanticAttributes.HTTP_METHOD.key}" "$method"
"${SemanticAttributes.HTTP_STATUS_CODE.key}" 200
"${SemanticAttributes.HTTP_USER_AGENT.key}" { it.startsWith("aws-sdk-java/") }
"${SemanticAttributes.HTTP_FLAVOR.key}" "1.1"
"aws.service" "DynamoDb"
"aws.operation" "CreateTable"
"aws.agent" "java-aws-sdk"
"aws.requestId" "$requestId"
"aws.table.name" "sometable"
"${SemanticAttributes.DB_SYSTEM.key}" "dynamodb"
"${SemanticAttributes.DB_NAME.key}" "sometable"
"${SemanticAttributes.DB_OPERATION.key}" "CreateTable"
"awssdk.global_secondary_indexes" "{\"indexName\":\"globalIndex\",\"keySchema\":[{\"attributeName\":\"attribute\",\"keyType\":null}],\"projection\":null,\"provisionedThroughput\":{\"readCapacityUnits\":10,\"writeCapacityUnits\":12}}"
"awssdk.provisioned_throughput.read_capacity_units" "1"
"awssdk.provisioned_throughput.write_capacity_units" "1"
}
}
}
}
server.lastRequest.headers.get("X-Amzn-Trace-Id") != null
server.lastRequest.headers.get("traceparent") == null
}

def assertDynamoDbRequest(service, operation, path, method, requestId) {
assertTraces(1) {
trace(0, 1) {
Expand Down Expand Up @@ -162,7 +208,7 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification {
static dynamoDbRequestDataTable(client) {
[
["DynamoDb", "CreateTable", "POST", "/", "UNKNOWN", client,
{ c -> c.createTable(CreateTableRequest.builder().tableName("sometable").build()) }],
{ c -> c.createTable(createTableRequest()) }],
["DynamoDb", "DeleteItem", "POST", "/", "UNKNOWN", client,
{ c -> c.deleteItem(DeleteItemRequest.builder().tableName("sometable").key(of("anotherKey", val("value"), "key", val("value"))).conditionExpression("property in (:one :two)").build()) }],
["DynamoDb", "DeleteTable", "POST", "/", "UNKNOWN", client,
Expand All @@ -178,6 +224,32 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification {
]
}

static CreateTableRequest createTableRequest() {
return CreateTableRequest.builder()
.tableName("sometable")
.globalSecondaryIndexes(Arrays.asList(
GlobalSecondaryIndex.builder()
.indexName("globalIndex")
.keySchema(
KeySchemaElement.builder()
.attributeName("attribute")
.build())
.provisionedThroughput(
ProvisionedThroughput.builder()
.readCapacityUnits(10)
.writeCapacityUnits(12)
.build()
)
.build()))
.provisionedThroughput(
ProvisionedThroughput.builder()
.readCapacityUnits(1)
.writeCapacityUnits(1)
.build()
)
.build()
}

static val(String value) {
return AttributeValue.builder().s(value).build()
}
Expand Down