diff --git a/Directory.Packages.props b/Directory.Packages.props
index 8d70a9957..1ea00d408 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -1,197 +1,199 @@
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/apps/Elsa.Server.Web/Program.cs b/src/apps/Elsa.Server.Web/Program.cs
index 7f0887c07..526cd58b0 100644
--- a/src/apps/Elsa.Server.Web/Program.cs
+++ b/src/apps/Elsa.Server.Web/Program.cs
@@ -68,6 +68,8 @@ using Medallion.Threading.Postgres;
using Medallion.Threading.Redis;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Options;
+using OpenTelemetry;
+using OpenTelemetry.Trace;
using Proto.Cluster.Kubernetes;
using Proto.Persistence.Sqlite;
using Proto.Persistence.SqlServer;
@@ -120,6 +122,14 @@ var sqlDatabaseProvider = Enum.Parse(configuration["Databas
TypeAliasRegistry.RegisterAlias("OrderReceivedProducerFactory", typeof(GenericProducerFactory));
TypeAliasRegistry.RegisterAlias("OrderReceivedConsumerFactory", typeof(GenericConsumerFactory));
+// Configure OpenTelemetry Tracing
+using var tracerProvider = Sdk.CreateTracerProviderBuilder()
+ .AddSource("Elsa.Workflows") // Match your ActivitySource name here
+ .SetSampler(new AlwaysOnSampler()) // Always record traces for testing
+ .AddConsoleExporter() // Export spans to the console (optional)
+ .Build();
+
+
// Add Elsa services.
services
.AddElsa(elsa =>
@@ -454,6 +464,7 @@ services
alterations.UseMassTransitDispatcher();
}
})
+ .UseOpenTelemetry()
.UseWorkflowContexts();
if (useQuartz)
diff --git a/src/modules/Elsa.OpenTelemetry/Abstractions/ErrorSpanHandlerBase.cs b/src/modules/Elsa.OpenTelemetry/Abstractions/ErrorSpanHandlerBase.cs
new file mode 100644
index 000000000..420936119
--- /dev/null
+++ b/src/modules/Elsa.OpenTelemetry/Abstractions/ErrorSpanHandlerBase.cs
@@ -0,0 +1,9 @@
+using Elsa.OpenTelemetry.Contracts;
+using Elsa.OpenTelemetry.Models;
+
+namespace Elsa.OpenTelemetry.Abstractions;
+
+public abstract class ErrorSpanHandlerBase : IErrorSpanHandler
+{
+ public abstract void Handle(ErrorSpanContext context);
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.OpenTelemetry/Contracts/IErrorSpanHandler.cs b/src/modules/Elsa.OpenTelemetry/Contracts/IErrorSpanHandler.cs
new file mode 100644
index 000000000..d15c3dee4
--- /dev/null
+++ b/src/modules/Elsa.OpenTelemetry/Contracts/IErrorSpanHandler.cs
@@ -0,0 +1,10 @@
+using Elsa.OpenTelemetry.Abstractions;
+using Elsa.OpenTelemetry.Models;
+
+namespace Elsa.OpenTelemetry.Contracts;
+
+public interface IErrorSpanHandler
+{
+ void Handle(ErrorSpanContext context);
+}
+
diff --git a/src/modules/Elsa.OpenTelemetry/Extensions/ModuleExtensions.cs b/src/modules/Elsa.OpenTelemetry/Extensions/ModuleExtensions.cs
new file mode 100644
index 000000000..25edd2794
--- /dev/null
+++ b/src/modules/Elsa.OpenTelemetry/Extensions/ModuleExtensions.cs
@@ -0,0 +1,14 @@
+using Elsa.Features.Services;
+using Elsa.OpenTelemetry.Features;
+
+// ReSharper disable once CheckNamespace
+namespace Elsa.Extensions;
+
+public static class ModuleExtensions
+{
+ public static IModule UseOpenTelemetry(this IModule configuration, Action? configure = null)
+ {
+ configuration.Configure(configure);
+ return configuration;
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.OpenTelemetry/Features/OpenTelemetryFeature.cs b/src/modules/Elsa.OpenTelemetry/Features/OpenTelemetryFeature.cs
index 67201c44d..d80f308b7 100644
--- a/src/modules/Elsa.OpenTelemetry/Features/OpenTelemetryFeature.cs
+++ b/src/modules/Elsa.OpenTelemetry/Features/OpenTelemetryFeature.cs
@@ -1,5 +1,8 @@
using Elsa.Features.Abstractions;
using Elsa.Features.Services;
+using Elsa.OpenTelemetry.Contracts;
+using Elsa.OpenTelemetry.Handlers;
+using Microsoft.Extensions.DependencyInjection;
namespace Elsa.OpenTelemetry.Features;
@@ -7,5 +10,8 @@ public class OpenTelemetryFeature(IModule module) : FeatureBase(module)
{
public override void Configure()
{
+ Services
+ .AddScoped()
+ .AddScoped();
}
}
\ No newline at end of file
diff --git a/src/modules/Elsa.OpenTelemetry/Handlers/DefaultErrorSpanHandler.cs b/src/modules/Elsa.OpenTelemetry/Handlers/DefaultErrorSpanHandler.cs
new file mode 100644
index 000000000..04bebef62
--- /dev/null
+++ b/src/modules/Elsa.OpenTelemetry/Handlers/DefaultErrorSpanHandler.cs
@@ -0,0 +1,24 @@
+using Elsa.OpenTelemetry.Abstractions;
+using Elsa.OpenTelemetry.Models;
+
+namespace Elsa.OpenTelemetry.Handlers;
+
+public class DefaultErrorSpanHandler : ErrorSpanHandlerBase
+{
+ public override void Handle(ErrorSpanContext context)
+ {
+ var span = context.Span;
+ var exception = context.Exception;
+ var errorMessage = string.IsNullOrWhiteSpace(exception?.Message) ? "Unknown error" : exception.Message;
+ span.SetTag("error", true);
+ span.SetTag("error.message", errorMessage);
+
+ if (exception != null)
+ {
+ span.SetTag("error.exceptionType", exception.GetType().FullName);
+
+ if (!string.IsNullOrEmpty(exception.StackTrace))
+ span.SetTag("error.stackTrace", exception.StackTrace);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.OpenTelemetry/Handlers/FaultExceptionErrorSpanHandler.cs b/src/modules/Elsa.OpenTelemetry/Handlers/FaultExceptionErrorSpanHandler.cs
new file mode 100644
index 000000000..14aa2860a
--- /dev/null
+++ b/src/modules/Elsa.OpenTelemetry/Handlers/FaultExceptionErrorSpanHandler.cs
@@ -0,0 +1,19 @@
+using Elsa.OpenTelemetry.Abstractions;
+using Elsa.OpenTelemetry.Models;
+using Elsa.Workflows.Exceptions;
+
+namespace Elsa.OpenTelemetry.Handlers;
+
+public class FaultExceptionErrorSpanHandler : ErrorSpanHandlerBase
+{
+ public override void Handle(ErrorSpanContext context)
+ {
+ if(context.Exception is not FaultException faultException)
+ return;
+
+ var span = context.Span;
+ span.SetTag("error.code", faultException.Code);
+ span.SetTag("error.category", faultException.Category);
+ span.SetTag("error.faultType", faultException.Type);
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingActivityExecutionMiddleware.cs b/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingActivityExecutionMiddleware.cs
index 0045fde75..5410d70bf 100644
--- a/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingActivityExecutionMiddleware.cs
+++ b/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingActivityExecutionMiddleware.cs
@@ -1,6 +1,8 @@
using System.Diagnostics;
using Elsa.Common;
+using Elsa.OpenTelemetry.Contracts;
using Elsa.OpenTelemetry.Helpers;
+using Elsa.OpenTelemetry.Models;
using Elsa.Workflows;
using Elsa.Workflows.Pipelines.ActivityExecution;
using JetBrains.Annotations;
@@ -40,14 +42,13 @@ public class OpenTelemetryTracingActivityExecutionMiddleware(ActivityMiddlewareD
{
span.AddEvent(new("Faulted", tags: CreateStatusTags(context)));
span.SetStatus(ActivityStatusCode.Error);
- span.SetTag("error", true);
span.SetTag("activityInstance.hasIncidents", true);
- var errorMessage = string.IsNullOrWhiteSpace(context.Exception?.Message) ? "Unknown error" : context.Exception.Message;
- span.SetTag("error.message", errorMessage);
+ var errorSpanHandlers = context.GetServices();
+ var errorSpanHandlerContext = new ErrorSpanContext(span, context.Exception);
- if (!string.IsNullOrEmpty(context.Exception?.StackTrace))
- span.SetTag("error.stackTrace", context.Exception.StackTrace);
+ foreach (var handler in errorSpanHandlers)
+ handler.Handle(errorSpanHandlerContext);
}
else
{
diff --git a/src/modules/Elsa.OpenTelemetry/Models/ErrorSpanContext.cs b/src/modules/Elsa.OpenTelemetry/Models/ErrorSpanContext.cs
new file mode 100644
index 000000000..a364b1b68
--- /dev/null
+++ b/src/modules/Elsa.OpenTelemetry/Models/ErrorSpanContext.cs
@@ -0,0 +1,9 @@
+using System.Diagnostics;
+
+namespace Elsa.OpenTelemetry.Models;
+
+public class ErrorSpanContext(Activity span, Exception? exception)
+{
+ public Activity Span => span;
+ public Exception? Exception => exception;
+}
\ No newline at end of file