diff --git a/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingWorkflowExecutionMiddleware.cs b/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingWorkflowExecutionMiddleware.cs index cb209a0c0..a379dc384 100644 --- a/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingWorkflowExecutionMiddleware.cs +++ b/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingWorkflowExecutionMiddleware.cs @@ -27,8 +27,17 @@ public class OpenTelemetryTracingWorkflowExecutionMiddleware(WorkflowMiddlewareD { var workflowInstanceId = context.Id; var workflow = context.Workflow; - using var span = ElsaOpenTelemetry.ActivitySource.StartActivity($"execute workflow {workflow.WorkflowMetadata.Name}", ActivityKind.Server, Activity.Current?.Context ?? default); - var now = systemClock.UtcNow; + var startNewTrace = context.Properties.TryGetValue("StartNewTrace", out var startNewTraceValue) && (bool)startNewTraceValue; + var parentTraceContext = startNewTrace ? default : Activity.Current?.Context ?? default; + var linkedTraceContext = startNewTrace ? Activity.Current : null; + + if(startNewTrace) + { + Activity.Current?.Stop(); + Activity.Current = null; + } + + using var span = ElsaOpenTelemetry.ActivitySource.StartActivity($"execute workflow {workflow.WorkflowMetadata.Name}", ActivityKind.Server, parentTraceContext); if (span == null) // No listener is registered. { @@ -36,6 +45,12 @@ public class OpenTelemetryTracingWorkflowExecutionMiddleware(WorkflowMiddlewareD return; } + if(startNewTrace) + { + if (linkedTraceContext != null) + span.AddLink(new(linkedTraceContext.Context)); + } + span.SetTag("operation.name", "elsa.workflow.execution"); span.SetTag("span.type", "workflow"); span.SetTag("workflow.definition.id", workflow.Identity.DefinitionId); diff --git a/src/modules/Elsa.Workflows.Runtime/Activities/BulkDispatchWorkflows.cs b/src/modules/Elsa.Workflows.Runtime/Activities/BulkDispatchWorkflows.cs index 8074be822..2207e05bd 100644 --- a/src/modules/Elsa.Workflows.Runtime/Activities/BulkDispatchWorkflows.cs +++ b/src/modules/Elsa.Workflows.Runtime/Activities/BulkDispatchWorkflows.cs @@ -78,6 +78,12 @@ public class BulkDispatchWorkflows : Activity Description = "Wait for the dispatched workflows to complete before completing this activity.", DefaultValue = true)] public Input WaitForCompletion { get; set; } = new(true); + + /// + /// Indicates whether a new trace context should be started for the workflow execution. + /// + [Input(Description = "Start a new trace context when using Open Telemetry.", Category = "Open Telemetry")] + public Input StartNewTrace { get; set; } /// /// The channel to dispatch the workflow to. @@ -104,12 +110,13 @@ public class BulkDispatchWorkflows : Activity protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) { var waitForCompletion = WaitForCompletion.GetOrDefault(context); + var startNewTrace = StartNewTrace.GetOrDefault(context); var items = await context.GetItemSource(Items).ToListAsync(context.CancellationToken); var count = items.Count; // Dispatch the child workflows. foreach (var item in items) - await DispatchChildWorkflowAsync(context, item, waitForCompletion); + await DispatchChildWorkflowAsync(context, item, waitForCompletion, startNewTrace); // Store the number of dispatched instances for tracking. context.SetProperty(DispatchedInstancesCountKey, count); @@ -139,7 +146,7 @@ public class BulkDispatchWorkflows : Activity } } - private async ValueTask DispatchChildWorkflowAsync(ActivityExecutionContext context, object item, bool waitForCompletion) + private async ValueTask DispatchChildWorkflowAsync(ActivityExecutionContext context, object item, bool waitForCompletion, bool startNewTrace) { var workflowDefinitionId = WorkflowDefinitionId.Get(context); var workflowDefinitionService = context.GetRequiredService(); @@ -157,8 +164,8 @@ public class BulkDispatchWorkflows : Activity ["ParentInstanceId"] = parentInstanceId }; - if (waitForCompletion) - properties["WaitForCompletion"] = true; + if (waitForCompletion) properties["WaitForCompletion"] = true; + if (startNewTrace) properties["StartNewTrace"] = true; var itemDictionary = new Dictionary { diff --git a/src/modules/Elsa.Workflows.Runtime/Activities/DispatchWorkflow.cs b/src/modules/Elsa.Workflows.Runtime/Activities/DispatchWorkflow.cs index 566c79937..9619e1d73 100644 --- a/src/modules/Elsa.Workflows.Runtime/Activities/DispatchWorkflow.cs +++ b/src/modules/Elsa.Workflows.Runtime/Activities/DispatchWorkflow.cs @@ -20,7 +20,7 @@ namespace Elsa.Workflows.Runtime.Activities; public class DispatchWorkflow : Activity { /// - public DispatchWorkflow([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) + public DispatchWorkflow([CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) { } @@ -32,7 +32,7 @@ public class DispatchWorkflow : Activity Description = "The definition ID of the workflow to dispatch.", UIHint = InputUIHints.WorkflowDefinitionPicker )] - public Input WorkflowDefinitionId { get; set; } = default!; + public Input WorkflowDefinitionId { get; set; } = null!; /// /// The correlation ID to associate the workflow with. @@ -41,19 +41,25 @@ public class DispatchWorkflow : Activity DisplayName = "Correlation ID", Description = "The correlation ID to associate the workflow with." )] - public Input CorrelationId { get; set; } = default!; + public Input CorrelationId { get; set; } = null!; /// /// The input to send to the workflow. /// [Input(Description = "The input to send to the workflow.")] - public Input?> Input { get; set; } = default!; + public Input?> Input { get; set; } = null!; /// /// True to wait for the child workflow to complete before completing this activity, false to "fire and forget". /// [Input(Description = "Wait for the child workflow to complete before completing this activity.")] - public Input WaitForCompletion { get; set; } = default!; + public Input WaitForCompletion { get; set; } = null!; + + /// + /// Indicates whether a new trace context should be started for the workflow execution. + /// + [Input(Description = "Start a new trace context when using Open Telemetry.", Category = "Open Telemetry")] + public Input StartNewTrace { get; set; } /// /// The channel to dispatch the workflow to. @@ -64,7 +70,7 @@ public class DispatchWorkflow : Activity UIHint = InputUIHints.DropDown, UIHandler = typeof(DispatcherChannelOptionsProvider) )] - public Input ChannelName { get; set; } = default!; + public Input ChannelName { get; set; } = null!; /// protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) @@ -103,16 +109,17 @@ public class DispatchWorkflow : Activity var input = Input.GetOrDefault(context) ?? new Dictionary(); var channelName = ChannelName.GetOrDefault(context); + var startNewTrace = StartNewTrace.GetOrDefault(context); var parentInstanceId = context.WorkflowExecutionContext.Id; var properties = new Dictionary { - ["ParentInstanceId"] = parentInstanceId + ["ParentInstanceId"] = parentInstanceId, }; // If we need to wait for the child workflow to complete, set the property. This will be used by the ResumeDispatchWorkflowActivity handler. - if (waitForCompletion) - properties["WaitForCompletion"] = true; - + if (waitForCompletion) properties["WaitForCompletion"] = true; + if (startNewTrace) properties["StartNewTrace"] = true; + input["ParentInstanceId"] = parentInstanceId; var correlationId = CorrelationId.GetOrDefault(context); diff --git a/src/modules/Elsa.Workflows.Runtime/Services/BackgroundWorkflowDispatcher.cs b/src/modules/Elsa.Workflows.Runtime/Services/BackgroundWorkflowDispatcher.cs index e9cbefc06..09e889628 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/BackgroundWorkflowDispatcher.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/BackgroundWorkflowDispatcher.cs @@ -22,7 +22,7 @@ public class BackgroundWorkflowDispatcher : IWorkflowDispatcher } /// - public async Task DispatchAsync(DispatchWorkflowDefinitionRequest request, DispatchWorkflowOptions? options = default, CancellationToken cancellationToken = default) + public async Task DispatchAsync(DispatchWorkflowDefinitionRequest request, DispatchWorkflowOptions? options = null, CancellationToken cancellationToken = default) { var command = new DispatchWorkflowDefinitionCommand(request.DefinitionVersionId) { @@ -38,7 +38,7 @@ public class BackgroundWorkflowDispatcher : IWorkflowDispatcher } /// - public async Task DispatchAsync(DispatchWorkflowInstanceRequest request, DispatchWorkflowOptions? options = default, CancellationToken cancellationToken = default) + public async Task DispatchAsync(DispatchWorkflowInstanceRequest request, DispatchWorkflowOptions? options = null, CancellationToken cancellationToken = default) { var command = new DispatchWorkflowInstanceCommand(request.InstanceId){ BookmarkId = request.BookmarkId, @@ -52,7 +52,7 @@ public class BackgroundWorkflowDispatcher : IWorkflowDispatcher } /// - public async Task DispatchAsync(DispatchTriggerWorkflowsRequest request, DispatchWorkflowOptions? options = default, CancellationToken cancellationToken = default) + public async Task DispatchAsync(DispatchTriggerWorkflowsRequest request, DispatchWorkflowOptions? options = null, CancellationToken cancellationToken = default) { var command = new DispatchTriggerWorkflowsCommand(request.ActivityTypeName, request.BookmarkPayload) { @@ -67,7 +67,7 @@ public class BackgroundWorkflowDispatcher : IWorkflowDispatcher } /// - public async Task DispatchAsync(DispatchResumeWorkflowsRequest request, DispatchWorkflowOptions? options = default, CancellationToken cancellationToken = default) + public async Task DispatchAsync(DispatchResumeWorkflowsRequest request, DispatchWorkflowOptions? options = null, CancellationToken cancellationToken = default) { var command = new DispatchResumeWorkflowsCommand(request.ActivityTypeName, request.BookmarkPayload) {