Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Updated exception messages #1298

Merged
merged 9 commits into from
Mar 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -99,7 +100,7 @@ public virtual Stream Content
/// <summary>
/// Gets the reason for a failure in the current response.
/// </summary>
public virtual string ErrorMessage => this.CosmosException?.ToString(includeDiagnostics: false);
public virtual string ErrorMessage => this.CosmosException?.Message;

/// <summary>
/// Gets the current <see cref="ResponseMessage"/> HTTP headers.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -162,12 +167,13 @@ public virtual bool TryGetHeader(string headerName, out string value)
/// <returns>A string representation of the exception.</returns>
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)
Expand All @@ -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(" ---> ");
Expand All @@ -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();
}
Expand Down
2 changes: 1 addition & 1 deletion Microsoft.Azure.Cosmos/src/Util/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1230,10 +1230,23 @@ public async Task ItemRequestOptionAccessConditionTest()
IfMatchEtag = Guid.NewGuid().ToString(),
};

using (ResponseMessage responseMessage = await this.Container.UpsertItemStreamAsync(
streamPayload: TestCommon.SerializerCore.ToStream<ToDoActivity>(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<ToDoActivity> response = await this.Container.ReplaceItemAsync<ToDoActivity>(
id: testItem.id,
ItemResponse<ToDoActivity> response = await this.Container.UpsertItemAsync<ToDoActivity>(
item: testItem,
requestOptions: itemRequestOptions);
Assert.Fail("Access condition should have failed");
Expand All @@ -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
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -64,17 +65,42 @@ 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();
Assert.IsNotNull(diagnostics);
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
Expand Down
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down