diff --git a/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs b/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs index f788900a73..19c204b799 100644 --- a/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs +++ b/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs @@ -8,6 +8,7 @@ namespace Microsoft.Azure.Cosmos using System.Diagnostics; using System.IO; using System.Net; + using System.Text; using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Documents; @@ -99,7 +100,7 @@ public virtual Stream Content /// /// Gets the reason for a failure in the current response. /// - public virtual string ErrorMessage => this.CosmosException?.ToString(includeDiagnostics: false); + public virtual string ErrorMessage => this.CosmosException?.Message; /// /// Gets the current HTTP headers. diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs index b6b5b6c09a..e8a940fb39 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs @@ -28,8 +28,13 @@ internal CosmosException( CosmosDiagnosticsContext diagnosticsContext, Error error, Exception innerException) - : base(message, innerException) + : base(CosmosException.GetMessageHelper( + statusCodes, + subStatusCode, + message, + activityId), innerException) { + this.ResponseBody = message; this.stackTrace = stackTrace; this.ActivityId = activityId; this.StatusCode = statusCodes; @@ -162,12 +167,13 @@ public virtual bool TryGetHeader(string headerName, out string value) /// A string representation of the exception. public override string ToString() { - return this.ToStringHelper(true); - } + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.Append(this.GetType().FullName); + stringBuilder.Append(" : "); - internal string ToString(bool includeDiagnostics) - { - return this.ToStringHelper(includeDiagnostics); + this.ToStringHelper(stringBuilder); + + return stringBuilder.ToString(); } internal ResponseMessage ToCosmosResponseMessage(RequestMessage request) @@ -180,35 +186,38 @@ internal ResponseMessage ToCosmosResponseMessage(RequestMessage request) diagnostics: this.DiagnosticsContext); } - private string ToStringHelper(bool includeDiagnostics) + private static string GetMessageHelper( + HttpStatusCode statusCode, + int subStatusCode, + string responseBody, + string activityId) { StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.Append(this.GetType().FullName); - if (this.Message != null) - { - stringBuilder.Append(" : "); - stringBuilder.Append(this.Message); - stringBuilder.AppendLine(); - } - - stringBuilder.AppendFormat("StatusCode = {0};", this.StatusCode); - stringBuilder.AppendLine(); - - stringBuilder.AppendFormat("SubStatusCode = {0};", this.SubStatusCode); - stringBuilder.AppendLine(); - stringBuilder.AppendFormat("ActivityId = {0};", this.ActivityId ?? Guid.Empty.ToString()); - stringBuilder.AppendLine(); + stringBuilder.Append($"Response status code does not indicate success: "); + stringBuilder.Append($"{statusCode} ({(int)statusCode})"); + stringBuilder.Append("; Substatus: "); + stringBuilder.Append(subStatusCode); + stringBuilder.Append("; ActivityId: "); + stringBuilder.Append(activityId ?? string.Empty); + stringBuilder.Append("; Reason: ("); + stringBuilder.Append(responseBody ?? string.Empty); + stringBuilder.Append(");"); - stringBuilder.AppendFormat("RequestCharge = {0};", this.RequestCharge); - stringBuilder.AppendLine(); + return stringBuilder.ToString(); + } - if (includeDiagnostics && this.Diagnostics != null) + private string ToStringHelper( + StringBuilder stringBuilder) + { + if (stringBuilder == null) { - stringBuilder.Append(this.Diagnostics); - stringBuilder.AppendLine(); + throw new ArgumentNullException(nameof(stringBuilder)); } + stringBuilder.Append(this.Message); + stringBuilder.AppendLine(); + if (this.InnerException != null) { stringBuilder.Append(" ---> "); @@ -219,7 +228,16 @@ private string ToStringHelper(bool includeDiagnostics) stringBuilder.AppendLine(); } - stringBuilder.Append(this.StackTrace); + if (this.StackTrace != null) + { + stringBuilder.Append(this.StackTrace); + stringBuilder.AppendLine(); + } + + if (this.Diagnostics != null) + { + stringBuilder.Append(this.Diagnostics); + } return stringBuilder.ToString(); } diff --git a/Microsoft.Azure.Cosmos/src/Util/Extensions.cs b/Microsoft.Azure.Cosmos/src/Util/Extensions.cs index 9ef10d731b..c523b9f181 100644 --- a/Microsoft.Azure.Cosmos/src/Util/Extensions.cs +++ b/Microsoft.Azure.Cosmos/src/Util/Extensions.cs @@ -99,7 +99,7 @@ internal static ResponseMessage ToCosmosResponseMessage(this DocumentClientExcep subStatusCode: (int)SubStatusCodes.Unknown, responseTimeUtc: DateTime.UtcNow, requestCharge: cosmosException.Headers.RequestCharge, - errorMessage: cosmosException.Message, + errorMessage: documentClientException.ToString(), method: requestMessage?.Method, requestUri: requestMessage?.RequestUri, requestSessionToken: requestMessage?.Headers?.Session, diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs index 3311c3fa92..b00dafbfa2 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs @@ -1230,10 +1230,23 @@ public async Task ItemRequestOptionAccessConditionTest() IfMatchEtag = Guid.NewGuid().ToString(), }; + using (ResponseMessage responseMessage = await this.Container.UpsertItemStreamAsync( + streamPayload: TestCommon.SerializerCore.ToStream(testItem), + partitionKey: new Cosmos.PartitionKey(testItem.status), + requestOptions: itemRequestOptions)) + { + Assert.IsNotNull(responseMessage); + Assert.IsNull(responseMessage.Content); + Assert.AreEqual(HttpStatusCode.PreconditionFailed, responseMessage.StatusCode, responseMessage.ErrorMessage); + Assert.AreNotEqual(responseMessage.Headers.ActivityId, Guid.Empty); + Assert.IsTrue(responseMessage.Headers.RequestCharge > 0); + Assert.IsFalse(string.IsNullOrEmpty(responseMessage.ErrorMessage)); + Assert.IsTrue(responseMessage.ErrorMessage.Contains("One of the specified pre-condition is not met")); + } + try { - ItemResponse response = await this.Container.ReplaceItemAsync( - id: testItem.id, + ItemResponse response = await this.Container.UpsertItemAsync( item: testItem, requestOptions: itemRequestOptions); Assert.Fail("Access condition should have failed"); @@ -1242,8 +1255,11 @@ public async Task ItemRequestOptionAccessConditionTest() { Assert.IsNotNull(e); Assert.AreEqual(HttpStatusCode.PreconditionFailed, e.StatusCode, e.Message); - Assert.IsNotNull(e.ActivityId); + Assert.AreNotEqual(e.ActivityId, Guid.Empty); Assert.IsTrue(e.RequestCharge > 0); + Assert.AreEqual($"{{{Environment.NewLine} \"Errors\": [{Environment.NewLine} \"One of the specified pre-condition is not met\"{Environment.NewLine} ]{Environment.NewLine}}}", e.ResponseBody); + string expectedMessage = $"Response status code does not indicate success: PreconditionFailed (412); Substatus: 0; ActivityId: {e.ActivityId}; Reason: ({{{Environment.NewLine} \"Errors\": [{Environment.NewLine} \"One of the specified pre-condition is not met\"{Environment.NewLine} ]{Environment.NewLine}}});"; + Assert.AreEqual(expectedMessage, e.Message); } finally { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/TransportWrapperTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/TransportWrapperTests.cs index 87cceb4c79..f5399dc5a5 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/TransportWrapperTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/TransportWrapperTests.cs @@ -6,6 +6,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests { using System; using System.Diagnostics; + using System.Net; using System.Threading.Tasks; using Microsoft.Azure.Documents; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -64,10 +65,24 @@ public async Task TransportExceptionValidationTest() { this.ValidateTransportException(ce); } + + using (ResponseMessage responseMessage = await container.CreateItemStreamAsync( + TestCommon.SerializerCore.ToStream(new TestPayload { id = "bad" }), + new Cosmos.PartitionKey("bad"))) + { + this.ValidateTransportException(responseMessage); + } + + FeedIterator streamIterator = container.GetItemQueryStreamIterator("select * from T where T.Random = 19827 "); + using (ResponseMessage responseMessage = await streamIterator.ReadNextAsync()) + { + this.ValidateTransportException(responseMessage); + } } private void ValidateTransportException(CosmosException cosmosException) { + Assert.AreEqual(HttpStatusCode.ServiceUnavailable, cosmosException.StatusCode); string message = cosmosException.ToString(); Assert.IsTrue(message.Contains("TransportException: A client transport error occurred: The connection failed"), "StoreResult Exception is missing"); string diagnostics = cosmosException.Diagnostics.ToString(); @@ -75,6 +90,17 @@ private void ValidateTransportException(CosmosException cosmosException) Assert.IsTrue(diagnostics.Contains("TransportException: A client transport error occurred: The connection failed")); } + private void ValidateTransportException(ResponseMessage responseMessage) + { + Assert.AreEqual(HttpStatusCode.ServiceUnavailable, responseMessage.StatusCode); + string message = responseMessage.ErrorMessage; + Assert.AreEqual(responseMessage.ErrorMessage, responseMessage.CosmosException.Message); + Assert.IsTrue(message.Contains("TransportException: A client transport error occurred: The connection failed"), "StoreResult Exception is missing"); + string diagnostics = responseMessage.Diagnostics.ToString(); + Assert.IsNotNull(diagnostics); + Assert.IsTrue(diagnostics.Contains("TransportException: A client transport error occurred: The connection failed")); + } + private static void Interceptor( Uri physicalAddress, ResourceOperation resourceOperation, diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs index 29aba6e7a8..b39f13c294 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs @@ -111,7 +111,7 @@ public void VerifyDocumentClientExceptionToResponseMessage() Assert.AreEqual(HttpStatusCode.BadRequest, responseMessage.StatusCode); Assert.AreEqual(SubStatusCodes.WriteForbidden, responseMessage.Headers.SubStatusCode); Assert.IsTrue(responseMessage.ErrorMessage.Contains(errorMessage)); - Assert.IsTrue(responseMessage.ErrorMessage.Contains("VerifyDocumentClientExceptionToResponseMessage"), $"Message should have method name for the stack trace {responseMessage.ErrorMessage}"); + Assert.IsFalse(responseMessage.ErrorMessage.Contains("VerifyDocumentClientExceptionToResponseMessage"), $"Message should not have the stack trace {responseMessage.ErrorMessage}. StackTrace should be in Diagnostics."); } [TestMethod] @@ -143,22 +143,21 @@ public void VerifyTransportExceptionToResponseMessage() Assert.IsFalse(responseMessage.IsSuccessStatusCode); Assert.AreEqual(HttpStatusCode.ServiceUnavailable, responseMessage.StatusCode); Assert.IsTrue(responseMessage.ErrorMessage.Contains(errorMessage)); - Assert.IsTrue(responseMessage.ErrorMessage.Contains(transportException.ToString())); - Assert.IsTrue(responseMessage.ErrorMessage.Contains("VerifyTransportExceptionToResponseMessage"), $"Message should have method name for the stack trace {responseMessage.ErrorMessage}"); + Assert.IsFalse(responseMessage.ErrorMessage.Contains(transportException.ToString()), "InnerException tracked in Diagnostics"); } [TestMethod] public void EnsureCorrectStatusCode() { string testMessage = "Test" + Guid.NewGuid().ToString(); - + List<(HttpStatusCode statusCode, CosmosException exception)> exceptionsToStatusCodes = new List<(HttpStatusCode, CosmosException)>() { - (HttpStatusCode.NotFound, CosmosExceptionFactory.CreateNotFoundException(testMessage)), - (HttpStatusCode.InternalServerError, CosmosExceptionFactory.CreateInternalServerErrorException(testMessage)), - (HttpStatusCode.BadRequest, CosmosExceptionFactory.CreateBadRequestException(testMessage)), - (HttpStatusCode.RequestTimeout,CosmosExceptionFactory.CreateRequestTimeoutException(testMessage)), - ((HttpStatusCode)429, CosmosExceptionFactory.CreateThrottledException(testMessage)), + (HttpStatusCode.NotFound, CosmosExceptionFactory.CreateNotFoundException(testMessage, activityId: Guid.NewGuid().ToString())), + (HttpStatusCode.InternalServerError, CosmosExceptionFactory.CreateInternalServerErrorException(testMessage, activityId: Guid.NewGuid().ToString())), + (HttpStatusCode.BadRequest, CosmosExceptionFactory.CreateBadRequestException(testMessage, activityId: Guid.NewGuid().ToString())), + (HttpStatusCode.RequestTimeout,CosmosExceptionFactory.CreateRequestTimeoutException(testMessage, activityId: Guid.NewGuid().ToString())), + ((HttpStatusCode)429, CosmosExceptionFactory.CreateThrottledException(testMessage, activityId: Guid.NewGuid().ToString())), }; foreach((HttpStatusCode statusCode, CosmosException exception) item in exceptionsToStatusCodes) @@ -239,8 +238,13 @@ private void ValidateExceptionInfo( HttpStatusCode httpStatusCode, string message) { + exception.DiagnosticsContext.GetOverallScope().Dispose(); + Assert.AreEqual(message, exception.ResponseBody); Assert.AreEqual(httpStatusCode, exception.StatusCode); Assert.IsTrue(exception.ToString().Contains(message)); + string expectedMessage = $"Response status code does not indicate success: {httpStatusCode} ({(int)httpStatusCode}); Substatus: 0; ActivityId: {exception.ActivityId}; Reason: ({message});"; + + Assert.AreEqual(expectedMessage, exception.Message); } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs index 471faf3250..c92986352a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs @@ -40,8 +40,10 @@ public void VerifyNegativeCosmosQueryResponseStream() string errorMessage = "TestErrorMessage"; string activityId = "TestActivityId"; double requestCharge = 42.42; - CosmosException cosmosException = CosmosExceptionFactory.CreateBadRequestException(errorMessage); CosmosDiagnosticsContext diagnostics = new CosmosDiagnosticsContextCore(); + CosmosException cosmosException = CosmosExceptionFactory.CreateBadRequestException(errorMessage, diagnosticsContext: diagnostics); + + diagnostics.GetOverallScope().Dispose(); QueryResponse queryResponse = QueryResponse.CreateFailure( statusCode: HttpStatusCode.NotFound, cosmosException: cosmosException, @@ -58,7 +60,7 @@ public void VerifyNegativeCosmosQueryResponseStream() diagnostics: diagnostics); Assert.AreEqual(HttpStatusCode.NotFound, queryResponse.StatusCode); - Assert.AreEqual(cosmosException.ToString(includeDiagnostics: false), queryResponse.ErrorMessage); + Assert.AreEqual(cosmosException.Message, queryResponse.ErrorMessage); Assert.AreEqual(requestCharge, queryResponse.Headers.RequestCharge); Assert.AreEqual(activityId, queryResponse.Headers.ActivityId); Assert.AreEqual(diagnostics, queryResponse.DiagnosticsContext); diff --git a/changelog.md b/changelog.md index 44690ce2fe..e0d365b3b7 100644 --- a/changelog.md +++ b/changelog.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#1213](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/1213) CosmosException now returns the original stack trace. - [#1213](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/1213) ResponseMessage.ErrorMessage is now always correctly populated. There was bug in some scenarios where the error message was left in the content stream. +- [#1298](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/1298) CosmosException.Message contains the same information as CosmosException.ToString() to ensure all the information is being tracked - [#1242](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/1242) Client encryption - Fix bug in read path without encrypted properties - [#1189](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/1189) Query diagnostics shows correct overall time. - [#1189](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/1189) Fixed a bug that caused duplicate information in diagnostic context.