Add OpenTelemetry module (#5810)

* Add OpenTelemetry integration for workflow tracing

Introduced a new module, Elsa.OpenTelemetry, to provide OpenTelemetry sources for tracing workflow and activity execution. Updated various components and pipeline extensions to support OpenTelemetry tracing throughout the workflow execution process.

* Refactor workflow execution pipelines

Deleted `WorkflowsFeatureExtensions` and migrated methods to `PipelineWorkflowsFeatureExtensions` with added configurability. Enhanced `ActivityExecutionMiddlewareExtensions` and `ActivityExecutionPipelinePipelineBuilder` to support middleware insertion. Added comprehensive tracing to `OpenTelemetryTracingWorkflowExecutionMiddleware`.

* Add OpenTelemetry tracing to activity execution

Introduced `OpenTelemetryTracingActivityExecutionMiddleware` to capture tracing information for activity execution within workflows. This middleware logs activity execution start and end events, attaching pertinent activity tags. Added extension method to register this middleware in the workflow execution pipeline.

* Set `PYTHONNET_PYDLL` consistently and update workflow pipelines

Update Dockerfiles to set the `PYTHONNET_PYDLL` environment variable consistently without spaces. Additionally, refactor `Program.cs` to streamline workflow and activity execution pipeline configurations by using the `WithDefaultWorkflowExecutionPipeline` and `WithDefaultActivityExecutionPipeline` methods.

* Enhance OpenTelemetry Tracing Middleware Implementation

Add missing activity tags and events to improve telemetry data. Simplify middleware installation syntax for both workflow and activity execution tracing pipelines, ensuring consistent and clear tracing across modules.
This commit is contained in:
Sipke Schoorstra 2024-07-22 14:36:33 +02:00 committed by GitHub
parent b4cd4dd465
commit 70b8582b23
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 231 additions and 84 deletions

View file

@ -373,6 +373,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.ServerAndStudio.Web",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Studio.Web", "src\apps\Elsa.Studio.Web\Elsa.Studio.Web.csproj", "{169A82A5-2DB3-40EA-801E-14C08D743DF7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.OpenTelemetry", "src\modules\Elsa.OpenTelemetry\Elsa.OpenTelemetry.csproj", "{2CDF3E1C-267D-4198-B1C7-7E1F548FC120}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -885,6 +887,10 @@ Global
{169A82A5-2DB3-40EA-801E-14C08D743DF7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{169A82A5-2DB3-40EA-801E-14C08D743DF7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{169A82A5-2DB3-40EA-801E-14C08D743DF7}.Release|Any CPU.Build.0 = Release|Any CPU
{2CDF3E1C-267D-4198-B1C7-7E1F548FC120}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2CDF3E1C-267D-4198-B1C7-7E1F548FC120}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2CDF3E1C-267D-4198-B1C7-7E1F548FC120}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2CDF3E1C-267D-4198-B1C7-7E1F548FC120}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1039,6 +1045,7 @@ Global
{690B0274-291F-4D9E-BA76-54EFF7D3E4BC} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1}
{060FD0BA-BD78-48E1-A8A7-4906A5AD5E39} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1}
{169A82A5-2DB3-40EA-801E-14C08D743DF7} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1}
{2CDF3E1C-267D-4198-B1C7-7E1F548FC120} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D4B5CEAA-7D70-4FCB-A68E-B03FBE5E0E5E}

View file

@ -36,7 +36,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
rm -rf /var/lib/apt/lists/*
# Set PYTHONNET_PYDLL environment variable
ENV PYTHONNET_PYDLL /usr/lib/aarch64-linux-gnu/libpython3.11.so
ENV PYTHONNET_PYDLL=/usr/lib/aarch64-linux-gnu/libpython3.11.so
# Copy the tracer from build target
COPY --from=build /tmp/datadog-dotnet-apm.deb /tmp/datadog-dotnet-apm.deb

View file

@ -28,7 +28,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
rm -rf /var/lib/apt/lists/*
# Set PYTHONNET_PYDLL environment variable
ENV PYTHONNET_PYDLL /usr/lib/aarch64-linux-gnu/libpython3.11.so
ENV PYTHONNET_PYDLL=/usr/lib/aarch64-linux-gnu/libpython3.11.so
EXPOSE 80/tcp
EXPOSE 443/tcp

View file

@ -29,7 +29,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
rm -rf /var/lib/apt/lists/*
# Set PYTHONNET_PYDLL environment variable
ENV PYTHONNET_PYDLL /usr/lib/aarch64-linux-gnu/libpython3.11.so
ENV PYTHONNET_PYDLL=/usr/lib/aarch64-linux-gnu/libpython3.11.so
EXPOSE 80/tcp
EXPOSE 443/tcp

View file

@ -5,6 +5,7 @@
<ProjectReference Include="..\..\modules\Elsa.Caching.Distributed.MassTransit\Elsa.Caching.Distributed.MassTransit.csproj" />
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.PostgreSql\Elsa.EntityFrameworkCore.PostgreSql.csproj"/>
<ProjectReference Include="..\..\modules\Elsa.MassTransit.AzureServiceBus\Elsa.MassTransit.AzureServiceBus.csproj"/>
<ProjectReference Include="..\..\modules\Elsa.OpenTelemetry\Elsa.OpenTelemetry.csproj" />
<ProjectReference Include="..\..\modules\Elsa\Elsa.csproj"/>
<ProjectReference Include="..\..\common\Elsa.DropIns\Elsa.DropIns.csproj"/>
<ProjectReference Include="..\..\modules\Elsa.Alterations.MassTransit\Elsa.Alterations.MassTransit.csproj"/>

View file

@ -21,13 +21,17 @@ using Elsa.MongoDb.Modules.Alterations;
using Elsa.MongoDb.Modules.Identity;
using Elsa.MongoDb.Modules.Management;
using Elsa.MongoDb.Modules.Runtime;
using Elsa.OpenTelemetry.Middleware;
using Elsa.Server.Web;
using Elsa.Tenants.Extensions;
using Elsa.Workflows;
using Elsa.Workflows.Api;
using Elsa.Workflows.Management.Compression;
using Elsa.Workflows.Management.Stores;
using Elsa.Workflows.Pipelines.ActivityExecution;
using Elsa.Workflows.Pipelines.WorkflowExecution;
using Elsa.Workflows.Runtime.Distributed.Extensions;
using Elsa.Workflows.Runtime.Extensions;
using Elsa.Workflows.Runtime.Stores;
using JetBrains.Annotations;
using Medallion.Threading.FileSystem;
@ -133,6 +137,11 @@ services
identity.UseConfigurationBasedRoleProvider(options => identitySection.Bind(options));
})
.UseDefaultAuthentication()
.UseWorkflows(workflows =>
{
workflows.WithDefaultWorkflowExecutionPipeline(pipeline => pipeline.UseWorkflowExecutionTracing());
workflows.WithDefaultActivityExecutionPipeline(pipeline => pipeline.UseActivityExecutionTracing());
})
.UseWorkflowManagement(management =>
{
if (persistenceProvider == PersistenceProvider.MongoDb)

View file

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>
Provides OpenTelemetry sources for tracing workflow and activity execution.
</Description>
<PackageTags>elsa module open-telemetry</PackageTags>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Elsa.Workflows.Core\Elsa.Workflows.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Extensions\" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,11 @@
using Elsa.Features.Abstractions;
using Elsa.Features.Services;
namespace Elsa.OpenTelemetry.Features;
public class OpenTelemetryFeature(IModule module) : FeatureBase(module)
{
public override void Configure()
{
}
}

View file

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait />
</Weavers>

View file

@ -0,0 +1,8 @@
using System.Diagnostics;
namespace Elsa.OpenTelemetry.Helpers;
public class ElsaOpenTelemetry
{
public static readonly ActivitySource ActivitySource = new("Elsa.Workflows");
}

View file

@ -0,0 +1,36 @@
using System.Diagnostics;
using Elsa.OpenTelemetry.Helpers;
using Elsa.Workflows;
using Elsa.Workflows.Pipelines.ActivityExecution;
using Elsa.Workflows.Pipelines.WorkflowExecution;
using JetBrains.Annotations;
using Activity = System.Diagnostics.Activity;
using ActivityKind = System.Diagnostics.ActivityKind;
namespace Elsa.OpenTelemetry.Middleware;
[UsedImplicitly]
public class OpenTelemetryTracingActivityExecutionMiddleware(ActivityMiddlewareDelegate next) : IActivityExecutionMiddleware
{
public async ValueTask InvokeAsync(ActivityExecutionContext context)
{
var activity = context.Activity;
using var span = ElsaOpenTelemetry.ActivitySource.StartActivity($"ActivityExecution {context.ActivityDescriptor.TypeName}", ActivityKind.Internal, Activity.Current?.Context ?? default);
span?.AddTag("activity.nodeId", activity.NodeId);
span?.AddTag("activity.type", activity.Type);
span?.AddTag("activity.name", activity.Name);
span?.AddTag("activityInstance.id", context.Id);
span?.AddTag("activityInstance.originalStatus", context.Status.ToString());
span?.AddEvent(new ActivityEvent("Executing"));
await next(context);
span?.AddEvent(new ActivityEvent("Executed"));
span?.AddTag("activityInstance.newStatus", context.Status.ToString());
}
}
[UsedImplicitly]
public static class OpenTelemetryTracingActivityExecutionMiddlewareExtensions
{
/// Installs the <see cref="OpenTelemetryTracingActivityExecutionMiddleware"/> component in the workflow execution pipeline.
public static IActivityExecutionPipelineBuilder UseActivityExecutionTracing(this IActivityExecutionPipelineBuilder pipelineBuilder) => pipelineBuilder.Insert<OpenTelemetryTracingActivityExecutionMiddleware>(0);
}

View file

@ -0,0 +1,38 @@
using System.Diagnostics;
using Elsa.OpenTelemetry.Helpers;
using Elsa.Workflows;
using Elsa.Workflows.Pipelines.WorkflowExecution;
using JetBrains.Annotations;
using Activity = System.Diagnostics.Activity;
using ActivityKind = System.Diagnostics.ActivityKind;
namespace Elsa.OpenTelemetry.Middleware;
[UsedImplicitly]
public class OpenTelemetryTracingWorkflowExecutionMiddleware(WorkflowMiddlewareDelegate next) : WorkflowExecutionMiddleware(next)
{
public override async ValueTask InvokeAsync(WorkflowExecutionContext context)
{
var workflowInstanceId = context.Id;
var workflow = context.Workflow;
using var activity = ElsaOpenTelemetry.ActivitySource.StartActivity($"WorkflowExecution {workflow.WorkflowMetadata.Name}", ActivityKind.Internal, Activity.Current?.Context ?? default);
activity?.AddTag("workflowInstance.id", workflowInstanceId);
activity?.AddTag("workflowDefinition.definitionId", workflow.Identity.DefinitionId);
activity?.AddTag("workflowDefinition.version", workflow.Identity.Version);
activity?.AddTag("tenantId", workflow.Identity.TenantId);
activity?.AddTag("workflowInstance.originalStatus", context.Status.ToString());
activity?.AddTag("workflowInstance.originalSubStatus", context.SubStatus.ToString());
activity?.AddEvent(new ActivityEvent("Executing"));
await Next(context);
activity?.AddEvent(new ActivityEvent("Executed"));
activity?.AddTag("workflowInstance.newStatus", context.Status.ToString());
activity?.AddTag("workflowInstance.newSubStatus", context.SubStatus.ToString());
}
}
[UsedImplicitly]
public static class OpenTelemetryWorkflowExecutionMiddlewareExtensions
{
/// Installs the <see cref="OpenTelemetryTracingWorkflowExecutionMiddleware"/> component in the workflow execution pipeline.
public static IWorkflowExecutionPipelineBuilder UseWorkflowExecutionTracing(this IWorkflowExecutionPipelineBuilder pipelineBuilder) => pipelineBuilder.Insert<OpenTelemetryTracingWorkflowExecutionMiddleware>(0);
}

View file

@ -1,4 +1,5 @@
using Elsa.WorkflowContexts.Middleware;
using Elsa.Workflows;
using Elsa.Workflows.Contracts;
using Elsa.Workflows.Pipelines.ActivityExecution;
using Elsa.Workflows.Pipelines.WorkflowExecution;

View file

@ -1,4 +1,4 @@
namespace Elsa.Workflows.Contracts;
namespace Elsa.Workflows;
public interface IActivityExecutionMiddleware
{

View file

@ -1,6 +1,6 @@
using Elsa.Workflows.Pipelines.ActivityExecution;
namespace Elsa.Workflows.Contracts;
namespace Elsa.Workflows;
/// <summary>
/// Builds an activity execution pipeline.
@ -19,6 +19,9 @@ public interface IActivityExecutionPipelineBuilder
/// <returns>The current <see cref="IActivityExecutionPipelineBuilder"/>.</returns>
IActivityExecutionPipelineBuilder Use(Func<ActivityMiddlewareDelegate, ActivityMiddlewareDelegate> middleware);
/// Inserts the middleware component at the specified index.
IActivityExecutionPipelineBuilder Insert(int index, Func<ActivityMiddlewareDelegate, ActivityMiddlewareDelegate> middleware);
/// <summary>
/// Constructs the final <see cref="ActivityMiddlewareDelegate"/> delegate that invokes each installed middleware component.
/// </summary>

View file

@ -1,41 +1,30 @@
using Elsa.Workflows.Pipelines.WorkflowExecution;
namespace Elsa.Workflows.Contracts;
namespace Elsa.Workflows;
/// <summary>
/// Builds a workflow execution pipeline.
/// </summary>
public interface IWorkflowExecutionPipelineBuilder
{
/// <summary>
/// A general-purpose dictionary of values that can be used by middleware components.
/// </summary>
public IDictionary<object, object?> Properties { get; }
/// <summary>
/// The middleware components that have been installed.
/// </summary>
public IEnumerable<Func<WorkflowMiddlewareDelegate, WorkflowMiddlewareDelegate>> Components { get; }
/// <summary>
/// The current service provider to resolve services from.
/// </summary>
IServiceProvider ServiceProvider { get; }
/// <summary>
/// Installs the specified delegate as a middleware component.
/// </summary>
IWorkflowExecutionPipelineBuilder Use(Func<WorkflowMiddlewareDelegate, WorkflowMiddlewareDelegate> middleware);
/// <summary>
/// Constructs the final <see cref="WorkflowMiddlewareDelegate"/> delegate that invokes each installed middleware component.
/// </summary>
public WorkflowMiddlewareDelegate Build();
/// <summary>
/// Clears the current pipeline.
/// </summary>
IWorkflowExecutionPipelineBuilder Reset();
/// Inserts the middleware component at the specified index.
IWorkflowExecutionPipelineBuilder Insert(int index, Func<WorkflowMiddlewareDelegate, WorkflowMiddlewareDelegate> middleware);
/// <summary>
/// Replaces the middleware component at the specified index with the specified delegate.

View file

@ -1,20 +1,37 @@
using Elsa.Workflows.Contracts;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.Pipelines.ActivityExecution;
public static class ActivityExecutionMiddlewareExtensions
{
public static IActivityExecutionPipelineBuilder UseMiddleware<TMiddleware>(this IActivityExecutionPipelineBuilder pipelineBuilder, params object[] args) where TMiddleware : IActivityExecutionMiddleware
public static IActivityExecutionPipelineBuilder UseMiddleware<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TMiddleware>(this IActivityExecutionPipelineBuilder pipelineBuilder, params object[] args) where TMiddleware : IActivityExecutionMiddleware
{
var delegateFactory = CreateMiddlewareDelegateFactory<TMiddleware>(pipelineBuilder, args);
return pipelineBuilder.Use(delegateFactory);
}
public static IActivityExecutionPipelineBuilder Insert<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]TMiddleware>(this IActivityExecutionPipelineBuilder pipelineBuilder, int index, params object[] args) where TMiddleware : IActivityExecutionMiddleware
{
var delegateFactory = CreateMiddlewareDelegateFactory<TMiddleware>(pipelineBuilder, args);
return pipelineBuilder.Insert(index, delegateFactory);
}
/// Creates a middleware delegate for the specified middleware component.
public static Func<ActivityMiddlewareDelegate, ActivityMiddlewareDelegate> CreateMiddlewareDelegateFactory<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TMiddleware>(
this IActivityExecutionPipelineBuilder pipelineBuilder, params object[] args) where TMiddleware : IActivityExecutionMiddleware
{
var middleware = typeof(TMiddleware);
return pipelineBuilder.Use(next =>
return next =>
{
var invokeMethod = MiddlewareHelpers.GetInvokeMethod(middleware);
var ctorArgs = new[] { next }.Concat(args).Select(x => x!).ToArray();
var ctorArgs = new[]
{
next
}.Concat(args).Select(x => x).ToArray();
var instance = ActivatorUtilities.CreateInstance(pipelineBuilder.ServiceProvider, middleware, ctorArgs);
return (ActivityMiddlewareDelegate)invokeMethod.CreateDelegate(typeof(ActivityMiddlewareDelegate), instance);
});
};
}
}

View file

@ -1,22 +1,12 @@
using Elsa.Workflows.Contracts;
namespace Elsa.Workflows.Pipelines.ActivityExecution;
/// <inheritdoc />
public class ActivityExecutionPipelinePipelineBuilder : IActivityExecutionPipelineBuilder
public class ActivityExecutionPipelinePipelineBuilder(IServiceProvider serviceProvider) : IActivityExecutionPipelineBuilder
{
private readonly IList<Func<ActivityMiddlewareDelegate, ActivityMiddlewareDelegate>> _components = new List<Func<ActivityMiddlewareDelegate, ActivityMiddlewareDelegate>>();
/// <summary>
/// Initializes a new instance of the <see cref="ActivityExecutionPipelinePipelineBuilder"/> class.
/// </summary>
public ActivityExecutionPipelinePipelineBuilder(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
}
/// <inheritdoc />
public IServiceProvider ServiceProvider { get; }
public IServiceProvider ServiceProvider { get; } = serviceProvider;
/// <inheritdoc />
public IActivityExecutionPipelineBuilder Use(Func<ActivityMiddlewareDelegate, ActivityMiddlewareDelegate> middleware)
@ -25,6 +15,12 @@ public class ActivityExecutionPipelinePipelineBuilder : IActivityExecutionPipeli
return this;
}
public IActivityExecutionPipelineBuilder Insert(int index, Func<ActivityMiddlewareDelegate, ActivityMiddlewareDelegate> middleware)
{
_components.Insert(index, middleware);
return this;
}
/// <inheritdoc />
public ActivityMiddlewareDelegate Build()
{

View file

@ -4,44 +4,42 @@ using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.Pipelines.WorkflowExecution;
/// <summary>
/// Provides extensions to <see cref="IWorkflowExecutionPipelineBuilder"/> that adds support for installing <see cref="IWorkflowExecutionMiddleware"/> components.
/// </summary>
public static class WorkflowExecutionMiddlewareExtensions
{
/// <summary>
/// Installs the specified middleware component into the pipeline being built.
/// </summary>
public static IWorkflowExecutionPipelineBuilder UseMiddleware<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TMiddleware>(
this IWorkflowExecutionPipelineBuilder pipelineBuilder, params object[] args) where TMiddleware : IWorkflowExecutionMiddleware
{
var delegateFactory = CreateMiddlewareDelegateFactory<TMiddleware>(pipelineBuilder, args);
return pipelineBuilder.Use(delegateFactory);
}
/// Installs the specified middleware component into the pipeline being built.
public static IWorkflowExecutionPipelineBuilder Insert<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TMiddleware>(
this IWorkflowExecutionPipelineBuilder pipelineBuilder, int index, params object[] args) where TMiddleware : IWorkflowExecutionMiddleware
{
var delegateFactory = CreateMiddlewareDelegateFactory<TMiddleware>(pipelineBuilder, args);
return pipelineBuilder.Insert(index, delegateFactory);
}
/// <summary>
/// Replaces the terminal middleware component with the specified middleware component.
/// </summary>
public static IWorkflowExecutionPipelineBuilder ReplaceTerminal<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TMiddleware>(
this IWorkflowExecutionPipelineBuilder pipelineBuilder, params object[] args) where TMiddleware : IWorkflowExecutionMiddleware
{
var index = pipelineBuilder.Components.Count() - 1;
return pipelineBuilder.Replace<TMiddleware>(index, args);
}
/// <summary>
/// Replaces the middleware component at the specified index with the specified middleware component.
/// </summary>
public static IWorkflowExecutionPipelineBuilder Replace<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TMiddleware>(
this IWorkflowExecutionPipelineBuilder pipelineBuilder, int index, params object[] args) where TMiddleware : IWorkflowExecutionMiddleware
{
var delegateFactory = CreateMiddlewareDelegateFactory<TMiddleware>(pipelineBuilder, args);
return pipelineBuilder.Replace(index, delegateFactory);
}
/// <summary>
/// Creates a middleware delegate for the specified middleware component.
/// </summary>
public static Func<WorkflowMiddlewareDelegate, WorkflowMiddlewareDelegate> CreateMiddlewareDelegateFactory<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TMiddleware>(
this IWorkflowExecutionPipelineBuilder pipelineBuilder, params object[] args) where TMiddleware : IWorkflowExecutionMiddleware
{

View file

@ -1,5 +1,3 @@
using Elsa.Workflows.Contracts;
namespace Elsa.Workflows.Pipelines.WorkflowExecution;
/// <inheritdoc />
@ -54,6 +52,12 @@ public class WorkflowExecutionPipelineBuilder : IWorkflowExecutionPipelineBuilde
return this;
}
public IWorkflowExecutionPipelineBuilder Insert(int index, Func<WorkflowMiddlewareDelegate, WorkflowMiddlewareDelegate> middleware)
{
_components.Insert(index, middleware);
return this;
}
public IWorkflowExecutionPipelineBuilder Replace(int index, Func<WorkflowMiddlewareDelegate, WorkflowMiddlewareDelegate> middleware)
{
_components[index] = middleware;

View file

@ -1,3 +1,4 @@
using Elsa.Workflows;
using Elsa.Workflows.Contracts;
using Elsa.Workflows.Pipelines.ActivityExecution;
using Elsa.Workflows.Runtime.Middleware.Activities;

View file

@ -0,0 +1,36 @@
using Elsa.Extensions;
using Elsa.Workflows.Features;
using Elsa.Workflows.Middleware.Activities;
// ReSharper disable once CheckNamespace
namespace Elsa.Workflows.Runtime.Extensions;
/// Adds an extension method to the <see cref="WorkflowsFeature"/> that installs a default workflow runtime execution pipeline.
public static class PipelineWorkflowsFeatureExtensions
{
/// Installs a default workflow runtime execution pipeline.
public static WorkflowsFeature WithDefaultWorkflowExecutionPipeline(this WorkflowsFeature workflowsFeature, Action<IWorkflowExecutionPipelineBuilder>? configurePipeline = null)
{
return workflowsFeature.WithWorkflowExecutionPipeline(pipeline =>
{
pipeline.UseDefaultPipeline();
configurePipeline?.Invoke(pipeline);
});
}
/// Installs an activity invoker that can run activities asynchronously in the background.
public static WorkflowsFeature WithDefaultActivityExecutionPipeline(this WorkflowsFeature workflowsFeature, Action<IActivityExecutionPipelineBuilder>? configurePipeline = null)
{
return workflowsFeature.WithActivityExecutionPipeline(pipeline =>
{
pipeline
.UseExceptionHandling()
.UseExecutionLogging()
.UseNotifications()
.UseBackgroundActivityInvoker();
configurePipeline?.Invoke(pipeline);
});
}
}

View file

@ -1,4 +1,4 @@
using Elsa.Workflows.Contracts;
using Elsa.Workflows;
using Elsa.Workflows.Middleware.Workflows;
using Elsa.Workflows.Pipelines.WorkflowExecution;
using Elsa.Workflows.Runtime.Middleware.Workflows;

View file

@ -1,30 +0,0 @@
using Elsa.Extensions;
using Elsa.Workflows.Features;
using Elsa.Workflows.Middleware.Activities;
// ReSharper disable once CheckNamespace
namespace Elsa.Workflows.Runtime.Extensions;
/// <summary>
/// Adds an extension method to the <see cref="WorkflowsFeature"/> that installs a default workflow runtime execution pipeline.
/// </summary>
public static class WorkflowsFeatureExtensions
{
/// <summary>
/// Installs a default workflow runtime execution pipeline.
/// </summary>
public static WorkflowsFeature WithDefaultWorkflowExecutionPipeline(this WorkflowsFeature workflowsFeature) =>
workflowsFeature.WithWorkflowExecutionPipeline(pipeline =>
pipeline.UseDefaultPipeline());
/// <summary>
/// Installs an activity invoker that can run activities asynchronously in the background.
/// </summary>
public static WorkflowsFeature WithBackgroundActivityExecutionPipeline(this WorkflowsFeature workflowsFeature) =>
workflowsFeature.WithActivityExecutionPipeline(pipeline =>
pipeline
.UseExceptionHandling()
.UseExecutionLogging()
.UseNotifications()
.UseBackgroundActivityInvoker());
}

View file

@ -1,3 +1,4 @@
using Elsa.Workflows;
using Elsa.Workflows.Contracts;
using Microsoft.Extensions.DependencyInjection;

View file

@ -37,7 +37,7 @@ public class ElsaFeature : FeatureBase
Module
.UseWorkflows(workflows => workflows
.WithDefaultWorkflowExecutionPipeline()
.WithBackgroundActivityExecutionPipeline())
.WithDefaultActivityExecutionPipeline())
.UseWorkflowManagement(management =>
{
if (!DisableAutomaticActivityRegistration)