diff --git a/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosClientSideRequestStatistics.cs b/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosClientSideRequestStatistics.cs
index 2e4cee24de..62edbbb0df 100644
--- a/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosClientSideRequestStatistics.cs
+++ b/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosClientSideRequestStatistics.cs
@@ -16,6 +16,7 @@ namespace Microsoft.Azure.Cosmos
internal sealed class CosmosClientSideRequestStatistics : CosmosDiagnosticsInternal, IClientSideRequestStatistics
{
+ public const string DefaultToStringMessage = "Please see CosmosDiagnostics";
private readonly object lockObject = new object();
public CosmosClientSideRequestStatistics(CosmosDiagnosticsContext diagnosticsContext = null)
@@ -151,20 +152,24 @@ public void RecordAddressResolutionEnd(string identifier)
}
}
+ ///
+ /// The new Cosmos Exception always includes the diagnostics and the
+ /// document client exception message. Some of the older document client exceptions
+ /// include the request statistics in the message causing a circle reference.
+ /// This always returns empty string to prevent the circle reference which
+ /// would cause the diagnostic string to grow exponentially.
+ ///
public override string ToString()
{
- // This is required for the older IClientSideRequestStatistics
- // Capture the entire diagnostic context in the toString to avoid losing any information
- // for any APIs using the older interface.
- return this.DiagnosticsContext.ToString();
+ return DefaultToStringMessage;
}
+ ///
+ /// Please see ToString() documentation
+ ///
public void AppendToBuilder(StringBuilder stringBuilder)
{
- // This is required for the older IClientSideRequestStatistics
- // Capture the entire diagnostic context in the toString to avoid losing any information
- // for any APIs using the older interface.
- stringBuilder.Append(this.DiagnosticsContext.ToString());
+ stringBuilder.Append(DefaultToStringMessage);
}
public override void Accept(CosmosDiagnosticsInternalVisitor visitor)
diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs
index e8a940fb39..0bf29c9bd0 100644
--- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs
+++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs
@@ -236,6 +236,7 @@ private string ToStringHelper(
if (this.Diagnostics != null)
{
+ stringBuilder.Append("--- Cosmos Diagnostics ---");
stringBuilder.Append(this.Diagnostics);
}
diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs
index a67ddbedab..5b42c842c9 100644
--- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs
+++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs
@@ -16,6 +16,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
using System.Linq;
using System.Net;
using System.Runtime.CompilerServices;
+ using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
@@ -129,6 +130,88 @@ public async Task PointOperationRequestTimeoutDiagnostic(bool disableDiagnostics
}
}
+ [TestMethod]
+ public async Task PointOperationForbiddenDiagnostic()
+ {
+ List stringLength = new List();
+ foreach (int maxCount in new int[] { 1, 2, 4 })
+ {
+ int count = 0;
+ List<(string, string)> activityIdAndErrorMessage = new List<(string, string)>(maxCount);
+ Guid transportExceptionActivityId = Guid.NewGuid();
+ string transportErrorMessage = $"TransportErrorMessage{Guid.NewGuid()}";
+
+ Action interceptor =
+ (uri, operation, request) =>
+ {
+ if (request.ResourceType == Documents.ResourceType.Document)
+ {
+ if (count >= maxCount)
+ {
+ TransportClientHelper.ThrowTransportExceptionOnItemOperation(
+ uri,
+ operation,
+ request,
+ transportExceptionActivityId,
+ transportErrorMessage);
+ }
+
+ count++;
+ string activityId = Guid.NewGuid().ToString();
+ string errorMessage = $"Error{Guid.NewGuid()}";
+
+ activityIdAndErrorMessage.Add((activityId, errorMessage));
+ TransportClientHelper.ThrowForbiddendExceptionOnItemOperation(
+ uri,
+ request,
+ activityId,
+ errorMessage);
+ }
+ };
+
+ Container containerWithTransportException = TransportClientHelper.GetContainerWithIntercepter(
+ this.database.Id,
+ this.Container.Id,
+ interceptor);
+ //Checking point operation diagnostics on typed operations
+ ToDoActivity testItem = ToDoActivity.CreateRandomToDoActivity();
+
+ try
+ {
+ ItemResponse createResponse = await containerWithTransportException.CreateItemAsync(
+ item: testItem);
+ Assert.Fail("Should have thrown a request timeout exception");
+ }
+ catch (CosmosException ce) when (ce.StatusCode == System.Net.HttpStatusCode.RequestTimeout)
+ {
+ string exceptionToString = ce.ToString();
+ Assert.IsNotNull(exceptionToString);
+ stringLength.Add(exceptionToString.Length);
+
+ // Request timeout info will be in the exception message and in the diagnostic info.
+ Assert.AreEqual(2, Regex.Matches(exceptionToString, transportExceptionActivityId.ToString()).Count);
+ Assert.AreEqual(2, Regex.Matches(exceptionToString, transportErrorMessage).Count);
+
+ // Check to make sure the diagnostics does not include duplicate info.
+ foreach ((string activityId, string errorMessage) in activityIdAndErrorMessage)
+ {
+ Assert.AreEqual(1, Regex.Matches(exceptionToString, activityId).Count);
+ Assert.AreEqual(1, Regex.Matches(exceptionToString, errorMessage).Count);
+ }
+ }
+ }
+
+ // Check if the exception message is not growing exponentially
+ Assert.IsTrue(stringLength.Count > 2);
+ for (int i = 0; i < stringLength.Count - 1; i++)
+ {
+ int currLength = stringLength[i];
+ int nextLength = stringLength[i + 1];
+ Assert.IsTrue( nextLength < currLength * 2,
+ $"The diagnostic string is growing faster than linear. Length: {currLength}, Next Length: {nextLength}");
+ }
+ }
+
[TestMethod]
[DataRow(true)]
[DataRow(false)]
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 2dd8809b28..d5c5fc6354 100644
--- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs
+++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs
@@ -1667,7 +1667,9 @@ public async Task VerifySessionNotFoundStatistics()
}
catch (CosmosException cosmosException)
{
- Assert.IsTrue(cosmosException.Message.Contains("StorePhysicalAddress"), cosmosException.Message);
+ Assert.IsTrue(cosmosException.Message.Contains("The read session is not available for the input session token."), cosmosException.Message);
+ string exception = cosmosException.ToString();
+ Assert.IsTrue(exception.Contains("StorePhysicalAddress"), exception);
}
}
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 69f723fc1d..13ae0c87be 100644
--- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/TransportWrapperTests.cs
+++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/TransportWrapperTests.cs
@@ -125,8 +125,9 @@ 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");
+ Assert.AreEqual(message, responseMessage.CosmosException.Message);
+ Assert.IsTrue(message.Contains("ServiceUnavailable (503); Substatus: 0; ActivityId:"));
+ Assert.IsTrue(message.Contains("Reason: (Message: Channel is closed"), "Should contain exception message");
string diagnostics = responseMessage.Diagnostics.ToString();
Assert.IsNotNull(diagnostics);
Assert.IsTrue(diagnostics.Contains("TransportException: A client transport error occurred: The connection failed"));
diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/TransportClientHelper.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/TransportClientHelper.cs
index a0bb0ba87b..90da0c895b 100644
--- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/TransportClientHelper.cs
+++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/TransportClientHelper.cs
@@ -7,11 +7,12 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
using System;
using System.Collections.Generic;
using System.Diagnostics;
+ using System.Globalization;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos.SDK.EmulatorTests;
using Microsoft.Azure.Documents;
-
+ using Microsoft.Azure.Documents.Collections;
internal static class TransportClientHelper
{
@@ -20,24 +21,35 @@ internal static Container GetContainerWithItemTransportException(
string containerId,
Guid activityId,
string transportExceptionSourceDescription)
+ {
+ return GetContainerWithIntercepter(
+ databaseId,
+ containerId,
+ (uri, resourceOperation, request) => TransportClientHelper.ThrowTransportExceptionOnItemOperation(
+ uri,
+ resourceOperation,
+ request,
+ activityId,
+ transportExceptionSourceDescription));
+ }
+
+ internal static Container GetContainerWithIntercepter(
+ string databaseId,
+ string containerId,
+ Action interceptor)
{
CosmosClient clientWithIntercepter = TestCommon.CreateCosmosClient(
builder =>
{
builder.WithTransportClientHandlerFactory(transportClient => new TransportClientWrapper(
transportClient,
- (uri, resourceOperation, request) => TransportClientHelper.ThrowTransportExceptionOnItemOperation(
- uri,
- resourceOperation,
- request,
- activityId,
- transportExceptionSourceDescription)));
+ interceptor));
});
return clientWithIntercepter.GetContainer(databaseId, containerId);
}
- private static void ThrowTransportExceptionOnItemOperation(
+ public static void ThrowTransportExceptionOnItemOperation(
Uri physicalAddress,
ResourceOperation resourceOperation,
DocumentServiceRequest request,
@@ -60,6 +72,27 @@ private static void ThrowTransportExceptionOnItemOperation(
}
}
+ public static void ThrowForbiddendExceptionOnItemOperation(
+ Uri physicalAddress,
+ DocumentServiceRequest request,
+ string activityId,
+ string errorMessage)
+ {
+ if (request.ResourceType == ResourceType.Document)
+ {
+ DictionaryNameValueCollection headers = new DictionaryNameValueCollection();
+ headers.Add(HttpConstants.HttpHeaders.ActivityId, activityId.ToString());
+ headers.Add(WFConstants.BackendHeaders.SubStatus, ((int)SubStatusCodes.WriteForbidden).ToString(CultureInfo.InvariantCulture));
+
+ ForbiddenException forbiddenException = new ForbiddenException(
+ errorMessage,
+ headers,
+ physicalAddress);
+
+ throw forbiddenException;
+ }
+ }
+
private sealed class TransportClientWrapper : TransportClient
{
private readonly TransportClient baseClient;
diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosDiagnosticsUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosDiagnosticsUnitTests.cs
index 77b571b432..6256494325 100644
--- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosDiagnosticsUnitTests.cs
+++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosDiagnosticsUnitTests.cs
@@ -126,14 +126,12 @@ public void ValidateClientSideRequestStatisticsToString()
CosmosClientSideRequestStatistics clientSideRequestStatistics = new CosmosClientSideRequestStatistics(diagnosticsContext);
string noInfo = clientSideRequestStatistics.ToString();
- Assert.IsNotNull(noInfo);
+ Assert.AreEqual("Please see CosmosDiagnostics", noInfo);
StringBuilder stringBuilder = new StringBuilder();
clientSideRequestStatistics.AppendToBuilder(stringBuilder);
string noInfoStringBuilder = stringBuilder.ToString();
- Assert.IsNotNull(noInfoStringBuilder);
-
- Assert.AreEqual(noInfo, noInfoStringBuilder);
+ Assert.AreEqual("Please see CosmosDiagnostics", noInfo);
string id = clientSideRequestStatistics.RecordAddressResolutionStart(new Uri("https://testuri"));
clientSideRequestStatistics.RecordAddressResolutionEnd(id);
@@ -168,9 +166,7 @@ public void ValidateClientSideRequestStatisticsToString()
usingLocalLSN: true));
string statistics = clientSideRequestStatistics.ToString();
- Assert.IsTrue(statistics.Contains("\"UserAgent\":\"cosmos-netstandard-sdk"));
- Assert.IsTrue(statistics.Contains("UsingLocalLSN: True, TransportException: null"));
- Assert.IsTrue(statistics.Contains("AddressResolutionStatistics\",\"StartTimeUtc"));
+ Assert.AreEqual("Please see CosmosDiagnostics", statistics);
}