From 59cdcdad6c67e968db0408ba344013175127f8f5 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Mon, 1 Feb 2021 16:14:45 +0100 Subject: [PATCH] Log inner exceptions --- .../ActivityResults/FaultResult.cs | 17 +++++++++++----- .../Models/SimpleException.cs | 20 +++++++++++++++++++ .../Elsa.Abstractions/Models/WorkflowFault.cs | 2 +- .../Elsa.Abstractions/Services/Activity.cs | 1 + .../Models/WorkflowExecutionContext.cs | 8 +++++--- .../Workflows/RunWorkflow/RunWorkflow.cs | 1 + src/core/Elsa.Core/Services/WorkflowRunner.cs | 10 +++++----- .../Workflows/ProducerWorkflow.cs | 3 ++- 8 files changed, 47 insertions(+), 15 deletions(-) create mode 100644 src/core/Elsa.Abstractions/Models/SimpleException.cs diff --git a/src/core/Elsa.Abstractions/ActivityResults/FaultResult.cs b/src/core/Elsa.Abstractions/ActivityResults/FaultResult.cs index af1458989..4cedfec82 100644 --- a/src/core/Elsa.Abstractions/ActivityResults/FaultResult.cs +++ b/src/core/Elsa.Abstractions/ActivityResults/FaultResult.cs @@ -1,14 +1,21 @@ +using System; using Elsa.Services.Models; namespace Elsa.ActivityResults { public class FaultResult : ActivityExecutionResult { + public FaultResult(Exception exception) => Exception = exception; public FaultResult(string message) => Message = message; - public string Message { get; set; } - public string? StackTrace { get; set; } - - protected override void Execute(ActivityExecutionContext activityExecutionContext) => - activityExecutionContext.WorkflowExecutionContext.Fault(activityExecutionContext.ActivityBlueprint.Id, Message, StackTrace, activityExecutionContext.Input, activityExecutionContext.Resuming); + public Exception Exception { get; } = default!; + public string Message { get; } = default!; + + protected override void Execute(ActivityExecutionContext activityExecutionContext) + { + if(Exception != null!) + activityExecutionContext.WorkflowExecutionContext.Fault(Exception, activityExecutionContext.ActivityBlueprint.Id, activityExecutionContext.Input, activityExecutionContext.Resuming); + else + activityExecutionContext.WorkflowExecutionContext.Fault(Message!, activityExecutionContext.ActivityBlueprint.Id, activityExecutionContext.Input, activityExecutionContext.Resuming); + } } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Models/SimpleException.cs b/src/core/Elsa.Abstractions/Models/SimpleException.cs new file mode 100644 index 000000000..32378a7a7 --- /dev/null +++ b/src/core/Elsa.Abstractions/Models/SimpleException.cs @@ -0,0 +1,20 @@ +using System; + +namespace Elsa.Models +{ + public record SimpleException(Type Type, string Message, string StackTrace, SimpleException? InnerException = default) + { + public static SimpleException? FromException(Exception? ex) + { + if (ex == null) + return null; + + var simpleException = new SimpleException(ex.GetType(), ex.Message, ex.StackTrace); + + if (ex.InnerException != null) + simpleException = simpleException with { InnerException = FromException(ex.InnerException) }; + + return simpleException; + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Models/WorkflowFault.cs b/src/core/Elsa.Abstractions/Models/WorkflowFault.cs index 4375b1e32..da668467e 100644 --- a/src/core/Elsa.Abstractions/Models/WorkflowFault.cs +++ b/src/core/Elsa.Abstractions/Models/WorkflowFault.cs @@ -1,4 +1,4 @@ namespace Elsa.Models { - public record WorkflowFault(string? FaultedActivityId, string? Message, string? StackTrace, object? ActivityInput, bool Resuming); + public record WorkflowFault(SimpleException? Exception, string Message, string? FaultedActivityId, object? ActivityInput, bool Resuming); } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Services/Activity.cs b/src/core/Elsa.Abstractions/Services/Activity.cs index 104f1420d..9a44376a0 100644 --- a/src/core/Elsa.Abstractions/Services/Activity.cs +++ b/src/core/Elsa.Abstractions/Services/Activity.cs @@ -46,6 +46,7 @@ namespace Elsa.Services protected ScheduleActivitiesResult Schedule(IEnumerable activities) => new(activities); protected CombinedResult Combine(IEnumerable results) => new(results); protected CombinedResult Combine(params IActivityExecutionResult[] results) => new(results); + protected FaultResult Fault(Exception exception) => new(exception); protected FaultResult Fault(string message) => new(message); protected T? GetState([CallerMemberName] string name = null!) => Data.GetState(name); diff --git a/src/core/Elsa.Abstractions/Services/Models/WorkflowExecutionContext.cs b/src/core/Elsa.Abstractions/Services/Models/WorkflowExecutionContext.cs index 6bbf6ef4d..8e12a6e3c 100644 --- a/src/core/Elsa.Abstractions/Services/Models/WorkflowExecutionContext.cs +++ b/src/core/Elsa.Abstractions/Services/Models/WorkflowExecutionContext.cs @@ -111,13 +111,15 @@ namespace Elsa.Services.Models public void Begin() => WorkflowInstance.WorkflowStatus = WorkflowStatus.Running; public void Resume() => WorkflowInstance.WorkflowStatus = WorkflowStatus.Running; public void Suspend() => WorkflowInstance.WorkflowStatus = WorkflowStatus.Suspended; - - public void Fault(string? activityId, string? message, string? stackTrace, object? activityInput, bool resuming) + public void Fault(Exception ex, string? activityId, object? activityInput, bool resuming) => Fault(ex, ex.Message, activityId, activityInput, resuming); + public void Fault(string message, string? activityId, object? activityInput, bool resuming) => Fault(null, message, activityId, activityInput, resuming); + + public void Fault(Exception? exception, string message, string? activityId, object? activityInput, bool resuming) { var clock = ServiceProvider.GetRequiredService(); WorkflowInstance.WorkflowStatus = WorkflowStatus.Faulted; WorkflowInstance.FaultedAt = clock.GetCurrentInstant(); - WorkflowInstance.Fault = new WorkflowFault(activityId, message, stackTrace, activityInput, resuming); + WorkflowInstance.Fault = new WorkflowFault(SimpleException.FromException(exception), message, activityId, activityInput, resuming); } public void Complete() => WorkflowInstance.WorkflowStatus = WorkflowStatus.Finished; diff --git a/src/core/Elsa.Core/Activities/Workflows/RunWorkflow/RunWorkflow.cs b/src/core/Elsa.Core/Activities/Workflows/RunWorkflow/RunWorkflow.cs index 907a9b14c..d3f057e20 100644 --- a/src/core/Elsa.Core/Activities/Workflows/RunWorkflow/RunWorkflow.cs +++ b/src/core/Elsa.Core/Activities/Workflows/RunWorkflow/RunWorkflow.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using Elsa.ActivityResults; using Elsa.Attributes; +using Elsa.Exceptions; using Elsa.Models; using Elsa.Services; using Elsa.Services.Models; diff --git a/src/core/Elsa.Core/Services/WorkflowRunner.cs b/src/core/Elsa.Core/Services/WorkflowRunner.cs index 42218a7c9..e17ce8a3b 100644 --- a/src/core/Elsa.Core/Services/WorkflowRunner.cs +++ b/src/core/Elsa.Core/Services/WorkflowRunner.cs @@ -176,10 +176,10 @@ namespace Elsa.Services var loadContext = new LoadWorkflowContext(workflowExecutionContext); workflowExecutionContext.WorkflowContext = await _workflowContextManager.LoadContext(loadContext, cancellationToken); } - + // If the workflow instance has a CurrentActivity, it means the workflow instance is being retried. var currentActivity = workflowInstance.CurrentActivity; - + if (currentActivity != null) { activityId = currentActivity.ActivityId; @@ -187,7 +187,7 @@ namespace Elsa.Services } var activity = activityId != null ? workflowBlueprint.GetActivity(activityId) : default; - + // Give application a chance to prevent workflow from executing. var validateWorkflowExecution = new ValidateWorkflowExecution(workflowExecutionContext, activity); await _mediator.Publish(validateWorkflowExecution, cancellationToken); @@ -309,7 +309,7 @@ namespace Elsa.Services catch (Exception e) { _logger.LogWarning(e, "Failed to run workflow {WorkflowInstanceId}", workflowExecutionContext.WorkflowInstance.Id); - workflowExecutionContext.Fault(null, e.Message, e.StackTrace, null, activityOperation == Resume); + workflowExecutionContext.Fault(e, null, null, activityOperation == Resume); } } @@ -371,7 +371,7 @@ namespace Elsa.Services catch (Exception e) { _logger.LogWarning(e, "Failed to run activity {ActivityId} of workflow {WorkflowInstanceId}", activity.Id, activityExecutionContext.WorkflowInstance.Id); - activityExecutionContext.WorkflowExecutionContext.Fault(activity.Id, e.Message, e.StackTrace, activityExecutionContext.Input, activityExecutionContext.Resuming); + activityExecutionContext.WorkflowExecutionContext.Fault(e, activity.Id, activityExecutionContext.Input, activityExecutionContext.Resuming); await _mediator.Publish(new ActivityFaulted(e, activityExecutionContext), cancellationToken); } diff --git a/src/samples/worker/Elsa.Samples.Faulting/Workflows/ProducerWorkflow.cs b/src/samples/worker/Elsa.Samples.Faulting/Workflows/ProducerWorkflow.cs index de253fcf8..b64df11ac 100644 --- a/src/samples/worker/Elsa.Samples.Faulting/Workflows/ProducerWorkflow.cs +++ b/src/samples/worker/Elsa.Samples.Faulting/Workflows/ProducerWorkflow.cs @@ -1,4 +1,5 @@ using System; +using System.Net; using Elsa.Activities.Console; using Elsa.Activities.Timers; using Elsa.Builders; @@ -13,7 +14,7 @@ namespace Elsa.Samples.Faulting.Workflows builder .StartIn(Duration.FromSeconds(1)) .WriteLine("Catch this!") - .Then(() => throw new ArithmeticException("Does not compute")); + .Then(() => throw new ArithmeticException("Does not compute", new ArgumentException("Incorrect argument", new ArgumentOutOfRangeException("This is the root problem", default(Exception))))); } } } \ No newline at end of file