Merge pull request #5708 from elsa-workflows/enh/log_sink

Add sink for persisting workflows execution log records
This commit is contained in:
raymonddenhaan 2024-07-05 09:06:25 +02:00 committed by GitHub
commit 703bd81d0c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 76 additions and 55 deletions

View file

@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Elsa.Common.Models;
using Elsa.Expressions.Contracts;
@ -7,8 +8,6 @@ using Elsa.Extensions;
using Elsa.Workflows.Activities.Flowchart.Attributes;
using Elsa.Workflows.Attributes;
using Elsa.Workflows.Contracts;
using Elsa.Workflows.Exceptions;
using Elsa.Workflows.UIHints;
using Elsa.Workflows.Memory;
using Elsa.Workflows.Models;
using Elsa.Workflows.Options;
@ -18,8 +17,8 @@ using Elsa.Workflows.Runtime.Models;
using Elsa.Workflows.Runtime.Requests;
using Elsa.Workflows.Runtime.UIHints;
using Elsa.Workflows.Services;
using Elsa.Workflows.UIHints;
using JetBrains.Annotations;
using System.Diagnostics.CodeAnalysis;
namespace Elsa.Workflows.Runtime.Activities;
@ -232,7 +231,6 @@ public class BulkDispatchWorkflows : Activity
var input = context.WorkflowInput;
var workflowInstanceId = input["WorkflowInstanceId"].ConvertTo<string>()!;
var workflowSubStatus = input["WorkflowSubStatus"].ConvertTo<WorkflowSubStatus>();
var workflowOutput = input["WorkflowOutput"].ConvertTo<IDictionary<string, object>>();
var finishedInstancesCount = context.GetProperty<long>(CompletedInstancesCountKey) + 1;
context.SetProperty(CompletedInstancesCountKey, finishedInstancesCount);

View file

@ -3,14 +3,13 @@ using Elsa.Common.Models;
using Elsa.Extensions;
using Elsa.Workflows.Attributes;
using Elsa.Workflows.Contracts;
using Elsa.Workflows.Exceptions;
using Elsa.Workflows.UIHints;
using Elsa.Workflows.Models;
using Elsa.Workflows.Runtime.Bookmarks;
using Elsa.Workflows.Runtime.Contracts;
using Elsa.Workflows.Runtime.Models;
using Elsa.Workflows.Runtime.Requests;
using Elsa.Workflows.Runtime.UIHints;
using Elsa.Workflows.UIHints;
using JetBrains.Annotations;
namespace Elsa.Workflows.Runtime.Activities;

View file

@ -1,14 +1,23 @@
namespace Elsa.Workflows.Runtime.Bookmarks;
/// <summary>
/// The payload for an event bookmark.
/// </summary>
public class EventBookmarkPayload
{
private readonly string _eventName = default!;
/// <summary>
/// The payload for an event bookmark.
/// </summary>
public EventBookmarkPayload(string eventName)
{
EventName = eventName;
}
/// <summary>
/// The name of the event.
/// </summary>
public string EventName
{
get => _eventName;

View file

@ -0,0 +1,12 @@
namespace Elsa.Workflows.Runtime.Contracts;
/// <summary>
/// Represents a sink for storing workflow execution log records.
/// </summary>
public interface IWorkflowExecutionLogSink
{
/// <summary>
/// Persists the execution logs of a workflow.
/// </summary>
Task PersistExecutionLogsAsync(WorkflowExecutionContext context, CancellationToken cancellationToken = default);
}

View file

@ -107,6 +107,11 @@ public class WorkflowRuntimeFeature : FeatureBase
/// A factory that instantiates an <see cref="IBackgroundActivityScheduler"/>.
/// </summary>
public Func<IServiceProvider, IBackgroundActivityScheduler> BackgroundActivityScheduler { get; set; } = sp => ActivatorUtilities.CreateInstance<LocalBackgroundActivityScheduler>(sp);
/// <summary>
/// Represents a sink for workflow execution logs.
/// </summary>
public Func<IServiceProvider, IWorkflowExecutionLogSink> WorkflowExecutionLogSink { get; set; } = sp => sp.GetRequiredService<StoreWorkflowExecutionLogSink>();
/// <summary>
/// A delegate to configure the <see cref="DistributedLockingOptions"/>.
@ -206,6 +211,7 @@ public class WorkflowRuntimeFeature : FeatureBase
.AddScoped(WorkflowCancellationDispatcher)
.AddScoped(WorkflowExecutionContextStore)
.AddScoped(RunTaskDispatcher)
.AddScoped(WorkflowExecutionLogSink)
.AddSingleton(BackgroundActivityScheduler)
.AddSingleton<RandomLongIdentityGenerator>()
.AddScoped<IBookmarkManager, DefaultBookmarkManager>()
@ -218,6 +224,7 @@ public class WorkflowRuntimeFeature : FeatureBase
.AddScoped<ITaskReporter, TaskReporter>()
.AddScoped<SynchronousTaskDispatcher>()
.AddScoped<BackgroundTaskDispatcher>()
.AddScoped<StoreWorkflowExecutionLogSink>()
.AddScoped<IEventPublisher, EventPublisher>()
.AddScoped<IWorkflowInbox, DefaultWorkflowInbox>()
.AddScoped<IBookmarkUpdater, BookmarkUpdater>()

View file

@ -1,66 +1,19 @@
using Elsa.Mediator.Contracts;
using Elsa.Workflows.Contracts;
using Elsa.Workflows.Pipelines.WorkflowExecution;
using Elsa.Workflows.Runtime.Contracts;
using Elsa.Workflows.Runtime.Entities;
using Elsa.Workflows.Runtime.Notifications;
namespace Elsa.Workflows.Runtime.Middleware.Workflows;
/// <summary>
/// Takes care of persisting workflow execution log entries.
/// </summary>
public class PersistWorkflowExecutionLogMiddleware : WorkflowExecutionMiddleware
public class PersistWorkflowExecutionLogMiddleware(WorkflowMiddlewareDelegate next, IWorkflowExecutionLogSink sink) : WorkflowExecutionMiddleware(next)
{
private readonly IWorkflowExecutionLogStore _workflowExecutionLogStore;
private readonly INotificationSender _notificationSender;
private readonly IIdentityGenerator _identityGenerator;
/// <inheritdoc />
public PersistWorkflowExecutionLogMiddleware(
WorkflowMiddlewareDelegate next,
IWorkflowExecutionLogStore workflowExecutionLogStore,
INotificationSender notificationSender,
IIdentityGenerator identityGenerator) : base(next)
{
_workflowExecutionLogStore = workflowExecutionLogStore;
_notificationSender = notificationSender;
_identityGenerator = identityGenerator;
}
/// <inheritdoc />
public override async ValueTask InvokeAsync(WorkflowExecutionContext context)
{
// Invoke next middleware.
await Next(context);
// Persist workflow execution log entries.
var entries = context.ExecutionLog.Select(x => new WorkflowExecutionLogRecord
{
Id = _identityGenerator.GenerateId(),
ActivityInstanceId = x.ActivityInstanceId,
ParentActivityInstanceId = x.ParentActivityInstanceId,
ActivityNodeId = x.NodeId,
ActivityId = x.ActivityId,
ActivityType = x.ActivityType,
ActivityTypeVersion = x.ActivityTypeVersion,
ActivityName = x.ActivityName,
Message = x.Message,
EventName = x.EventName,
WorkflowDefinitionId = context.Workflow.Identity.DefinitionId,
WorkflowDefinitionVersionId = context.Workflow.Identity.Id,
WorkflowInstanceId = context.Id,
WorkflowVersion = context.Workflow.Version,
Source = x.Source,
ActivityState = x.ActivityState,
Payload = x.Payload,
Timestamp = x.Timestamp,
Sequence = x.Sequence
}).ToList();
await _workflowExecutionLogStore.AddManyAsync(entries, context.CancellationTokens.SystemCancellationToken);
// Publish notification.
await _notificationSender.SendAsync(new WorkflowExecutionLogUpdated(context), context.CancellationTokens.SystemCancellationToken);
await sink.PersistExecutionLogsAsync(context);
}
}

View file

@ -0,0 +1,43 @@
using Elsa.Mediator.Contracts;
using Elsa.Workflows.Contracts;
using Elsa.Workflows.Runtime.Contracts;
using Elsa.Workflows.Runtime.Entities;
using Elsa.Workflows.Runtime.Notifications;
namespace Elsa.Workflows.Runtime.Services;
/// <summary>
/// This implementation saves <see cref="WorkflowExecutionLogRecord"/> directly through the store.
/// </summary>
public class StoreWorkflowExecutionLogSink(IWorkflowExecutionLogStore store, IIdentityGenerator identityGenerator, INotificationSender notificationSender) : IWorkflowExecutionLogSink
{
/// <inheritdoc />
public async Task PersistExecutionLogsAsync(WorkflowExecutionContext context, CancellationToken cancellationToken)
{
var records = context.ExecutionLog.Select(x => new WorkflowExecutionLogRecord
{
Id = identityGenerator.GenerateId(),
ActivityInstanceId = x.ActivityInstanceId,
ParentActivityInstanceId = x.ParentActivityInstanceId,
ActivityNodeId = x.NodeId,
ActivityId = x.ActivityId,
ActivityType = x.ActivityType,
ActivityTypeVersion = x.ActivityTypeVersion,
ActivityName = x.ActivityName,
Message = x.Message,
EventName = x.EventName,
WorkflowDefinitionId = context.Workflow.Identity.DefinitionId,
WorkflowDefinitionVersionId = context.Workflow.Identity.Id,
WorkflowInstanceId = context.Id,
WorkflowVersion = context.Workflow.Version,
Source = x.Source,
ActivityState = x.ActivityState,
Payload = x.Payload,
Timestamp = x.Timestamp,
Sequence = x.Sequence
}).ToList();
await store.AddManyAsync(records, context.CancellationTokens.SystemCancellationToken);
await notificationSender.SendAsync(new WorkflowExecutionLogUpdated(context), context.CancellationTokens.SystemCancellationToken);
}
}