Initial implementation of log activity + base sink (#6859)
* Initial implementation of log activity + base sink * Update src/modules/Elsa.ProcessLogging/Extensions/ModuleExtensions.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Refactor logging implementation: replace `Elsa.ProcessLogging` with a new modular `Elsa.Logging` framework, introducing support for configurable log sinks, enhanced logging extensibility, and updated dependencies in consuming projects. * Enhance logging framework: introduce custom `NullableBoolConverter` and update JSON serialization/deserialization logic for log sink handling. * Update description for `Log` activity input: clarify target sinks configuration * Set default value of `SinkNames` input in `Log` activity to non-nullable collection * Set `DisplayName` for `Sinks` input in `Log` activity * Refactor logging framework: update `ILogSink` and `ILogSinkRouter` to support arguments and attributes, enhance `Log` activity to use updated interfaces, and add default category handling. * Refactor logging framework: simplify argument handling in `ILogSink` and `ILogSinkRouter`, update `Log` activity inputs, and improve message formatting in `MelLogSink`. * Update logging framework to simplify log sink creation, enhance category filtering, and refactor `ILogSink`/`ILogSinkRouter` interface methods. * Introduce modular logging framework enhancements: add `Console` and `Serilog` logging features, refactor `ILogSink` framework, and update projects to align with a modular architecture. * Refactor logging framework: introduce `AddCategoryFilters` extension, replace `DefaultCategory` handling with enhanced category filters, and update sink creation logic for consistency. * Refactor logging framework: rename `SinkOptions` to `LogSinkOptions`, standardize naming across log sink types, and update configuration and sink factory logic for consistency. * Enhance logging framework: add `ConfigureDefaults` methods, update `ILogSinkCatalog` to use `IServiceScopeFactory`, and improve logging configuration handling and defaults setup. * Introduce asynchronous log entry processing: add `ILogEntryQueue`, `LogEntryBackgroundWorker`, and related models to enable queue-based logging and background processing. Update `Log` activity to enqueue log entries for processing. * Add unit and integration tests for `Elsa.Logging.Core` library, refactor logger setup in `Elsa.Server.Web`, enhance logging configuration, and standardize `Directory.Packages.props` file. * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Add documentation comments to logging framework classes, interfaces, methods, and factories to enhance code readability and maintainability. Remove unused `CustomPurpleConsoleFormatter` class and `logs` folder from server project. * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Make `LogEntryInstruction` and `LogEntryQueue` classes public and simplify return statement in `LogSinkCatalog.ListAsync` method. * Standardize terminology in `ILogSink` interface and `LoggerSink` implementation: rename `properties` to `attributes`. Update project files and solution structure to reflect integration test additions. * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update documentation comments in `LoggingFeature` and `LogEntryInstruction` to clarify functionality and improve precision. * Add README for `Elsa.Logging` module with configuration examples, usage details, and extension guidance. * Add `Dictionary` UI hint to `InputUIHints` and update `Attributes` in `Log` activity to use it. * Update `Log` activity default category to "Process", add integration tests for logging, and enhance null safety in `ConfigurationLogSinkProvider`. * Remove `UseLoggingFramework` middleware from `Program.cs` to streamline workflow initialization. * Log Activity * Auto stash before merge of "feat/6854-process-log-activity" and "patch/3.5.1" * Refactor `LoggerSink` to simplify logging logic and remove unused `FormatMessage` method. Enhance `Log` activity argument handling by introducing JSON parsing for string inputs. * Refactor logging tests to improve consistency and update variable initialization in `WorkflowExecutionContext` for cleaner syntax. * Replace mocked logger dependencies in tests with `TestLogger` and `TestLoggerFactory` for improved readability and maintainability. * Add `JsonDestructuringConsoleFormatter` for structured JSON logging and update logging configuration to support new formatter. * Handling Dictionary component values with expressions * Small improvements * Increasing studio version * Introduce custom activity input evaluators and support evaluator type registration. (#6889) * Introduce custom activity input evaluators and support evaluator type registration. Added `DefaultActivityInputEvaluator` and `DictionaryValueEvaluator` for custom input evaluation logic. Modified `InputAttribute` and `InputDescriptor` to support specifying evaluator types. Updated relevant activity, service, and feature registrations to integrate the new evaluators. * Remove legacy Dictionary UI hint implementation and refactor `DictionaryValueEvaluator` integration. * Removed obsolete `Dictionary` UI hint-related classes. * Introduced `DictionaryUIHintInputModifier` for centralized modifier logic. * Relocated `DictionaryValueEvaluator` to the appropriate namespace and updated feature registrations. * Fix formatting of `Category` property in `Log` activity. * Refactor input evaluation: replace `ActivatorUtilities` with `GetRequiredService` and update `DictionaryValueEvaluator` to handle dynamic types. * Remove unused logging-related imports from `Program.cs`. * Update src/modules/Elsa.Workflows.Core/UIHints/Dictionary/DictionaryValueEvaluator.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Add logging to `DictionaryValueEvaluator` for missing property warnings. --------- Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com> Co-authored-by: lukhipolito-nexxbiz <lucas.hipolito@nexxbiz.io> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
17bb1d08c9
commit
90b4e7d6c5
|
|
@ -4,7 +4,7 @@
|
|||
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ElsaStudioVersion>3.5.0</ElsaStudioVersion>
|
||||
<ElsaStudioVersion>3.5.1-preview.1211</ElsaStudioVersion>
|
||||
<MicrosoftVersion>9.0.8</MicrosoftVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -69,8 +69,6 @@ using JetBrains.Annotations;
|
|||
using Medallion.Threading.FileSystem;
|
||||
using Medallion.Threading.Postgres;
|
||||
using Medallion.Threading.Redis;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Logging.Console;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenTelemetry.Metrics;
|
||||
using OpenTelemetry.Resources;
|
||||
|
|
|
|||
|
|
@ -47,12 +47,22 @@ public class Log : CodeActivity
|
|||
/// </summary>
|
||||
[Input(Description = "The log message to emit.")]
|
||||
public Input<string> Message { get; set; } = new(string.Empty);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Arguments for the templated string message.
|
||||
/// </summary>
|
||||
[Input(Description = "Values of named or indexed placeholders in the log message.")]
|
||||
public Input<object?> Arguments { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Additional attributes to include in the log entry.
|
||||
/// </summary>
|
||||
[Input(Description = "Values of named or indexed placeholders in the log message.")]
|
||||
public Input<object?> Arguments { get; set; } = null!;
|
||||
[Input(
|
||||
Description = "Flat dictionary of key/value pairs to include as attributes.",
|
||||
DisplayName = "Attributes",
|
||||
UIHint = InputUIHints.Dictionary
|
||||
)]
|
||||
public Input<IDictionary<string, object?>> Attributes { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The log level.
|
||||
|
|
@ -66,15 +76,6 @@ public class Log : CodeActivity
|
|||
[Input(Description = "The category. Defaults to 'Process'.", DefaultValue = "Process")]
|
||||
public Input<string> Category { get; set; } = new("Process");
|
||||
|
||||
/// <summary>
|
||||
/// Additional attributes to include in the log entry.
|
||||
/// </summary>
|
||||
[Input(
|
||||
Description = "Flat dictionary of key/value pairs to include as attributes.",
|
||||
UIHint = InputUIHints.Dictionary
|
||||
)]
|
||||
public Input<IDictionary<string, object?>> Attributes { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Target sinks to write to.
|
||||
/// </summary>
|
||||
|
|
@ -98,8 +99,8 @@ public class Log : CodeActivity
|
|||
// Could be JSON created from e.g., Liquid template. If so, parse it into an ExpandoObject.
|
||||
arguments = TryParseJson(argumentString);
|
||||
}
|
||||
|
||||
var attributes = Attributes.GetOrDefault(context) ?? new Dictionary<string, object?>();
|
||||
|
||||
var attributes = Attributes.GetOrDefault(context) ?? new Dictionary<string, object?>()!;
|
||||
var sinkNames = SinkNames.GetOrDefault(context) ?? new List<string>();
|
||||
var category = Category.GetOrDefault(context);
|
||||
if (string.IsNullOrWhiteSpace(category)) category = "Process";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>
|
||||
|
|
@ -8,9 +8,9 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Elsa.Logging.Core\Elsa.Logging.Core.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Logging.Core\Elsa.Logging.Core.csproj"/>
|
||||
<ProjectReference Include="..\Elsa.Workflows.Core\Elsa.Workflows.Core.csproj"/>
|
||||
<ProjectReference Include="..\Elsa.Workflows.Management\Elsa.Workflows.Management.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
@ -78,6 +78,12 @@ public class InputAttribute : Attribute
|
|||
/// </summary>
|
||||
public bool AutoEvaluate { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the type of a custom evaluator to use for evaluating the input property value.
|
||||
/// The evaluator type determines how the value for the property is resolved at runtime.
|
||||
/// </summary>
|
||||
public Type? EvaluatorType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A value indicating whether this input can be serialized as part of the workflow instance,
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
using Elsa.Expressions.Contracts;
|
||||
using Elsa.Expressions.Models;
|
||||
using Elsa.Workflows.Models;
|
||||
|
||||
namespace Elsa.Workflows;
|
||||
|
||||
public record ActivityInputEvaluatorContext(
|
||||
ActivityExecutionContext ActivityExecutionContext,
|
||||
ExpressionExecutionContext ExpressionExecutionContext,
|
||||
InputDescriptor InputDescriptor,
|
||||
Input Input,
|
||||
IExpressionEvaluator ExpressionEvaluator);
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace Elsa.Workflows;
|
||||
|
||||
public interface IActivityInputEvaluator
|
||||
{
|
||||
Task<object?> EvaluateAsync(ActivityInputEvaluatorContext context);
|
||||
}
|
||||
|
|
@ -91,9 +91,16 @@ public static partial class ActivityExecutionContextExtensions
|
|||
}
|
||||
else
|
||||
{
|
||||
var evaluator = context.GetRequiredService<IExpressionEvaluator>();
|
||||
var expressionEvaluator = context.GetRequiredService<IExpressionEvaluator>();
|
||||
var expressionExecutionContext = context.ExpressionExecutionContext;
|
||||
value = wrappedInput?.Expression != null ? await evaluator.EvaluateAsync(wrappedInput, expressionExecutionContext) : defaultValue;
|
||||
var inputEvaluatorType = inputDescriptor.EvaluatorType ?? typeof(DefaultActivityInputEvaluator);
|
||||
|
||||
if (wrappedInput?.Expression != null)
|
||||
{
|
||||
var inputEvaluator = (IActivityInputEvaluator)context.GetRequiredService(inputEvaluatorType);
|
||||
var inputEvaluatorContext = new ActivityInputEvaluatorContext(context, expressionExecutionContext, inputDescriptor, wrappedInput, expressionEvaluator);
|
||||
value = await inputEvaluator.EvaluateAsync(inputEvaluatorContext);
|
||||
}
|
||||
}
|
||||
|
||||
var memoryReference = wrappedInput?.MemoryBlockReference();
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ using Elsa.Workflows.Serialization.Helpers;
|
|||
using Elsa.Workflows.Serialization.Serializers;
|
||||
using Elsa.Workflows.Services;
|
||||
using Elsa.Workflows.UIHints.CheckList;
|
||||
using Elsa.Workflows.UIHints.Dictionary;
|
||||
using Elsa.Workflows.UIHints.Dropdown;
|
||||
using Elsa.Workflows.UIHints.JsonEditor;
|
||||
using Elsa.Workflows.UIHints.RadioList;
|
||||
|
|
@ -187,6 +188,7 @@ public class WorkflowsFeature : FeatureBase
|
|||
.AddScoped<IActivityStateFilterManager, DefaultActivityStateFilterManager>()
|
||||
.AddScoped<IWorkflowInstanceVariableReader, DefaultWorkflowInstanceVariableReader>()
|
||||
.AddScoped<IWorkflowInstanceVariableWriter, DefaultWorkflowInstanceVariableWriter>()
|
||||
.AddScoped<DefaultActivityInputEvaluator>()
|
||||
|
||||
// Incident Strategies.
|
||||
.AddTransient<IIncidentStrategy, FaultStrategy>()
|
||||
|
|
@ -228,17 +230,17 @@ public class WorkflowsFeature : FeatureBase
|
|||
// Instantiation strategies.
|
||||
.AddScoped<IWorkflowActivationStrategy, AllowAlwaysStrategy>()
|
||||
|
||||
// UI hints.
|
||||
// UI.
|
||||
.AddScoped<IUIHintHandler, DropDownUIHintHandler>()
|
||||
.AddScoped<IUIHintHandler, CheckListUIHintHandler>()
|
||||
.AddScoped<IUIHintHandler, RadioListUIHintHandler>()
|
||||
.AddScoped<IUIHintHandler, JsonEditorUIHintHandler>()
|
||||
|
||||
// UI property handlers.
|
||||
.AddScoped<IPropertyUIHandler, StaticCheckListOptionsProvider>()
|
||||
.AddScoped<IPropertyUIHandler, StaticRadioListOptionsProvider>()
|
||||
.AddScoped<IPropertyUIHandler, StaticDropDownOptionsProvider>()
|
||||
.AddScoped<IPropertyUIHandler, JsonCodeOptionsProvider>()
|
||||
.AddScoped<DictionaryValueEvaluator>()
|
||||
.AddSingleton<IActivityDescriptorModifier, DictionaryUIHintInputModifier>()
|
||||
|
||||
// Logger state generators.
|
||||
.AddSingleton(WorkflowLoggerStateGenerator)
|
||||
|
|
|
|||
|
|
@ -21,19 +21,20 @@ public class InputDescriptor : PropertyDescriptor
|
|||
bool isWrapped,
|
||||
string uiHint,
|
||||
string displayName,
|
||||
string? description = default,
|
||||
string? category = default,
|
||||
string? description = null,
|
||||
string? category = null,
|
||||
float order = 0,
|
||||
object? defaultValue = default,
|
||||
object? defaultValue = null,
|
||||
string? defaultSyntax = "Literal",
|
||||
bool isReadOnly = false,
|
||||
bool isBrowsable = true,
|
||||
bool isSerializable = true,
|
||||
bool isSynthetic = false,
|
||||
bool autoEvaluate = true,
|
||||
Type? storageDriverType = default,
|
||||
PropertyInfo? propertyInfo = default,
|
||||
IDictionary<string, object>? uiSpecifications = default
|
||||
Type? evaluatorType = null,
|
||||
Type? storageDriverType = null,
|
||||
PropertyInfo? propertyInfo = null,
|
||||
IDictionary<string, object>? uiSpecifications = null
|
||||
)
|
||||
{
|
||||
Name = name;
|
||||
|
|
@ -50,6 +51,7 @@ public class InputDescriptor : PropertyDescriptor
|
|||
DefaultSyntax = defaultSyntax;
|
||||
IsReadOnly = isReadOnly;
|
||||
AutoEvaluate = autoEvaluate;
|
||||
EvaluatorType = evaluatorType;
|
||||
StorageDriverType = storageDriverType;
|
||||
IsSynthetic = isSynthetic;
|
||||
IsBrowsable = isBrowsable;
|
||||
|
|
@ -66,7 +68,7 @@ public class InputDescriptor : PropertyDescriptor
|
|||
/// <summary>
|
||||
/// A string value that hints at what UI control might be used to render in a UI tool.
|
||||
/// </summary>
|
||||
public string UIHint { get; set; } = default!;
|
||||
public string UIHint { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The category to which this input belongs. Can be used by UI to e.g. render different inputs in different tabs.
|
||||
|
|
@ -104,6 +106,12 @@ public class InputDescriptor : PropertyDescriptor
|
|||
/// True if the expression should be evaluated automatically, false otherwise. Defaults to true.
|
||||
/// </summary>
|
||||
public bool AutoEvaluate { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the type of a custom evaluator to use for evaluating the input property value.
|
||||
/// The evaluator type determines how the value for the property is resolved at runtime.
|
||||
/// </summary>
|
||||
public Type? EvaluatorType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary of UI specifications to be used by the UI.
|
||||
|
|
|
|||
|
|
@ -150,8 +150,7 @@ public class ActivityDescriber(IPropertyDefaultValueResolver defaultValueResolve
|
|||
|
||||
var uiSpecification = await propertyUIHandlerResolver.GetUIPropertiesAsync(propertyInfo, null, cancellationToken);
|
||||
|
||||
return new InputDescriptor
|
||||
(
|
||||
return new(
|
||||
inputAttribute?.Name ?? propertyInfo.Name,
|
||||
wrappedPropertyType,
|
||||
propertyInfo.GetValue,
|
||||
|
|
@ -169,7 +168,8 @@ public class ActivityDescriber(IPropertyDefaultValueResolver defaultValueResolve
|
|||
inputAttribute?.IsSerializable ?? true,
|
||||
false,
|
||||
autoEvaluate,
|
||||
default,
|
||||
inputAttribute?.EvaluatorType,
|
||||
null,
|
||||
propertyInfo,
|
||||
uiSpecification
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
using Elsa.Extensions;
|
||||
|
||||
namespace Elsa.Workflows;
|
||||
|
||||
public class DefaultActivityInputEvaluator : IActivityInputEvaluator
|
||||
{
|
||||
public async Task<object?> EvaluateAsync(ActivityInputEvaluatorContext context)
|
||||
{
|
||||
var wrappedInput = context.Input;
|
||||
var evaluator = context.ExpressionEvaluator;
|
||||
var expressionExecutionContext = context.ExpressionExecutionContext;
|
||||
return await evaluator.EvaluateAsync(wrappedInput, expressionExecutionContext);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
using Elsa.Workflows.Models;
|
||||
|
||||
namespace Elsa.Workflows.UIHints.Dictionary;
|
||||
|
||||
public class DictionaryUIHintInputModifier : IActivityDescriptorModifier
|
||||
{
|
||||
public void Modify(ActivityDescriptor descriptor)
|
||||
{
|
||||
var dictionaryInputs = descriptor.Inputs.Where(x => x.UIHint == InputUIHints.Dictionary).ToList();
|
||||
|
||||
foreach (var dictionaryInput in dictionaryInputs)
|
||||
dictionaryInput.EvaluatorType = typeof(DictionaryValueEvaluator);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
using System.Text.Json;
|
||||
using Elsa.Expressions.Models;
|
||||
using Elsa.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Elsa.Workflows.UIHints.Dictionary;
|
||||
|
||||
public class DictionaryValueEvaluator(ILogger<DictionaryValueEvaluator> logger) : IActivityInputEvaluator
|
||||
{
|
||||
public async Task<object?> EvaluateAsync(ActivityInputEvaluatorContext context)
|
||||
{
|
||||
var wrappedInput = context.Input;
|
||||
var evaluator = context.ExpressionEvaluator;
|
||||
var expressionExecutionContext = context.ExpressionExecutionContext;
|
||||
var inputDescriptor = context.InputDescriptor;
|
||||
var defaultValue = inputDescriptor.DefaultValue;
|
||||
var value = wrappedInput.Expression != null ? await evaluator.EvaluateAsync(wrappedInput, expressionExecutionContext) : defaultValue;
|
||||
if (value is IDictionary<string, object> dictionary && inputDescriptor.UIHint == InputUIHints.Dictionary)
|
||||
{
|
||||
var tempDictionary = new Dictionary<string, object?>(dictionary.Count);
|
||||
foreach (var dict in dictionary)
|
||||
{
|
||||
if (dict.Value is not JsonElement json)
|
||||
{
|
||||
// Not a JSON object, so just use the value as-is.
|
||||
tempDictionary[dict.Key] = dict.Value;
|
||||
continue;
|
||||
}
|
||||
|
||||
// JSON object, so extract the type and value properties.
|
||||
var hasType = json.TryGetProperty("type", out var typeProperty);
|
||||
var hasValue = json.TryGetProperty("value", out var valueProperty);
|
||||
|
||||
if (!hasType || !hasValue)
|
||||
{
|
||||
// Skip this entry or handle as needed (e.g., log, throw, etc.)
|
||||
logger.LogWarning("Dictionary entry is missing type or value property: {Json}", JsonSerializer.Serialize(json));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Evaluate the expression.
|
||||
var expression = new Expression(typeProperty.ToString(), valueProperty.ToString());
|
||||
var val = await evaluator.EvaluateAsync<object>(expression, expressionExecutionContext);
|
||||
|
||||
// Add the evaluated value to the dictionary.
|
||||
tempDictionary[dict.Key] = val;
|
||||
}
|
||||
|
||||
// Replace the original dictionary with the evaluated one.
|
||||
value = tempDictionary;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue