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 8 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?.ToString();

/// <summary>
/// Gets the current <see cref="ResponseMessage"/> HTTP headers.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ internal CosmosException(
Exception innerException)
: base(message, innerException)
{
this.ResponseBody = message;
this.stackTrace = stackTrace;
this.ActivityId = activityId;
this.StatusCode = statusCodes;
Expand Down Expand Up @@ -68,6 +69,9 @@ public CosmosException(
this.DiagnosticsContext = new CosmosDiagnosticsContextCore();
}

/// <inheritdoc/>
public override string Message => this.ToString();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove - ToString() contain information like the call stack that is intentionally not included in the Exception.Message. Changing this violates basic Exception contract - same message as ToString without callstack or diagnostics might be fine or the base class implementation just using the message explicitly passed in. I think we discussed this in the past - and due to the fact that the ctor of CosmosException is internal - so the exception cannot ever be created by customers this is kind of a gray area - you could argue that we are the only entity deciding which information is critical. But if there is no hard reason requiring us to include stacktrace and diagnostics I wouldn't include them in Message to avoid exposing PII or data considered sensitive by customers in an Exception.Message - where they usually aren't included.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind - I remember teh discussion and think the stackTrace and diagnostics would be null in the exceptions actually thornw to the customer - and were just used to pass through context through the request pipeline. If so ignore the comment above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated it so the Message only contains the StatusCode, SubStatusCode, ActivityId, and original message from the service. Only the ToString() will contain the full stack trace and diagnostics.


/// <summary>
/// The body of the cosmos response message as a string
/// </summary>
Expand Down Expand Up @@ -162,12 +166,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 +185,23 @@ internal ResponseMessage ToCosmosResponseMessage(RequestMessage request)
diagnostics: this.DiagnosticsContext);
}

private string ToStringHelper(bool includeDiagnostics)
private string ToStringHelper(
StringBuilder stringBuilder)
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append(this.GetType().FullName);
if (this.Message != null)
if (stringBuilder == null)
{
stringBuilder.Append(" : ");
stringBuilder.Append(this.Message);
stringBuilder.AppendLine();
throw new ArgumentNullException(nameof(stringBuilder));
}

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.Append($"Response status code does not indicate success: ");
stringBuilder.Append($"{this.StatusCode} ({(int)this.StatusCode})");
stringBuilder.Append("; Substatus: ");
j82w marked this conversation as resolved.
Show resolved Hide resolved
stringBuilder.Append(this.SubStatusCode);
stringBuilder.Append("; Reason: (");
stringBuilder.Append(this.ResponseBody);
stringBuilder.Append(");");
stringBuilder.AppendLine();

stringBuilder.AppendFormat("RequestCharge = {0};", this.RequestCharge);
stringBuilder.AppendLine();

if (includeDiagnostics && this.Diagnostics != null)
{
stringBuilder.Append(this.Diagnostics);
stringBuilder.AppendLine();
}

if (this.InnerException != null)
{
stringBuilder.Append(" ---> ");
Expand All @@ -219,7 +212,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
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 = $"Microsoft.Azure.Cosmos.CosmosException : Response status code does not indicate success: PreconditionFailed (412); Substatus: 0; Reason: ({{{Environment.NewLine} \"Errors\": [{Environment.NewLine} \"One of the specified pre-condition is not met\"{Environment.NewLine} ]{Environment.NewLine}}});{Environment.NewLine}{e.StackTrace}{Environment.NewLine}{e.Diagnostics.ToString()}";
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.ToString());
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 @@ -144,7 +144,6 @@ public void VerifyTransportExceptionToResponseMessage()
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}");
}

[TestMethod]
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 = $"Microsoft.Azure.Cosmos.CosmosException : Response status code does not indicate success: {httpStatusCode} ({(int)httpStatusCode}); Substatus: 0; Reason: ({message});{Environment.NewLine}{exception.Diagnostics.ToString()}";

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
Original file line number Diff line number Diff line change
Expand Up @@ -1812,6 +1812,11 @@
],
"MethodInfo": "System.String get_ActivityId()"
},
"System.String get_Message()": {
"Type": "Method",
"Attributes": [],
"MethodInfo": "System.String get_Message()"
},
"System.String get_ResponseBody()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": {
"Type": "Method",
"Attributes": [
Expand All @@ -1824,6 +1829,11 @@
"Attributes": [],
"MethodInfo": "System.String get_StackTrace()"
},
"System.String Message": {
"Type": "Property",
"Attributes": [],
"MethodInfo": null
},
"System.String ResponseBody": {
"Type": "Property",
"Attributes": [],
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