diff --git a/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatchOperationResult.cs b/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatchOperationResult.cs index c9d311b227..7631d2cfb4 100644 --- a/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatchOperationResult.cs +++ b/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatchOperationResult.cs @@ -212,9 +212,8 @@ internal ResponseMessage ToResponseMessage() ResponseMessage responseMessage = new ResponseMessage( statusCode: this.StatusCode, requestMessage: null, - errorMessage: null, - error: null, headers: headers, + cosmosException: null, diagnostics: this.DiagnosticsContext ?? CosmosDiagnosticsContext.Create()) { Content = this.ResourceStream diff --git a/Microsoft.Azure.Cosmos/src/Encryption/EncryptionProcessor.cs b/Microsoft.Azure.Cosmos/src/Encryption/EncryptionProcessor.cs index 31b74e612f..da39bddbd0 100644 --- a/Microsoft.Azure.Cosmos/src/Encryption/EncryptionProcessor.cs +++ b/Microsoft.Azure.Cosmos/src/Encryption/EncryptionProcessor.cs @@ -11,6 +11,7 @@ namespace Microsoft.Azure.Cosmos using System.Text; using System.Threading; using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Documents; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -134,7 +135,7 @@ public async Task DecryptAsync( EncryptionProperties encryptionProperties = encryptionPropertiesJObj.ToObject(); if (encryptionProperties.EncryptionFormatVersion != 1) { - throw new CosmosException(HttpStatusCode.InternalServerError, $"Unknown encryption format version: {encryptionProperties.EncryptionFormatVersion}. Please upgrade your SDK to the latest version."); + throw CosmosExceptionFactory.CreateInternalServerErrorException($"Unknown encryption format version: {encryptionProperties.EncryptionFormatVersion}. Please upgrade your SDK to the latest version."); } DataEncryptionKeyCore tempDek = (DataEncryptionKeyInlineCore)database.GetDataEncryptionKey(id: "unknown"); diff --git a/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs b/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs index 847b8481c7..e8985efc23 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 Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Documents; /// @@ -22,6 +23,7 @@ public ResponseMessage() { this.Headers = new Headers(); this.DiagnosticsContext = CosmosDiagnosticsContext.Create(); + this.CosmosException = null; } /// @@ -42,9 +44,16 @@ public ResponseMessage( this.StatusCode = statusCode; this.RequestMessage = requestMessage; - this.ErrorMessage = errorMessage; this.Headers = new Headers(); this.DiagnosticsContext = requestMessage?.DiagnosticsContext ?? CosmosDiagnosticsContext.Create(); + + if (!string.IsNullOrEmpty(errorMessage)) + { + this.CosmosException = CosmosExceptionFactory.Create( + statusCode, + requestMessage, + errorMessage); + } } /// @@ -52,22 +61,19 @@ public ResponseMessage( /// /// The HttpStatusCode of the response /// The object - /// The reason for failures if any. - /// The inner error object /// The headers for the response. + /// The exception if the response is from an error. /// The diagnostics for the request internal ResponseMessage( HttpStatusCode statusCode, RequestMessage requestMessage, - string errorMessage, - Error error, Headers headers, + CosmosException cosmosException, CosmosDiagnosticsContext diagnostics) { this.StatusCode = statusCode; this.RequestMessage = requestMessage; - this.ErrorMessage = errorMessage; - this.Error = error; + this.CosmosException = cosmosException; this.Headers = headers ?? new Headers(); this.DiagnosticsContext = diagnostics ?? throw new ArgumentNullException(nameof(diagnostics)); } @@ -93,7 +99,7 @@ public virtual Stream Content /// /// Gets the reason for a failure in the current response. /// - public virtual string ErrorMessage { get; internal set; } + public virtual string ErrorMessage => this.CosmosException?.ToString(includeDiagnostics: false); /// /// Gets the current HTTP headers. @@ -120,10 +126,7 @@ public virtual Stream Content internal CosmosDiagnosticsContext DiagnosticsContext { get; } - /// - /// Gets the internal error object. - /// - internal virtual Error Error { get; set; } + internal CosmosException CosmosException { get; } private bool disposed; @@ -132,24 +135,18 @@ public virtual Stream Content /// /// Asserts if the current is a success. /// - public virtual bool IsSuccessStatusCode => ((int)this.StatusCode >= 200) && ((int)this.StatusCode <= 299); + public virtual bool IsSuccessStatusCode => this.StatusCode.IsSuccess(); /// /// Checks if the current has a successful status code, otherwise, throws. /// - /// An instance of representing the error state. + /// An instance of representing the error state. /// The current . public virtual ResponseMessage EnsureSuccessStatusCode() { if (!this.IsSuccessStatusCode) { - this.EnsureErrorMessage(); - string message = $"Response status code does not indicate success: {(int)this.StatusCode} Substatus: {(int)this.Headers.SubStatusCode} Reason: ({this.ErrorMessage})."; - - throw new CosmosException( - this, - message, - this.Error); + throw CosmosExceptionFactory.Create(this); } return this; @@ -210,44 +207,5 @@ private void CheckDisposed() throw new ObjectDisposedException(this.GetType().ToString()); } } - - private void EnsureErrorMessage() - { - if (this.Error != null - || !string.IsNullOrEmpty(this.ErrorMessage)) - { - return; - } - - if (this.content != null - && this.content.CanRead) - { - try - { - Error error = Documents.Resource.LoadFrom(this.content); - if (error != null) - { - // Error format is not consistent across modes - if (!string.IsNullOrEmpty(error.Message)) - { - this.ErrorMessage = error.Message; - } - else - { - this.ErrorMessage = error.ToString(); - } - } - } - catch (Newtonsoft.Json.JsonReaderException) - { - // Content is not Json - this.content.Position = 0; - using (StreamReader streamReader = new StreamReader(this.content)) - { - this.ErrorMessage = streamReader.ReadToEnd(); - } - } - } - } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/CosmosCrossPartitionQueryExecutionContext.cs b/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/CosmosCrossPartitionQueryExecutionContext.cs index b279395834..2bb1056365 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/CosmosCrossPartitionQueryExecutionContext.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/CosmosCrossPartitionQueryExecutionContext.cs @@ -401,12 +401,7 @@ protected async Task TryInitializeAsync( if (failureResponse.HasValue) { return TryCatch.FromException( - new CosmosException( - statusCode: failureResponse.Value.StatusCode, - subStatusCode: (int)failureResponse.Value.SubStatusCode.GetValueOrDefault(0), - message: failureResponse.Value.ErrorMessage, - activityId: failureResponse.Value.ActivityId, - requestCharge: failureResponse.Value.RequestCharge)); + failureResponse.Value.CosmosException); } if (!movedToNextPage) diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/ItemProducers/ItemProducer.cs b/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/ItemProducers/ItemProducer.cs index b9bdbcb48a..0008c30e3e 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/ItemProducers/ItemProducer.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/ItemProducers/ItemProducer.cs @@ -14,6 +14,8 @@ namespace Microsoft.Azure.Cosmos.Query.Core.ExecutionContext.ItemProducers using Microsoft.Azure.Cosmos.Query.Core.Collections; using Microsoft.Azure.Cosmos.Query.Core.Metrics; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; + using Microsoft.Azure.Documents; using PartitionKeyRange = Documents.PartitionKeyRange; using PartitionKeyRangeIdentity = Documents.PartitionKeyRangeIdentity; @@ -289,7 +291,7 @@ public async Task BufferMoreDocumentsAsync(CancellationToken token) feedResponse = QueryResponseCore.CreateFailure( statusCode: (System.Net.HttpStatusCode)429, subStatusCodes: null, - errorMessage: "Request Rate Too Large", + cosmosException: CosmosExceptionFactory.CreateThrottledException("Request Rate Too Large"), requestCharge: 0, activityId: QueryResponseCore.EmptyGuidString, diagnostics: QueryResponseCore.EmptyDiagnostics); diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/OrderBy/CosmosOrderByItemQueryExecutionContext.cs b/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/OrderBy/CosmosOrderByItemQueryExecutionContext.cs index b705e575be..64d859262f 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/OrderBy/CosmosOrderByItemQueryExecutionContext.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/OrderBy/CosmosOrderByItemQueryExecutionContext.cs @@ -641,12 +641,7 @@ private async Task TryFilterAsync( if (failureResponse.HasValue) { return TryCatch.FromException( - new CosmosException( - statusCode: failureResponse.Value.StatusCode, - subStatusCode: (int)failureResponse.Value.SubStatusCode.GetValueOrDefault(0), - message: failureResponse.Value.ErrorMessage, - activityId: failureResponse.Value.ActivityId, - requestCharge: 0)); + failureResponse.Value.CosmosException); } break; diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/QueryClient/QueryResponseCore.cs b/Microsoft.Azure.Cosmos/src/Query/Core/QueryClient/QueryResponseCore.cs index 061153859f..368c79b432 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/QueryClient/QueryResponseCore.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/QueryClient/QueryResponseCore.cs @@ -35,7 +35,7 @@ private QueryResponseCore( long responseLengthBytes, string disallowContinuationTokenMessage, string continuationToken, - string errorMessage, + CosmosException cosmosException, SubStatusCodes? subStatusCode) { this.IsSuccess = isSuccess; @@ -47,13 +47,13 @@ private QueryResponseCore( this.RequestCharge = requestCharge; this.DisallowContinuationTokenMessage = disallowContinuationTokenMessage; this.ContinuationToken = continuationToken; - this.ErrorMessage = errorMessage; + this.CosmosException = cosmosException; this.SubStatusCode = subStatusCode; } internal IReadOnlyList CosmosElements { get; } - internal string ErrorMessage { get; } + internal CosmosException CosmosException { get; } internal SubStatusCodes? SubStatusCode { get; } @@ -92,7 +92,7 @@ internal static QueryResponseCore CreateSuccess( responseLengthBytes: responseLengthBytes, disallowContinuationTokenMessage: disallowContinuationTokenMessage, continuationToken: continuationToken, - errorMessage: null, + cosmosException: null, subStatusCode: null); return cosmosQueryResponse; @@ -101,7 +101,7 @@ internal static QueryResponseCore CreateSuccess( internal static QueryResponseCore CreateFailure( HttpStatusCode statusCode, SubStatusCodes? subStatusCodes, - string errorMessage, + CosmosException cosmosException, double requestCharge, string activityId, IReadOnlyCollection diagnostics) @@ -116,7 +116,7 @@ internal static QueryResponseCore CreateFailure( responseLengthBytes: 0, disallowContinuationTokenMessage: null, continuationToken: null, - errorMessage: errorMessage, + cosmosException: cosmosException, subStatusCode: subStatusCodes); return cosmosQueryResponse; diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPlanRetriever.cs b/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPlanRetriever.cs index a5b8a29827..8d56169b29 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPlanRetriever.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPlanRetriever.cs @@ -9,6 +9,7 @@ namespace Microsoft.Azure.Cosmos.Query.Core.QueryPlan using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Query.Core.Monads; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using OperationType = Documents.OperationType; using PartitionKeyDefinition = Documents.PartitionKeyDefinition; using ResourceType = Documents.ResourceType; @@ -62,9 +63,14 @@ public static async Task GetQueryPlanWithServiceI if (!tryGetQueryPlan.Succeeded) { - throw new CosmosException( - System.Net.HttpStatusCode.BadRequest, - tryGetQueryPlan.Exception.ToString()); + if (tryGetQueryPlan.Exception is CosmosException) + { + throw tryGetQueryPlan.Exception; + } + + throw CosmosExceptionFactory.CreateBadRequestException( + message: tryGetQueryPlan.Exception.ToString(), + stackTrace: tryGetQueryPlan.Exception.StackTrace); } return tryGetQueryPlan.Result; diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs b/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs index 5859b1dfd5..2342e38f17 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs @@ -5,10 +5,13 @@ namespace Microsoft.Azure.Cosmos.Query.Core { using System; + using System.Diagnostics; + using System.Runtime.CompilerServices; using Microsoft.Azure.Cosmos.Query.Core.Exceptions; using Microsoft.Azure.Cosmos.Query.Core.Monads; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; + using Microsoft.Azure.Documents; internal static class QueryResponseFactory { @@ -32,14 +35,7 @@ public static QueryResponseCore CreateFromException(Exception exception) } else if (exception is ExceptionWithStackTraceException exceptionWithStackTrace) { - QueryResponseCore innerExceptionResponse = QueryResponseFactory.CreateFromException(exceptionWithStackTrace.InnerException); - queryResponseCore = QueryResponseCore.CreateFailure( - statusCode: innerExceptionResponse.StatusCode, - subStatusCodes: innerExceptionResponse.SubStatusCode, - errorMessage: exceptionWithStackTrace.ToString(), - requestCharge: innerExceptionResponse.RequestCharge, - activityId: innerExceptionResponse.ActivityId, - diagnostics: innerExceptionResponse.Diagnostics); + queryResponseCore = QueryResponseFactory.CreateFromExceptionWithStackTrace(exceptionWithStackTrace); } else { @@ -50,11 +46,22 @@ public static QueryResponseCore CreateFromException(Exception exception) } else { + CosmosException unkownCosmosException = CosmosExceptionFactory.CreateInternalServerErrorException( + subStatusCode: default, + message: exception.Message, + stackTrace: exception.StackTrace, + activityId: QueryResponseCore.EmptyGuidString, + requestCharge: 0, + retryAfter: null, + headers: null, + diagnosticsContext: null, + innerException: exception); + // Unknown exception type should become a 500 queryResponseCore = QueryResponseCore.CreateFailure( statusCode: System.Net.HttpStatusCode.InternalServerError, subStatusCodes: null, - errorMessage: exception?.ToString(), + cosmosException: unkownCosmosException, requestCharge: 0, activityId: QueryResponseCore.EmptyGuidString, diagnostics: QueryResponseCore.EmptyDiagnostics); @@ -69,7 +76,7 @@ private static QueryResponseCore CreateFromCosmosException(CosmosException cosmo QueryResponseCore queryResponseCore = QueryResponseCore.CreateFailure( statusCode: cosmosException.StatusCode, subStatusCodes: (Microsoft.Azure.Documents.SubStatusCodes)cosmosException.SubStatusCode, - errorMessage: cosmosException.ToString(), + cosmosException: cosmosException, requestCharge: 0, activityId: cosmosException.ActivityId, diagnostics: QueryResponseCore.EmptyDiagnostics); @@ -79,10 +86,14 @@ private static QueryResponseCore CreateFromCosmosException(CosmosException cosmo private static QueryResponseCore CreateFromDocumentClientException(Microsoft.Azure.Documents.DocumentClientException documentClientException) { + CosmosException cosmosException = CosmosExceptionFactory.Create( + documentClientException, + null); + QueryResponseCore queryResponseCore = QueryResponseCore.CreateFailure( statusCode: documentClientException.StatusCode.GetValueOrDefault(System.Net.HttpStatusCode.InternalServerError), subStatusCodes: null, - errorMessage: documentClientException.ToString(), + cosmosException: cosmosException, requestCharge: 0, activityId: documentClientException.ActivityId, diagnostics: QueryResponseCore.EmptyDiagnostics); @@ -90,6 +101,40 @@ private static QueryResponseCore CreateFromDocumentClientException(Microsoft.Azu return queryResponseCore; } + private static QueryResponseCore CreateFromExceptionWithStackTrace(ExceptionWithStackTraceException exceptionWithStackTrace) + { + // Use the original stack trace from the inner exception. + if (exceptionWithStackTrace.InnerException is DocumentClientException + || exceptionWithStackTrace.InnerException is CosmosException) + { + return QueryResponseFactory.CreateFromException(exceptionWithStackTrace.InnerException); + } + + QueryResponseCore queryResponseCore = QueryResponseFactory.CreateFromException(exceptionWithStackTrace.InnerException); + CosmosException cosmosException = queryResponseCore.CosmosException; + + queryResponseCore = QueryResponseCore.CreateFailure( + statusCode: queryResponseCore.StatusCode, + subStatusCodes: queryResponseCore.SubStatusCode, + cosmosException: CosmosExceptionFactory.Create( + cosmosException.StatusCode, + cosmosException.SubStatusCode, + cosmosException.Message, + exceptionWithStackTrace.StackTrace, + cosmosException.ActivityId, + cosmosException.RequestCharge, + cosmosException.RetryAfter, + cosmosException.Headers, + cosmosException.DiagnosticsContext, + cosmosException.Error, + cosmosException.InnerException), + requestCharge: queryResponseCore.RequestCharge, + activityId: queryResponseCore.ActivityId, + diagnostics: queryResponseCore.Diagnostics); + + return queryResponseCore; + } + private sealed class QueryExceptionConverter : QueryExceptionVisitor { public static readonly QueryExceptionConverter Singleton = new QueryExceptionConverter(); @@ -100,23 +145,25 @@ private QueryExceptionConverter() public override CosmosException Visit(MalformedContinuationTokenException malformedContinuationTokenException) { - return new BadRequestException( - $"{nameof(BadRequestException)} due to {nameof(MalformedContinuationTokenException)}", - malformedContinuationTokenException); + return CosmosExceptionFactory.CreateBadRequestException( + message: malformedContinuationTokenException.Message, + stackTrace: malformedContinuationTokenException.StackTrace, + innerException: malformedContinuationTokenException); } public override CosmosException Visit(UnexpectedQueryPartitionProviderException unexpectedQueryPartitionProviderException) { - return new InternalServerErrorException( - $"{nameof(InternalServerErrorException)} due to {nameof(UnexpectedQueryPartitionProviderException)}", - unexpectedQueryPartitionProviderException); + return CosmosExceptionFactory.CreateInternalServerErrorException( + message: $"{nameof(CosmosException)} due to {nameof(UnexpectedQueryPartitionProviderException)}", + innerException: unexpectedQueryPartitionProviderException); } public override CosmosException Visit(ExpectedQueryPartitionProviderException expectedQueryPartitionProviderException) { - return new BadRequestException( - $"{nameof(BadRequestException)} due to {nameof(ExpectedQueryPartitionProviderException)}", - expectedQueryPartitionProviderException); + return CosmosExceptionFactory.CreateBadRequestException( + message: expectedQueryPartitionProviderException.Message, + stackTrace: expectedQueryPartitionProviderException.StackTrace, + innerException: expectedQueryPartitionProviderException); } } } diff --git a/Microsoft.Azure.Cosmos/src/Query/v3Query/CosmosQueryClientCore.cs b/Microsoft.Azure.Cosmos/src/Query/v3Query/CosmosQueryClientCore.cs index 106c2cc472..24098c24ba 100644 --- a/Microsoft.Azure.Cosmos/src/Query/v3Query/CosmosQueryClientCore.cs +++ b/Microsoft.Azure.Cosmos/src/Query/v3Query/CosmosQueryClientCore.cs @@ -289,7 +289,7 @@ private QueryResponseCore GetCosmosElementResponse( return QueryResponseCore.CreateFailure( statusCode: cosmosResponseMessage.StatusCode, subStatusCodes: cosmosResponseMessage.Headers.SubStatusCode, - errorMessage: cosmosResponseMessage.ErrorMessage, + cosmosException: cosmosResponseMessage.CosmosException, requestCharge: cosmosResponseMessage.Headers.RequestCharge, activityId: cosmosResponseMessage.Headers.ActivityId, diagnostics: pageDiagnostics); diff --git a/Microsoft.Azure.Cosmos/src/Query/v3Query/QueryIterator.cs b/Microsoft.Azure.Cosmos/src/Query/v3Query/QueryIterator.cs index 3a6ab7b258..aa4cab2358 100644 --- a/Microsoft.Azure.Cosmos/src/Query/v3Query/QueryIterator.cs +++ b/Microsoft.Azure.Cosmos/src/Query/v3Query/QueryIterator.cs @@ -126,8 +126,7 @@ public override async Task ReadNextAsync(CancellationToken canc { queryResponse = QueryResponse.CreateFailure( statusCode: responseCore.StatusCode, - error: null, - errorMessage: responseCore.ErrorMessage, + cosmosException: responseCore.CosmosException, requestMessage: null, diagnostics: diagnostics, responseHeaders: new CosmosQueryResponseMessageHeaders( diff --git a/Microsoft.Azure.Cosmos/src/Query/v3Query/QueryResponse.cs b/Microsoft.Azure.Cosmos/src/Query/v3Query/QueryResponse.cs index fac7773717..5cbf7597cc 100644 --- a/Microsoft.Azure.Cosmos/src/Query/v3Query/QueryResponse.cs +++ b/Microsoft.Azure.Cosmos/src/Query/v3Query/QueryResponse.cs @@ -41,15 +41,13 @@ private QueryResponse( HttpStatusCode statusCode, RequestMessage requestMessage, CosmosDiagnosticsContext diagnostics, - string errorMessage, - Error error, + CosmosException cosmosException, Lazy memoryStream, CosmosSerializationFormatOptions serializationOptions) : base( statusCode: statusCode, requestMessage: requestMessage, - errorMessage: errorMessage, - error: error, + cosmosException: cosmosException, headers: responseHeaders, diagnostics: diagnostics) { @@ -120,8 +118,7 @@ internal static QueryResponse CreateSuccess( responseHeaders: responseHeaders, diagnostics: diagnostics, statusCode: HttpStatusCode.OK, - errorMessage: null, - error: null, + cosmosException: null, requestMessage: null, memoryStream: memoryStream, serializationOptions: serializationOptions); @@ -133,8 +130,7 @@ internal static QueryResponse CreateFailure( CosmosQueryResponseMessageHeaders responseHeaders, HttpStatusCode statusCode, RequestMessage requestMessage, - string errorMessage, - Error error, + CosmosException cosmosException, CosmosDiagnosticsContext diagnostics) { QueryResponse cosmosQueryResponse = new QueryResponse( @@ -144,8 +140,7 @@ internal static QueryResponse CreateFailure( responseHeaders: responseHeaders, diagnostics: diagnostics, statusCode: statusCode, - errorMessage: errorMessage, - error: error, + cosmosException: cosmosException, requestMessage: requestMessage, memoryStream: null, serializationOptions: null); diff --git a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs index 1cf3a7437c..7ae2727837 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs @@ -11,6 +11,7 @@ namespace Microsoft.Azure.Cosmos using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Handlers; using Microsoft.Azure.Cosmos.Query; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Cosmos.Routing; using Microsoft.Azure.Documents; @@ -199,17 +200,21 @@ internal override async Task GetCachedContainerPropertiesAs string containerUri, CancellationToken cancellationToken = default(CancellationToken)) { + CosmosDiagnosticsContextCore diagnosticsContext = new CosmosDiagnosticsContextCore(); ClientCollectionCache collectionCache = await this.DocumentClient.GetCollectionCacheAsync(); try { - return await collectionCache.ResolveByNameAsync( - HttpConstants.Versions.CurrentVersion, - containerUri, - cancellationToken); + using (diagnosticsContext.CreateScope("ContainerCache.ResolveByNameAsync")) + { + return await collectionCache.ResolveByNameAsync( + HttpConstants.Versions.CurrentVersion, + containerUri, + cancellationToken); + } } catch (DocumentClientException ex) { - throw new CosmosException(ex.ToCosmosResponseMessage(null), ex.Message, ex.Error); + throw CosmosExceptionFactory.Create(ex, diagnosticsContext); } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs index 15fe117605..b64ebd395a 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs @@ -11,6 +11,7 @@ namespace Microsoft.Azure.Cosmos using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Cosmos.Routing; using Microsoft.Azure.Cosmos.Scripts; using Microsoft.Azure.Documents; @@ -379,7 +380,9 @@ async Task> GetPartitionKeyRangesAsync( } catch (DocumentClientException ex) { - throw new CosmosException(ex.ToCosmosResponseMessage(null), ex.Message, ex.Error); + throw CosmosExceptionFactory.Create( + dce: ex, + diagnosticsContext: null); } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/BadRequestException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/BadRequestException.cs deleted file mode 100644 index c7646d369b..0000000000 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/BadRequestException.cs +++ /dev/null @@ -1,27 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions -{ - using System; - using System.Net; - - internal sealed class BadRequestException : CosmosHttpException - { - public BadRequestException() - : base(statusCode: HttpStatusCode.BadRequest, message: null) - { - } - - public BadRequestException(string message) - : base(statusCode: HttpStatusCode.BadRequest, message: message) - { - } - - public BadRequestException(string message, Exception innerException) - : base(statusCode: HttpStatusCode.BadRequest, message: message, innerException: innerException) - { - } - } -} diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs similarity index 66% rename from Microsoft.Azure.Cosmos/src/Resource/CosmosException.cs rename to Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs index 0f95bc120d..0d797f9848 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs @@ -5,7 +5,6 @@ namespace Microsoft.Azure.Cosmos { using System; - using System.IO; using System.Net; using System.Text; using Microsoft.Azure.Documents; @@ -15,49 +14,33 @@ namespace Microsoft.Azure.Cosmos /// public class CosmosException : Exception { - private static readonly string FullName = typeof(CosmosException).FullName; - internal CosmosException( - HttpStatusCode statusCode, - string message, - Error error = null, - Exception inner = null) - : base(message, inner) - { - this.StatusCode = statusCode; - this.Error = error; - this.Headers = new Headers(); - } + private readonly string stackTrace; internal CosmosException( - ResponseMessage cosmosResponseMessage, + HttpStatusCode statusCodes, string message, - Error error = null) - : base(message) + int subStatusCode, + string stackTrace, + string activityId, + double requestCharge, + TimeSpan? retryAfter, + Headers headers, + CosmosDiagnosticsContext diagnosticsContext, + Error error, + Exception innerException) + : base(MergeErrorMessages(message, error), innerException) { - if (cosmosResponseMessage != null) - { - this.StatusCode = cosmosResponseMessage.StatusCode; - this.Headers = cosmosResponseMessage.Headers; - if (this.Headers == null) - { - this.Headers = new Headers(); - } - - this.ActivityId = this.Headers.ActivityId; - this.RequestCharge = this.Headers.RequestCharge; - this.RetryAfter = this.Headers.RetryAfter; - this.SubStatusCode = (int)this.Headers.SubStatusCode; - this.Diagnostics = cosmosResponseMessage.Diagnostics; - if (this.Headers.ContentLengthAsLong > 0) - { - using (StreamReader responseReader = new StreamReader(cosmosResponseMessage.Content)) - { - this.ResponseBody = responseReader.ReadToEnd(); - } - } - } - + this.stackTrace = stackTrace; + this.ActivityId = activityId; + this.StatusCode = statusCodes; + this.SubStatusCode = subStatusCode; + this.RetryAfter = retryAfter; + this.RequestCharge = requestCharge; + this.Headers = headers; this.Error = error; + + // Always have a diagnostic context. A new diagnostic will have useful info like user agent + this.DiagnosticsContext = diagnosticsContext ?? CosmosDiagnosticsContext.Create(); } /// @@ -76,11 +59,13 @@ public CosmosException( double requestCharge) : base(message) { + this.stackTrace = null; this.SubStatusCode = subStatusCode; this.StatusCode = statusCode; this.RequestCharge = requestCharge; this.ActivityId = activityId; this.Headers = new Headers(); + this.DiagnosticsContext = CosmosDiagnosticsContext.Create(); } /// @@ -129,12 +114,30 @@ public CosmosException( /// /// Gets the diagnostics for the request /// - public virtual CosmosDiagnostics Diagnostics { get; } + public virtual CosmosDiagnostics Diagnostics => this.DiagnosticsContext; + + /// + public override string StackTrace + { + get + { + if (this.stackTrace != null) + { + return this.stackTrace; + } + else + { + return base.StackTrace; + } + } + } + + internal virtual CosmosDiagnosticsContext DiagnosticsContext { get; } /// - /// Gets the internal error object + /// Gets the internal error object. /// - internal virtual Error Error { get; } + internal virtual Documents.Error Error { get; set; } /// /// Try to get a header from the cosmos response message @@ -158,9 +161,47 @@ public virtual bool TryGetHeader(string headerName, out string value) /// /// A string representation of the exception. public override string ToString() + { + return this.ToStringHelper(true); + } + + internal string ToString(bool includeDiagnostics) + { + return this.ToStringHelper(includeDiagnostics); + } + + internal ResponseMessage ToCosmosResponseMessage(RequestMessage request) + { + return new ResponseMessage( + headers: this.Headers, + requestMessage: request, + cosmosException: this, + statusCode: this.StatusCode, + diagnostics: this.DiagnosticsContext); + } + + /// + /// This handles the scenario there is a message and the error object is set. + /// + private static string MergeErrorMessages(string message, Error error) + { + if (error == null) + { + return message; + } + + if (string.IsNullOrEmpty(message)) + { + return error.Message; + } + + return $"{message}; Inner Message:{error.Message}"; + } + + private string ToStringHelper(bool includeDiagnostics) { StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.Append(CosmosException.FullName); + stringBuilder.Append(this.GetType().FullName); if (this.Message != null) { stringBuilder.Append(" : "); @@ -168,6 +209,14 @@ public override string ToString() stringBuilder.AppendLine(); } + if (this.Error != null) + { + stringBuilder.Append(" : "); + stringBuilder.Append($"Code :{this.Error.Code ?? string.Empty}; Details :{this.Error.ErrorDetails ?? string.Empty}; "); + stringBuilder.Append($"Additional Details: {this.Error.AdditionalErrorInfo ?? string.Empty};"); + stringBuilder.AppendLine(); + } + stringBuilder.AppendFormat("StatusCode = {0};", this.StatusCode); stringBuilder.AppendLine(); @@ -180,7 +229,7 @@ public override string ToString() stringBuilder.AppendFormat("RequestCharge = {0};", this.RequestCharge); stringBuilder.AppendLine(); - if (this.Diagnostics != null) + if (includeDiagnostics && this.Diagnostics != null) { stringBuilder.Append(this.Diagnostics); stringBuilder.AppendLine(); @@ -200,16 +249,5 @@ public override string ToString() return stringBuilder.ToString(); } - - internal ResponseMessage ToCosmosResponseMessage(RequestMessage request) - { - return new ResponseMessage( - headers: this.Headers, - requestMessage: request, - errorMessage: this.Message, - statusCode: this.StatusCode, - error: this.Error, - diagnostics: request.DiagnosticsContext); - } } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs new file mode 100644 index 0000000000..d83ae36ae0 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs @@ -0,0 +1,372 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions +{ + using System; + using System.IO; + using System.Net; + using Microsoft.Azure.Documents; + + internal static class CosmosExceptionFactory + { + internal static CosmosException Create( + DocumentClientException dce, + CosmosDiagnosticsContext diagnosticsContext) + { + Headers headers = new Headers(); + if (dce.Headers != null) + { + foreach (string key in dce.Headers) + { + headers.Add(key, dce.Headers[key]); + } + } + + HttpStatusCode httpStatusCode; + if (dce.StatusCode.HasValue) + { + httpStatusCode = dce.StatusCode.Value; + } + else if (dce.InnerException != null && dce.InnerException is TransportException) + { + httpStatusCode = HttpStatusCode.RequestTimeout; + } + else + { + httpStatusCode = HttpStatusCode.InternalServerError; + } + + return CosmosExceptionFactory.Create( + httpStatusCode, + (int)dce.GetSubStatus(), + dce.Message, + dce.StackTrace, + dce.ActivityId, + dce.RequestCharge, + dce.RetryAfter, + headers, + diagnosticsContext, + dce.Error, + dce.InnerException); + } + + internal static CosmosException Create( + HttpStatusCode statusCode, + RequestMessage requestMessage, + string errorMessage) + { + return CosmosExceptionFactory.Create( + statusCode: statusCode, + subStatusCode: default, + message: errorMessage, + stackTrace: null, + activityId: requestMessage?.Headers?.ActivityId, + requestCharge: 0, + retryAfter: default, + headers: requestMessage?.Headers, + diagnosticsContext: default, + error: default, + innerException: default); + } + + internal static CosmosException Create( + ResponseMessage responseMessage) + { + // If there is no content and there is cosmos exception + // then use the existing exception + if (responseMessage.Content == null + && responseMessage.CosmosException != null) + { + return responseMessage.CosmosException; + } + + // If content was added after the response message + // creation the exception should be updated. + string errorMessage = responseMessage.CosmosException?.Message; + (Error error, string contentMessage) = CosmosExceptionFactory.GetErrorFromStream(responseMessage.Content); + if (!string.IsNullOrEmpty(contentMessage)) + { + if (string.IsNullOrEmpty(errorMessage)) + { + errorMessage = contentMessage; + } + else + { + errorMessage = $"Error Message: {errorMessage}; Content {contentMessage};"; + } + } + + return CosmosExceptionFactory.Create( + responseMessage.StatusCode, + (int)responseMessage.Headers.SubStatusCode, + errorMessage, + responseMessage?.CosmosException?.StackTrace, + responseMessage.Headers.ActivityId, + responseMessage.Headers.RequestCharge, + responseMessage.Headers.RetryAfter, + responseMessage.Headers, + responseMessage.DiagnosticsContext, + error, + responseMessage.CosmosException?.InnerException); + } + + internal static CosmosException Create( + DocumentServiceResponse documentServiceResponse, + Headers responseHeaders, + RequestMessage requestMessage) + { + if (documentServiceResponse == null) + { + throw new ArgumentNullException(nameof(documentServiceResponse)); + } + + if (requestMessage == null) + { + throw new ArgumentNullException(nameof(requestMessage)); + } + + if (responseHeaders == null) + { + responseHeaders = documentServiceResponse.Headers.ToCosmosHeaders(); + } + + (Error error, string errorMessage) = CosmosExceptionFactory.GetErrorFromStream(documentServiceResponse.ResponseBody); + + return CosmosExceptionFactory.Create( + statusCode: documentServiceResponse.StatusCode, + subStatusCode: (int)responseHeaders.SubStatusCode, + message: errorMessage, + stackTrace: null, + activityId: responseHeaders.ActivityId, + requestCharge: responseHeaders.RequestCharge, + retryAfter: responseHeaders.RetryAfter, + headers: responseHeaders, + diagnosticsContext: requestMessage.DiagnosticsContext, + error: error, + innerException: null); + } + + internal static CosmosException Create( + StoreResponse storeResponse, + RequestMessage requestMessage) + { + if (storeResponse == null) + { + throw new ArgumentNullException(nameof(storeResponse)); + } + + if (requestMessage == null) + { + throw new ArgumentNullException(nameof(requestMessage)); + } + + (Error error, string errorMessage) = CosmosExceptionFactory.GetErrorFromStream(storeResponse.ResponseBody); + Headers headers = storeResponse.ToCosmosHeaders(); + + return CosmosExceptionFactory.Create( + statusCode: storeResponse.StatusCode, + subStatusCode: (int)headers.SubStatusCode, + message: errorMessage, + stackTrace: null, + activityId: headers.ActivityId, + requestCharge: headers.RequestCharge, + retryAfter: headers.RetryAfter, + headers: headers, + diagnosticsContext: requestMessage.DiagnosticsContext, + error: error, + innerException: null); + } + + internal static (Error, string) GetErrorFromStream( + Stream content) + { + using (content) + { + if (content != null + && content.CanRead) + { + try + { + Error error = Documents.Resource.LoadFrom(content); + if (error != null) + { + // Error format is not consistent across modes + return (error, null); + } + } + catch (Newtonsoft.Json.JsonReaderException) + { + } + + // Content is not Json + content.Position = 0; + using (StreamReader streamReader = new StreamReader(content)) + { + return (null, streamReader.ReadToEnd()); + } + } + + return (null, null); + } + } + + internal static CosmosException CreateRequestTimeoutException( + string message, + int subStatusCode = default, + string stackTrace = default, + string activityId = default, + double requestCharge = default, + TimeSpan? retryAfter = default, + Headers headers = default, + CosmosDiagnosticsContext diagnosticsContext = default, + Error error = default, + Exception innerException = default) + { + return CosmosExceptionFactory.Create( + HttpStatusCode.RequestTimeout, + subStatusCode, + message, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + error, + innerException); + } + + internal static CosmosException CreateThrottledException( + string message, + int subStatusCode = default, + string stackTrace = default, + string activityId = default, + double requestCharge = default, + TimeSpan? retryAfter = default, + Headers headers = default, + CosmosDiagnosticsContext diagnosticsContext = default, + Error error = default, + Exception innerException = default) + { + return CosmosExceptionFactory.Create( + (HttpStatusCode)StatusCodes.TooManyRequests, + subStatusCode, + message, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + error, + innerException); + } + + internal static CosmosException CreateNotFoundException( + string message, + int subStatusCode = default, + string stackTrace = default, + string activityId = default, + double requestCharge = default, + TimeSpan? retryAfter = default, + Headers headers = default, + CosmosDiagnosticsContext diagnosticsContext = default, + Error error = default, + Exception innerException = default) + { + return CosmosExceptionFactory.Create( + HttpStatusCode.NotFound, + subStatusCode, + message, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + error, + innerException); + } + + internal static CosmosException CreateInternalServerErrorException( + string message, + int subStatusCode = default, + string stackTrace = default, + string activityId = default, + double requestCharge = default, + TimeSpan? retryAfter = default, + Headers headers = default, + CosmosDiagnosticsContext diagnosticsContext = default, + Error error = default, + Exception innerException = default) + { + return CosmosExceptionFactory.Create( + HttpStatusCode.InternalServerError, + subStatusCode, + message, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + error, + innerException); + } + + internal static CosmosException CreateBadRequestException( + string message, + int subStatusCode = default, + string stackTrace = default, + string activityId = default, + double requestCharge = default, + TimeSpan? retryAfter = default, + Headers headers = default, + CosmosDiagnosticsContext diagnosticsContext = default, + Error error = default, + Exception innerException = default) + { + return CosmosExceptionFactory.Create( + HttpStatusCode.BadRequest, + subStatusCode, + message, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + error, + innerException); + } + + internal static CosmosException Create( + HttpStatusCode statusCode, + int subStatusCode, + string message, + string stackTrace, + string activityId, + double requestCharge, + TimeSpan? retryAfter, + Headers headers, + CosmosDiagnosticsContext diagnosticsContext, + Error error, + Exception innerException) + { + return new CosmosException( + statusCode, + message, + subStatusCode, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + error, + innerException); + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosHttpException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosHttpException.cs deleted file mode 100644 index 1b1a76b486..0000000000 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosHttpException.cs +++ /dev/null @@ -1,27 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions -{ - using System; - using System.Net; - - internal abstract class CosmosHttpException : CosmosException - { - protected CosmosHttpException(HttpStatusCode statusCode) - : this(statusCode, message: null, innerException: null) - { - } - - protected CosmosHttpException(HttpStatusCode statusCode, string message) - : this(statusCode, message: message, innerException: null) - { - } - - protected CosmosHttpException(HttpStatusCode statusCode, string message, Exception innerException) - : base(statusCode: statusCode, message: message, inner: innerException) - { - } - } -} diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/InternalServerErrorException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/InternalServerErrorException.cs deleted file mode 100644 index 4d4ceb9122..0000000000 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/InternalServerErrorException.cs +++ /dev/null @@ -1,27 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions -{ - using System; - using System.Net; - - internal sealed class InternalServerErrorException : CosmosHttpException - { - public InternalServerErrorException() - : base(statusCode: HttpStatusCode.InternalServerError, message: null) - { - } - - public InternalServerErrorException(string message) - : base(statusCode: HttpStatusCode.InternalServerError, message: message) - { - } - - public InternalServerErrorException(string message, Exception innerException) - : base(statusCode: HttpStatusCode.InternalServerError, message: message, innerException: innerException) - { - } - } -} diff --git a/Microsoft.Azure.Cosmos/src/Resource/DataEncryptionKey/DataEncryptionKeyCore.cs b/Microsoft.Azure.Cosmos/src/Resource/DataEncryptionKey/DataEncryptionKeyCore.cs index dfc71ad6c1..5ffd3a221a 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/DataEncryptionKey/DataEncryptionKeyCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/DataEncryptionKey/DataEncryptionKeyCore.cs @@ -11,6 +11,7 @@ namespace Microsoft.Azure.Cosmos using System.Net; using System.Threading; using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Documents; /// @@ -135,7 +136,10 @@ internal static Uri CreateLinkUri(CosmosClientContext clientContext, DatabaseCor } catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { - throw new CosmosException(HttpStatusCode.NotFound, ClientResources.DataEncryptionKeyNotFound, inner: ex); + throw CosmosExceptionFactory.CreateNotFoundException( + ClientResources.DataEncryptionKeyNotFound, + diagnosticsContext: diagnosticsContext, + innerException: ex); } InMemoryRawDek inMemoryRawDek = await this.ClientContext.DekCache.GetOrAddRawDekAsync( @@ -172,7 +176,10 @@ internal static Uri CreateLinkUri(CosmosClientContext clientContext, DatabaseCor } catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { - throw new CosmosException(HttpStatusCode.NotFound, ClientResources.DataEncryptionKeyNotFound, inner: ex); + throw CosmosExceptionFactory.CreateNotFoundException( + ClientResources.DataEncryptionKeyNotFound, + diagnosticsContext: diagnosticsContext, + innerException: ex); } InMemoryRawDek inMemoryRawDek = await this.ClientContext.DekCache.GetOrAddRawDekAsync( @@ -223,7 +230,8 @@ internal virtual byte[] GenerateKey(CosmosEncryptionAlgorithm encryptionAlgorith InMemoryRawDek roundTripResponse = await this.UnwrapAsync(tempDekProperties, diagnosticsContext, cancellationToken); if (!roundTripResponse.RawDek.SequenceEqual(key)) { - throw new CosmosException(HttpStatusCode.BadRequest, ClientResources.KeyWrappingDidNotRoundtrip); + throw CosmosExceptionFactory.CreateBadRequestException(ClientResources.KeyWrappingDidNotRoundtrip, + diagnosticsContext: diagnosticsContext); } return (keyWrapResponse.WrappedDataEncryptionKey, keyWrapResponse.EncryptionKeyWrapMetadata, roundTripResponse); diff --git a/Microsoft.Azure.Cosmos/src/Resource/Offer/CosmosOffers.cs b/Microsoft.Azure.Cosmos/src/Resource/Offer/CosmosOffers.cs index 34bc33629a..f5750f9bd8 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Offer/CosmosOffers.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Offer/CosmosOffers.cs @@ -11,6 +11,7 @@ namespace Microsoft.Azure.Cosmos using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Handlers; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Documents; internal class CosmosOffers @@ -145,7 +146,8 @@ private async Task GetOfferV2Async( if (offerV2 == null && failIfNotConfigured) { - throw new CosmosException(HttpStatusCode.NotFound, $"Throughput is not configured for {targetRID}"); + throw (CosmosException)CosmosExceptionFactory.CreateNotFoundException( + $"Throughput is not configured for {targetRID}"); } return offerV2; diff --git a/Microsoft.Azure.Cosmos/src/Util/Extensions.cs b/Microsoft.Azure.Cosmos/src/Util/Extensions.cs index 6045ae0e90..819148626b 100644 --- a/Microsoft.Azure.Cosmos/src/Util/Extensions.cs +++ b/Microsoft.Azure.Cosmos/src/Util/Extensions.cs @@ -13,93 +13,96 @@ namespace Microsoft.Azure.Cosmos using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Core.Trace; using Microsoft.Azure.Cosmos.Diagnostics; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Documents; + using Microsoft.Azure.Documents.Collections; internal static class Extensions { private static readonly char[] NewLineCharacters = new[] { '\r', '\n' }; + internal static bool IsSuccess(this HttpStatusCode httpStatusCode) + { + return ((int)httpStatusCode >= 200) && ((int)httpStatusCode <= 299); + } + internal static ResponseMessage ToCosmosResponseMessage(this DocumentServiceResponse documentServiceResponse, RequestMessage requestMessage) { Debug.Assert(requestMessage != null, nameof(requestMessage)); - ResponseMessage responseMessage = new ResponseMessage(documentServiceResponse.StatusCode, requestMessage); - if (documentServiceResponse.ResponseBody != null) - { - responseMessage.Content = documentServiceResponse.ResponseBody; - } - - if (documentServiceResponse.Headers != null) - { - foreach (string key in documentServiceResponse.Headers) - { - responseMessage.Headers.Add(key, documentServiceResponse.Headers[key]); - } - } - + Headers headers = documentServiceResponse.Headers.ToCosmosHeaders(); CosmosClientSideRequestStatistics cosmosClientSideRequestStatistics = documentServiceResponse.RequestStats as CosmosClientSideRequestStatistics; - PointOperationStatistics pointOperationStatistics = new PointOperationStatistics( - activityId: responseMessage.Headers.ActivityId, + requestMessage.DiagnosticsContext.AddDiagnosticsInternal(new PointOperationStatistics( + activityId: headers.ActivityId, statusCode: documentServiceResponse.StatusCode, subStatusCode: documentServiceResponse.SubStatusCode, - requestCharge: responseMessage.Headers.RequestCharge, - errorMessage: responseMessage.ErrorMessage, + requestCharge: headers.RequestCharge, + errorMessage: null, method: requestMessage?.Method, requestUri: requestMessage?.RequestUri, requestSessionToken: requestMessage?.Headers?.Session, - responseSessionToken: responseMessage.Headers.Session, - clientSideRequestStatistics: cosmosClientSideRequestStatistics); + responseSessionToken: headers.Session, + clientSideRequestStatistics: cosmosClientSideRequestStatistics)); - requestMessage.DiagnosticsContext.AddDiagnosticsInternal(pointOperationStatistics); - return responseMessage; - } - - internal static ResponseMessage ToCosmosResponseMessage(this DocumentClientException documentClientException, RequestMessage requestMessage) - { - // if StatusCode is null it is a client business logic error and it never hit the backend, so throw - if (documentClientException.StatusCode == null) + // If it's considered a failure create the corresponding CosmosException + if (!documentServiceResponse.StatusCode.IsSuccess()) { - throw documentClientException; + CosmosException cosmosException = CosmosExceptionFactory.Create( + documentServiceResponse, + headers, + requestMessage); + + return cosmosException.ToCosmosResponseMessage(requestMessage); } - // if there is a status code then it came from the backend, return error as http error instead of throwing the exception - ResponseMessage responseMessage = new ResponseMessage(documentClientException.StatusCode ?? HttpStatusCode.InternalServerError, requestMessage); - string reasonPhraseString = documentClientException.ToString(); - if (!string.IsNullOrEmpty(reasonPhraseString)) + ResponseMessage responseMessage = new ResponseMessage( + statusCode: documentServiceResponse.StatusCode, + requestMessage: requestMessage, + headers: headers, + cosmosException: null, + diagnostics: requestMessage.DiagnosticsContext) { - if (documentClientException.Message.IndexOfAny(Extensions.NewLineCharacters) >= 0) - { - StringBuilder sb = new StringBuilder(reasonPhraseString); - sb = sb.Replace("\r", string.Empty); - sb = sb.Replace("\n", string.Empty); - reasonPhraseString = sb.ToString(); - } - } + Content = documentServiceResponse.ResponseBody + }; - responseMessage.ErrorMessage = reasonPhraseString; - responseMessage.Error = documentClientException.Error; + return responseMessage; + } - if (documentClientException.Headers != null) + internal static ResponseMessage ToCosmosResponseMessage(this DocumentClientException documentClientException, RequestMessage requestMessage) + { + CosmosDiagnosticsContext diagnosticsContext = requestMessage?.DiagnosticsContext; + if (diagnosticsContext == null) { - foreach (string header in documentClientException.Headers.AllKeys()) - { - responseMessage.Headers.Add(header, documentClientException.Headers[header]); - } + diagnosticsContext = CosmosDiagnosticsContextCore.Create(); } + CosmosException cosmosException = CosmosExceptionFactory.Create( + documentClientException, + diagnosticsContext); + PointOperationStatistics pointOperationStatistics = new PointOperationStatistics( - activityId: responseMessage.Headers.ActivityId, - statusCode: documentClientException.StatusCode.Value, + activityId: cosmosException.Headers.ActivityId, + statusCode: cosmosException.StatusCode, subStatusCode: (int)SubStatusCodes.Unknown, - requestCharge: responseMessage.Headers.RequestCharge, - errorMessage: responseMessage.ErrorMessage, + requestCharge: cosmosException.Headers.RequestCharge, + errorMessage: cosmosException.Message, method: requestMessage?.Method, requestUri: requestMessage?.RequestUri, requestSessionToken: requestMessage?.Headers?.Session, - responseSessionToken: responseMessage.Headers.Session, + responseSessionToken: cosmosException.Headers.Session, clientSideRequestStatistics: documentClientException.RequestStatistics as CosmosClientSideRequestStatistics); - responseMessage.DiagnosticsContext.AddDiagnosticsInternal(pointOperationStatistics); + diagnosticsContext.AddDiagnosticsInternal(pointOperationStatistics); + + // if StatusCode is null it is a client business logic error and it never hit the backend, so throw + if (documentClientException.StatusCode == null) + { + throw cosmosException; + } + + // if there is a status code then it came from the backend, return error as http error instead of throwing the exception + ResponseMessage responseMessage = cosmosException.ToCosmosResponseMessage(requestMessage); + if (requestMessage != null) { requestMessage.Properties.Remove(nameof(DocumentClientException)); @@ -111,6 +114,16 @@ internal static ResponseMessage ToCosmosResponseMessage(this DocumentClientExcep internal static ResponseMessage ToCosmosResponseMessage(this StoreResponse storeResponse, RequestMessage requestMessage) { + // If it's considered a failure create the corresponding CosmosException + if (!storeResponse.StatusCode.IsSuccess()) + { + CosmosException cosmosException = CosmosExceptionFactory.Create( + storeResponse, + requestMessage); + + return cosmosException.ToCosmosResponseMessage(requestMessage); + } + // Is status code conversion lossy? ResponseMessage responseMessage = new ResponseMessage((HttpStatusCode)storeResponse.Status, requestMessage); if (storeResponse.ResponseBody != null) @@ -118,12 +131,29 @@ internal static ResponseMessage ToCosmosResponseMessage(this StoreResponse store responseMessage.Content = storeResponse.ResponseBody; } + return responseMessage; + } + + internal static Headers ToCosmosHeaders(this StoreResponse storeResponse) + { + Headers headers = new Headers(); for (int i = 0; i < storeResponse.ResponseHeaderNames.Length; i++) { - responseMessage.Headers.Add(storeResponse.ResponseHeaderNames[i], storeResponse.ResponseHeaderValues[i]); + headers.Add(storeResponse.ResponseHeaderNames[i], storeResponse.ResponseHeaderValues[i]); } - return responseMessage; + return headers; + } + + internal static Headers ToCosmosHeaders(this INameValueCollection nameValueCollection) + { + Headers headers = new Headers(); + foreach (string key in nameValueCollection) + { + headers.Add(key, nameValueCollection[key]); + } + + return headers; } internal static void TraceException(Exception exception) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/BaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/BaselineTests.cs index c41c2de489..e94c04fca7 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/BaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/BaselineTests.cs @@ -139,7 +139,9 @@ public void ExecuteTestSuite(IEnumerable inputs, [CallerMemberName] stri matched, $@" Expected: {baselineTextSuffix}, - Actual: {outputTextSuffix}"); + Actual: {outputTextSuffix}, + OutputPath: {outputPath}, + BaselinePath: {baselinePath}"); } /// diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqGeneralBaselineTests.TestThenByTranslation.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqGeneralBaselineTests.TestThenByTranslation.xml index 1705c5db5b..7b5f11cb11 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqGeneralBaselineTests.TestThenByTranslation.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqGeneralBaselineTests.TestThenByTranslation.xml @@ -415,7 +415,7 @@ FROM ( ) AS r1 ORDER BY r1["FamilyId"] ASC, r1["FamilyNumber"] ASC ]]> - + @@ -467,7 +467,7 @@ FROM ( ) AS r0 ORDER BY r0["FamilyId"] ASC, r0["FamilyNumber"] ASC ]]> - + diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/SmokeTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/SmokeTests.cs index b15023554a..92801fe474 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/SmokeTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/SmokeTests.cs @@ -11,6 +11,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.ChangeFeed using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Query.Core; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs index 1dbdb6dbef..4b32bba569 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -10,6 +10,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using System.Linq; using System.Net; using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Documents; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json.Linq; 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 f92f115025..450d1b4f5e 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs @@ -6,6 +6,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests { using Microsoft.Azure.Cosmos.Query.Core; using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; @@ -71,6 +72,53 @@ public async Task CustomHandlersDiagnostic() await databaseResponse.Database.DeleteAsync(); } + [TestMethod] + [DataRow(true)] + [DataRow(false)] + public async Task PointOperationRequestTimeoutDiagnostic(bool disableDiagnostics) + { + ItemRequestOptions requestOptions = new ItemRequestOptions() + { + DiagnosticContext = disableDiagnostics ? EmptyCosmosDiagnosticsContext.Singleton : null + }; + + Guid exceptionActivityId = Guid.NewGuid(); + string transportExceptionDescription = "transportExceptionDescription" + Guid.NewGuid(); + Container containerWithTransportException = TransportClientHelper.GetContainerWithItemTransportException( + this.database.Id, + this.Container.Id, + exceptionActivityId, + transportExceptionDescription); + + //Checking point operation diagnostics on typed operations + ToDoActivity testItem = ToDoActivity.CreateRandomToDoActivity(); + try + { + ItemResponse createResponse = await containerWithTransportException.CreateItemAsync( + item: testItem, + requestOptions: requestOptions); + Assert.Fail("Should have thrown a request timeout exception"); + } + catch(CosmosException ce) when (ce.StatusCode == System.Net.HttpStatusCode.RequestTimeout) + { + string exception = ce.ToString(); + Assert.IsNotNull(exception); + Assert.IsTrue(exception.Contains(exceptionActivityId.ToString())); + Assert.IsTrue(exception.Contains(transportExceptionDescription)); + + string diagnosics = ce.Diagnostics.ToString(); + if (disableDiagnostics) + { + Assert.IsTrue(string.IsNullOrEmpty(diagnosics)); + } + else + { + Assert.IsFalse(string.IsNullOrEmpty(diagnosics)); + Assert.IsTrue(exception.Contains(diagnosics)); + } + } + } + [TestMethod] [DataRow(true)] [DataRow(false)] @@ -92,6 +140,7 @@ public async Task PointOperationDiagnostic(bool disableDiagnostics) id: testItem.id, partitionKey: new PartitionKey(testItem.status), requestOptions); + CosmosDiagnosticsTests.VerifyPointDiagnostics(readResponse.Diagnostics, disableDiagnostics); Assert.IsNotNull(readResponse.Diagnostics); testItem.description = "NewDescription"; @@ -102,7 +151,7 @@ public async Task PointOperationDiagnostic(bool disableDiagnostics) requestOptions: requestOptions); Assert.AreEqual(replaceResponse.Resource.description, "NewDescription"); - CosmosDiagnosticsTests.VerifyPointDiagnostics(createResponse.Diagnostics, disableDiagnostics); + CosmosDiagnosticsTests.VerifyPointDiagnostics(replaceResponse.Diagnostics, disableDiagnostics); ItemResponse deleteResponse = await this.Container.DeleteItemAsync( partitionKey: new Cosmos.PartitionKey(testItem.status), diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CrossPartitionQueryTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CrossPartitionQueryTests.cs index b2b1989397..a2773f5b76 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CrossPartitionQueryTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CrossPartitionQueryTests.cs @@ -27,6 +27,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using Microsoft.Azure.Cosmos.Query.Core.Metrics; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; using Microsoft.Azure.Cosmos.Query.Core.QueryPlan; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Cosmos.Routing; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Routing; @@ -796,7 +797,7 @@ private async Task TestBadQueriesOverMultiplePartitionsHelper(Container containe } catch (CosmosException exception) when (exception.StatusCode == HttpStatusCode.BadRequest) { - Assert.IsTrue(exception.Message.Contains(@"{""errors"":[{""severity"":""Error"",""location"":{""start"":27,""end"":28},""code"":""SC2001"",""message"":""Identifier 'a' could not be resolved.""}]}"), + Assert.IsTrue(exception.Message.Contains(@"Identifier 'a' could not be resolved."), exception.Message); } } 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 new file mode 100644 index 0000000000..a0bb0ba87b --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/TransportClientHelper.cs @@ -0,0 +1,91 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Text; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.SDK.EmulatorTests; + using Microsoft.Azure.Documents; + + + internal static class TransportClientHelper + { + internal static Container GetContainerWithItemTransportException( + string databaseId, + string containerId, + Guid activityId, + string transportExceptionSourceDescription) + { + CosmosClient clientWithIntercepter = TestCommon.CreateCosmosClient( + builder => + { + builder.WithTransportClientHandlerFactory(transportClient => new TransportClientWrapper( + transportClient, + (uri, resourceOperation, request) => TransportClientHelper.ThrowTransportExceptionOnItemOperation( + uri, + resourceOperation, + request, + activityId, + transportExceptionSourceDescription))); + }); + + return clientWithIntercepter.GetContainer(databaseId, containerId); + } + + private static void ThrowTransportExceptionOnItemOperation( + Uri physicalAddress, + ResourceOperation resourceOperation, + DocumentServiceRequest request, + Guid activityId, + string transportExceptionSourceDescription) + { + if (request.ResourceType == ResourceType.Document) + { + TransportException transportException = new TransportException( + errorCode: TransportErrorCode.RequestTimeout, + innerException: null, + activityId: activityId, + requestUri: physicalAddress, + sourceDescription: transportExceptionSourceDescription, + userPayload: true, + payloadSent: false); + + throw Documents.Rntbd.TransportExceptions.GetRequestTimeoutException(physicalAddress, Guid.NewGuid(), + transportException); + } + } + + private sealed class TransportClientWrapper : TransportClient + { + private readonly TransportClient baseClient; + private readonly Action interceptor; + + internal TransportClientWrapper( + TransportClient client, + Action interceptor) + { + Debug.Assert(client != null); + Debug.Assert(interceptor != null); + + this.baseClient = client; + this.interceptor = interceptor; + } + + internal override async Task InvokeStoreAsync( + Uri physicalAddress, + ResourceOperation resourceOperation, + DocumentServiceRequest request) + { + this.interceptor(physicalAddress, resourceOperation, request); + + StoreResponse response = await this.baseClient.InvokeStoreAsync(physicalAddress, resourceOperation, request); + return response; + } + } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs index 552a76ef45..3084d29794 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs @@ -13,6 +13,7 @@ namespace Microsoft.Azure.Cosmos.Tests using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Query; using Microsoft.Azure.Cosmos.Query.Core.ContinuationTokens; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Newtonsoft.Json; @@ -34,9 +35,15 @@ public async Task ContinuationTokenIsNotUpdatedOnFails() ResponseMessage firstResponse = new ResponseMessage(HttpStatusCode.NotModified); firstResponse.Headers.ETag = "FirstContinuation"; - ResponseMessage secondResponse = new ResponseMessage(HttpStatusCode.NotFound); - secondResponse.Headers.ETag = "ShouldNotContainThis"; - secondResponse.ErrorMessage = "something"; + ResponseMessage secondResponse = new ResponseMessage( + statusCode: HttpStatusCode.NotFound, + requestMessage: null, + headers: new Headers() + { + ETag = "ShouldNotContainThis" + }, + cosmosException: CosmosExceptionFactory.CreateNotFoundException("something"), + diagnostics: CosmosDiagnosticsContext.Create()); mockContext.SetupSequence(x => x.ProcessResourceOperationAsync( It.IsAny(), 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 7b0b58af51..e265ede0cc 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs @@ -5,6 +5,7 @@ namespace Microsoft.Azure.Cosmos { using System; + using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Http; @@ -12,6 +13,7 @@ namespace Microsoft.Azure.Cosmos using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Common; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Cosmos.Routing; using Microsoft.Azure.Documents; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -111,5 +113,133 @@ public void VerifyDocumentClientExceptionToResponseMessage() Assert.IsTrue(responseMessage.ErrorMessage.Contains(errorMessage)); Assert.IsTrue(responseMessage.ErrorMessage.Contains("VerifyDocumentClientExceptionToResponseMessage"), $"Message should have method name for the stack trace {responseMessage.ErrorMessage}"); } + + [TestMethod] + public void VerifyTransportExceptionToResponseMessage() + { + string errorMessage = "Test Exception!"; + DocumentClientException dce = null; + TransportException transportException = new TransportException( + errorCode: TransportErrorCode.ConnectionBroken, + innerException: null, + activityId: Guid.NewGuid(), + requestUri: new Uri("https://localhost"), + sourceDescription: "The SourceDescription", + userPayload: true, + payloadSent: true); + + try + { + throw new ServiceUnavailableException( + message: errorMessage, + innerException: transportException); + } + catch (DocumentClientException exception) + { + dce = exception; + } + + ResponseMessage responseMessage = dce.ToCosmosResponseMessage(null); + 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}"); + } + + [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)), + }; + + foreach((HttpStatusCode statusCode, CosmosException exception) item in exceptionsToStatusCodes) + { + this.ValidateExceptionInfo(item.exception, item.statusCode, testMessage); + } + } + + [TestMethod] + public void ValidateExceptionStackTraceHandling() + { + CosmosException cosmosException = CosmosExceptionFactory.CreateNotFoundException("TestMessage"); + Assert.AreEqual(null, cosmosException.StackTrace); + Assert.IsFalse(cosmosException.ToString().Contains(nameof(ValidateExceptionStackTraceHandling))); + try + { + throw cosmosException; + } + catch(CosmosException ce) + { + Assert.IsTrue(ce.StackTrace.Contains(nameof(ValidateExceptionStackTraceHandling)), ce.StackTrace); + } + + string stackTrace = "OriginalDocumentClientExceptionStackTrace"; + try + { + throw CosmosExceptionFactory.CreateNotFoundException("TestMessage", stackTrace: stackTrace); + } + catch (CosmosException ce) + { + Assert.AreEqual(stackTrace, ce.StackTrace); + } + } + + [TestMethod] + public void ValidateErrorHandling() + { + Error error = new Error() + { + Code = System.Net.HttpStatusCode.BadRequest.ToString(), + Message = "Unsupported Query", + AdditionalErrorInfo = "Additional error info message" + }; + + CosmosDiagnosticsContext diagnostics = new CosmosDiagnosticsContextCore(); + + CosmosException cosmosException = CosmosExceptionFactory.CreateBadRequestException( + null, + error: error, + diagnosticsContext: diagnostics); + + ResponseMessage responseMessage = QueryResponse.CreateFailure( + statusCode: System.Net.HttpStatusCode.BadRequest, + cosmosException: cosmosException, + requestMessage: null, + diagnostics: diagnostics, + responseHeaders: null); + + Assert.AreEqual(error, responseMessage.CosmosException.Error); + Assert.IsTrue(responseMessage.ErrorMessage.Contains(error.Message)); + Assert.IsTrue(responseMessage.ErrorMessage.Contains(error.AdditionalErrorInfo)); + + try + { + responseMessage.EnsureSuccessStatusCode(); + Assert.Fail("Should throw exception"); + }catch(CosmosException ce ) when (ce.StatusCode == HttpStatusCode.BadRequest) + { + Assert.IsTrue(ce.Message.Contains(error.Message)); + Assert.IsTrue(ce.ToString().Contains(error.Message)); + Assert.IsTrue(ce.ToString().Contains(error.AdditionalErrorInfo)); + } + } + + private void ValidateExceptionInfo( + CosmosException exception, + HttpStatusCode httpStatusCode, + string message) + { + Assert.AreEqual(httpStatusCode, exception.StatusCode); + Assert.IsTrue(exception.ToString().Contains(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 c0502d1f30..2291308714 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs @@ -24,6 +24,7 @@ namespace Microsoft.Azure.Cosmos.Tests using Microsoft.Azure.Cosmos.Query.Core.Monads; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; using Microsoft.Azure.Cosmos.Query.Core.QueryPlan; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Documents; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -38,13 +39,12 @@ public void VerifyNegativeCosmosQueryResponseStream() string errorMessage = "TestErrorMessage"; string activityId = "TestActivityId"; double requestCharge = 42.42; - + CosmosException cosmosException = CosmosExceptionFactory.CreateBadRequestException(errorMessage); CosmosDiagnosticsContext diagnostics = CosmosDiagnosticsContext.Create(); QueryResponse queryResponse = QueryResponse.CreateFailure( statusCode: HttpStatusCode.NotFound, - errorMessage: errorMessage, + cosmosException: cosmosException, requestMessage: null, - error: null, responseHeaders: new CosmosQueryResponseMessageHeaders( null, null, @@ -57,7 +57,7 @@ public void VerifyNegativeCosmosQueryResponseStream() diagnostics: diagnostics); Assert.AreEqual(HttpStatusCode.NotFound, queryResponse.StatusCode); - Assert.AreEqual(errorMessage, queryResponse.ErrorMessage); + Assert.AreEqual(cosmosException.ToString(includeDiagnostics: false), queryResponse.ErrorMessage); Assert.AreEqual(requestCharge, queryResponse.Headers.RequestCharge); Assert.AreEqual(activityId, queryResponse.Headers.ActivityId); Assert.AreEqual(diagnostics, queryResponse.Diagnostics); @@ -283,7 +283,7 @@ public async Task TestCosmosQueryPartitionKeyDefinition() QueryResponseCore queryResponse = await context.ExecuteNextAsync(cancellationtoken); Assert.AreEqual(HttpStatusCode.BadRequest, queryResponse.StatusCode); - Assert.IsTrue(queryResponse.ErrorMessage.Contains(exceptionMessage), "response error message did not contain the proper substring."); + Assert.IsTrue(queryResponse.CosmosException.ToString().Contains(exceptionMessage), "response error message did not contain the proper substring."); } private async Task<(IList components, QueryResponseCore response)> GetAllExecutionComponents() @@ -363,7 +363,18 @@ public async Task TestCosmosQueryPartitionKeyDefinition() QueryResponseCore failure = QueryResponseCore.CreateFailure( System.Net.HttpStatusCode.Unauthorized, SubStatusCodes.PartitionKeyMismatch, - "Random error message", + new CosmosException( + statusCodes: HttpStatusCode.Unauthorized, + message: "Random error message", + subStatusCode: default, + stackTrace: default, + activityId: "TestActivityId", + requestCharge: 42.89, + retryAfter: default, + headers: default, + diagnosticsContext: default, + error: default, + innerException: default), 42.89, "TestActivityId", diagnostics); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DotNetSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DotNetSDKAPI.json index c455c8fbf1..88b93e2a55 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DotNetSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DotNetSDKAPI.json @@ -1756,11 +1756,9 @@ "Attributes": [], "MethodInfo": null }, - "Microsoft.Azure.Cosmos.CosmosDiagnostics get_Diagnostics()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Microsoft.Azure.Cosmos.CosmosDiagnostics get_Diagnostics()": { "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], + "Attributes": [], "MethodInfo": "Microsoft.Azure.Cosmos.CosmosDiagnostics get_Diagnostics()" }, "Microsoft.Azure.Cosmos.Headers get_Headers()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { @@ -1818,11 +1816,21 @@ ], "MethodInfo": "System.String get_ResponseBody()" }, + "System.String get_StackTrace()": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.String get_StackTrace()" + }, "System.String ResponseBody": { "Type": "Property", "Attributes": [], "MethodInfo": null }, + "System.String StackTrace": { + "Type": "Property", + "Attributes": [], + "MethodInfo": null + }, "System.String ToString()": { "Type": "Method", "Attributes": [], @@ -5231,11 +5239,9 @@ "Attributes": [], "MethodInfo": "System.String get_ContinuationToken()" }, - "System.String get_ErrorMessage()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "System.String get_ErrorMessage()": { "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], + "Attributes": [], "MethodInfo": "System.String get_ErrorMessage()" }, "Void .ctor()": { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GlobalEndpointManagerTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GlobalEndpointManagerTest.cs index e1983f15df..b17f9ab481 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GlobalEndpointManagerTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GlobalEndpointManagerTest.cs @@ -6,6 +6,7 @@ namespace Microsoft.Azure.Cosmos using System; using System.Collections.ObjectModel; using System.Threading; + using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Routing; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -20,7 +21,7 @@ public class GlobalEndpointManagerTest /// Tests for /// [TestMethod] - public void EndpointFailureMockTest() + public async Task EndpointFailureMockTest() { // Setup dummpy read locations for the database account Collection readableLocations = new Collection(); @@ -56,7 +57,7 @@ public void EndpointFailureMockTest() GlobalEndpointManager globalEndpointManager = new GlobalEndpointManager(mockOwner.Object, connectionPolicy); - globalEndpointManager.RefreshLocationAsync(databaseAccount).Wait(); + await globalEndpointManager.RefreshLocationAsync(databaseAccount); Assert.AreEqual(globalEndpointManager.ReadEndpoints[0], new Uri(readLocation1.Endpoint)); //Mark each of the read locations as unavailable and validate that the read endpoint switches to the next preferred region / default endpoint. @@ -65,12 +66,12 @@ public void EndpointFailureMockTest() Assert.AreEqual(globalEndpointManager.ReadEndpoints[0], new Uri(readLocation2.Endpoint)); globalEndpointManager.MarkEndpointUnavailableForRead(globalEndpointManager.ReadEndpoints[0]); - globalEndpointManager.RefreshLocationAsync(null).Wait(); + await globalEndpointManager.RefreshLocationAsync(null); Assert.AreEqual(globalEndpointManager.ReadEndpoints[0], globalEndpointManager.WriteEndpoints[0]); //Sleep a second for the unavailable endpoint entry to expire and background refresh timer to kick in - Thread.Sleep(1000); - globalEndpointManager.RefreshLocationAsync(null).Wait(); + Thread.Sleep(2000); + await globalEndpointManager.RefreshLocationAsync(null); Assert.AreEqual(globalEndpointManager.ReadEndpoints[0], new Uri(readLocation1.Endpoint)); } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/ItemProducerTreeUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/ItemProducerTreeUnitTests.cs index c945786868..d813926cb6 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/ItemProducerTreeUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/ItemProducerTreeUnitTests.cs @@ -19,6 +19,7 @@ namespace Microsoft.Azure.Cosmos.Tests using Microsoft.Azure.Cosmos.Query.Core.ExecutionContext.Parallel; using Microsoft.Azure.Cosmos.Query.Core.Metrics; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Documents; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -269,7 +270,8 @@ void produceAsyncCompleteCallback( Task.FromResult(QueryResponseCore.CreateFailure( statusCode: HttpStatusCode.InternalServerError, subStatusCodes: null, - errorMessage: "Error message", + cosmosException: CosmosExceptionFactory.CreateInternalServerErrorException( + "Error message"), requestCharge: 10.2, activityId: Guid.NewGuid().ToString(), diagnostics: pageDiagnostics))); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryPipelineMockTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryPipelineMockTests.cs index 95f0be3908..06e7ead29d 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryPipelineMockTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryPipelineMockTests.cs @@ -212,7 +212,7 @@ public async Task TestCosmosCrossPartitionQueryExecutionContextWithFailuresAsync Assert.IsNotNull(failure); Assert.AreEqual((HttpStatusCode)429, failure.Value.StatusCode); - Assert.IsNull(failure.Value.ErrorMessage); + Assert.IsNotNull(failure.Value.CosmosException.ToString()); Assert.AreEqual(allItems.Count, itemsRead.Count); List exepected = allItems.OrderBy(x => x.id).ToList(); @@ -478,7 +478,7 @@ public async Task TestCosmosOrderByQueryExecutionContextWithFailurePageAsync(boo Assert.IsNotNull(failure); Assert.AreEqual((HttpStatusCode)429, failure.Value.StatusCode); - Assert.IsNull(failure.Value.ErrorMessage); + Assert.IsNotNull(failure.Value.CosmosException.ToString()); Assert.AreEqual(0 /*We don't get any items, since we don't buffer the failure anymore*/, itemsRead.Count); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseFactoryTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseFactoryTests.cs index 9a64b5cd68..8d3734704f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseFactoryTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseFactoryTests.cs @@ -6,10 +6,12 @@ namespace Microsoft.Azure.Cosmos.Query { using System; using System.Net; + using System.Runtime.CompilerServices; using Microsoft.Azure.Cosmos.Query.Core; using Microsoft.Azure.Cosmos.Query.Core.Exceptions; using Microsoft.Azure.Cosmos.Query.Core.Monads; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using VisualStudio.TestTools.UnitTesting; [TestClass] @@ -18,12 +20,11 @@ public class QueryResponseFactoryTests [TestMethod] public void CosmosException() { - CosmosException cosmosException = new CosmosException( - statusCode: HttpStatusCode.BadRequest, + CosmosException cosmosException = CosmosExceptionFactory.CreateBadRequestException( message: "asdf"); QueryResponseCore queryResponse = QueryResponseFactory.CreateFromException(cosmosException); Assert.AreEqual(HttpStatusCode.BadRequest, queryResponse.StatusCode); - Assert.IsNotNull(queryResponse.ErrorMessage); + Assert.IsNotNull(queryResponse.CosmosException); } [TestMethod] @@ -32,7 +33,7 @@ public void DocumentClientException() Documents.DocumentClientException documentClientException = new Documents.RequestRateTooLargeException("asdf"); QueryResponseCore queryResponse = QueryResponseFactory.CreateFromException(documentClientException); Assert.AreEqual((HttpStatusCode)429, queryResponse.StatusCode); - Assert.IsNotNull(queryResponse.ErrorMessage); + Assert.IsNotNull(queryResponse.CosmosException); } [TestMethod] @@ -48,18 +49,48 @@ public void QueryException() QueryException queryException = new MalformedContinuationTokenException(); QueryResponseCore queryResponse = QueryResponseFactory.CreateFromException(queryException); Assert.AreEqual(HttpStatusCode.BadRequest, queryResponse.StatusCode); - Assert.IsNotNull(queryResponse.ErrorMessage); + Assert.IsNotNull(queryResponse.CosmosException); } [TestMethod] public void ExceptionFromTryCatch() { - QueryException queryException = new MalformedContinuationTokenException(); - TryCatch tryCatch = TryCatch.FromException(queryException); + TryCatch tryCatch = this.QueryExceptionHelper(new MalformedContinuationTokenException("TestMessage")); + QueryResponseCore queryResponse = QueryResponseFactory.CreateFromException(tryCatch.Exception); + Assert.AreEqual(HttpStatusCode.BadRequest, queryResponse.StatusCode); + Assert.IsNotNull(queryResponse.CosmosException); + Assert.IsTrue(queryResponse.CosmosException.ToString().Contains("TestMessage")); + Assert.IsTrue(queryResponse.CosmosException.ToString().Contains(nameof(QueryExceptionHelper)), queryResponse.CosmosException.ToString()); + } + + [TestMethod] + public void ExceptionFromTryCatchWithCosmosException() + { + CosmosException cosmosException; + try + { + throw CosmosExceptionFactory.CreateBadRequestException("InternalServerTestMessage"); + } + catch (CosmosException ce) + { + cosmosException = ce; + } + + TryCatch tryCatch = this.QueryExceptionHelper(cosmosException); QueryResponseCore queryResponse = QueryResponseFactory.CreateFromException(tryCatch.Exception); Assert.AreEqual(HttpStatusCode.BadRequest, queryResponse.StatusCode); - Assert.IsNotNull(queryResponse.ErrorMessage); - Assert.IsTrue(queryResponse.ErrorMessage.Contains(nameof(ExceptionFromTryCatch))); + Assert.IsNotNull(queryResponse.CosmosException); + + // Should preserve the original stack trace. + string exceptionMessage = queryResponse.CosmosException.ToString(); + Assert.IsTrue(exceptionMessage.Contains("InternalServerTestMessage")); + Assert.IsFalse(exceptionMessage.Contains(nameof(QueryExceptionHelper))); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private TryCatch QueryExceptionHelper(Exception exception) + { + return TryCatch.FromException(exception); } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseMessageFactory.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseMessageFactory.cs index 06ca77ca80..ddcbf71b93 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseMessageFactory.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseMessageFactory.cs @@ -15,6 +15,7 @@ namespace Microsoft.Azure.Cosmos.Tests using Microsoft.Azure.Cosmos.Json; using Microsoft.Azure.Cosmos.Query.Core.Metrics; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Documents; internal static class QueryResponseMessageFactory @@ -87,7 +88,7 @@ public static QueryResponseCore CreateQueryResponse( if (isOrderByQuery) { memoryStream = SerializeForOrderByQuery(items); - using(StreamReader sr = new StreamReader(SerializeForOrderByQuery(items))) + using (StreamReader sr = new StreamReader(SerializeForOrderByQuery(items))) { json = sr.ReadToEnd(); } @@ -146,9 +147,10 @@ public static QueryResponseCore CreateFailureResponse( SubStatusCodes subStatusCodes, string errorMessage) { + string acitivityId = Guid.NewGuid().ToString(); CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(); diagnosticsContext.AddDiagnosticsInternal(new PointOperationStatistics( - Guid.NewGuid().ToString(), + acitivityId, System.Net.HttpStatusCode.Gone, subStatusCode: SubStatusCodes.PartitionKeyRangeGone, requestCharge: 10.4, @@ -170,9 +172,20 @@ public static QueryResponseCore CreateFailureResponse( QueryResponseCore splitResponse = QueryResponseCore.CreateFailure( statusCode: httpStatusCode, subStatusCodes: subStatusCodes, - errorMessage: errorMessage, + cosmosException: CosmosExceptionFactory.Create( + statusCode: httpStatusCode, + subStatusCode: (int)subStatusCodes, + message: errorMessage, + stackTrace: new System.Diagnostics.StackTrace().ToString(), + activityId: acitivityId, + requestCharge: 10.4, + retryAfter: default, + headers: default, + diagnosticsContext: diagnosticsContext, + error: default, + innerException: default), requestCharge: 10.4, - activityId: Guid.NewGuid().ToString(), + activityId: acitivityId, diagnostics: diagnostics); return splitResponse; diff --git a/changelog.md b/changelog.md index ed07a36d32..d42d318923 100644 --- a/changelog.md +++ b/changelog.md @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- [#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. + ## [3.7.0-preview](https://www.nuget.org/packages/Microsoft.Azure.Cosmos/3.7.0-preview) - 2020-02-25 - [#1210](hhttps://github.com/Azure/azure-cosmos-dotnet-v3/pull/1210) Change Feed pull model