diff --git a/.changes/next-release/feature-AWSSDKforJavav2-00d47cf.json b/.changes/next-release/feature-AWSSDKforJavav2-00d47cf.json new file mode 100644 index 000000000000..0df1f91f13f3 --- /dev/null +++ b/.changes/next-release/feature-AWSSDKforJavav2-00d47cf.json @@ -0,0 +1,5 @@ +{ + "category": "AWS SDK for Java v2", + "type": "feature", + "description": "Expose the `extendedRequestId` from `SdkServiceException`, so it can be provided to support to investigate issues." +} diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/exception/AwsServiceException.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/exception/AwsServiceException.java index ac8146aef8fc..9f93a307baa2 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/exception/AwsServiceException.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/exception/AwsServiceException.java @@ -65,7 +65,8 @@ public String getMessage() { return awsErrorDetails().errorMessage() + " (Service: " + awsErrorDetails().serviceName() + ", Status Code: " + statusCode() + - ", Request ID: " + requestId() + ")"; + ", Request ID: " + requestId() + + ", Extended Request ID: " + extendedRequestId() + ")"; } return super.getMessage(); @@ -164,6 +165,9 @@ public interface Builder extends SdkServiceException.Builder { @Override Builder requestId(String requestId); + @Override + Builder extendedRequestId(String extendedRequestId); + @Override Builder statusCode(int statusCode); @@ -232,6 +236,12 @@ public Builder requestId(String requestId) { return this; } + @Override + public Builder extendedRequestId(String extendedRequestId) { + this.extendedRequestId = extendedRequestId; + return this; + } + @Override public Builder statusCode(int statusCode) { this.statusCode = statusCode; diff --git a/core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/unmarshall/AwsJsonProtocolErrorUnmarshaller.java b/core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/unmarshall/AwsJsonProtocolErrorUnmarshaller.java index ac1de74e5c37..62f77d8f3804 100644 --- a/core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/unmarshall/AwsJsonProtocolErrorUnmarshaller.java +++ b/core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/unmarshall/AwsJsonProtocolErrorUnmarshaller.java @@ -85,6 +85,7 @@ private AwsServiceException unmarshall(SdkHttpFullResponse response, ExecutionAt exception.message(errorMessage); exception.statusCode(statusCode(response, modeledExceptionMetadata)); exception.requestId(getRequestIdFromHeaders(response.headers())); + exception.extendedRequestId(getExtendedRequestIdFromHeaders(response.headers())); return exception.build(); } @@ -136,6 +137,10 @@ private String getRequestIdFromHeaders(Map> headers) { return SdkHttpUtils.firstMatchingHeader(headers, X_AMZN_REQUEST_ID_HEADER).orElse(null); } + private String getExtendedRequestIdFromHeaders(Map> headers) { + return SdkHttpUtils.firstMatchingHeader(headers, X_AMZ_ID_2_HEADER).orElse(null); + } + public static Builder builder() { return new Builder(); } diff --git a/core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/unmarshall/JsonResponseHandler.java b/core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/unmarshall/JsonResponseHandler.java index fbf0e001069f..4af3e85d0717 100644 --- a/core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/unmarshall/JsonResponseHandler.java +++ b/core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/unmarshall/JsonResponseHandler.java @@ -74,6 +74,9 @@ public T handle(SdkHttpFullResponse response, ExecutionAttributes executionAttri response.firstMatchingHeader(X_AMZN_REQUEST_ID_HEADER) .orElse("not available")); + SdkStandardLogger.REQUEST_ID_LOGGER.debug(() -> X_AMZ_ID_2_HEADER + " : " + + response.firstMatchingHeader(X_AMZ_ID_2_HEADER) + .orElse("not available")); try { T result = unmarshaller.unmarshall(pojoSupplier.apply(response), response); diff --git a/core/protocols/aws-query-protocol/src/main/java/software/amazon/awssdk/protocols/query/internal/unmarshall/AwsXmlErrorUnmarshaller.java b/core/protocols/aws-query-protocol/src/main/java/software/amazon/awssdk/protocols/query/internal/unmarshall/AwsXmlErrorUnmarshaller.java index 1ff4f6f44ce7..b94d2e3ca48c 100644 --- a/core/protocols/aws-query-protocol/src/main/java/software/amazon/awssdk/protocols/query/internal/unmarshall/AwsXmlErrorUnmarshaller.java +++ b/core/protocols/aws-query-protocol/src/main/java/software/amazon/awssdk/protocols/query/internal/unmarshall/AwsXmlErrorUnmarshaller.java @@ -40,6 +40,7 @@ @SdkInternalApi public final class AwsXmlErrorUnmarshaller { private static final String X_AMZN_REQUEST_ID_HEADER = "x-amzn-RequestId"; + private static final String X_AMZ_ID_2_HEADER = "x-amz-id-2"; private final List exceptions; private final Supplier defaultExceptionSupplier; @@ -90,6 +91,7 @@ public AwsServiceException unmarshall(XmlElement documentRoot, .build(); builder.requestId(getRequestId(response, documentRoot)) + .extendedRequestId(getExtendedRequestId(response)) .statusCode(response.statusCode()) .clockSkew(getClockSkew(executionAttributes)) .awsErrorDetails(awsErrorDetails); @@ -176,6 +178,16 @@ private String getRequestId(SdkHttpFullResponse response, XmlElement document) { response.firstMatchingHeader(X_AMZN_REQUEST_ID_HEADER).orElse(null); } + /** + * Extracts the extended request ID from the response headers. + * + * @param response The HTTP response object. + * @return Extended Request ID string or null if not present. + */ + private String getExtendedRequestId(SdkHttpFullResponse response) { + return response.firstMatchingHeader(X_AMZ_ID_2_HEADER).orElse(null); + } + /** * Builder for {@link AwsXmlErrorUnmarshaller}. */ diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/exception/SdkServiceException.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/exception/SdkServiceException.java index ddf13cf5a15f..134e0b501f25 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/exception/SdkServiceException.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/exception/SdkServiceException.java @@ -41,11 +41,13 @@ public class SdkServiceException extends SdkException implements SdkPojo { private final String requestId; + private final String extendedRequestId; private final int statusCode; protected SdkServiceException(Builder b) { super(b); this.requestId = b.requestId(); + this.extendedRequestId = b.extendedRequestId(); this.statusCode = b.statusCode(); } @@ -57,6 +59,14 @@ public String requestId() { return requestId; } + /** + * The extendedRequestId that was returned by the called service. + * @return String ctontaining the extendedRequestId + */ + public String extendedRequestId() { + return extendedRequestId; + } + /** * The status code that was returned by the called service. * @return int containing the status code. @@ -127,6 +137,21 @@ public interface Builder extends SdkException.Builder, SdkPojo { */ String requestId(); + /** + * Specifies the extendedRequestId returned by the called service. + * + * @param extendedRequestId A string that identifies the request made to a service. + * @return This object for method chaining. + */ + Builder extendedRequestId(String extendedRequestId); + + /** + * The extendedRequestId returned by the called service. + * + * @return String containing the extendedRequestId + */ + String extendedRequestId(); + /** * Specifies the status code returned by the service. * @@ -153,6 +178,7 @@ public interface Builder extends SdkException.Builder, SdkPojo { protected static class BuilderImpl extends SdkException.BuilderImpl implements Builder { protected String requestId; + protected String extendedRequestId; protected int statusCode; protected BuilderImpl() { @@ -161,6 +187,7 @@ protected BuilderImpl() { protected BuilderImpl(SdkServiceException ex) { super(ex); this.requestId = ex.requestId(); + this.extendedRequestId = ex.extendedRequestId(); this.statusCode = ex.statusCode(); } @@ -182,6 +209,12 @@ public Builder requestId(String requestId) { return this; } + @Override + public Builder extendedRequestId(String extendedRequestId) { + this.extendedRequestId = extendedRequestId; + return this; + } + @Override public String requestId() { return requestId; @@ -195,6 +228,19 @@ public void setRequestId(String requestId) { this.requestId = requestId; } + @Override + public String extendedRequestId() { + return extendedRequestId; + } + + public String getExtendedRequestId() { + return extendedRequestId; + } + + public void setExtendedRequestId(String extendedRequestId) { + this.extendedRequestId = extendedRequestId; + } + @Override public Builder statusCode(int statusCode) { this.statusCode = statusCode; diff --git a/test/protocol-tests/src/test/java/software/amazon/awssdk/protocol/tests/exception/AwsJsonExceptionTest.java b/test/protocol-tests/src/test/java/software/amazon/awssdk/protocol/tests/exception/AwsJsonExceptionTest.java index a988c735bb1c..7797845a9168 100644 --- a/test/protocol-tests/src/test/java/software/amazon/awssdk/protocol/tests/exception/AwsJsonExceptionTest.java +++ b/test/protocol-tests/src/test/java/software/amazon/awssdk/protocol/tests/exception/AwsJsonExceptionTest.java @@ -126,6 +126,29 @@ public void modeledException_HasExceptionMetadataSet() { assertThat(awsErrorDetails.serviceName()).isEqualTo("ProtocolJsonRpc"); assertThat(awsErrorDetails.sdkHttpResponse()).isNotNull(); assertThat(e.requestId()).isEqualTo("1234"); + assertThat(e.extendedRequestId()).isNull(); + assertThat(e.statusCode()).isEqualTo(404); + } + } + + @Test + public void modeledException_HasExceptionMetadataIncludingExtendedRequestIdSet() { + stubFor(post(urlEqualTo(PATH)).willReturn( + aResponse() + .withStatus(404) + .withHeader("x-amzn-RequestId", "1234") + .withHeader("x-amz-id-2", "5678") + .withBody("{\"__type\": \"EmptyModeledException\", \"Message\": \"This is the service message\"}"))); + try { + client.allTypes(); + } catch (EmptyModeledException e) { + AwsErrorDetails awsErrorDetails = e.awsErrorDetails(); + assertThat(awsErrorDetails.errorCode()).isEqualTo("EmptyModeledException"); + assertThat(awsErrorDetails.errorMessage()).isEqualTo("This is the service message"); + assertThat(awsErrorDetails.serviceName()).isEqualTo("ProtocolJsonRpc"); + assertThat(awsErrorDetails.sdkHttpResponse()).isNotNull(); + assertThat(e.requestId()).isEqualTo("1234"); + assertThat(e.extendedRequestId()).isEqualTo("5678"); assertThat(e.statusCode()).isEqualTo(404); } } diff --git a/test/protocol-tests/src/test/java/software/amazon/awssdk/protocol/tests/exception/QueryExceptionTests.java b/test/protocol-tests/src/test/java/software/amazon/awssdk/protocol/tests/exception/QueryExceptionTests.java index 9d3f75ebf8b0..1470d492041c 100644 --- a/test/protocol-tests/src/test/java/software/amazon/awssdk/protocol/tests/exception/QueryExceptionTests.java +++ b/test/protocol-tests/src/test/java/software/amazon/awssdk/protocol/tests/exception/QueryExceptionTests.java @@ -231,6 +231,7 @@ public void modeledException_HasExceptionMetadataSet() { assertThat(awsErrorDetails.serviceName()).isEqualTo("ProtocolQuery"); assertThat(awsErrorDetails.sdkHttpResponse()).isNotNull(); assertThat(e.requestId()).isEqualTo("1234"); + assertThat(e.extendedRequestId()).isNull(); assertThat(e.statusCode()).isEqualTo(404); } } @@ -252,6 +253,7 @@ public void modeledException_RequestIDInXml_SetCorrectly() { client.allTypes(); } catch (EmptyModeledException e) { assertThat(e.requestId()).isEqualTo("1234"); + assertThat(e.extendedRequestId()).isNull(); } } @@ -265,6 +267,22 @@ public void requestIdInHeader_IsSetOnException() { client.allTypes(); } catch (ProtocolQueryException e) { assertThat(e.requestId()).isEqualTo("1234"); + assertThat(e.extendedRequestId()).isNull(); + } + } + + @Test + public void requestIdAndExtendedRequestIdInHeader_IsSetOnException() { + stubFor(post(urlEqualTo(PATH)).willReturn( + aResponse() + .withStatus(404) + .withHeader("x-amzn-RequestId", "1234") + .withHeader("x-amz-id-2", "5678"))); + try { + client.allTypes(); + } catch (ProtocolQueryException e) { + assertThat(e.requestId()).isEqualTo("1234"); + assertThat(e.extendedRequestId()).isEqualTo("5678"); } } diff --git a/test/protocol-tests/src/test/java/software/amazon/awssdk/protocol/tests/exception/RestJsonExceptionTests.java b/test/protocol-tests/src/test/java/software/amazon/awssdk/protocol/tests/exception/RestJsonExceptionTests.java index 5a82e4ff0bd5..818bd1c4a8e6 100644 --- a/test/protocol-tests/src/test/java/software/amazon/awssdk/protocol/tests/exception/RestJsonExceptionTests.java +++ b/test/protocol-tests/src/test/java/software/amazon/awssdk/protocol/tests/exception/RestJsonExceptionTests.java @@ -158,6 +158,29 @@ public void modeledException_HasExceptionMetadataSet() { assertThat(awsErrorDetails.serviceName()).isEqualTo("ProtocolRestJson"); assertThat(awsErrorDetails.sdkHttpResponse()).isNotNull(); assertThat(e.requestId()).isEqualTo("1234"); + assertThat(e.extendedRequestId()).isNull(); + assertThat(e.statusCode()).isEqualTo(404); + } + } + + @Test + public void modeledException_HasExceptionMetadataIncludingExtendedRequestIdSet() { + stubFor(post(urlEqualTo(ALL_TYPES_PATH)).willReturn( + aResponse() + .withStatus(404) + .withHeader("x-amzn-RequestId", "1234") + .withHeader("x-amz-id-2", "5678") + .withBody("{\"__type\": \"EmptyModeledException\", \"Message\": \"This is the service message\"}"))); + try { + client.allTypes(); + } catch (EmptyModeledException e) { + AwsErrorDetails awsErrorDetails = e.awsErrorDetails(); + assertThat(awsErrorDetails.errorCode()).isEqualTo("EmptyModeledException"); + assertThat(awsErrorDetails.errorMessage()).isEqualTo("This is the service message"); + assertThat(awsErrorDetails.serviceName()).isEqualTo("ProtocolRestJson"); + assertThat(awsErrorDetails.sdkHttpResponse()).isNotNull(); + assertThat(e.requestId()).isEqualTo("1234"); + assertThat(e.extendedRequestId()).isEqualTo("5678"); assertThat(e.statusCode()).isEqualTo(404); } } diff --git a/test/protocol-tests/src/test/java/software/amazon/awssdk/protocol/tests/exception/RestXmlExceptionTests.java b/test/protocol-tests/src/test/java/software/amazon/awssdk/protocol/tests/exception/RestXmlExceptionTests.java index b4d53596fa3d..99dc7a2b97a9 100644 --- a/test/protocol-tests/src/test/java/software/amazon/awssdk/protocol/tests/exception/RestXmlExceptionTests.java +++ b/test/protocol-tests/src/test/java/software/amazon/awssdk/protocol/tests/exception/RestXmlExceptionTests.java @@ -155,6 +155,35 @@ public void modeledException_HasExceptionMetadataSet() { assertThat(awsErrorDetails.serviceName()).isEqualTo("ProtocolRestXml"); assertThat(awsErrorDetails.sdkHttpResponse()).isNotNull(); assertThat(e.requestId()).isEqualTo("1234"); + assertThat(e.extendedRequestId()).isNull(); + assertThat(e.statusCode()).isEqualTo(404); + } + } + + @Test + public void modeledException_HasExceptionMetadataIncludingExtendedRequestIdSet() { + String xml = "" + + " " + + " EmptyModeledException" + + " This is the service message" + + " " + + " 1234" + + ""; + stubFor(post(urlEqualTo(ALL_TYPES_PATH)).willReturn( + aResponse() + .withStatus(404) + .withHeader("x-amz-id-2", "5678") + .withBody(xml))); + try { + client.allTypes(); + } catch (EmptyModeledException e) { + AwsErrorDetails awsErrorDetails = e.awsErrorDetails(); + assertThat(awsErrorDetails.errorCode()).isEqualTo("EmptyModeledException"); + assertThat(awsErrorDetails.errorMessage()).isEqualTo("This is the service message"); + assertThat(awsErrorDetails.serviceName()).isEqualTo("ProtocolRestXml"); + assertThat(awsErrorDetails.sdkHttpResponse()).isNotNull(); + assertThat(e.requestId()).isEqualTo("1234"); + assertThat(e.extendedRequestId()).isEqualTo("5678"); assertThat(e.statusCode()).isEqualTo(404); } }