diff --git a/src/modules/Elsa.OpenTelemetry/Features/OpenTelemetryFeature.cs b/src/modules/Elsa.OpenTelemetry/Features/OpenTelemetryFeature.cs
index d80f308b7..32589960d 100644
--- a/src/modules/Elsa.OpenTelemetry/Features/OpenTelemetryFeature.cs
+++ b/src/modules/Elsa.OpenTelemetry/Features/OpenTelemetryFeature.cs
@@ -2,16 +2,34 @@ using Elsa.Features.Abstractions;
using Elsa.Features.Services;
using Elsa.OpenTelemetry.Contracts;
using Elsa.OpenTelemetry.Handlers;
+using Elsa.OpenTelemetry.Options;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.OpenTelemetry.Features;
public class OpenTelemetryFeature(IModule module) : FeatureBase(module)
{
+ ///
+ /// When active this will create a new root Activity if the current Activity has a remote parent.
+ ///
+ public bool UseNewRootActivityForRemoteParent { get; set; } = true;
+
+ ///
+ /// Determines if instead of a empty parent, a dummy parent Activity should be used to create a new Root Activity.
+ ///
+ /// This is needed when middleware is active that uses the previous Parent Activity despite creating an Activity based on an empty Parent.
+ public bool UseDummyParentActivityAsRootSpan { get; set; } = false;
+
public override void Configure()
{
Services
.AddScoped()
.AddScoped();
+
+ Services.Configure(options =>
+ {
+ options.UseNewRootActivityForRemoteParent = UseNewRootActivityForRemoteParent;
+ options.UseDummyParentActivityAsRootSpan = UseDummyParentActivityAsRootSpan;
+ });
}
}
\ No newline at end of file
diff --git a/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingWorkflowExecutionMiddleware.cs b/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingWorkflowExecutionMiddleware.cs
index 837226655..cbb1c583f 100644
--- a/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingWorkflowExecutionMiddleware.cs
+++ b/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingWorkflowExecutionMiddleware.cs
@@ -1,16 +1,12 @@
using System.Diagnostics;
-using System.Runtime.InteropServices.Marshalling;
-using System.Text.Json;
using Elsa.Common;
-using Elsa.Expressions.Services;
-using Elsa.Extensions;
using Elsa.OpenTelemetry.Helpers;
+using Elsa.OpenTelemetry.Options;
using Elsa.Workflows;
-using Elsa.Workflows.Activities;
using Elsa.Workflows.Models;
using Elsa.Workflows.Pipelines.WorkflowExecution;
-using Elsa.Workflows.Serialization.Converters;
using JetBrains.Annotations;
+using Microsoft.Extensions.Options;
using Activity = System.Diagnostics.Activity;
using ActivityKind = System.Diagnostics.ActivityKind;
@@ -20,37 +16,23 @@ namespace Elsa.OpenTelemetry.Middleware;
/// Middleware that traces workflow execution using OpenTelemetry.
///
[UsedImplicitly]
-public class OpenTelemetryTracingWorkflowExecutionMiddleware(WorkflowMiddlewareDelegate next, ISystemClock systemClock) : WorkflowExecutionMiddleware(next)
+public class OpenTelemetryTracingWorkflowExecutionMiddleware(WorkflowMiddlewareDelegate next, ISystemClock systemClock, IOptions options) : WorkflowExecutionMiddleware(next)
{
///
public override async ValueTask InvokeAsync(WorkflowExecutionContext context)
{
+ var workflowName = context.Workflow.WorkflowMetadata.Name;
var workflowInstanceId = context.Id;
var workflow = context.Workflow;
- 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.
+ using var span = CreateTraceActivity(context, workflowName);
+
+ if (span == null)
{
await Next(context);
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);
@@ -101,6 +83,38 @@ public class OpenTelemetryTracingWorkflowExecutionMiddleware(WorkflowMiddlewareD
span.SetTag("workflow.correlation_id", context.CorrelationId);
}
+ private Activity? CreateTraceActivity(WorkflowExecutionContext context, string? workflowName)
+ {
+ var startNewTraceOptionValue = context.Properties.TryGetValue("StartNewTrace", out var startNewTraceValue) && (bool)startNewTraceValue;
+ var startNewTraceForRemoteParent = options.Value.UseNewRootActivityForRemoteParent && Activity.Current?.HasRemoteParent == true;
+ var startNewTrace = startNewTraceOptionValue || startNewTraceForRemoteParent;
+
+ ActivityContext contextToUse;
+ ActivityContext? linkedTraceContext = null;
+
+ if (startNewTrace)
+ {
+ linkedTraceContext = Activity.Current?.Context;
+ Activity.Current?.Stop();
+ Activity.Current = null;
+
+ contextToUse = options.Value.UseDummyParentActivityAsRootSpan
+ ? new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded)
+ : default;
+ }
+ else
+ {
+ contextToUse = Activity.Current?.Context ?? default;
+ }
+
+ var span = ElsaOpenTelemetry.ActivitySource.StartActivity($"execute workflow {workflowName}", ActivityKind.Server, contextToUse);
+
+ if (span != null && linkedTraceContext != null)
+ span.AddLink(new (linkedTraceContext.Value));
+
+ return span;
+ }
+
private ActivityTagsCollection CreateIncidentTags(ActivityIncident incident)
{
var tags = new ActivityTagsCollection(new Dictionary
diff --git a/src/modules/Elsa.OpenTelemetry/Options/OpenTelemetryOptions.cs b/src/modules/Elsa.OpenTelemetry/Options/OpenTelemetryOptions.cs
new file mode 100644
index 000000000..4a52ea383
--- /dev/null
+++ b/src/modules/Elsa.OpenTelemetry/Options/OpenTelemetryOptions.cs
@@ -0,0 +1,7 @@
+namespace Elsa.OpenTelemetry.Options;
+
+public class OpenTelemetryOptions
+{
+ public bool UseNewRootActivityForRemoteParent { get; set; }
+ public bool UseDummyParentActivityAsRootSpan { get; set; }
+}
\ No newline at end of file