2024-09-23 09:35:43 +00:00
using System.Diagnostics ;
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 ;
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
{
2024-09-23 22:27:51 +00:00
private readonly JsonSerializerOptions ? _incidentSerializerOptions = new JsonSerializerOptions ( ) . WithConverters ( new TypeJsonConverter ( WellKnownTypeRegistry . CreateDefault ( ) ) ) ;
2024-09-23 09:35:43 +00:00
/// <inheritdoc />
public override async ValueTask InvokeAsync ( WorkflowExecutionContext context )
{
var workflowInstanceId = context . Id ;
var workflow = context . Workflow ;
2024-10-05 11:48:59 +00:00
using var span = ElsaOpenTelemetry . ActivitySource . StartActivity ( "WorkflowExecution" , ActivityKind . Internal , Activity . Current ? . Context ? ? default ) ;
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 ;
}
2024-10-05 11:48:59 +00:00
span . SetTag ( "workflowInstance.id" , workflowInstanceId ) ;
span . SetTag ( "workflowDefinition.definitionId" , workflow . Identity . DefinitionId ) ;
span . SetTag ( "workflowDefinition.version" , workflow . Identity . Version ) ;
span . SetTag ( "workflowDefinition.name" , workflow . WorkflowMetadata . Name ) ;
span . SetTag ( "workflowExecution.startTimeUtc" , span . StartTimeUtc ) ;
2024-10-07 08:15:35 +00:00
if ( context . TriggerActivityId ! = null )
{
var activity = context . FindActivityById ( context . TriggerActivityId ) ? ? throw new Exception ( $"Trigger activity with ID {context.TriggerActivityId} not found. This should not happen." ) ;
span . SetTag ( "workflowExecution.trigger.activityId" , activity . Id ) ;
span . SetTag ( "workflowExecution.trigger.activityName" , activity . Name ) ;
span . SetTag ( "workflowExecution.trigger.activityType" , activity . Type ) ;
}
2024-10-05 11:48:59 +00:00
span . AddEvent ( new ActivityEvent ( "Executing" , tags : CreateStatusTags ( context ) ) ) ;
2024-09-23 21:35:30 +00:00
await Next ( context ) ;
if ( context . SubStatus = = WorkflowSubStatus . Faulted )
{
2024-10-05 11:48:59 +00:00
span . AddEvent ( new ActivityEvent ( "Faulted" , tags : CreateStatusTags ( context ) ) ) ;
span . SetStatus ( ActivityStatusCode . Error ) ;
span . SetTag ( "error" , true ) ;
2024-09-23 21:35:30 +00:00
}
else
{
2024-10-05 11:48:59 +00:00
span . AddEvent ( new ActivityEvent ( "Executed" , tags : CreateStatusTags ( context ) ) ) ;
span . SetStatus ( ActivityStatusCode . Ok ) ;
2024-09-23 21:35:30 +00:00
}
2024-09-23 09:35:43 +00:00
2024-10-05 09:19:02 +00:00
if ( context . Incidents . Any ( ) )
{
2024-10-05 11:48:59 +00:00
span . SetStatus ( ActivityStatusCode . Error ) ;
2024-10-05 12:12:00 +00:00
span . SetTag ( "workflowInstance.hasIncidents" , true ) ;
2024-10-05 11:48:59 +00:00
span . SetTag ( "error" , true ) ;
2024-10-05 09:19:02 +00:00
if ( context . Incidents . Count > 0 )
2024-10-05 11:48:59 +00:00
span . SetTag ( "error.message" , JsonSerializer . Serialize ( context . Incidents , _incidentSerializerOptions ) ) ;
2024-10-05 09:19:02 +00:00
}
2024-09-23 21:35:30 +00:00
if ( ! string . IsNullOrWhiteSpace ( context . CorrelationId ) )
2024-10-05 12:12:00 +00:00
span . SetTag ( "workflowInstance.correlationId" , context . CorrelationId ) ;
2024-09-23 09:35:43 +00:00
2024-10-05 11:48:59 +00:00
var now = systemClock . UtcNow ;
span . SetTag ( "workflowExecution.endTimeUtc" , now ) ;
span . SetTag ( "workflowExecution.durationMs" , ( now - span . StartTimeUtc ) . TotalMilliseconds ) ;
2024-09-23 09:35:43 +00:00
}
2024-10-05 11:39:58 +00:00
private ActivityTagsCollection CreateStatusTags ( WorkflowExecutionContext context )
{
return new ActivityTagsCollection ( new Dictionary < string , object? >
{
["workflowInstance.status"] = context . Status . ToString ( ) ,
["workflowInstance.subStatus"] = context . SubStatus . ToString ( )
} ) ;
}
2024-09-23 09:35:43 +00:00
}
/// <summary>
/// Contains extension methods for <see cref="OpenTelemetryTracingWorkflowExecutionMiddleware"/>.
/// </summary>
[UsedImplicitly]
public static class OpenTelemetryWorkflowExecutionMiddlewareExtensions
{
/// Installs the <see cref="OpenTelemetryTracingWorkflowExecutionMiddleware"/> component in the workflow execution pipeline.
public static IWorkflowExecutionPipelineBuilder UseWorkflowExecutionTracing ( this IWorkflowExecutionPipelineBuilder pipelineBuilder ) = > pipelineBuilder . Insert < OpenTelemetryTracingWorkflowExecutionMiddleware > ( 0 ) ;
2024-09-23 21:35:30 +00:00
}