From dcdb060f912d67e618e09b3a88cccbda8d93bbc3 Mon Sep 17 00:00:00 2001 From: Christopher Radek Date: Mon, 21 Jan 2019 11:00:42 -0800 Subject: [PATCH 1/3] feat: adds aws-xray-recorder-sdk-aws-sdk-core package for shared logic between aws sdk v1 and v2 recorders --- aws-xray-recorder-sdk-aws-sdk-core/pom.xml | 15 + .../handlers/config/AWSOperationHandler.java | 58 ++ .../config/AWSOperationHandlerManifest.java | 14 + .../AWSOperationHandlerRequestDescriptor.java | 55 ++ ...AWSOperationHandlerResponseDescriptor.java | 55 ++ .../handlers/config/AWSServiceHandler.java | 14 + .../config/AWSServiceHandlerManifest.java | 14 + .../config/AWSServiceHandlerManifestTest.java | 78 ++ .../config/OperationParameterWhitelist.json | 733 ++++++++++++++++++ 9 files changed, 1036 insertions(+) create mode 100644 aws-xray-recorder-sdk-aws-sdk-core/pom.xml create mode 100644 aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandler.java create mode 100644 aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandlerManifest.java create mode 100644 aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandlerRequestDescriptor.java create mode 100644 aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandlerResponseDescriptor.java create mode 100644 aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/handlers/config/AWSServiceHandler.java create mode 100644 aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/handlers/config/AWSServiceHandlerManifest.java create mode 100644 aws-xray-recorder-sdk-aws-sdk-core/src/test/java/com/amazonaws/xray/handlers/config/AWSServiceHandlerManifestTest.java create mode 100644 aws-xray-recorder-sdk-aws-sdk-core/src/test/resources/com/amazonaws/xray/handlers/config/OperationParameterWhitelist.json diff --git a/aws-xray-recorder-sdk-aws-sdk-core/pom.xml b/aws-xray-recorder-sdk-aws-sdk-core/pom.xml new file mode 100644 index 00000000..09439e9d --- /dev/null +++ b/aws-xray-recorder-sdk-aws-sdk-core/pom.xml @@ -0,0 +1,15 @@ + + + + aws-xray-recorder-sdk-pom + com.amazonaws + 2.1.0 + + 4.0.0 + + aws-xray-recorder-sdk-aws-sdk-core + + + \ No newline at end of file diff --git a/aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandler.java b/aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandler.java new file mode 100644 index 00000000..c69ab14c --- /dev/null +++ b/aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandler.java @@ -0,0 +1,58 @@ +package com.amazonaws.xray.handlers.config; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.HashMap; +import java.util.HashSet; + +public class AWSOperationHandler { + @JsonProperty + private HashMap requestDescriptors; + + @JsonProperty + private HashSet requestParameters; + + @JsonProperty + private HashMap responseDescriptors; + + @JsonProperty + private HashSet responseParameters; + + @JsonProperty + private String requestIdHeader; + + /** + * @return the requestKeys + */ + public HashMap getRequestDescriptors() { + return requestDescriptors; + } + + /** + * @return the requestParameters + */ + public HashSet getRequestParameters() { + return requestParameters; + } + + /** + * @return the responseDescriptors + */ + public HashMap getResponseDescriptors() { + return responseDescriptors; + } + + /** + * @return the responseParameters + */ + public HashSet getResponseParameters() { + return responseParameters; + } + + /** + * @return the requestIdHeader + */ + public String getRequestIdHeader() { + return requestIdHeader; + } +} diff --git a/aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandlerManifest.java b/aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandlerManifest.java new file mode 100644 index 00000000..b3791986 --- /dev/null +++ b/aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandlerManifest.java @@ -0,0 +1,14 @@ +package com.amazonaws.xray.handlers.config; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Map; + +public class AWSOperationHandlerManifest { + @JsonProperty + private Map operations; + + public AWSOperationHandler getOperationHandler(String operationRequestClassName) { + return operations.get(operationRequestClassName); + } +} diff --git a/aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandlerRequestDescriptor.java b/aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandlerRequestDescriptor.java new file mode 100644 index 00000000..622ee5f7 --- /dev/null +++ b/aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandlerRequestDescriptor.java @@ -0,0 +1,55 @@ +package com.amazonaws.xray.handlers.config; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class AWSOperationHandlerRequestDescriptor { + @JsonProperty + private String renameTo; + + @JsonProperty + private boolean map = false; + + @JsonProperty + private boolean list = false; + + @JsonProperty + private boolean getKeys = false; + + @JsonProperty + private boolean getCount = false; + + /** + * @return the renameTo + */ + public String getRenameTo() { + return renameTo; + } + + /** + * @return the map + */ + public boolean isMap() { + return map; + } + + /** + * @return the list + */ + public boolean isList() { + return list; + } + + /** + * @return the getCount + */ + public boolean shouldGetCount() { + return getCount; + } + + /** + * @return the getKeys + */ + public boolean shouldGetKeys() { + return getKeys; + } +} diff --git a/aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandlerResponseDescriptor.java b/aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandlerResponseDescriptor.java new file mode 100644 index 00000000..8f6a6434 --- /dev/null +++ b/aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandlerResponseDescriptor.java @@ -0,0 +1,55 @@ +package com.amazonaws.xray.handlers.config; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class AWSOperationHandlerResponseDescriptor { + @JsonProperty + private String renameTo; + + @JsonProperty + private boolean map = false; + + @JsonProperty + private boolean list = false; + + @JsonProperty + private boolean getKeys = false; + + @JsonProperty + private boolean getCount = false; + + /** + * @return the renameTo + */ + public String getRenameTo() { + return renameTo; + } + + /** + * @return the map + */ + public boolean isMap() { + return map; + } + + /** + * @return the list + */ + public boolean isList() { + return list; + } + + /** + * @return the getCount + */ + public boolean shouldGetCount() { + return getCount; + } + + /** + * @return the getKeys + */ + public boolean shouldGetKeys() { + return getKeys; + } +} diff --git a/aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/handlers/config/AWSServiceHandler.java b/aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/handlers/config/AWSServiceHandler.java new file mode 100644 index 00000000..75d075f5 --- /dev/null +++ b/aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/handlers/config/AWSServiceHandler.java @@ -0,0 +1,14 @@ +package com.amazonaws.xray.handlers.config; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Map; + +public class AWSServiceHandler { + @JsonProperty + private Map operations; + + public AWSOperationHandler getOperationHandler(String operationName) { + return operations.get(operationName); + } +} diff --git a/aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/handlers/config/AWSServiceHandlerManifest.java b/aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/handlers/config/AWSServiceHandlerManifest.java new file mode 100644 index 00000000..4024d2fc --- /dev/null +++ b/aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/handlers/config/AWSServiceHandlerManifest.java @@ -0,0 +1,14 @@ +package com.amazonaws.xray.handlers.config; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Map; + +public class AWSServiceHandlerManifest { + @JsonProperty + private Map services; + + public AWSOperationHandlerManifest getOperationHandlerManifest(String serviceName) { + return services.get(serviceName); + } +} diff --git a/aws-xray-recorder-sdk-aws-sdk-core/src/test/java/com/amazonaws/xray/handlers/config/AWSServiceHandlerManifestTest.java b/aws-xray-recorder-sdk-aws-sdk-core/src/test/java/com/amazonaws/xray/handlers/config/AWSServiceHandlerManifestTest.java new file mode 100644 index 00000000..8daef3ac --- /dev/null +++ b/aws-xray-recorder-sdk-aws-sdk-core/src/test/java/com/amazonaws/xray/handlers/config/AWSServiceHandlerManifestTest.java @@ -0,0 +1,78 @@ +package com.amazonaws.xray.handlers.config; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import org.junit.Assert; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.mockito.runners.MockitoJUnitRunner; + +import java.net.URL; +import java.util.HashSet; + +@FixMethodOrder(MethodSorters.JVM) +@RunWith(MockitoJUnitRunner.class) +public class AWSServiceHandlerManifestTest { + private static URL testParameterWhitelist = AWSServiceHandlerManifestTest.class.getResource("/com/amazonaws/xray/handlers/config/OperationParameterWhitelist.json"); + + private ObjectMapper mapper = new ObjectMapper() + .setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(JsonParser.Feature.ALLOW_COMMENTS, true); + + @Test + public void testOperationRequestDescriptors() throws Exception { + AWSServiceHandlerManifest serviceManifest = mapper.readValue(testParameterWhitelist, AWSServiceHandlerManifest.class); + AWSOperationHandlerManifest operationManifest = serviceManifest.getOperationHandlerManifest("DynamoDb"); + AWSOperationHandler operationHandler = operationManifest.getOperationHandler("BatchGetItem"); + AWSOperationHandlerRequestDescriptor descriptor = operationHandler.getRequestDescriptors().get("RequestItems"); + + Assert.assertEquals(true, descriptor.isMap()); + Assert.assertEquals(false, descriptor.isList()); + Assert.assertEquals(true, descriptor.shouldGetKeys()); + Assert.assertEquals(false, descriptor.shouldGetCount()); + Assert.assertEquals("table_names", descriptor.getRenameTo()); + } + + @Test + public void testOperationRequestParameters() throws Exception { + AWSServiceHandlerManifest serviceManifest = mapper.readValue(testParameterWhitelist, AWSServiceHandlerManifest.class); + AWSOperationHandlerManifest operationManifest = serviceManifest.getOperationHandlerManifest("DynamoDb"); + AWSOperationHandler operationHandler = operationManifest.getOperationHandler("CreateTable"); + HashSet parameters = operationHandler.getRequestParameters(); + + Assert.assertEquals(true, parameters.contains("GlobalSecondaryIndexes")); + Assert.assertEquals(true, parameters.contains("LocalSecondaryIndexes")); + Assert.assertEquals(true, parameters.contains("ProvisionedThroughput")); + Assert.assertEquals(true, parameters.contains("TableName")); + } + + @Test + public void testOperationResponseDescriptors() throws Exception { + AWSServiceHandlerManifest serviceManifest = mapper.readValue(testParameterWhitelist, AWSServiceHandlerManifest.class); + AWSOperationHandlerManifest operationManifest = serviceManifest.getOperationHandlerManifest("DynamoDb"); + AWSOperationHandler operationHandler = operationManifest.getOperationHandler("ListTables"); + AWSOperationHandlerResponseDescriptor descriptor = operationHandler.getResponseDescriptors().get("TableNames"); + + Assert.assertEquals(false, descriptor.isMap()); + Assert.assertEquals(true, descriptor.isList()); + Assert.assertEquals(false, descriptor.shouldGetKeys()); + Assert.assertEquals(true, descriptor.shouldGetCount()); + Assert.assertEquals("table_count", descriptor.getRenameTo()); + } + + @Test + public void testOperationResponseParameters() throws Exception { + AWSServiceHandlerManifest serviceManifest = mapper.readValue(testParameterWhitelist, AWSServiceHandlerManifest.class); + AWSOperationHandlerManifest operationManifest = serviceManifest.getOperationHandlerManifest("DynamoDb"); + AWSOperationHandler operationHandler = operationManifest.getOperationHandler("DeleteItem"); + HashSet parameters = operationHandler.getResponseParameters(); + + Assert.assertEquals(true, parameters.contains("ConsumedCapacity")); + Assert.assertEquals(true, parameters.contains("ItemCollectionMetrics")); + } +} diff --git a/aws-xray-recorder-sdk-aws-sdk-core/src/test/resources/com/amazonaws/xray/handlers/config/OperationParameterWhitelist.json b/aws-xray-recorder-sdk-aws-sdk-core/src/test/resources/com/amazonaws/xray/handlers/config/OperationParameterWhitelist.json new file mode 100644 index 00000000..4926cdb9 --- /dev/null +++ b/aws-xray-recorder-sdk-aws-sdk-core/src/test/resources/com/amazonaws/xray/handlers/config/OperationParameterWhitelist.json @@ -0,0 +1,733 @@ +{ + "services": { + "DynamoDb": { + "operations": { + "BatchGetItem": { + "request_descriptors": { + "RequestItems": { + "map": true, + "get_keys": true, + "rename_to": "table_names" + } + }, + "response_parameters": [ + "ConsumedCapacity" + ] + }, + "BatchWriteItem": { + "request_descriptors": { + "RequestItems": { + "map": true, + "get_keys": true, + "rename_to": "table_names" + } + }, + "response_parameters": [ + "ConsumedCapacity", + "ItemCollectionMetrics" + ] + }, + "CreateTable": { + "request_parameters": [ + "GlobalSecondaryIndexes", + "LocalSecondaryIndexes", + "ProvisionedThroughput", + "TableName" + ] + }, + "DeleteItem": { + "request_parameters": [ + "TableName" + ], + "response_parameters": [ + "ConsumedCapacity", + "ItemCollectionMetrics" + ] + }, + "DeleteTable": { + "request_parameters": [ + "TableName" + ] + }, + "DescribeTable": { + "request_parameters": [ + "TableName" + ] + }, + "GetItem": { + "request_parameters": [ + "ConsistentRead", + "ProjectionExpression", + "TableName" + ], + "response_parameters": [ + "ConsumedCapacity" + ] + }, + "ListTables": { + "request_parameters": [ + "ExclusiveStartTableName", + "Limit" + ], + "response_descriptors": { + "TableNames": { + "list": true, + "get_count": true, + "rename_to": "table_count" + } + } + }, + "PutItem": { + "request_parameters": [ + "TableName" + ], + "response_parameters": [ + "ConsumedCapacity", + "ItemCollectionMetrics" + ] + }, + "Query": { + "request_parameters": [ + "AttributesToGet", + "ConsistentRead", + "IndexName", + "Limit", + "ProjectionExpression", + "ScanIndexForward", + "Select", + "TableName" + ], + "response_parameters": [ + "ConsumedCapacity" + ] + }, + "Scan": { + "request_parameters": [ + "AttributesToGet", + "ConsistentRead", + "IndexName", + "Limit", + "ProjectionExpression", + "Segment", + "Select", + "TableName", + "TotalSegments" + ], + "response_parameters": [ + "ConsumedCapacity", + "Count", + "ScannedCount" + ] + }, + "UpdateItem": { + "request_parameters": [ + "TableName" + ], + "response_parameters": [ + "ConsumedCapacity", + "ItemCollectionMetrics" + ] + }, + "UpdateTable": { + "request_parameters": [ + "AttributeDefinitions", + "GlobalSecondaryIndexUpdates", + "ProvisionedThroughput", + "TableName" + ] + } + } + }, + "SQS": { + "operations": { + "AddPermission": { + "request_parameters": [ + "Label", + "QueueUrl" + ] + }, + "ChangeMessageVisibility": { + "request_parameters": [ + "QueueUrl", + "VisibilityTimeout" + ] + }, + "ChangeMessageVisibilityBatch": { + "request_parameters": [ + "QueueUrl" + ], + "response_parameters": [ + "Failed" + ] + }, + "CreateQueue": { + "request_parameters": [ + "Attributes", + "QueueName" + ] + }, + "DeleteMessage": { + "request_parameters": [ + "QueueUrl" + ] + }, + "DeleteMessageBatch": { + "request_parameters": [ + "QueueUrl" + ], + "response_parameters": [ + "Failed" + ] + }, + "DeleteQueue": { + "request_parameters": [ + "QueueUrl" + ] + }, + "GetQueueAttributes": { + "request_parameters": [ + "QueueUrl" + ], + "response_parameters": [ + "Attributes" + ] + }, + "GetQueueUrl": { + "request_parameters": [ + "QueueName", + "QueueOwnerAWSAccountId" + ], + "response_parameters": [ + "QueueUrl" + ] + }, + "ListDeadLetterSourceQueues": { + "request_parameters": [ + "QueueUrl" + ], + "response_parameters": [ + "QueueUrls" + ] + }, + "ListQueues": { + "request_parameters": [ + "QueueNamePrefix" + ], + "response_descriptors": { + "QueueUrls": { + "list": true, + "get_count": true, + "rename_to": "queue_count" + } + } + }, + "PurgeQueue": { + "request_parameters": [ + "QueueUrl" + ] + }, + "ReceiveMessage": { + "request_parameters": [ + "AttributeNames", + "MaxNumberOfMessages", + "MessageAttributeNames", + "QueueUrl", + "VisibilityTimeout", + "WaitTimeSeconds" + ], + "response_descriptors": { + "Messages": { + "list": true, + "get_count": true, + "rename_to": "message_count" + } + } + }, + "RemovePermission": { + "request_parameters": [ + "QueueUrl" + ] + }, + "SendMessage": { + "request_parameters": [ + "DelaySeconds", + "QueueUrl" + ], + "request_descriptors": { + "MessageAttributes": { + "map": true, + "get_keys": true, + "rename_to": "message_attribute_names" + } + }, + "response_parameters": [ + "MessageId" + ] + }, + "SendMessageBatch": { + "request_parameters": [ + "QueueUrl" + ], + "request_descriptors": { + "Entries": { + "list": true, + "get_count": true, + "rename_to": "message_count" + } + }, + "response_descriptors": { + "Failed": { + "list": true, + "get_count": true, + "rename_to": "failed_count" + }, + "Successful": { + "list": true, + "get_count": true, + "rename_to": "successful_count" + } + } + }, + "SetQueueAttributes": { + "request_parameters": [ + "QueueUrl" + ], + "request_descriptors": { + "Attributes": { + "map": true, + "get_keys": true, + "rename_to": "attribute_names" + } + } + } + } + }, + "Lambda": { + "operations": { + "Invoke": { + "request_parameters": [ + "FunctionName", + "InvocationType", + "LogType", + "Qualifier" + ], + "response_parameters": [ + "FunctionError", + "StatusCode" + ] + }, + "InvokeAsync": { + "request_parameters": [ + "FunctionName" + ], + "response_parameters": [ + "Status" + ] + } + } + }, + "S3": { + "operations": { + "CopyObject": { + "request_parameters": [ + "SourceBucketName", + "SourceKey", + "DestinationBucketName", + "DestinationKey" + ] + }, + "CopyPart": { + "request_parameters": [ + "SourceBucketName", + "SourceKey", + "DestinationBucketName", + "DestinationKey" + ] + }, + "GetObject": { + "request_parameters": [ + "Key", + "VersionId", + "BucketName" + ] + }, + "PutObject": { + "request_parameters": [ + "Key", + "BucketName" + ] + }, + "GetObjectAcl": { + "request_parameters": [ + "Key", + "VersionId", + "BucketName" + ] + }, + "CreateBucket": { + "request_parameters": [ + "BucketName" + ] + }, + "ListObjectsV2": { + "request_parameters": [ + "Prefix", + "BucketName" + ] + }, + "ListObjects": { + "request_parameters": [ + "Prefix", + "BucketName" + ] + }, + "GetObjectTagging": { + "request_parameters": [ + "Key", + "VersionId", + "BucketName" + ] + }, + "SetObjectTagging": { + "request_parameters": [ + "Key", + "VersionId", + "BucketName" + ] + }, + "ListVersions": { + "request_parameters": [ + "Prefix", + "BucketName" + ] + }, + "SetObjectAcl": { + "request_parameters": [ + "Key", + "VersionId", + "BucketName" + ] + }, + "GetBucketAcl": { + "request_parameters": [ + "BucketName" + ] + }, + "SetBucketAcl": { + "request_parameters": [ + "BucketName" + ] + }, + "HeadBucket": { + "request_parameters": [ + "BucketName" + ] + }, + "UploadPart": { + "request_parameters": [ + "Key", + "BucketName" + ] + }, + "DeleteObject": { + "request_parameters": [ + "Key", + "BucketName" + ] + }, + "DeleteBucket": { + "request_parameters": [ + "BucketName" + ] + }, + "DeleteObjects": { + "request_parameters": [ + "BucketName" + ] + }, + "DeleteVersion": { + "request_parameters": [ + "Key", + "VersionId", + "BucketName" + ] + }, + "GetBucketPolicy": { + "request_parameters": [ + "BucketName" + ] + }, + "SetBucketPolicy": { + "request_parameters": [ + "BucketName" + ] + }, + "ListParts": { + "request_parameters": [ + "Key", + "BucketName" + ] + }, + "RestoreObject": { + "request_parameters": [ + "Key", + "VersionId", + "BucketName" + ] + }, + "RestoreObjectV2": { + "request_parameters": [ + "Key", + "VersionId", + "BucketName" + ] + }, + "SetBucketNotificationConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "DeleteBucketLifecycleConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketNotificationConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "DeleteBucketCrossOriginConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "SetBucketCrossOriginConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketCrossOriginConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "ListBucketInventoryConfigurations": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketReplicationConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "SetBucketReplicationConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "DeleteBucketReplicationConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "DeleteBucketAnalyticsConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "DeleteBucketInventoryConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "ListBucketAnalyticsConfigurations": { + "request_parameters": [ + "BucketName" + ] + }, + "DeleteObjectTagging": { + "request_parameters": [ + "Key", + "VersionId", + "BucketName" + ] + }, + "SetBucketVersioningConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketVersioningConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketWebsiteConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketLifecycleConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "SetBucketLifecycleConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketTaggingConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "SetBucketTaggingConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "GetObjectMetadata": { + "request_parameters": [ + "Key", + "VersionId", + "BucketName" + ] + }, + "GetBucketLocation": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketLoggingConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "ListMultipartUploads": { + "request_parameters": [ + "Prefix", + "BucketName" + ] + }, + "DeleteBucketPolicy": { + "request_parameters": [ + "BucketName" + ] + }, + "DeleteBucketEncryption": { + "request_parameters": [ + "BucketName" + ] + }, + "SetBucketAccelerateConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "SetBucketWebsiteConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "CompleteMultipartUpload": { + "request_parameters": [ + "Key", + "BucketName" + ] + }, + "InitiateMultipartUpload": { + "request_parameters": [ + "Key", + "BucketName" + ] + }, + "SetBucketEncryption": { + "request_parameters": [ + "BucketName" + ] + }, + "SetBucketLoggingConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "DeleteBucketWebsiteConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketEncryption": { + "request_parameters": [ + "BucketName" + ] + }, + "AbortMultipartUpload": { + "request_parameters": [ + "Key", + "BucketName" + ] + }, + "GeneratePresignedUrl": { + "request_parameters": [ + "Key", + "VersionId", + "BucketName" + ] + }, + "DeleteBucketTaggingConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketAccelerateConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketMetricsConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "ListBucketMetricsConfigurations": { + "request_parameters": [ + "BucketName" + ] + }, + "SetBucketInventoryConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "SetBucketMetricsConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "SetBucketAnalyticsConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "DeleteBucketMetricsConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketAnalyticsConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketInventoryConfiguration": { + "request_parameters": [ + "BucketName" + ] + } + } + } + } +} From e8443c568bf68e2b787f9efa3c45415cbf359e8e Mon Sep 17 00:00:00 2001 From: Christopher Radek Date: Tue, 22 Jan 2019 16:25:19 -0800 Subject: [PATCH 2/3] feat: adds packages for instrumenting the aws sdk for java v2 sdk --- aws-xray-recorder-sdk-aws-sdk-core/pom.xml | 47 +- .../amazonaws/xray/utils/StringTransform.java | 10 + .../xray/utils/StringTransformTest.java | 22 + .../pom.xml | 39 + .../global/handlers/execution.interceptors | 1 + aws-xray-recorder-sdk-aws-sdk-v2/pom.xml | 70 ++ .../xray/interceptors/TracingInterceptor.java | 435 +++++++++++ .../DefaultOperationParameterWhitelist.json | 733 ++++++++++++++++++ .../interceptors/TracingInterceptorTest.java | 539 +++++++++++++ aws-xray-recorder-sdk-aws-sdk/pom.xml | 2 +- .../xray/handlers/TracingHandler.java | 46 +- .../handlers/config/AWSOperationHandler.java | 58 -- .../config/AWSOperationHandlerManifest.java | 14 - .../AWSOperationHandlerRequestDescriptor.java | 55 -- ...AWSOperationHandlerResponseDescriptor.java | 55 -- .../handlers/config/AWSServiceHandler.java | 14 - .../config/AWSServiceHandlerManifest.java | 14 - .../com/amazonaws/xray/AWSXRayRecorder.java | 30 + .../xray/entities/EntityDataKeys.java | 31 + .../xray/entities/EntityHeaderKeys.java | 23 + .../amazonaws/xray/AWSXRayRecorderTest.java | 13 + pom.xml | 3 + 22 files changed, 2015 insertions(+), 239 deletions(-) create mode 100644 aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/utils/StringTransform.java create mode 100644 aws-xray-recorder-sdk-aws-sdk-core/src/test/java/com/amazonaws/xray/utils/StringTransformTest.java create mode 100644 aws-xray-recorder-sdk-aws-sdk-v2-instrumentor/pom.xml create mode 100644 aws-xray-recorder-sdk-aws-sdk-v2-instrumentor/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors create mode 100644 aws-xray-recorder-sdk-aws-sdk-v2/pom.xml create mode 100644 aws-xray-recorder-sdk-aws-sdk-v2/src/main/java/com/amazonaws/xray/interceptors/TracingInterceptor.java create mode 100644 aws-xray-recorder-sdk-aws-sdk-v2/src/main/resources/com/amazonaws/xray/interceptors/DefaultOperationParameterWhitelist.json create mode 100644 aws-xray-recorder-sdk-aws-sdk-v2/src/test/java/com/amazonaws/xray/interceptors/TracingInterceptorTest.java delete mode 100644 aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandler.java delete mode 100644 aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandlerManifest.java delete mode 100644 aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandlerRequestDescriptor.java delete mode 100644 aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandlerResponseDescriptor.java delete mode 100644 aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/config/AWSServiceHandler.java delete mode 100644 aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/config/AWSServiceHandlerManifest.java create mode 100644 aws-xray-recorder-sdk-core/src/main/java/com/amazonaws/xray/entities/EntityDataKeys.java create mode 100644 aws-xray-recorder-sdk-core/src/main/java/com/amazonaws/xray/entities/EntityHeaderKeys.java diff --git a/aws-xray-recorder-sdk-aws-sdk-core/pom.xml b/aws-xray-recorder-sdk-aws-sdk-core/pom.xml index 09439e9d..8de91311 100644 --- a/aws-xray-recorder-sdk-aws-sdk-core/pom.xml +++ b/aws-xray-recorder-sdk-aws-sdk-core/pom.xml @@ -10,6 +10,49 @@ 4.0.0 aws-xray-recorder-sdk-aws-sdk-core - - + 2.1.0 + AWS X-Ray Recorder SDK for Java - AWS SDK Core + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + 8 + 8 + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + ${basedir}/docs + true + ${basedir}/docs/overview.html + + + + + + + com.amazonaws + aws-xray-recorder-sdk-core + ${awsxrayrecordersdk.version} + + + + junit + junit + 4.12 + test + + + org.mockito + mockito-all + 1.10.19 + test + + \ No newline at end of file diff --git a/aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/utils/StringTransform.java b/aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/utils/StringTransform.java new file mode 100644 index 00000000..f5fbf99a --- /dev/null +++ b/aws-xray-recorder-sdk-aws-sdk-core/src/main/java/com/amazonaws/xray/utils/StringTransform.java @@ -0,0 +1,10 @@ +package com.amazonaws.xray.utils; + +public class StringTransform { + private static final String REGEX = "([a-z])([A-Z]+)"; + private static final String REPLACE = "$1_$2"; + + public static String toSnakeCase(String camelCase) { + return camelCase.replaceAll(REGEX, REPLACE).toLowerCase(); + } +} \ No newline at end of file diff --git a/aws-xray-recorder-sdk-aws-sdk-core/src/test/java/com/amazonaws/xray/utils/StringTransformTest.java b/aws-xray-recorder-sdk-aws-sdk-core/src/test/java/com/amazonaws/xray/utils/StringTransformTest.java new file mode 100644 index 00000000..9c2dc8bb --- /dev/null +++ b/aws-xray-recorder-sdk-aws-sdk-core/src/test/java/com/amazonaws/xray/utils/StringTransformTest.java @@ -0,0 +1,22 @@ +package com.amazonaws.xray.utils; + +import org.junit.Assert; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +@FixMethodOrder(MethodSorters.JVM) +public class StringTransformTest { + + @Test + public void testToSnakeCase() { + Assert.assertEquals("table_name", StringTransform.toSnakeCase("tableName")); + Assert.assertEquals("consumed_capacity", StringTransform.toSnakeCase("ConsumedCapacity")); + Assert.assertEquals("item_collection_metrics", StringTransform.toSnakeCase("ItemCollectionMetrics")); + } + + @Test + public void testToSnakeCaseNoExtraUnderscores() { + Assert.assertEquals("table_name", StringTransform.toSnakeCase("table_name")); + } +} diff --git a/aws-xray-recorder-sdk-aws-sdk-v2-instrumentor/pom.xml b/aws-xray-recorder-sdk-aws-sdk-v2-instrumentor/pom.xml new file mode 100644 index 00000000..f1eb89b3 --- /dev/null +++ b/aws-xray-recorder-sdk-aws-sdk-v2-instrumentor/pom.xml @@ -0,0 +1,39 @@ + + + + aws-xray-recorder-sdk-pom + com.amazonaws + 2.1.0 + + 4.0.0 + + aws-xray-recorder-sdk-aws-sdk-v2-instrumentor + 2.1.0 + AWS X-Ray Recorder SDK for Java - AWS SDK V2 Instrumentor + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + 8 + 8 + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + ${basedir}/docs + true + ${basedir}/docs/overview.html + + + + + + \ No newline at end of file diff --git a/aws-xray-recorder-sdk-aws-sdk-v2-instrumentor/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors b/aws-xray-recorder-sdk-aws-sdk-v2-instrumentor/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors new file mode 100644 index 00000000..b167a2b0 --- /dev/null +++ b/aws-xray-recorder-sdk-aws-sdk-v2-instrumentor/src/main/resources/software/amazon/awssdk/global/handlers/execution.interceptors @@ -0,0 +1 @@ +com.amazonaws.xray.interceptors.TracingInterceptor \ No newline at end of file diff --git a/aws-xray-recorder-sdk-aws-sdk-v2/pom.xml b/aws-xray-recorder-sdk-aws-sdk-v2/pom.xml new file mode 100644 index 00000000..19485889 --- /dev/null +++ b/aws-xray-recorder-sdk-aws-sdk-v2/pom.xml @@ -0,0 +1,70 @@ + + + + aws-xray-recorder-sdk-pom + com.amazonaws + 2.1.0 + + 4.0.0 + + aws-xray-recorder-sdk-aws-sdk-v2 + 2.1.0 + AWS X-Ray Recorder SDK for Java - AWS SDK V2 + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + 8 + 8 + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + ${basedir}/docs + true + ${basedir}/docs/overview.html + + + + + + + com.amazonaws + aws-xray-recorder-sdk-aws-sdk-core + ${awsxrayrecordersdk.version} + + + + junit + junit + 4.12 + test + + + org.mockito + mockito-all + 1.10.19 + test + + + org.skyscreamer + jsonassert + 1.3.0 + test + + + software.amazon.awssdk + aws-sdk-java + 2.2.0 + provided + + + \ No newline at end of file diff --git a/aws-xray-recorder-sdk-aws-sdk-v2/src/main/java/com/amazonaws/xray/interceptors/TracingInterceptor.java b/aws-xray-recorder-sdk-aws-sdk-v2/src/main/java/com/amazonaws/xray/interceptors/TracingInterceptor.java new file mode 100644 index 00000000..d6c44918 --- /dev/null +++ b/aws-xray-recorder-sdk-aws-sdk-v2/src/main/java/com/amazonaws/xray/interceptors/TracingInterceptor.java @@ -0,0 +1,435 @@ +package com.amazonaws.xray.interceptors; + +import com.amazonaws.xray.AWSXRay; +import com.amazonaws.xray.AWSXRayRecorder; + +import com.amazonaws.xray.entities.Entity; +import com.amazonaws.xray.entities.EntityDataKeys; +import com.amazonaws.xray.entities.EntityHeaderKeys; +import com.amazonaws.xray.entities.Namespace; +import com.amazonaws.xray.entities.Subsegment; +import com.amazonaws.xray.entities.TraceHeader; +import com.amazonaws.xray.handlers.config.AWSOperationHandler; +import com.amazonaws.xray.handlers.config.AWSOperationHandlerManifest; +import com.amazonaws.xray.handlers.config.AWSServiceHandlerManifest; +import com.amazonaws.xray.utils.StringTransform; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import software.amazon.awssdk.awscore.AwsExecutionAttribute; +import software.amazon.awssdk.awscore.AwsResponse; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.SdkResponse; +import software.amazon.awssdk.core.exception.SdkServiceException; +import software.amazon.awssdk.core.interceptor.*; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.SdkHttpResponse; +import software.amazon.awssdk.regions.Region; + +import java.io.IOException; +import java.net.URL; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +public class TracingInterceptor implements ExecutionInterceptor { + private static final Log logger = LogFactory.getLog(TracingInterceptor.class); + + private AWSServiceHandlerManifest awsServiceHandlerManifest; + private AWSXRayRecorder recorder; + private final String accountId; + + private ObjectMapper mapper = new ObjectMapper() + .setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(JsonParser.Feature.ALLOW_COMMENTS, true); + + private ExecutionAttribute entityKey = new ExecutionAttribute("AWS X-Ray Entity"); + + private static final URL DEFAULT_OPERATION_PARAMETER_WHITELIST = TracingInterceptor.class.getResource("/com/amazonaws/xray/interceptors/DefaultOperationParameterWhitelist.json"); + + private static final String UNKNOWN_REQUEST_ID = "UNKNOWN"; + private static final List REQUEST_ID_KEYS = Arrays.asList("x-amz-request-id", "x-amzn-requestid"); + + public TracingInterceptor() { + this(null, null, null); + } + + public TracingInterceptor(AWSXRayRecorder recorder, String accountId, URL parameterWhitelist) { + this.recorder = recorder; + this.accountId = accountId; + initInterceptorManifest(parameterWhitelist); + } + + private void initInterceptorManifest(URL parameterWhitelist) { + if (parameterWhitelist != null) { + try { + awsServiceHandlerManifest = mapper.readValue(parameterWhitelist, AWSServiceHandlerManifest.class); + return; + } catch (IOException e) { + logger.error( + "Unable to parse operation parameter whitelist at " + parameterWhitelist.getPath() + + ". Falling back to default operation parameter whitelist at " + TracingInterceptor.DEFAULT_OPERATION_PARAMETER_WHITELIST.getPath() + ".", + e + ); + } + } + try { + awsServiceHandlerManifest = mapper.readValue(TracingInterceptor.DEFAULT_OPERATION_PARAMETER_WHITELIST, AWSServiceHandlerManifest.class); + } catch (IOException e) { + logger.error( + "Unable to parse default operation parameter whitelist at " + TracingInterceptor.DEFAULT_OPERATION_PARAMETER_WHITELIST.getPath() + + ". This will affect this handler's ability to capture AWS operation parameter information.", + e + ); + } + } + + private AWSOperationHandler getOperationHandler(ExecutionAttributes executionAttributes) { + String serviceName = executionAttributes.getAttribute(SdkExecutionAttribute.SERVICE_NAME); + String operationName = executionAttributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME); + + if (awsServiceHandlerManifest == null) { + return null; + } + AWSOperationHandlerManifest operationManifest = awsServiceHandlerManifest.getOperationHandlerManifest(serviceName); + if (operationManifest == null) { + return null; + } + return operationManifest.getOperationHandler(operationName); + } + + private HashMap extractRequestParameters(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { + HashMap parameters = new HashMap<>(); + + AWSOperationHandler operationHandler = getOperationHandler(executionAttributes); + if (operationHandler == null) { + return parameters; + } + + if (operationHandler.getRequestParameters() != null) { + operationHandler.getRequestParameters().forEach((parameterName) -> { + SdkRequest request = context.request(); + Optional parameterValue = request.getValueForField(parameterName, Object.class); + if (parameterValue.isPresent()) { + parameters.put(StringTransform.toSnakeCase(parameterName), parameterValue.get()); + } + }); + } + + if (operationHandler.getRequestDescriptors() != null) { + operationHandler.getRequestDescriptors().forEach((key, descriptor) -> { + if (descriptor.isMap() && descriptor.shouldGetKeys()) { + SdkRequest request = context.request(); + Optional parameterValue = request.getValueForField(key, Map.class); + if (parameterValue.isPresent()) { + String renameTo = descriptor.getRenameTo() != null ? descriptor.getRenameTo() : key; + parameters.put(StringTransform.toSnakeCase(renameTo), parameterValue.get().keySet()); + } + } else if (descriptor.isList() && descriptor.shouldGetCount()) { + SdkRequest request = context.request(); + Optional parameterValue = request.getValueForField(key, List.class); + if (parameterValue.isPresent()) { + String renameTo = descriptor.getRenameTo() != null ? descriptor.getRenameTo() : key; + parameters.put(StringTransform.toSnakeCase(renameTo), parameterValue.get().size()); + } + } + }); + } + + return parameters; + } + + private HashMap extractResponseParameters(Context.AfterExecution context, ExecutionAttributes executionAttributes) { + HashMap parameters = new HashMap<>(); + + AWSOperationHandler operationHandler = getOperationHandler(executionAttributes); + if (operationHandler == null) { + return parameters; + } + + if (operationHandler.getResponseParameters() != null) { + operationHandler.getResponseParameters().forEach((parameterName) -> { + SdkResponse response = context.response(); + Optional parameterValue = response.getValueForField(parameterName, Object.class); + if (parameterValue.isPresent()) { + parameters.put(StringTransform.toSnakeCase(parameterName), parameterValue.get()); + } + }); + } + + if (operationHandler.getResponseDescriptors() != null) { + operationHandler.getResponseDescriptors().forEach((key, descriptor) -> { + if (descriptor.isMap() && descriptor.shouldGetKeys()) { + SdkResponse response = context.response(); + Optional parameterValue = response.getValueForField(key, Map.class); + if (parameterValue.isPresent()) { + String renameTo = descriptor.getRenameTo() != null ? descriptor.getRenameTo() : key; + parameters.put(StringTransform.toSnakeCase(renameTo), parameterValue.get().keySet()); + } + } else if (descriptor.isList() && descriptor.shouldGetCount()) { + SdkResponse response = context.response(); + Optional parameterValue = response.getValueForField(key, List.class); + if (parameterValue.isPresent()) { + String renameTo = descriptor.getRenameTo() != null ? descriptor.getRenameTo() : key; + parameters.put(StringTransform.toSnakeCase(renameTo), parameterValue.get().size()); + } + } + }); + } + + return parameters; + } + + @Override + public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { + AWSXRayRecorder recorder = getRecorder(); + Entity origin = recorder.getTraceEntity(); + + Subsegment subsegment = recorder.beginSubsegment(executionAttributes.getAttribute((SdkExecutionAttribute.SERVICE_NAME))); + if (subsegment == null) { + return; + } + subsegment.setNamespace(Namespace.AWS.toString()); + subsegment.putAws(EntityDataKeys.AWS.OPERATION_KEY, executionAttributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME)); + Region region = executionAttributes.getAttribute(AwsExecutionAttribute.AWS_REGION); + if (region != null) { + subsegment.putAws(EntityDataKeys.AWS.REGION_KEY, region.id()); + } + subsegment.putAllAws(extractRequestParameters(context, executionAttributes)); + if (accountId != null) { + subsegment.putAws(EntityDataKeys.AWS.ACCOUNT_ID_SUBSEGMENT_KEY, accountId); + } + + recorder.setTraceEntity(origin); + // store the subsegment in the AWS SDK's executionAttributes so it can be accessed across threads + executionAttributes.putAttribute(entityKey, subsegment); + } + + @Override + public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) { + SdkHttpRequest httpRequest = context.httpRequest(); + + Subsegment subsegment = executionAttributes.getAttribute(entityKey); + if (subsegment == null) { + return httpRequest; + } + + boolean isSampled = subsegment.getParentSegment().isSampled(); + TraceHeader header = new TraceHeader( + subsegment.getParentSegment().getTraceId(), + isSampled ? subsegment.getId() : null, + isSampled ? TraceHeader.SampleDecision.SAMPLED : TraceHeader.SampleDecision.NOT_SAMPLED + ); + + return httpRequest.toBuilder().appendHeader(TraceHeader.HEADER_KEY, header.toString()).build(); + } + + @Override + public void beforeTransmission(Context.BeforeTransmission context, ExecutionAttributes executionAttributes) { + Subsegment subsegment = executionAttributes.getAttribute(entityKey); + if (subsegment == null) { + return; + } + + Map awsProperties = subsegment.getAws(); + // beforeTransmission is run before every API call attempt + // default value is set to -1 and will always be -1 on the first API call attempt + // this value will be incremented by 1, so initial run will have a stored retryCount of 0 + int retryCount = (int) awsProperties.getOrDefault(EntityDataKeys.AWS.RETRIES_KEY, -1); + awsProperties.put(EntityDataKeys.AWS.RETRIES_KEY, retryCount + 1); + } + + public void afterExecution(Context.AfterExecution context, ExecutionAttributes executionAttributes) { + Subsegment subsegment = executionAttributes.getAttribute(entityKey); + if (subsegment == null) { + return; + } + + populateRequestId(subsegment, context); + populateSubsegmentWithResponse(subsegment, context.httpResponse()); + subsegment.putAllAws(extractResponseParameters(context, executionAttributes)); + + getRecorder().endSubsegment(subsegment); + } + + @Override + public void onExecutionFailure(Context.FailedExecution context, ExecutionAttributes executionAttributes) { + Subsegment subsegment = executionAttributes.getAttribute(entityKey); + if (subsegment == null) { + return; + } + + populateSubsegmentException(subsegment, context); + populateRequestId(subsegment, context); + if (context.httpResponse().isPresent()) { + populateSubsegmentWithResponse(subsegment, context.httpResponse().get()); + } + + getRecorder().endSubsegment(subsegment); + } + + private HashMap extractHttpResponseParameters(SdkHttpResponse httpResponse) { + HashMap parameters = new HashMap<>(); + HashMap responseData = new HashMap<>(); + + responseData.put(EntityDataKeys.HTTP.STATUS_CODE_KEY, httpResponse.statusCode()); + + try { + if (httpResponse.headers().containsKey(EntityHeaderKeys.HTTP.CONTENT_LENGTH_HEADER)) { + responseData.put(EntityDataKeys.HTTP.CONTENT_LENGTH_KEY, Long.parseLong( + httpResponse.headers().get(EntityHeaderKeys.HTTP.CONTENT_LENGTH_HEADER).get(0)) + ); + } + } catch (NumberFormatException e) { + logger.warn("Unable to parse Content-Length header.", e); + } + + parameters.put(EntityDataKeys.HTTP.RESPONSE_KEY, responseData); + return parameters; + } + + private void setRemoteForException(Subsegment subsegment, Throwable exception) { + subsegment.getCause().getExceptions().forEach((e) -> { + if (e.getThrowable() == exception) { + e.setRemote(true); + } + }); + } + + private void populateSubsegmentException(Subsegment subsegment, Context.FailedExecution context) { + Throwable exception = context.exception(); + subsegment.addException(exception); + + int statusCode = -1; + if (exception instanceof SdkServiceException) { + statusCode = ((SdkServiceException) exception).statusCode(); + subsegment.getCause().setMessage(exception.getMessage()); + if (((SdkServiceException) exception).isThrottlingException()) { + subsegment.setThrottle(true); + // throttling errors are considered client-side errors + subsegment.setError(true); + } + setRemoteForException(subsegment, exception); + } else if (context.httpResponse().isPresent()) { + statusCode = context.httpResponse().get().statusCode(); + } + + if (statusCode == -1) { + return; + } + + if (statusCode >= 400 && statusCode < 500) { + subsegment.setFault(false); + subsegment.setError(true); + if (statusCode == 429) { + subsegment.setThrottle(true); + } + } else if (statusCode >= 500) { + subsegment.setFault(true); + } + } + + private void populateRequestId( + Subsegment subsegment, + Optional response, + Optional httpResponse, + Throwable exception + ) { + String requestId = null; + + if (exception != null) { + requestId = extractRequestIdFromThrowable(exception); + } + if (requestId == null || requestId == UNKNOWN_REQUEST_ID) { + requestId = extractRequestIdFromResponse(response); + } + if (requestId == null || requestId == UNKNOWN_REQUEST_ID) { + requestId = extractRequestIdFromHttp(httpResponse); + } + if (requestId != null && requestId != UNKNOWN_REQUEST_ID) { + subsegment.putAws(EntityDataKeys.AWS.REQUEST_ID_KEY, requestId); + } + } + + private void populateRequestId(Subsegment subsegment, Context.FailedExecution context) { + populateRequestId(subsegment, context.response(), context.httpResponse(), context.exception()); + } + + private void populateRequestId(Subsegment subsegment, Context.AfterExecution context) { + populateRequestId(subsegment, Optional.of(context.response()), Optional.of(context.httpResponse()), null); + } + + + private String extractRequestIdFromHttp(Optional httpResponse) { + if (!httpResponse.isPresent()) { + return null; + } + return extractRequestIdFromHttp(httpResponse.get()); + } + + private String extractRequestIdFromHttp(SdkHttpResponse httpResponse) { + Map> headers = httpResponse.headers(); + Set headerKeys = headers.keySet(); + String requestIdKey = headerKeys + .stream() + .filter(key -> REQUEST_ID_KEYS.contains(key.toLowerCase())) + .findFirst() + .orElse(null); + return requestIdKey != null ? headers.get(requestIdKey).get(0) : null; + } + + private String extractExtendedRequestIdFromHttp(SdkHttpResponse httpResponse) { + Map> headers = httpResponse.headers(); + return headers.containsKey(EntityHeaderKeys.AWS.EXTENDED_REQUEST_ID_HEADER) ? + headers.get(EntityHeaderKeys.AWS.EXTENDED_REQUEST_ID_HEADER).get(0) : null; + } + + private String extractRequestIdFromThrowable(Throwable exception) { + if (exception instanceof SdkServiceException) { + return ((SdkServiceException) exception).requestId(); + } + return null; + } + + private String extractRequestIdFromResponse(Optional response) { + if (response.isPresent()) { + return extractRequestIdFromResponse(response.get()); + } + + return null; + } + + private String extractRequestIdFromResponse(SdkResponse response) { + if (response instanceof AwsResponse) { + return ((AwsResponse) response).responseMetadata().requestId(); + } + + return null; + } + + private void populateSubsegmentWithResponse(Subsegment subsegment, SdkHttpResponse httpResponse) { + if (subsegment == null || httpResponse == null) { + return; + } + + String extendedRequestId = extractExtendedRequestIdFromHttp(httpResponse); + if (extendedRequestId != null) { + subsegment.putAws(EntityDataKeys.AWS.EXTENDED_REQUEST_ID_KEY, extendedRequestId); + } + subsegment.putAllHttp(extractHttpResponseParameters(httpResponse)); + } + + private AWSXRayRecorder getRecorder() { + if (recorder == null) { + recorder = AWSXRay.getGlobalRecorder(); + } + return recorder; + } +} diff --git a/aws-xray-recorder-sdk-aws-sdk-v2/src/main/resources/com/amazonaws/xray/interceptors/DefaultOperationParameterWhitelist.json b/aws-xray-recorder-sdk-aws-sdk-v2/src/main/resources/com/amazonaws/xray/interceptors/DefaultOperationParameterWhitelist.json new file mode 100644 index 00000000..4926cdb9 --- /dev/null +++ b/aws-xray-recorder-sdk-aws-sdk-v2/src/main/resources/com/amazonaws/xray/interceptors/DefaultOperationParameterWhitelist.json @@ -0,0 +1,733 @@ +{ + "services": { + "DynamoDb": { + "operations": { + "BatchGetItem": { + "request_descriptors": { + "RequestItems": { + "map": true, + "get_keys": true, + "rename_to": "table_names" + } + }, + "response_parameters": [ + "ConsumedCapacity" + ] + }, + "BatchWriteItem": { + "request_descriptors": { + "RequestItems": { + "map": true, + "get_keys": true, + "rename_to": "table_names" + } + }, + "response_parameters": [ + "ConsumedCapacity", + "ItemCollectionMetrics" + ] + }, + "CreateTable": { + "request_parameters": [ + "GlobalSecondaryIndexes", + "LocalSecondaryIndexes", + "ProvisionedThroughput", + "TableName" + ] + }, + "DeleteItem": { + "request_parameters": [ + "TableName" + ], + "response_parameters": [ + "ConsumedCapacity", + "ItemCollectionMetrics" + ] + }, + "DeleteTable": { + "request_parameters": [ + "TableName" + ] + }, + "DescribeTable": { + "request_parameters": [ + "TableName" + ] + }, + "GetItem": { + "request_parameters": [ + "ConsistentRead", + "ProjectionExpression", + "TableName" + ], + "response_parameters": [ + "ConsumedCapacity" + ] + }, + "ListTables": { + "request_parameters": [ + "ExclusiveStartTableName", + "Limit" + ], + "response_descriptors": { + "TableNames": { + "list": true, + "get_count": true, + "rename_to": "table_count" + } + } + }, + "PutItem": { + "request_parameters": [ + "TableName" + ], + "response_parameters": [ + "ConsumedCapacity", + "ItemCollectionMetrics" + ] + }, + "Query": { + "request_parameters": [ + "AttributesToGet", + "ConsistentRead", + "IndexName", + "Limit", + "ProjectionExpression", + "ScanIndexForward", + "Select", + "TableName" + ], + "response_parameters": [ + "ConsumedCapacity" + ] + }, + "Scan": { + "request_parameters": [ + "AttributesToGet", + "ConsistentRead", + "IndexName", + "Limit", + "ProjectionExpression", + "Segment", + "Select", + "TableName", + "TotalSegments" + ], + "response_parameters": [ + "ConsumedCapacity", + "Count", + "ScannedCount" + ] + }, + "UpdateItem": { + "request_parameters": [ + "TableName" + ], + "response_parameters": [ + "ConsumedCapacity", + "ItemCollectionMetrics" + ] + }, + "UpdateTable": { + "request_parameters": [ + "AttributeDefinitions", + "GlobalSecondaryIndexUpdates", + "ProvisionedThroughput", + "TableName" + ] + } + } + }, + "SQS": { + "operations": { + "AddPermission": { + "request_parameters": [ + "Label", + "QueueUrl" + ] + }, + "ChangeMessageVisibility": { + "request_parameters": [ + "QueueUrl", + "VisibilityTimeout" + ] + }, + "ChangeMessageVisibilityBatch": { + "request_parameters": [ + "QueueUrl" + ], + "response_parameters": [ + "Failed" + ] + }, + "CreateQueue": { + "request_parameters": [ + "Attributes", + "QueueName" + ] + }, + "DeleteMessage": { + "request_parameters": [ + "QueueUrl" + ] + }, + "DeleteMessageBatch": { + "request_parameters": [ + "QueueUrl" + ], + "response_parameters": [ + "Failed" + ] + }, + "DeleteQueue": { + "request_parameters": [ + "QueueUrl" + ] + }, + "GetQueueAttributes": { + "request_parameters": [ + "QueueUrl" + ], + "response_parameters": [ + "Attributes" + ] + }, + "GetQueueUrl": { + "request_parameters": [ + "QueueName", + "QueueOwnerAWSAccountId" + ], + "response_parameters": [ + "QueueUrl" + ] + }, + "ListDeadLetterSourceQueues": { + "request_parameters": [ + "QueueUrl" + ], + "response_parameters": [ + "QueueUrls" + ] + }, + "ListQueues": { + "request_parameters": [ + "QueueNamePrefix" + ], + "response_descriptors": { + "QueueUrls": { + "list": true, + "get_count": true, + "rename_to": "queue_count" + } + } + }, + "PurgeQueue": { + "request_parameters": [ + "QueueUrl" + ] + }, + "ReceiveMessage": { + "request_parameters": [ + "AttributeNames", + "MaxNumberOfMessages", + "MessageAttributeNames", + "QueueUrl", + "VisibilityTimeout", + "WaitTimeSeconds" + ], + "response_descriptors": { + "Messages": { + "list": true, + "get_count": true, + "rename_to": "message_count" + } + } + }, + "RemovePermission": { + "request_parameters": [ + "QueueUrl" + ] + }, + "SendMessage": { + "request_parameters": [ + "DelaySeconds", + "QueueUrl" + ], + "request_descriptors": { + "MessageAttributes": { + "map": true, + "get_keys": true, + "rename_to": "message_attribute_names" + } + }, + "response_parameters": [ + "MessageId" + ] + }, + "SendMessageBatch": { + "request_parameters": [ + "QueueUrl" + ], + "request_descriptors": { + "Entries": { + "list": true, + "get_count": true, + "rename_to": "message_count" + } + }, + "response_descriptors": { + "Failed": { + "list": true, + "get_count": true, + "rename_to": "failed_count" + }, + "Successful": { + "list": true, + "get_count": true, + "rename_to": "successful_count" + } + } + }, + "SetQueueAttributes": { + "request_parameters": [ + "QueueUrl" + ], + "request_descriptors": { + "Attributes": { + "map": true, + "get_keys": true, + "rename_to": "attribute_names" + } + } + } + } + }, + "Lambda": { + "operations": { + "Invoke": { + "request_parameters": [ + "FunctionName", + "InvocationType", + "LogType", + "Qualifier" + ], + "response_parameters": [ + "FunctionError", + "StatusCode" + ] + }, + "InvokeAsync": { + "request_parameters": [ + "FunctionName" + ], + "response_parameters": [ + "Status" + ] + } + } + }, + "S3": { + "operations": { + "CopyObject": { + "request_parameters": [ + "SourceBucketName", + "SourceKey", + "DestinationBucketName", + "DestinationKey" + ] + }, + "CopyPart": { + "request_parameters": [ + "SourceBucketName", + "SourceKey", + "DestinationBucketName", + "DestinationKey" + ] + }, + "GetObject": { + "request_parameters": [ + "Key", + "VersionId", + "BucketName" + ] + }, + "PutObject": { + "request_parameters": [ + "Key", + "BucketName" + ] + }, + "GetObjectAcl": { + "request_parameters": [ + "Key", + "VersionId", + "BucketName" + ] + }, + "CreateBucket": { + "request_parameters": [ + "BucketName" + ] + }, + "ListObjectsV2": { + "request_parameters": [ + "Prefix", + "BucketName" + ] + }, + "ListObjects": { + "request_parameters": [ + "Prefix", + "BucketName" + ] + }, + "GetObjectTagging": { + "request_parameters": [ + "Key", + "VersionId", + "BucketName" + ] + }, + "SetObjectTagging": { + "request_parameters": [ + "Key", + "VersionId", + "BucketName" + ] + }, + "ListVersions": { + "request_parameters": [ + "Prefix", + "BucketName" + ] + }, + "SetObjectAcl": { + "request_parameters": [ + "Key", + "VersionId", + "BucketName" + ] + }, + "GetBucketAcl": { + "request_parameters": [ + "BucketName" + ] + }, + "SetBucketAcl": { + "request_parameters": [ + "BucketName" + ] + }, + "HeadBucket": { + "request_parameters": [ + "BucketName" + ] + }, + "UploadPart": { + "request_parameters": [ + "Key", + "BucketName" + ] + }, + "DeleteObject": { + "request_parameters": [ + "Key", + "BucketName" + ] + }, + "DeleteBucket": { + "request_parameters": [ + "BucketName" + ] + }, + "DeleteObjects": { + "request_parameters": [ + "BucketName" + ] + }, + "DeleteVersion": { + "request_parameters": [ + "Key", + "VersionId", + "BucketName" + ] + }, + "GetBucketPolicy": { + "request_parameters": [ + "BucketName" + ] + }, + "SetBucketPolicy": { + "request_parameters": [ + "BucketName" + ] + }, + "ListParts": { + "request_parameters": [ + "Key", + "BucketName" + ] + }, + "RestoreObject": { + "request_parameters": [ + "Key", + "VersionId", + "BucketName" + ] + }, + "RestoreObjectV2": { + "request_parameters": [ + "Key", + "VersionId", + "BucketName" + ] + }, + "SetBucketNotificationConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "DeleteBucketLifecycleConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketNotificationConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "DeleteBucketCrossOriginConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "SetBucketCrossOriginConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketCrossOriginConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "ListBucketInventoryConfigurations": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketReplicationConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "SetBucketReplicationConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "DeleteBucketReplicationConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "DeleteBucketAnalyticsConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "DeleteBucketInventoryConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "ListBucketAnalyticsConfigurations": { + "request_parameters": [ + "BucketName" + ] + }, + "DeleteObjectTagging": { + "request_parameters": [ + "Key", + "VersionId", + "BucketName" + ] + }, + "SetBucketVersioningConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketVersioningConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketWebsiteConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketLifecycleConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "SetBucketLifecycleConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketTaggingConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "SetBucketTaggingConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "GetObjectMetadata": { + "request_parameters": [ + "Key", + "VersionId", + "BucketName" + ] + }, + "GetBucketLocation": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketLoggingConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "ListMultipartUploads": { + "request_parameters": [ + "Prefix", + "BucketName" + ] + }, + "DeleteBucketPolicy": { + "request_parameters": [ + "BucketName" + ] + }, + "DeleteBucketEncryption": { + "request_parameters": [ + "BucketName" + ] + }, + "SetBucketAccelerateConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "SetBucketWebsiteConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "CompleteMultipartUpload": { + "request_parameters": [ + "Key", + "BucketName" + ] + }, + "InitiateMultipartUpload": { + "request_parameters": [ + "Key", + "BucketName" + ] + }, + "SetBucketEncryption": { + "request_parameters": [ + "BucketName" + ] + }, + "SetBucketLoggingConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "DeleteBucketWebsiteConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketEncryption": { + "request_parameters": [ + "BucketName" + ] + }, + "AbortMultipartUpload": { + "request_parameters": [ + "Key", + "BucketName" + ] + }, + "GeneratePresignedUrl": { + "request_parameters": [ + "Key", + "VersionId", + "BucketName" + ] + }, + "DeleteBucketTaggingConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketAccelerateConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketMetricsConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "ListBucketMetricsConfigurations": { + "request_parameters": [ + "BucketName" + ] + }, + "SetBucketInventoryConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "SetBucketMetricsConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "SetBucketAnalyticsConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "DeleteBucketMetricsConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketAnalyticsConfiguration": { + "request_parameters": [ + "BucketName" + ] + }, + "GetBucketInventoryConfiguration": { + "request_parameters": [ + "BucketName" + ] + } + } + } + } +} diff --git a/aws-xray-recorder-sdk-aws-sdk-v2/src/test/java/com/amazonaws/xray/interceptors/TracingInterceptorTest.java b/aws-xray-recorder-sdk-aws-sdk-v2/src/test/java/com/amazonaws/xray/interceptors/TracingInterceptorTest.java new file mode 100644 index 00000000..109e852d --- /dev/null +++ b/aws-xray-recorder-sdk-aws-sdk-v2/src/test/java/com/amazonaws/xray/interceptors/TracingInterceptorTest.java @@ -0,0 +1,539 @@ +package com.amazonaws.xray.interceptors; + +import com.amazonaws.xray.AWSXRay; +import com.amazonaws.xray.AWSXRayRecorderBuilder; +import com.amazonaws.xray.emitters.Emitter; +import com.amazonaws.xray.entities.Cause; +import com.amazonaws.xray.entities.Segment; +import com.amazonaws.xray.entities.Subsegment; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.mockito.Mockito; + +import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; +import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.core.async.EmptyPublisher; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.http.*; +import software.amazon.awssdk.http.async.AsyncExecuteRequest; +import software.amazon.awssdk.http.async.SdkAsyncHttpClient; +import software.amazon.awssdk.http.async.SdkAsyncHttpResponseHandler; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.ListTablesRequest; +import software.amazon.awssdk.services.lambda.LambdaAsyncClient; +import software.amazon.awssdk.services.lambda.LambdaClient; +import software.amazon.awssdk.services.lambda.model.InvokeRequest; + +import java.io.ByteArrayInputStream; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +@FixMethodOrder(MethodSorters.JVM) +@RunWith(MockitoJUnitRunner.class) +public class TracingInterceptorTest { + + @Before + public void setup() { + Emitter blankEmitter = Mockito.mock(Emitter.class); + Mockito.doReturn(true).when(blankEmitter).sendSegment(Mockito.anyObject()); + Mockito.doReturn(true).when(blankEmitter).sendSubsegment(Mockito.anyObject()); + + AWSXRay.setGlobalRecorder( + AWSXRayRecorderBuilder.standard() + .withEmitter(blankEmitter) + .build() + ); + AWSXRay.clearTraceEntity(); + AWSXRay.beginSegment("test"); + } + + @After + public void teardown() { + AWSXRay.endSegment(); + } + + private SdkHttpClient mockSdkHttpClient(SdkHttpResponse response) throws Exception { + return mockSdkHttpClient(response, "OK"); + } + + private SdkHttpClient mockSdkHttpClient(SdkHttpResponse response, String body) throws Exception { + ExecutableHttpRequest abortableCallable = Mockito.mock(ExecutableHttpRequest.class); + SdkHttpClient mockClient = Mockito.mock(SdkHttpClient.class); + + Mockito.when(mockClient.prepareRequest(Mockito.any())).thenReturn(abortableCallable); + Mockito.when(abortableCallable.call()).thenReturn(HttpExecuteResponse.builder() + .response(response) + .responseBody(AbortableInputStream.create( + new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8)) + )) + .build() + ); + return mockClient; + } + + private SdkAsyncHttpClient mockSdkAsyncHttpClient(SdkHttpResponse response) { + SdkAsyncHttpClient mockClient = Mockito.mock(SdkAsyncHttpClient.class); + Mockito.when(mockClient.execute(Mockito.any(AsyncExecuteRequest.class))).thenAnswer((Answer>) invocationOnMock -> { + SdkAsyncHttpResponseHandler handler = invocationOnMock.getArgumentAt(0, AsyncExecuteRequest.class).responseHandler(); + handler.onHeaders(response); + handler.onStream(new EmptyPublisher<>()); + + return CompletableFuture.completedFuture(null); + }); + + return mockClient; + } + + private SdkHttpResponse generateLambdaInvokeResponse(int statusCode) { + return SdkHttpResponse.builder() + .statusCode(statusCode) + .putHeader("x-amz-request-id", "1111-2222-3333-4444") + .putHeader("x-amz-id-2", "extended") + .putHeader("Content-Length", "2") + .putHeader("X-Amz-Function-Error", "Failure") + .build(); + } + + @Test + public void testResponseDescriptors() throws Exception { + String responseBody = "{\"LastEvaluatedTableName\":\"baz\",\"TableNames\":[\"foo\",\"bar\",\"baz\"]}"; + SdkHttpResponse mockResponse = SdkHttpResponse.builder() + .statusCode(200) + .putHeader("x-amzn-requestid", "1111-2222-3333-4444") + .putHeader("Content-Length", "84") + .putHeader("Content-Type", "application/x-amz-json-1.0") + .build(); + SdkHttpClient mockClient = mockSdkHttpClient(mockResponse, responseBody); + + DynamoDbClient client = DynamoDbClient.builder() + .httpClient(mockClient) + .endpointOverride(URI.create("http://example.com")) + .region(Region.of("us-west-42")) + .credentialsProvider(StaticCredentialsProvider.create( + AwsSessionCredentials.create("key", "secret", "session") + )) + .overrideConfiguration(ClientOverrideConfiguration.builder() + .addExecutionInterceptor(new TracingInterceptor()) + .build() + ) + .build(); + + Segment segment = AWSXRay.getCurrentSegment(); + client.listTables(ListTablesRequest.builder() + .limit(3) + .build() + ); + + Assert.assertEquals(1, segment.getSubsegments().size()); + Subsegment subsegment = segment.getSubsegments().get(0); + Map awsStats = subsegment.getAws(); + Map httpResponseStats = (Map)subsegment.getHttp().get("response"); + + Assert.assertEquals("ListTables", awsStats.get("operation")); + Assert.assertEquals(3, awsStats.get("limit")); + Assert.assertEquals("1111-2222-3333-4444", awsStats.get("request_id")); + Assert.assertEquals(3, awsStats.get("table_count")); + Assert.assertEquals("us-west-42", awsStats.get("region")); + Assert.assertEquals(0, awsStats.get("retries")); + Assert.assertEquals(84L, httpResponseStats.get("content_length")); + Assert.assertEquals(200, httpResponseStats.get("status")); + Assert.assertEquals(false, subsegment.isInProgress()); + } + + @Test + public void testLambdaInvokeSubsegmentContainsFunctionName() throws Exception { + SdkHttpClient mockClient = mockSdkHttpClient(generateLambdaInvokeResponse(200)); + + LambdaClient client = LambdaClient.builder() + .httpClient(mockClient) + .endpointOverride(URI.create("http://example.com")) + .region(Region.of("us-west-42")) + .credentialsProvider(StaticCredentialsProvider.create( + AwsSessionCredentials.create("key", "secret", "session") + )) + .overrideConfiguration(ClientOverrideConfiguration.builder() + .addExecutionInterceptor(new TracingInterceptor()) + .build() + ) + .build(); + + + Segment segment = AWSXRay.getCurrentSegment(); + + client.invoke(InvokeRequest.builder() + .functionName("testFunctionName") + .build() + ); + + Assert.assertEquals(1, segment.getSubsegments().size()); + Subsegment subsegment = segment.getSubsegments().get(0); + Map awsStats = subsegment.getAws(); + Map httpResponseStats = (Map)subsegment.getHttp().get("response"); + + Assert.assertEquals("Invoke", awsStats.get("operation")); + Assert.assertEquals("testFunctionName", awsStats.get("function_name")); + Assert.assertEquals("1111-2222-3333-4444", awsStats.get("request_id")); + Assert.assertEquals("extended", awsStats.get("id_2")); + Assert.assertEquals("Failure", awsStats.get("function_error")); + Assert.assertEquals("us-west-42", awsStats.get("region")); + Assert.assertEquals(0, awsStats.get("retries")); + Assert.assertEquals(2L, httpResponseStats.get("content_length")); + Assert.assertEquals(200, httpResponseStats.get("status")); + Assert.assertEquals(false, subsegment.isInProgress()); + } + + @Test + public void testAsyncLambdaInvokeSubsegmentContainsFunctionName() { + SdkAsyncHttpClient mockClient = mockSdkAsyncHttpClient(generateLambdaInvokeResponse(200)); + + LambdaAsyncClient client = LambdaAsyncClient.builder() + .httpClient(mockClient) + .endpointOverride(URI.create("http://example.com")) + .region(Region.of("us-west-42")) + .credentialsProvider(StaticCredentialsProvider.create( + AwsSessionCredentials.create("key", "secret", "session") + )) + .overrideConfiguration(ClientOverrideConfiguration.builder() + .addExecutionInterceptor(new TracingInterceptor()) + .build() + ) + .build(); + + Segment segment = AWSXRay.getCurrentSegment(); + + client.invoke(InvokeRequest.builder() + .functionName("testFunctionName") + .build() + ).join(); + + Assert.assertEquals(1, segment.getSubsegments().size()); + Subsegment subsegment = segment.getSubsegments().get(0); + Map awsStats = subsegment.getAws(); + Map httpResponseStats = (Map)subsegment.getHttp().get("response"); + + Assert.assertEquals("Invoke", awsStats.get("operation")); + Assert.assertEquals("testFunctionName", awsStats.get("function_name")); + Assert.assertEquals("1111-2222-3333-4444", awsStats.get("request_id")); + Assert.assertEquals("extended", awsStats.get("id_2")); + Assert.assertEquals("Failure", awsStats.get("function_error")); + Assert.assertEquals("us-west-42", awsStats.get("region")); + Assert.assertEquals(0, awsStats.get("retries")); + Assert.assertEquals(2L, httpResponseStats.get("content_length")); + Assert.assertEquals(200, httpResponseStats.get("status")); + Assert.assertEquals(false, subsegment.isInProgress()); + } + + @Test + public void test400Exception() throws Exception { + SdkHttpClient mockClient = mockSdkHttpClient(generateLambdaInvokeResponse(400)); + + LambdaClient client = LambdaClient.builder() + .httpClient(mockClient) + .endpointOverride(URI.create("http://example.com")) + .region(Region.of("us-west-42")) + .credentialsProvider(StaticCredentialsProvider.create( + AwsSessionCredentials.create("key", "secret", "session") + )) + .overrideConfiguration(ClientOverrideConfiguration.builder() + .addExecutionInterceptor(new TracingInterceptor()) + .build() + ) + .build(); + + + Segment segment = AWSXRay.getCurrentSegment(); + + try { + client.invoke(InvokeRequest.builder() + .functionName("testFunctionName") + .build() + ); + } catch (Exception e) { + // ignore SDK errors + } finally { + Assert.assertEquals(1, segment.getSubsegments().size()); + Subsegment subsegment = segment.getSubsegments().get(0); + Map awsStats = subsegment.getAws(); + Map httpResponseStats = (Map)subsegment.getHttp().get("response"); + Cause cause = subsegment.getCause(); + + Assert.assertEquals("Invoke", awsStats.get("operation")); + Assert.assertEquals("testFunctionName", awsStats.get("function_name")); + Assert.assertEquals("1111-2222-3333-4444", awsStats.get("request_id")); + Assert.assertEquals("extended", awsStats.get("id_2")); + Assert.assertEquals("us-west-42", awsStats.get("region")); + Assert.assertEquals(0, awsStats.get("retries")); + Assert.assertEquals(2L, httpResponseStats.get("content_length")); + Assert.assertEquals(400, httpResponseStats.get("status")); + Assert.assertEquals(false, subsegment.isInProgress()); + Assert.assertEquals(true, subsegment.isError()); + Assert.assertEquals(false, subsegment.isThrottle()); + Assert.assertEquals(false, subsegment.isFault()); + Assert.assertEquals(1, cause.getExceptions().size()); + Assert.assertEquals(true, cause.getExceptions().get(0).isRemote()); + } + } + + @Test + public void testAsync400Exception() { + SdkAsyncHttpClient mockClient = mockSdkAsyncHttpClient(generateLambdaInvokeResponse(400)); + + LambdaAsyncClient client = LambdaAsyncClient.builder() + .httpClient(mockClient) + .endpointOverride(URI.create("http://example.com")) + .region(Region.of("us-west-42")) + .credentialsProvider(StaticCredentialsProvider.create( + AwsSessionCredentials.create("key", "secret", "session") + )) + .overrideConfiguration(ClientOverrideConfiguration.builder() + .addExecutionInterceptor(new TracingInterceptor()) + .build() + ) + .build(); + + Segment segment = AWSXRay.getCurrentSegment(); + + try { + client.invoke(InvokeRequest.builder() + .functionName("testFunctionName") + .build() + ).get(); + } catch (Exception e) { + // ignore exceptions + } finally { + Assert.assertEquals(1, segment.getSubsegments().size()); + Subsegment subsegment = segment.getSubsegments().get(0); + Map awsStats = subsegment.getAws(); + Map httpResponseStats = (Map)subsegment.getHttp().get("response"); + Cause cause = subsegment.getCause(); + + Assert.assertEquals("Invoke", awsStats.get("operation")); + Assert.assertEquals("testFunctionName", awsStats.get("function_name")); + Assert.assertEquals("1111-2222-3333-4444", awsStats.get("request_id")); + Assert.assertEquals("extended", awsStats.get("id_2")); + Assert.assertEquals("us-west-42", awsStats.get("region")); + Assert.assertEquals(0, awsStats.get("retries")); + Assert.assertEquals(2L, httpResponseStats.get("content_length")); + Assert.assertEquals(400, httpResponseStats.get("status")); + Assert.assertEquals(false, subsegment.isInProgress()); + Assert.assertEquals(true, subsegment.isError()); + Assert.assertEquals(false, subsegment.isThrottle()); + Assert.assertEquals(false, subsegment.isFault()); + Assert.assertEquals(1, cause.getExceptions().size()); + Assert.assertEquals(true, cause.getExceptions().get(0).isRemote()); + } + } + + @Test + public void testThrottledException() throws Exception { + SdkHttpClient mockClient = mockSdkHttpClient(generateLambdaInvokeResponse(429)); + + LambdaClient client = LambdaClient.builder() + .httpClient(mockClient) + .endpointOverride(URI.create("http://example.com")) + .region(Region.of("us-west-42")) + .credentialsProvider(StaticCredentialsProvider.create( + AwsSessionCredentials.create("key", "secret", "session") + )) + .overrideConfiguration(ClientOverrideConfiguration.builder() + .addExecutionInterceptor(new TracingInterceptor()) + .build() + ) + .build(); + + + Segment segment = AWSXRay.getCurrentSegment(); + + try { + client.invoke(InvokeRequest.builder() + .functionName("testFunctionName") + .build() + ); + } catch (Exception e) { + // ignore SDK errors + } finally { + Assert.assertEquals(1, segment.getSubsegments().size()); + Subsegment subsegment = segment.getSubsegments().get(0); + Map awsStats = subsegment.getAws(); + Map httpResponseStats = (Map)subsegment.getHttp().get("response"); + Cause cause = subsegment.getCause(); + + Assert.assertEquals("Invoke", awsStats.get("operation")); + Assert.assertEquals("testFunctionName", awsStats.get("function_name")); + Assert.assertEquals("1111-2222-3333-4444", awsStats.get("request_id")); + Assert.assertEquals("extended", awsStats.get("id_2")); + Assert.assertEquals("us-west-42", awsStats.get("region")); + // throttled requests are retried + Assert.assertEquals(3, awsStats.get("retries")); + Assert.assertEquals(2L, httpResponseStats.get("content_length")); + Assert.assertEquals(429, httpResponseStats.get("status")); + Assert.assertEquals(true, subsegment.isError()); + Assert.assertEquals(true, subsegment.isThrottle()); + Assert.assertEquals(false, subsegment.isFault()); + Assert.assertEquals(1, cause.getExceptions().size()); + Assert.assertEquals(true, cause.getExceptions().get(0).isRemote()); + } + } + + @Test + public void testAsyncThrottledException() { + SdkAsyncHttpClient mockClient = mockSdkAsyncHttpClient(generateLambdaInvokeResponse(429)); + + LambdaAsyncClient client = LambdaAsyncClient.builder() + .httpClient(mockClient) + .endpointOverride(URI.create("http://example.com")) + .region(Region.of("us-west-42")) + .credentialsProvider(StaticCredentialsProvider.create( + AwsSessionCredentials.create("key", "secret", "session") + )) + .overrideConfiguration(ClientOverrideConfiguration.builder() + .addExecutionInterceptor(new TracingInterceptor()) + .build() + ) + .build(); + + Segment segment = AWSXRay.getCurrentSegment(); + + try { + client.invoke(InvokeRequest.builder() + .functionName("testFunctionName") + .build() + ).get(); + } catch (Exception e) { + // ignore exceptions + } finally { + Assert.assertEquals(1, segment.getSubsegments().size()); + Subsegment subsegment = segment.getSubsegments().get(0); + Map awsStats = subsegment.getAws(); + Map httpResponseStats = (Map)subsegment.getHttp().get("response"); + Cause cause = subsegment.getCause(); + + Assert.assertEquals("Invoke", awsStats.get("operation")); + Assert.assertEquals("testFunctionName", awsStats.get("function_name")); + Assert.assertEquals("1111-2222-3333-4444", awsStats.get("request_id")); + Assert.assertEquals("extended", awsStats.get("id_2")); + Assert.assertEquals("us-west-42", awsStats.get("region")); + // throttled requests are retried + Assert.assertEquals(3, awsStats.get("retries")); + Assert.assertEquals(2L, httpResponseStats.get("content_length")); + Assert.assertEquals(429, httpResponseStats.get("status")); + Assert.assertEquals(true, subsegment.isError()); + Assert.assertEquals(true, subsegment.isThrottle()); + Assert.assertEquals(false, subsegment.isFault()); + Assert.assertEquals(1, cause.getExceptions().size()); + Assert.assertEquals(true, cause.getExceptions().get(0).isRemote()); + } + } + + @Test + public void test500Exception() throws Exception { + SdkHttpClient mockClient = mockSdkHttpClient(generateLambdaInvokeResponse(500)); + + LambdaClient client = LambdaClient.builder() + .httpClient(mockClient) + .endpointOverride(URI.create("http://example.com")) + .region(Region.of("us-west-42")) + .credentialsProvider(StaticCredentialsProvider.create( + AwsSessionCredentials.create("key", "secret", "session") + )) + .overrideConfiguration(ClientOverrideConfiguration.builder() + .addExecutionInterceptor(new TracingInterceptor()) + .build() + ) + .build(); + + + Segment segment = AWSXRay.getCurrentSegment(); + + try { + client.invoke(InvokeRequest.builder() + .functionName("testFunctionName") + .build() + ); + } catch (Exception e) { + // ignore SDK errors + } finally { + Assert.assertEquals(1, segment.getSubsegments().size()); + Subsegment subsegment = segment.getSubsegments().get(0); + Map awsStats = subsegment.getAws(); + Map httpResponseStats = (Map)subsegment.getHttp().get("response"); + Cause cause = subsegment.getCause(); + + Assert.assertEquals("Invoke", awsStats.get("operation")); + Assert.assertEquals("testFunctionName", awsStats.get("function_name")); + Assert.assertEquals("1111-2222-3333-4444", awsStats.get("request_id")); + Assert.assertEquals("extended", awsStats.get("id_2")); + Assert.assertEquals("us-west-42", awsStats.get("region")); + // 500 exceptions are retried + Assert.assertEquals(3, awsStats.get("retries")); + Assert.assertEquals(2L, httpResponseStats.get("content_length")); + Assert.assertEquals(500, httpResponseStats.get("status")); + Assert.assertEquals(false, subsegment.isError()); + Assert.assertEquals(false, subsegment.isThrottle()); + Assert.assertEquals(true, subsegment.isFault()); + Assert.assertEquals(1, cause.getExceptions().size()); + Assert.assertEquals(true, cause.getExceptions().get(0).isRemote()); + } + } + + @Test + public void testAsync500Exception() { + SdkAsyncHttpClient mockClient = mockSdkAsyncHttpClient(generateLambdaInvokeResponse(500)); + + LambdaAsyncClient client = LambdaAsyncClient.builder() + .httpClient(mockClient) + .endpointOverride(URI.create("http://example.com")) + .region(Region.of("us-west-42")) + .credentialsProvider(StaticCredentialsProvider.create( + AwsSessionCredentials.create("key", "secret", "session") + )) + .overrideConfiguration(ClientOverrideConfiguration.builder() + .addExecutionInterceptor(new TracingInterceptor()) + .build() + ) + .build(); + + Segment segment = AWSXRay.getCurrentSegment(); + + try { + client.invoke(InvokeRequest.builder() + .functionName("testFunctionName") + .build() + ).get(); + } catch (Exception e) { + // ignore exceptions + } finally { + Assert.assertEquals(1, segment.getSubsegments().size()); + Subsegment subsegment = segment.getSubsegments().get(0); + Map awsStats = subsegment.getAws(); + Map httpResponseStats = (Map)subsegment.getHttp().get("response"); + Cause cause = subsegment.getCause(); + + Assert.assertEquals("Invoke", awsStats.get("operation")); + Assert.assertEquals("testFunctionName", awsStats.get("function_name")); + Assert.assertEquals("1111-2222-3333-4444", awsStats.get("request_id")); + Assert.assertEquals("extended", awsStats.get("id_2")); + Assert.assertEquals("us-west-42", awsStats.get("region")); + // 500 exceptions are retried + Assert.assertEquals(3, awsStats.get("retries")); + Assert.assertEquals(2L, httpResponseStats.get("content_length")); + Assert.assertEquals(500, httpResponseStats.get("status")); + Assert.assertEquals(false, subsegment.isError()); + Assert.assertEquals(false, subsegment.isThrottle()); + Assert.assertEquals(true, subsegment.isFault()); + Assert.assertEquals(1, cause.getExceptions().size()); + Assert.assertEquals(true, cause.getExceptions().get(0).isRemote()); + } + } +} + diff --git a/aws-xray-recorder-sdk-aws-sdk/pom.xml b/aws-xray-recorder-sdk-aws-sdk/pom.xml index e7733538..bf16ab7e 100644 --- a/aws-xray-recorder-sdk-aws-sdk/pom.xml +++ b/aws-xray-recorder-sdk-aws-sdk/pom.xml @@ -36,7 +36,7 @@ com.amazonaws - aws-xray-recorder-sdk-core + aws-xray-recorder-sdk-aws-sdk-core ${awsxrayrecordersdk.version} diff --git a/aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/TracingHandler.java b/aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/TracingHandler.java index 3736122d..5de23e45 100644 --- a/aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/TracingHandler.java +++ b/aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/TracingHandler.java @@ -8,6 +8,12 @@ import java.util.Map; import java.util.Optional; +import com.amazonaws.xray.entities.Entity; +import com.amazonaws.xray.entities.EntityDataKeys; +import com.amazonaws.xray.entities.EntityHeaderKeys; +import com.amazonaws.xray.entities.Namespace; +import com.amazonaws.xray.entities.Subsegment; +import com.amazonaws.xray.entities.TraceHeader; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -23,10 +29,6 @@ import com.amazonaws.retry.RetryUtils; import com.amazonaws.xray.AWSXRay; import com.amazonaws.xray.AWSXRayRecorder; -import com.amazonaws.xray.entities.Entity; -import com.amazonaws.xray.entities.Namespace; -import com.amazonaws.xray.entities.Subsegment; -import com.amazonaws.xray.entities.TraceHeader; import com.amazonaws.xray.entities.TraceHeader.SampleDecision; import com.amazonaws.xray.handlers.config.AWSOperationHandler; import com.amazonaws.xray.handlers.config.AWSOperationHandlerManifest; @@ -55,20 +57,12 @@ public class TracingHandler extends RequestHandler2 { private static final String S3_SERVICE_NAME = "Amazon S3"; private static final String S3_PRESIGN_REQUEST = "GeneratePresignedUrl"; private static final String S3_REQUEST_ID_HEADER_KEY = "x-amz-request-id"; - private static final String S3_EXTENDED_REQUEST_ID_HEADER_KEY = "x-amz-id-2"; - private static final String CONTENT_LENGTH_HEADER_KEY = "Content-Length"; private static final String XRAY_SERVICE_NAME = "AWSXRay"; private static final String XRAY_SAMPLING_RULE_REQUEST = "GetSamplingRules"; private static final String XRAY_SAMPLING_TARGET_REQUEST = "GetSamplingTargets"; private static final String REQUEST_ID_SUBSEGMENT_KEY = "request_id"; - private static final String EXTENDED_REQUEST_ID_SUBSEGMENT_KEY = "id_2"; - private static final String CONTENT_LENGTH_SUBSEGMENT_KEY = "content_length"; - private static final String STATUS_SUBSEGMENT_KEY = "status"; - private static final String OPERATION_SUBSEGMENT_KEY = "operation"; - private static final String ACCOUNT_ID_SUBSEGMENT_KEY = "account_id"; - private static final String RESPONSE_SUBSEGMENT_KEY = "response"; private final String accountId; @@ -167,9 +161,9 @@ public void beforeRequest(Request request) { return; } currentSubsegment.putAllAws(extractRequestParameters(request)); - currentSubsegment.putAws(OPERATION_SUBSEGMENT_KEY, operationName); + currentSubsegment.putAws(EntityDataKeys.AWS.OPERATION_KEY, operationName); if (null != accountId) { - currentSubsegment.putAws(ACCOUNT_ID_SUBSEGMENT_KEY, accountId); + currentSubsegment.putAws(EntityDataKeys.AWS.ACCOUNT_ID_SUBSEGMENT_KEY, accountId); } currentSubsegment.setNamespace(Namespace.AWS.toString()); @@ -312,16 +306,16 @@ private HashMap extractHttpResponseInformation(AmazonServiceExce HashMap ret = new HashMap<>(); HashMap response = new HashMap<>(); - response.put(STATUS_SUBSEGMENT_KEY, ase.getStatusCode()); + response.put(EntityDataKeys.HTTP.STATUS_CODE_KEY, ase.getStatusCode()); try { - if (null != ase.getHttpHeaders() && null != ase.getHttpHeaders().get(CONTENT_LENGTH_HEADER_KEY)) { - response.put(CONTENT_LENGTH_SUBSEGMENT_KEY, Long.parseLong(ase.getHttpHeaders().get(CONTENT_LENGTH_HEADER_KEY))); + if (null != ase.getHttpHeaders() && null != ase.getHttpHeaders().get(EntityHeaderKeys.HTTP.CONTENT_LENGTH_HEADER)) { + response.put(EntityDataKeys.HTTP.CONTENT_LENGTH_KEY, Long.parseLong(ase.getHttpHeaders().get(EntityHeaderKeys.HTTP.CONTENT_LENGTH_HEADER))); } } catch (NumberFormatException nfe) { logger.warn("Unable to parse Content-Length header.", nfe); } - ret.put(RESPONSE_SUBSEGMENT_KEY, response); + ret.put(EntityDataKeys.HTTP.RESPONSE_KEY, response); return ret; } @@ -329,16 +323,16 @@ private HashMap extractHttpResponseInformation(HttpResponse http HashMap ret = new HashMap<>(); HashMap response = new HashMap<>(); - response.put(STATUS_SUBSEGMENT_KEY, httpResponse.getStatusCode()); + response.put(EntityDataKeys.HTTP.STATUS_CODE_KEY, httpResponse.getStatusCode()); try { - if (null != httpResponse.getHeaders().get(CONTENT_LENGTH_HEADER_KEY) ) { - response.put(CONTENT_LENGTH_SUBSEGMENT_KEY, Long.parseLong(httpResponse.getHeaders().get(CONTENT_LENGTH_HEADER_KEY))); + if (null != httpResponse.getHeaders().get(EntityHeaderKeys.HTTP.CONTENT_LENGTH_HEADER) ) { + response.put(EntityDataKeys.HTTP.CONTENT_LENGTH_KEY, Long.parseLong(httpResponse.getHeaders().get(EntityHeaderKeys.HTTP.CONTENT_LENGTH_HEADER))); } } catch (NumberFormatException nfe) { logger.warn("Unable to parse Content-Length header.", nfe); } - ret.put(RESPONSE_SUBSEGMENT_KEY, response); + ret.put(EntityDataKeys.HTTP.RESPONSE_KEY, response); return ret; } @@ -422,8 +416,8 @@ private void populateAndEndSubsegment(Subsegment currentSubsegment, Request r if (null != response.getHttpResponse().getHeader(S3_REQUEST_ID_HEADER_KEY)) { currentSubsegment.putAws(REQUEST_ID_SUBSEGMENT_KEY, response.getHttpResponse().getHeader(S3_REQUEST_ID_HEADER_KEY)); } - if (null != response.getHttpResponse().getHeader(S3_EXTENDED_REQUEST_ID_HEADER_KEY)) { - currentSubsegment.putAws(EXTENDED_REQUEST_ID_SUBSEGMENT_KEY, response.getHttpResponse().getHeader(S3_EXTENDED_REQUEST_ID_HEADER_KEY)); + if (null != response.getHttpResponse().getHeader(EntityHeaderKeys.AWS.EXTENDED_REQUEST_ID_HEADER)) { + currentSubsegment.putAws(EntityDataKeys.AWS.EXTENDED_REQUEST_ID_KEY, response.getHttpResponse().getHeader(EntityHeaderKeys.AWS.EXTENDED_REQUEST_ID_HEADER)); } } currentSubsegment.putAllAws(extractResponseParameters(request, response.getAwsResponse())); @@ -441,8 +435,8 @@ private void populateAndEndSubsegment(Subsegment currentSubsegment, Request r if (null != ase.getRequestId()) { currentSubsegment.putAws(REQUEST_ID_SUBSEGMENT_KEY, ase.getRequestId()); } - if (null != ase.getHttpHeaders() && null != ase.getHttpHeaders().get(S3_EXTENDED_REQUEST_ID_HEADER_KEY)) { - currentSubsegment.putAws(EXTENDED_REQUEST_ID_SUBSEGMENT_KEY, ase.getHttpHeaders().get(S3_EXTENDED_REQUEST_ID_HEADER_KEY)); + if (null != ase.getHttpHeaders() && null != ase.getHttpHeaders().get(EntityHeaderKeys.AWS.EXTENDED_REQUEST_ID_HEADER)) { + currentSubsegment.putAws(EntityDataKeys.AWS.EXTENDED_REQUEST_ID_KEY, ase.getHttpHeaders().get(EntityHeaderKeys.AWS.EXTENDED_REQUEST_ID_HEADER)); } if (null != ase.getErrorMessage()) { currentSubsegment.getCause().setMessage(ase.getErrorMessage()); diff --git a/aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandler.java b/aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandler.java deleted file mode 100644 index b753cfea..00000000 --- a/aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandler.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.amazonaws.xray.handlers.config; - -import java.util.HashMap; -import java.util.HashSet; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class AWSOperationHandler { - @JsonProperty - private HashMap requestDescriptors; - - @JsonProperty - private HashSet requestParameters; - - @JsonProperty - private HashMap responseDescriptors; - - @JsonProperty - private HashSet responseParameters; - - @JsonProperty - private String requestIdHeader; - - /** - * @return the requestKeys - */ - public HashMap getRequestDescriptors() { - return requestDescriptors; - } - - /** - * @return the requestParameters - */ - public HashSet getRequestParameters() { - return requestParameters; - } - - /** - * @return the responseDescriptors - */ - public HashMap getResponseDescriptors() { - return responseDescriptors; - } - - /** - * @return the responseParameters - */ - public HashSet getResponseParameters() { - return responseParameters; - } - - /** - * @return the requestIdHeader - */ - public String getRequestIdHeader() { - return requestIdHeader; - } -} diff --git a/aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandlerManifest.java b/aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandlerManifest.java deleted file mode 100644 index ba5b3b73..00000000 --- a/aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandlerManifest.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.amazonaws.xray.handlers.config; - -import java.util.Map; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class AWSOperationHandlerManifest { - @JsonProperty - private Map operations; - - public AWSOperationHandler getOperationHandler(String operationRequestClassName) { - return operations.get(operationRequestClassName); - } -} diff --git a/aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandlerRequestDescriptor.java b/aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandlerRequestDescriptor.java deleted file mode 100644 index 622ee5f7..00000000 --- a/aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandlerRequestDescriptor.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.amazonaws.xray.handlers.config; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class AWSOperationHandlerRequestDescriptor { - @JsonProperty - private String renameTo; - - @JsonProperty - private boolean map = false; - - @JsonProperty - private boolean list = false; - - @JsonProperty - private boolean getKeys = false; - - @JsonProperty - private boolean getCount = false; - - /** - * @return the renameTo - */ - public String getRenameTo() { - return renameTo; - } - - /** - * @return the map - */ - public boolean isMap() { - return map; - } - - /** - * @return the list - */ - public boolean isList() { - return list; - } - - /** - * @return the getCount - */ - public boolean shouldGetCount() { - return getCount; - } - - /** - * @return the getKeys - */ - public boolean shouldGetKeys() { - return getKeys; - } -} diff --git a/aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandlerResponseDescriptor.java b/aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandlerResponseDescriptor.java deleted file mode 100644 index 8f6a6434..00000000 --- a/aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/config/AWSOperationHandlerResponseDescriptor.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.amazonaws.xray.handlers.config; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class AWSOperationHandlerResponseDescriptor { - @JsonProperty - private String renameTo; - - @JsonProperty - private boolean map = false; - - @JsonProperty - private boolean list = false; - - @JsonProperty - private boolean getKeys = false; - - @JsonProperty - private boolean getCount = false; - - /** - * @return the renameTo - */ - public String getRenameTo() { - return renameTo; - } - - /** - * @return the map - */ - public boolean isMap() { - return map; - } - - /** - * @return the list - */ - public boolean isList() { - return list; - } - - /** - * @return the getCount - */ - public boolean shouldGetCount() { - return getCount; - } - - /** - * @return the getKeys - */ - public boolean shouldGetKeys() { - return getKeys; - } -} diff --git a/aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/config/AWSServiceHandler.java b/aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/config/AWSServiceHandler.java deleted file mode 100644 index 248570b5..00000000 --- a/aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/config/AWSServiceHandler.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.amazonaws.xray.handlers.config; - -import java.util.Map; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class AWSServiceHandler { - @JsonProperty - private Map operations; - - public AWSOperationHandler getOperationHandler(String operationName) { - return operations.get(operationName); - } -} diff --git a/aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/config/AWSServiceHandlerManifest.java b/aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/config/AWSServiceHandlerManifest.java deleted file mode 100644 index 587630fc..00000000 --- a/aws-xray-recorder-sdk-aws-sdk/src/main/java/com/amazonaws/xray/handlers/config/AWSServiceHandlerManifest.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.amazonaws.xray.handlers.config; - -import java.util.Map; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class AWSServiceHandlerManifest { - @JsonProperty - private Map services; - - public AWSOperationHandlerManifest getOperationHandlerManifest(String serviceName) { - return services.get(serviceName); - } -} diff --git a/aws-xray-recorder-sdk-core/src/main/java/com/amazonaws/xray/AWSXRayRecorder.java b/aws-xray-recorder-sdk-core/src/main/java/com/amazonaws/xray/AWSXRayRecorder.java index b80648e8..b70ce088 100644 --- a/aws-xray-recorder-sdk-core/src/main/java/com/amazonaws/xray/AWSXRayRecorder.java +++ b/aws-xray-recorder-sdk-core/src/main/java/com/amazonaws/xray/AWSXRayRecorder.java @@ -403,6 +403,36 @@ public void endSegment() { } + /** + * Ends the provided subsegment. This method doesn't touch context storage. + * + * @param subsegment + * the subsegment to close. + */ + public void endSubsegment(Subsegment subsegment) { + if(subsegment == null) { + logger.debug("No input subsegment to end. No-op."); + return; + } + boolean rootReady = subsegment.end(); + // First handling the special case where its direct parent is a facade segment + if(subsegment.getParent() instanceof FacadeSegment) { + if(((FacadeSegment) subsegment.getParent()).isSampled()) { + getEmitter().sendSubsegment(subsegment); + } + return; + } + // Otherwise we check the happy case where the entire segment is ready + if (rootReady && !(subsegment.getParentSegment() instanceof FacadeSegment)) { + sendSegment(subsegment.getParentSegment()); + return; + } + // If not we try to stream closed subsegments regardless the root segment is facade or real + if (this.getStreamingStrategy().requiresStreaming(subsegment.getParentSegment())) { + this.getStreamingStrategy().streamSome(subsegment.getParentSegment(), this.getEmitter()); + } + } + /** * Begins a subsegment. * diff --git a/aws-xray-recorder-sdk-core/src/main/java/com/amazonaws/xray/entities/EntityDataKeys.java b/aws-xray-recorder-sdk-core/src/main/java/com/amazonaws/xray/entities/EntityDataKeys.java new file mode 100644 index 00000000..8f02adb2 --- /dev/null +++ b/aws-xray-recorder-sdk-core/src/main/java/com/amazonaws/xray/entities/EntityDataKeys.java @@ -0,0 +1,31 @@ +package com.amazonaws.xray.entities; + +public final class EntityDataKeys { + + private EntityDataKeys() { + + } + + public static final class AWS { + private AWS() { + + } + + public static final String ACCOUNT_ID_SUBSEGMENT_KEY = "account_id"; + public static final String EXTENDED_REQUEST_ID_KEY = "id_2"; + public static final String OPERATION_KEY = "operation"; + public static final String REGION_KEY = "region"; + public static final String REQUEST_ID_KEY = "request_id"; + public static final String RETRIES_KEY = "retries"; + } + + public static final class HTTP { + private HTTP() { + + } + + public static final String CONTENT_LENGTH_KEY = "content_length"; + public static final String RESPONSE_KEY = "response"; + public static final String STATUS_CODE_KEY = "status"; + } +} diff --git a/aws-xray-recorder-sdk-core/src/main/java/com/amazonaws/xray/entities/EntityHeaderKeys.java b/aws-xray-recorder-sdk-core/src/main/java/com/amazonaws/xray/entities/EntityHeaderKeys.java new file mode 100644 index 00000000..92f2e0be --- /dev/null +++ b/aws-xray-recorder-sdk-core/src/main/java/com/amazonaws/xray/entities/EntityHeaderKeys.java @@ -0,0 +1,23 @@ +package com.amazonaws.xray.entities; + +public final class EntityHeaderKeys { + private EntityHeaderKeys() { + + } + + public static final class AWS { + private AWS() { + + } + + public static final String EXTENDED_REQUEST_ID_HEADER = "x-amz-id-2"; + } + + public static final class HTTP { + private HTTP() { + + } + + public static final String CONTENT_LENGTH_HEADER = "Content-Length"; + } +} diff --git a/aws-xray-recorder-sdk-core/src/test/java/com/amazonaws/xray/AWSXRayRecorderTest.java b/aws-xray-recorder-sdk-core/src/test/java/com/amazonaws/xray/AWSXRayRecorderTest.java index c95405aa..9faf6d49 100644 --- a/aws-xray-recorder-sdk-core/src/test/java/com/amazonaws/xray/AWSXRayRecorderTest.java +++ b/aws-xray-recorder-sdk-core/src/test/java/com/amazonaws/xray/AWSXRayRecorderTest.java @@ -250,6 +250,19 @@ public void testSegmentEmitted() { Mockito.verify(mockEmitter, Mockito.times(1)).sendSegment(Mockito.any()); } + @Test + public void testExplicitSubsegmentEmitted() { + Emitter mockEmitter = Mockito.mock(UDPEmitter.class); + AWSXRayRecorder recorder = AWSXRayRecorderBuilder.standard().withEmitter(mockEmitter).build(); + + recorder.beginSegment("test"); + Subsegment subsegment = recorder.beginSubsegment("test"); + recorder.endSubsegment(subsegment); + recorder.endSegment(); + + Mockito.verify(mockEmitter, Mockito.times(1)).sendSegment(Mockito.any()); + } + @Test public void testDummySegmentNotEmitted() { Emitter mockEmitter = Mockito.mock(UDPEmitter.class); diff --git a/pom.xml b/pom.xml index c71448d7..c6fa6af8 100644 --- a/pom.xml +++ b/pom.xml @@ -37,6 +37,9 @@ aws-xray-recorder-sdk-sql-mysql aws-xray-recorder-sdk-sql-postgres aws-xray-recorder-sdk-spring + aws-xray-recorder-sdk-aws-sdk-core + aws-xray-recorder-sdk-aws-sdk-v2 + aws-xray-recorder-sdk-aws-sdk-v2-instrumentor From a1b4de15b453137df8bcb7b02954ada004287418 Mon Sep 17 00:00:00 2001 From: Christopher Radek Date: Mon, 28 Jan 2019 16:50:24 -0800 Subject: [PATCH 3/3] makes aws-sdk-v2 instrumentation expicitly depend on aws-sdk-v2 --- .../pom.xml | 8 +++++++- aws-xray-recorder-sdk-aws-sdk-v2/pom.xml | 15 +++++++++++++-- .../xray/interceptors/TracingInterceptorTest.java | 7 ++++++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/aws-xray-recorder-sdk-aws-sdk-v2-instrumentor/pom.xml b/aws-xray-recorder-sdk-aws-sdk-v2-instrumentor/pom.xml index f1eb89b3..f7999e2b 100644 --- a/aws-xray-recorder-sdk-aws-sdk-v2-instrumentor/pom.xml +++ b/aws-xray-recorder-sdk-aws-sdk-v2-instrumentor/pom.xml @@ -35,5 +35,11 @@ - + + + com.amazonaws + aws-xray-recorder-sdk-aws-sdk-v2 + ${awsxrayrecordersdk.version} + + \ No newline at end of file diff --git a/aws-xray-recorder-sdk-aws-sdk-v2/pom.xml b/aws-xray-recorder-sdk-aws-sdk-v2/pom.xml index 19485889..61884316 100644 --- a/aws-xray-recorder-sdk-aws-sdk-v2/pom.xml +++ b/aws-xray-recorder-sdk-aws-sdk-v2/pom.xml @@ -41,6 +41,11 @@ aws-xray-recorder-sdk-aws-sdk-core ${awsxrayrecordersdk.version} + + software.amazon.awssdk + aws-core + 2.2.0 + junit @@ -62,9 +67,15 @@ software.amazon.awssdk - aws-sdk-java + dynamodb + 2.2.0 + test + + + software.amazon.awssdk + lambda 2.2.0 - provided + test \ No newline at end of file diff --git a/aws-xray-recorder-sdk-aws-sdk-v2/src/test/java/com/amazonaws/xray/interceptors/TracingInterceptorTest.java b/aws-xray-recorder-sdk-aws-sdk-v2/src/test/java/com/amazonaws/xray/interceptors/TracingInterceptorTest.java index 109e852d..bdbb07cd 100644 --- a/aws-xray-recorder-sdk-aws-sdk-v2/src/test/java/com/amazonaws/xray/interceptors/TracingInterceptorTest.java +++ b/aws-xray-recorder-sdk-aws-sdk-v2/src/test/java/com/amazonaws/xray/interceptors/TracingInterceptorTest.java @@ -21,7 +21,12 @@ import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.core.async.EmptyPublisher; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; -import software.amazon.awssdk.http.*; + +import software.amazon.awssdk.http.AbortableInputStream; +import software.amazon.awssdk.http.ExecutableHttpRequest; +import software.amazon.awssdk.http.HttpExecuteResponse; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.SdkHttpResponse; import software.amazon.awssdk.http.async.AsyncExecuteRequest; import software.amazon.awssdk.http.async.SdkAsyncHttpClient; import software.amazon.awssdk.http.async.SdkAsyncHttpResponseHandler;