2024-09-23 09:35:43 +00:00
using System.Diagnostics ;
2025-03-14 22:17:54 +00:00
using System.Runtime.InteropServices.Marshalling ;
2024-09-23 21:35:30 +00:00
using System.Text.Json ;
2024-10-11 18:41:49 +00:00
using Elsa.Common ;
2024-09-23 22:27:51 +00:00
using Elsa.Expressions.Services ;
using Elsa.Extensions ;
2024-09-23 09:35:43 +00:00
using Elsa.OpenTelemetry.Helpers ;
using Elsa.Workflows ;
2025-03-14 22:17:54 +00:00
using Elsa.Workflows.Activities ;
using Elsa.Workflows.Models ;
2024-09-23 09:35:43 +00:00
using Elsa.Workflows.Pipelines.WorkflowExecution ;
2024-09-23 22:27:51 +00:00
using Elsa.Workflows.Serialization.Converters ;
2024-09-23 09:35:43 +00:00
using JetBrains.Annotations ;
using Activity = System . Diagnostics . Activity ;
using ActivityKind = System . Diagnostics . ActivityKind ;
namespace Elsa.OpenTelemetry.Middleware ;
/// <summary>
/// Middleware that traces workflow execution using OpenTelemetry.
/// </summary>
[UsedImplicitly]
2024-09-23 21:35:30 +00:00
public class OpenTelemetryTracingWorkflowExecutionMiddleware ( WorkflowMiddlewareDelegate next , ISystemClock systemClock ) : WorkflowExecutionMiddleware ( next )
2024-09-23 09:35:43 +00:00
{
/// <inheritdoc />
public override async ValueTask InvokeAsync ( WorkflowExecutionContext context )
{
var workflowInstanceId = context . Id ;
var workflow = context . Workflow ;
2025-03-14 22:17:54 +00:00
using var span = ElsaOpenTelemetry . ActivitySource . StartActivity ( $"execute workflow {workflow.WorkflowMetadata.Name}" , ActivityKind . Server , Activity . Current ? . Context ? ? default ) ;
var now = systemClock . UtcNow ;
2024-09-23 21:35:30 +00:00
2024-10-05 11:48:59 +00:00
if ( span = = null ) // No listener is registered.
2024-09-23 21:35:30 +00:00
{
await Next ( context ) ;
return ;
}
2025-03-14 22:17:54 +00:00
span . SetTag ( "operation.name" , "elsa.workflow.execution" ) ;
2025-03-14 22:55:02 +00:00
span . SetTag ( "span.type" , "workflow" ) ;
2025-03-14 22:17:54 +00:00
span . SetTag ( "workflow.definition.id" , workflow . Identity . DefinitionId ) ;
span . SetTag ( "workflow.definition.version" , workflow . Identity . Version ) ;
span . SetTag ( "workflow.definition.name" , workflow . WorkflowMetadata . Name ) ;
span . SetTag ( "workflow.definition.tenant.id" , workflow . Identity . TenantId ) ;
span . SetTag ( "workflow.instance.id" , workflowInstanceId ) ;
if ( context . TriggerActivityId ! = null )
2024-10-07 08:15:35 +00:00
{
2025-03-14 22:17:54 +00:00
var activity = context . FindActivityById ( context . TriggerActivityId ) ? ? throw new ( $"Trigger activity with ID {context.TriggerActivityId} not found. This should not happen." ) ;
span . SetTag ( "workflow.trigger.activity.id" , activity . Id ) ;
span . SetTag ( "workflow.trigger.activity.name" , activity . Name ) ;
span . SetTag ( "workflow.trigger.activity.type" , activity . Type ) ;
span . SetTag ( "workflow.trigger.activity.version" , activity . Version ) ;
2024-10-07 08:15:35 +00:00
}
2025-03-14 22:17:54 +00:00
span . AddEvent ( new ( "executing" ) ) ;
2024-09-23 21:35:30 +00:00
await Next ( context ) ;
if ( context . SubStatus = = WorkflowSubStatus . Faulted )
{
2025-03-14 22:17:54 +00:00
span . AddEvent ( new ( "faulted" ) ) ;
2025-03-14 22:27:16 +00:00
span . SetStatus ( ActivityStatusCode . Error , "The workflow entered the Faulted state. See incidents for details." ) ;
2024-09-23 21:35:30 +00:00
}
2025-03-14 22:17:54 +00:00
else if ( context . SubStatus = = WorkflowSubStatus . Finished )
2024-09-23 21:35:30 +00:00
{
2025-03-14 22:17:54 +00:00
span . AddEvent ( new ( "finished" ) ) ;
2024-10-05 11:48:59 +00:00
span . SetStatus ( ActivityStatusCode . Ok ) ;
2024-09-23 21:35:30 +00:00
}
2025-03-14 22:17:54 +00:00
else if ( context . SubStatus = = WorkflowSubStatus . Cancelled )
2024-10-05 09:19:02 +00:00
{
2025-03-14 22:17:54 +00:00
span . AddEvent ( new ( "canceled" ) ) ;
span . SetStatus ( ActivityStatusCode . Ok ) ;
}
else if ( context . SubStatus = = WorkflowSubStatus . Suspended )
{
span . AddEvent ( new ( "suspended" ) ) ;
span . SetStatus ( ActivityStatusCode . Ok ) ;
}
if ( context . Incidents . Any ( ) )
{
span . SetTag ( "workflow.incidents" , true ) ;
span . SetTag ( "workflow.incidents.count" , context . Incidents . Count ) ;
2024-10-05 09:19:02 +00:00
2025-03-14 22:17:54 +00:00
foreach ( var incident in context . Incidents )
span . AddEvent ( new ( "incident" , incident . Timestamp , CreateIncidentTags ( incident ) ) ) ;
2024-10-05 09:19:02 +00:00
}
2025-03-14 22:17:54 +00:00
2024-09-23 21:35:30 +00:00
if ( ! string . IsNullOrWhiteSpace ( context . CorrelationId ) )
2025-03-14 22:17:54 +00:00
span . SetTag ( "workflow.correlation_id" , context . CorrelationId ) ;
2024-09-23 09:35:43 +00:00
}
2025-03-14 22:17:54 +00:00
private ActivityTagsCollection CreateIncidentTags ( ActivityIncident incident )
2024-10-05 11:39:58 +00:00
{
2025-03-14 22:17:54 +00:00
var tags = new ActivityTagsCollection ( new Dictionary < string , object? >
2024-10-05 11:39:58 +00:00
{
2025-03-14 22:17:54 +00:00
["incident.message"] = incident . Message ,
2024-10-05 11:39:58 +00:00
} ) ;
2025-03-14 22:17:54 +00:00
if ( incident . Exception ! = null )
{
tags [ "incident.exception.message" ] = incident . Exception . Message ;
tags [ "incident.exception.stackTrace" ] = incident . Exception . StackTrace ;
2025-03-14 22:27:16 +00:00
tags [ "incident.exception.type" ] = incident . Exception . Type . FullName ;
2025-03-14 22:17:54 +00:00
}
return tags ;
2024-10-05 11:39:58 +00:00
}
2024-09-23 09:35:43 +00:00
}
/// <summary>
/// Contains extension methods for <see cref="OpenTelemetryTracingWorkflowExecutionMiddleware"/>.
/// </summary>
[UsedImplicitly]
public static class OpenTelemetryWorkflowExecutionMiddlewareExtensions
{
2024-11-18 12:41:27 +00:00
/// <summary>
2024-09-23 09:35:43 +00:00
/// Installs the <see cref="OpenTelemetryTracingWorkflowExecutionMiddleware"/> component in the workflow execution pipeline.
2024-11-18 12:41:27 +00:00
/// </summary>
2024-09-23 09:35:43 +00:00
public static IWorkflowExecutionPipelineBuilder UseWorkflowExecutionTracing ( this IWorkflowExecutionPipelineBuilder pipelineBuilder ) = > pipelineBuilder . Insert < OpenTelemetryTracingWorkflowExecutionMiddleware > ( 0 ) ;
2024-09-23 21:35:30 +00:00
}