diff --git a/src/DurableTask.ApplicationInsights/DurableTask.ApplicationInsights.csproj b/src/DurableTask.ApplicationInsights/DurableTask.ApplicationInsights.csproj index 9427a8215..1c168a38c 100644 --- a/src/DurableTask.ApplicationInsights/DurableTask.ApplicationInsights.csproj +++ b/src/DurableTask.ApplicationInsights/DurableTask.ApplicationInsights.csproj @@ -12,7 +12,7 @@ 0 7 - 0 + 1 $(MajorVersion).$(MinorVersion).$(PatchVersion) $(VersionPrefix).0 diff --git a/src/DurableTask.AzureStorage/DurableTask.AzureStorage.csproj b/src/DurableTask.AzureStorage/DurableTask.AzureStorage.csproj index a72a2bc52..09d6cc84f 100644 --- a/src/DurableTask.AzureStorage/DurableTask.AzureStorage.csproj +++ b/src/DurableTask.AzureStorage/DurableTask.AzureStorage.csproj @@ -22,7 +22,7 @@ 2 6 - 0 + 1 $(MajorVersion).$(MinorVersion).$(PatchVersion) $(VersionPrefix).0 diff --git a/src/DurableTask.Core/DurableTask.Core.csproj b/src/DurableTask.Core/DurableTask.Core.csproj index 848c454a8..b7acf7eec 100644 --- a/src/DurableTask.Core/DurableTask.Core.csproj +++ b/src/DurableTask.Core/DurableTask.Core.csproj @@ -18,7 +18,7 @@ 3 5 - 0 + 1 $(MajorVersion).$(MinorVersion).$(PatchVersion) $(VersionPrefix).0 diff --git a/src/DurableTask.Core/TaskOrchestrationContext.cs b/src/DurableTask.Core/TaskOrchestrationContext.cs index 87fc435b3..0210a10f6 100644 --- a/src/DurableTask.Core/TaskOrchestrationContext.cs +++ b/src/DurableTask.Core/TaskOrchestrationContext.cs @@ -686,7 +686,7 @@ public void FailOrchestration(Exception failure, OrchestrationRuntimeState runti { if (this.ErrorPropagationMode == ErrorPropagationMode.UseFailureDetails) { - failureDetails = new FailureDetails(failure); + failureDetails = new FailureDetails(failure, this.ExceptionPropertiesProvider.ExtractProperties(failure)); } else { diff --git a/test/DurableTask.Core.Tests/ExceptionHandlingIntegrationTests.cs b/test/DurableTask.Core.Tests/ExceptionHandlingIntegrationTests.cs index a480279d5..8480418eb 100644 --- a/test/DurableTask.Core.Tests/ExceptionHandlingIntegrationTests.cs +++ b/test/DurableTask.Core.Tests/ExceptionHandlingIntegrationTests.cs @@ -335,6 +335,71 @@ await this.worker } } + [TestMethod] + // Test that when a provider is set, properties of exception thrown by orchestration directly will be included + // if excception type is matched. + public async Task ExceptionPropertiesProvider_SimpleThrowExceptionOrchestration() + { + this.worker.ExceptionPropertiesProvider = new TestExceptionPropertiesProvider(); + this.worker.ErrorPropagationMode = ErrorPropagationMode.UseFailureDetails; + + try + { + await this.worker + .AddTaskOrchestrations(typeof(SimpleThrowExceptionOrchestration)) + .StartAsync(); + + var instance = await this.client.CreateOrchestrationInstanceAsync(typeof(SimpleThrowExceptionOrchestration), "test-input"); + var result = await this.client.WaitForOrchestrationAsync(instance, DefaultTimeout); + + // Check that custom properties were extracted + Assert.AreEqual(OrchestrationStatus.Failed, result.OrchestrationStatus); + Assert.IsNotNull(result.FailureDetails); + Assert.IsNotNull(result.FailureDetails.Properties); + + // Check the properties match the ArgumentOutOfRangeException. + Assert.AreEqual("count", result.FailureDetails.Properties["Name"]); + Assert.AreEqual("100", result.FailureDetails.Properties["Value"]); + } + finally + { + await this.worker.StopAsync(); + } + } + + [TestMethod] + // Test that when a provider is set, exception properties are included in failure details with propogation. + public async Task ExceptionPropertiesProvider_SubOrchestrationThrowExceptionOrchestration() + { + this.worker.ExceptionPropertiesProvider = new TestExceptionPropertiesProvider(); + this.worker.ErrorPropagationMode = ErrorPropagationMode.UseFailureDetails; + + try + { + await this.worker + .AddTaskOrchestrations(typeof(SubOrchestrationThrowExceptionOrchestration)) + .AddTaskOrchestrations(typeof(ThrowArgumentOutofRangeExceptionASubOrchestration)) + .AddTaskActivities(typeof(ThrowArgumentOutofRangeExceptionActivity)) + .StartAsync(); + + var instance = await this.client.CreateOrchestrationInstanceAsync(typeof(SubOrchestrationThrowExceptionOrchestration), "test-input"); + var result = await this.client.WaitForOrchestrationAsync(instance, DefaultTimeout); + + // Check that custom properties were extracted + Assert.AreEqual(OrchestrationStatus.Failed, result.OrchestrationStatus); + Assert.IsNotNull(result.FailureDetails); + Assert.IsNotNull(result.FailureDetails.Properties); + + // Check the properties match the ArgumentOutOfRangeException. + Assert.AreEqual("count", result.FailureDetails.Properties["Name"]); + Assert.AreEqual("100", result.FailureDetails.Properties["Value"]); + } + finally + { + await this.worker.StopAsync(); + } + } + class ThrowCustomExceptionOrchestration : TaskOrchestration { public override async Task RunTask(OrchestrationContext context, string input) @@ -352,6 +417,40 @@ protected override string Execute(TaskContext context, string input) } } + class SimpleThrowExceptionOrchestration : TaskOrchestration + { + public override Task RunTask(OrchestrationContext context, string input) + { + throw new ArgumentOutOfRangeException("count", 100, "Count is not valid."); + } + } + + class SubOrchestrationThrowExceptionOrchestration : TaskOrchestration + { + public override async Task RunTask(OrchestrationContext context, string input) + { + await context.CreateSubOrchestrationInstance(typeof(ThrowArgumentOutofRangeExceptionASubOrchestration), input); + return "This should never be reached"; + } + } + + class ThrowArgumentOutofRangeExceptionASubOrchestration : TaskOrchestration + { + public override async Task RunTask(OrchestrationContext context, string input) + { + await context.ScheduleTask(typeof(ThrowArgumentOutofRangeExceptionActivity), input); + return "This should never be reached"; + } + } + + class ThrowArgumentOutofRangeExceptionActivity : TaskActivity + { + protected override string Execute(TaskContext context, string input) + { + throw new ArgumentOutOfRangeException("count", 100, "Count is not valid."); + } + } + class ThrowInvalidOperationExceptionOrchestration : TaskOrchestration { public override async Task RunTask(OrchestrationContext context, string input) @@ -409,6 +508,11 @@ class TestExceptionPropertiesProvider : IExceptionPropertiesProvider { return exception switch { + ArgumentOutOfRangeException e => new Dictionary + { + ["Name"] = e.ParamName ?? string.Empty, + ["Value"] = e.ActualValue?.ToString() ?? string.Empty, + }, CustomBusinessException businessEx => new Dictionary { ["ExceptionTypeName"] = nameof(CustomBusinessException),