Enhance logging by adding logger state to activity and workflow pipelines (#5985)

* Enhance logging by adding logger state to activity and workflow pipelines

* Change logger state for ActivityExecutionContext to use key of ActivityInstanceId instead of ActivityId

* Adjusted logger state registration to conform to the Elsa Features framework delegate pattern

---------

Co-authored-by: Bob Hauser <rhauser@kinaxis.com>
Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
This commit is contained in:
bobhauser 2024-10-07 07:51:05 -04:00 committed by GitHub
parent 368d8d9770
commit dd711bf4ff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 101 additions and 25 deletions

View file

@ -0,0 +1,14 @@
namespace Elsa.Workflows.Contracts;
/// <summary>
/// Generates logger state for the specified <typeparamref name="TContext"/>.
/// </summary>
public interface ILoggerStateGenerator<TContext>
{
/// <summary>
/// Generates logger state for the specified <typeparamref name="TContext"/>.
/// </summary>
/// <param name="context">The <typeparamref name="TContext"/> to generate logger state for.</param>
/// <returns>A <see cref="Dictionary{String, Object}" /> containing the state related to the <typeparamref name="TContext"/>.</returns>
Dictionary<string, object> GenerateLoggerState(TContext context);
}

View file

@ -18,6 +18,7 @@ using Elsa.Workflows.PortResolvers;
using Elsa.Workflows.Serialization.Configurators;
using Elsa.Workflows.Serialization.Helpers;
using Elsa.Workflows.Serialization.Serializers;
using Elsa.Workflows.Services;
using Elsa.Workflows.UIHints.CheckList;
using Elsa.Workflows.UIHints.Dropdown;
using Elsa.Workflows.UIHints.JsonEditor;
@ -50,6 +51,12 @@ public class WorkflowsFeature : FeatureBase
/// A handler for committing workflow execution state.
public Func<IServiceProvider, ICommitStateHandler> CommitStateHandler { get; set; } = sp => new NoopCommitStateHandler();
/// A factory that instantiates a concrete <see cref="ILoggerStateGenerator{WorkflowExecutionContext}"/>.
public Func<IServiceProvider, ILoggerStateGenerator<WorkflowExecutionContext>> WorkflowLoggerStateGenerator { get; set; } = sp => new WorkflowLoggerStateGenerator();
/// A factory that instantiates a concrete <see cref="ILoggerStateGenerator{ActivityExecutionContext}"/>.
public Func<IServiceProvider, ILoggerStateGenerator<ActivityExecutionContext>> ActivityLoggerStateGenerator { get; set; } = sp => new ActivityLoggerStateGenerator();
/// A delegate to configure the <see cref="IWorkflowExecutionPipeline"/>.
public Action<IWorkflowExecutionPipelineBuilder> WorkflowExecutionPipeline { get; set; } = builder => builder
.UseExceptionHandling()
@ -58,9 +65,7 @@ public class WorkflowsFeature : FeatureBase
/// A delegate to configure the <see cref="IActivityExecutionPipeline"/>.
public Action<IActivityExecutionPipelineBuilder> ActivityExecutionPipeline { get; set; } = builder => builder.UseDefaultActivityInvoker();
/// <summary>
/// Fluent method to set <see cref="StandardInStreamProvider"/>.
/// </summary>
public WorkflowsFeature WithStandardInStreamProvider(Func<IServiceProvider, IStandardInStreamProvider> provider)
{
StandardInStreamProvider = provider;
@ -79,8 +84,22 @@ public class WorkflowsFeature : FeatureBase
{
IdentityGenerator = generator;
return this;
}
/// Fluent method to set <see cref="ILoggerStateGenerator{WorkflowExecutionContext}"/>.
public WorkflowsFeature WithWorkflowLoggerStateGenerator(Func<IServiceProvider, ILoggerStateGenerator<WorkflowExecutionContext>> generator)
{
WorkflowLoggerStateGenerator = generator;
return this;
}
/// Fluent method to set <see cref="ILoggerStateGenerator{ActivityExecutionContext}"/>.
public WorkflowsFeature WithActivityLoggerStateGenerator(Func<IServiceProvider, ILoggerStateGenerator<ActivityExecutionContext>> generator)
{
ActivityLoggerStateGenerator = generator;
return this;
}
/// Fluent method to configure the <see cref="IWorkflowExecutionPipeline"/>.
public WorkflowsFeature WithWorkflowExecutionPipeline(Action<IWorkflowExecutionPipelineBuilder> setup)
{
@ -176,7 +195,11 @@ public class WorkflowsFeature : FeatureBase
.AddScoped<IUIHintHandler, CheckListUIHintHandler>()
.AddScoped<IUIHintHandler, JsonEditorUIHintHandler>()
// Logging
// Logger state generators
.AddSingleton(WorkflowLoggerStateGenerator)
.AddSingleton(ActivityLoggerStateGenerator)
// Logging
.AddLogging();
}
}

View file

@ -1,20 +1,16 @@
using Elsa.Workflows.Contracts;
using Elsa.Workflows.Options;
using Microsoft.Extensions.Logging;
namespace Elsa.Workflows;
/// <inheritdoc />
public class ActivityInvoker : IActivityInvoker
public class ActivityInvoker(
IActivityExecutionPipeline pipeline,
ILoggerStateGenerator<ActivityExecutionContext> loggerStateGenerator,
ILogger<ActivityInvoker> logger)
: IActivityInvoker
{
private readonly IActivityExecutionPipeline _pipeline;
/// <summary>
/// Constructor.
/// </summary>
public ActivityInvoker(IActivityExecutionPipeline pipeline)
{
_pipeline = pipeline;
}
/// <inheritdoc />
public async Task InvokeAsync(WorkflowExecutionContext workflowExecutionContext, IActivity activity, ActivityInvocationOptions? options = default)
@ -42,8 +38,11 @@ public class ActivityInvoker : IActivityInvoker
/// <inheritdoc />
public async Task InvokeAsync(ActivityExecutionContext activityExecutionContext)
{
// Execute the activity execution pipeline.
await _pipeline.ExecuteAsync(activityExecutionContext);
{
var loggerState = loggerStateGenerator.GenerateLoggerState(activityExecutionContext);
using var loggingScope = logger.BeginScope(loggerState);
// Execute the activity execution pipeline.
await pipeline.ExecuteAsync(activityExecutionContext);
}
}

View file

@ -0,0 +1,22 @@
using Elsa.Workflows.Contracts;
namespace Elsa.Workflows.Services;
/// <summary>
/// Generates logger state for the specified <see cref="ActivityExecutionContext"/>.
/// </summary>
public class ActivityLoggerStateGenerator : ILoggerStateGenerator<ActivityExecutionContext>
{
/// <summary>
/// Generates logger state for the specified <see cref="ActivityExecutionContext"/>.
/// </summary>
/// <param name="activityExecutionContext">The <see cref="ActivityExecutionContext"/> to generate logger state for.</param>
/// <returns>A <see cref="Dictionary{String, Object}" /> containing the state related to the <see cref="ActivityExecutionContext"/>.</returns>
public Dictionary<string, object> GenerateLoggerState(ActivityExecutionContext activityExecutionContext)
{
return new Dictionary<string, object>
{
["ActivityInstanceId"] = activityExecutionContext.Id
};
}
}

View file

@ -0,0 +1,22 @@
using Elsa.Workflows.Contracts;
namespace Elsa.Workflows.Services;
/// <summary>
/// Generates logger state for the specified <see cref="WorkflowExecutionContext"/>.
/// </summary>
public class WorkflowLoggerStateGenerator : ILoggerStateGenerator<WorkflowExecutionContext>
{
/// <summary>
/// Generates logger state for the specified <see cref="WorkflowExecutionContext"/>.
/// </summary>
/// <param name="workflowExecutionContext">The <see cref="WorkflowExecutionContext"/> to generate logger state for.</param>
/// <returns>A <see cref="Dictionary{String, Object}" /> containing the state related to the <see cref="WorkflowExecutionContext"/>.</returns>
public Dictionary<string, object> GenerateLoggerState(WorkflowExecutionContext workflowExecutionContext)
{
return new Dictionary<string, object>
{
["WorkflowInstanceId"] = workflowExecutionContext.Id
};
}
}

View file

@ -18,7 +18,8 @@ public class WorkflowRunner(
IWorkflowBuilderFactory workflowBuilderFactory,
IWorkflowGraphBuilder workflowGraphBuilder,
IIdentityGenerator identityGenerator,
INotificationSender notificationSender,
INotificationSender notificationSender,
ILoggerStateGenerator<WorkflowExecutionContext> loggerStateGenerator,
ICommitStateHandler commitStateHandler,
ILogger<WorkflowRunner> logger)
: IWorkflowRunner
@ -163,16 +164,11 @@ public class WorkflowRunner(
return await RunAsync(workflowExecutionContext);
}
/// <inheritdoc />
public async Task<RunWorkflowResult> RunAsync(WorkflowExecutionContext workflowExecutionContext)
{
var workflowInstanceId = workflowExecutionContext.Id;
var logContext = new Dictionary<string, object>
{
["WorkflowInstanceId"] = workflowInstanceId
};
using var loggingScope = logger.BeginScope(logContext);
var loggerState = loggerStateGenerator.GenerateLoggerState(workflowExecutionContext);
using var loggingScope = logger.BeginScope(loggerState);
var workflow = workflowExecutionContext.Workflow;
var cancellationToken = workflowExecutionContext.CancellationToken;