< Summary

Information
Class: Elsa.Workflows.Telemetry.ActivityInstrumentationScope
Assembly: Elsa.Workflows.Core
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Core/Telemetry/WorkflowInstrumentation.cs
Line coverage
100%
Covered lines: 1
Uncovered lines: 0
Coverable lines: 1
Total lines: 282
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_Activity()100%11100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Core/Telemetry/WorkflowInstrumentation.cs

#LineLine coverage
 1using System.Diagnostics;
 2using System.Diagnostics.Metrics;
 3
 4using DiagnosticsActivity = System.Diagnostics.Activity;
 5using DiagnosticsActivityKind = System.Diagnostics.ActivityKind;
 6using WorkflowActivityStatus = Elsa.Workflows.ActivityStatus;
 7using WorkflowInstanceStatus = Elsa.Workflows.WorkflowStatus;
 8using WorkflowInstanceSubStatus = Elsa.Workflows.WorkflowSubStatus;
 9
 10namespace Elsa.Workflows.Telemetry;
 11
 12/// <summary>
 13/// Provides tracing and metrics instrumentation for workflow and activity execution.
 14/// </summary>
 15public static class WorkflowInstrumentation
 16{
 17    public const string ActivitySourceName = "Elsa.Workflows";
 18    public const string MeterName = "Elsa.Workflows";
 19
 20    public const string WorkflowSystem = "workflow.system";
 21    public const string WorkflowOperationName = "workflow.operation.name";
 22    public const string WorkflowName = "workflow.name";
 23    public const string WorkflowInstanceId = "workflow.instance.id";
 24    public const string WorkflowDefinitionId = "workflow.definition.id";
 25    public const string WorkflowDefinitionVersion = "workflow.definition.version";
 26    public const string WorkflowDefinitionVersionId = "workflow.definition.version.id";
 27    public const string WorkflowStatus = "workflow.status";
 28    public const string WorkflowSubStatus = "workflow.substatus";
 29    public const string WorkflowFaulted = "workflow.faulted";
 30    public const string WorkflowParentInstanceId = "workflow.parent.instance.id";
 31    public const string WorkflowCorrelationId = "workflow.correlation.id";
 32
 33    public const string ActivityOperationName = "workflow.activity.operation.name";
 34    public const string ActivityId = "workflow.activity.id";
 35    public const string ActivityName = "workflow.activity.name";
 36    public const string ActivityType = "workflow.activity.type";
 37    public const string ActivityVersion = "workflow.activity.version";
 38    public const string ActivityExecutionId = "workflow.activity.execution.id";
 39    public const string ActivityStatus = "workflow.activity.status";
 40    public const string ActivityOutcome = "workflow.activity.outcome";
 41    public const string ActivityParentExecutionId = "workflow.activity.parent.execution.id";
 42    public const string ActivityScheduledByExecutionId = "workflow.activity.scheduled.by.execution.id";
 43    public const string ActivityFaulted = "workflow.activity.faulted";
 44
 45    public const string TenantId = "elsa.tenant.id";
 46    public const string ExceptionType = "exception.type";
 47
 48    [Obsolete("Use ExceptionType. Workflow spans follow OpenTelemetry exception semantic conventions.")]
 49    public const string ErrorType = ExceptionType;
 50
 51    private const string SystemName = "elsa";
 52    private const string WorkflowExecuteOperation = "workflow.execute";
 53    private const string ActivityExecuteOperation = "activity.execute";
 54
 55    private static readonly string? Version = typeof(WorkflowInstrumentation).Assembly.GetName().Version?.ToString();
 56    private static readonly ActivitySource Source = new(ActivitySourceName, Version);
 57    private static readonly Meter Meter = new(MeterName, Version);
 58    private static readonly Counter<long> WorkflowStartedCounter = Meter.CreateCounter<long>("elsa.workflow.started", de
 59    private static readonly Counter<long> WorkflowCompletedCounter = Meter.CreateCounter<long>("elsa.workflow.completed"
 60    private static readonly Counter<long> WorkflowFaultedCounter = Meter.CreateCounter<long>("elsa.workflow.faulted", de
 61    private static readonly Histogram<double> ActivityDuration = Meter.CreateHistogram<double>("elsa.activity.duration",
 62
 63    internal static WorkflowInstrumentationScope StartWorkflow(WorkflowExecutionContext context, bool? isStarting = null
 64    {
 65        var shouldRecordStarted = isStarting ?? context.SubStatus == WorkflowInstanceSubStatus.Pending;
 66        var activity = Source.StartActivity(WorkflowExecuteOperation, DiagnosticsActivityKind.Internal);
 67
 68        if (activity != null)
 69        {
 70            SetWorkflowTags(activity, context);
 71            activity.SetTag(WorkflowOperationName, WorkflowExecuteOperation);
 72        }
 73
 74        if (shouldRecordStarted && WorkflowStartedCounter.Enabled)
 75            WorkflowStartedCounter.Add(1, CreateWorkflowTags(context, false));
 76
 77        return new WorkflowInstrumentationScope(activity);
 78    }
 79
 80    internal static ActivityInstrumentationScope StartActivity(ActivityExecutionContext context)
 81    {
 82        var activity = Source.StartActivity(ActivityExecuteOperation, DiagnosticsActivityKind.Internal);
 83
 84        if (activity != null)
 85        {
 86            SetWorkflowTags(activity, context.WorkflowExecutionContext);
 87            SetActivityTags(activity, context);
 88            activity.SetTag(ActivityOperationName, ActivityExecuteOperation);
 89        }
 90
 91        return new ActivityInstrumentationScope(activity, Stopwatch.GetTimestamp());
 92    }
 93
 94    internal static void StopWorkflow(WorkflowInstrumentationScope scope, WorkflowExecutionContext context, Exception? e
 95    {
 96        var activity = scope.Activity;
 97        var workflowException = exception ?? (context.SubStatus == WorkflowInstanceSubStatus.Faulted ? context.Exception
 98        var cancelled = exception is OperationCanceledException || (exception == null && context.SubStatus == WorkflowIn
 99        var faulted = !cancelled && (workflowException != null || context.SubStatus == WorkflowInstanceSubStatus.Faulted
 100        var workflowSubStatus = cancelled ? WorkflowInstanceSubStatus.Cancelled : faulted ? WorkflowInstanceSubStatus.Fa
 101
 102        if (activity != null)
 103        {
 104            SetWorkflowTags(activity, context, workflowSubStatus);
 105            activity.SetTag(WorkflowFaulted, faulted);
 106            SetError(activity, workflowException, faulted);
 107            activity.Dispose();
 108        }
 109
 110        if (faulted && WorkflowFaultedCounter.Enabled)
 111            WorkflowFaultedCounter.Add(1, CreateWorkflowTags(context, workflowSubStatusOverride: workflowSubStatus));
 112        else if (context.SubStatus == WorkflowInstanceSubStatus.Finished && WorkflowCompletedCounter.Enabled)
 113            WorkflowCompletedCounter.Add(1, CreateWorkflowTags(context));
 114    }
 115
 116    internal static void StopActivity(ActivityInstrumentationScope scope, ActivityExecutionContext context, Exception? e
 117    {
 118        var activity = scope.Activity;
 119        var cancelled = exception is OperationCanceledException || (exception == null && context.Status == WorkflowActiv
 120        var faulted = !cancelled && (exception != null || context.Status == WorkflowActivityStatus.Faulted);
 121        var activityStatus = cancelled ? WorkflowActivityStatus.Canceled : context.Status;
 122        var duration = Stopwatch.GetElapsedTime(scope.StartTimestamp).TotalSeconds;
 123
 124        if (ActivityDuration.Enabled)
 125            ActivityDuration.Record(duration, CreateActivityTags(context, activityStatus, faulted));
 126
 127        if (activity != null)
 128        {
 129            SetActivityTags(activity, context, activityStatus);
 130            activity.SetTag(ActivityFaulted, faulted);
 131            if (faulted)
 132                activity.SetTag(ActivityStatus, WorkflowActivityStatus.Faulted.ToString());
 133            SetActivityOutcome(activity, context);
 134            SetError(activity, exception ?? context.Exception, faulted);
 135            activity.Dispose();
 136        }
 137    }
 138
 139    private static void SetWorkflowTags(DiagnosticsActivity activity, WorkflowExecutionContext context, WorkflowInstance
 140    {
 141        var workflow = context.Workflow;
 142        var identity = workflow.Identity;
 143        var workflowSubStatus = workflowSubStatusOverride ?? context.SubStatus;
 144        var workflowStatus = GetWorkflowStatus(context, workflowSubStatusOverride);
 145
 146        activity.SetTag(WorkflowSystem, SystemName);
 147        activity.SetTag(WorkflowInstanceId, context.Id);
 148        activity.SetTag(WorkflowDefinitionId, identity.DefinitionId);
 149        activity.SetTag(WorkflowDefinitionVersion, identity.Version);
 150        activity.SetTag(WorkflowDefinitionVersionId, identity.Id);
 151        activity.SetTag(WorkflowStatus, workflowStatus.ToString());
 152        activity.SetTag(WorkflowSubStatus, workflowSubStatus.ToString());
 153        AddIfNotNull(activity, WorkflowName, workflow.WorkflowMetadata.Name ?? workflow.Name);
 154        AddIfNotNull(activity, WorkflowParentInstanceId, context.ParentWorkflowInstanceId);
 155        AddIfNotNull(activity, WorkflowCorrelationId, context.CorrelationId);
 156        AddIfNotNull(activity, TenantId, identity.TenantId);
 157    }
 158
 159    private static void SetActivityTags(DiagnosticsActivity activity, ActivityExecutionContext context, WorkflowActivity
 160    {
 161        var currentActivity = context.Activity;
 162        var activityStatus = activityStatusOverride ?? context.Status;
 163
 164        activity.SetTag(ActivityId, currentActivity.Id);
 165        activity.SetTag(ActivityType, currentActivity.Type);
 166        activity.SetTag(ActivityVersion, currentActivity.Version);
 167        activity.SetTag(ActivityExecutionId, context.Id);
 168        activity.SetTag(ActivityStatus, activityStatus.ToString());
 169        AddIfNotNull(activity, ActivityName, currentActivity.Name ?? context.ActivityDescriptor.DisplayName ?? context.A
 170        AddIfNotNull(activity, ActivityParentExecutionId, context.ParentActivityExecutionContext?.Id);
 171        AddIfNotNull(activity, ActivityScheduledByExecutionId, context.SchedulingActivityExecutionId);
 172    }
 173
 174    private static void SetActivityOutcome(DiagnosticsActivity activity, ActivityExecutionContext context)
 175    {
 176        if (!context.JournalData.TryGetValue("Outcomes", out var outcomes))
 177            return;
 178
 179        var outcome = outcomes switch
 180        {
 181            IEnumerable<string> names => string.Join(",", names),
 182            _ => outcomes?.ToString()
 183        };
 184
 185        AddIfNotNull(activity, ActivityOutcome, outcome);
 186    }
 187
 188    private static void SetError(DiagnosticsActivity activity, Exception? exception, bool faulted)
 189    {
 190        if (!faulted)
 191        {
 192            activity.SetStatus(ActivityStatusCode.Ok);
 193            return;
 194        }
 195
 196        activity.SetStatus(ActivityStatusCode.Error, "Faulted");
 197
 198        if (exception != null)
 199        {
 200            var exceptionType = exception.GetType().FullName;
 201            activity.SetTag(ExceptionType, exceptionType);
 202            activity.AddEvent(new("exception", tags: new ActivityTagsCollection
 203            {
 204                { ExceptionType, exceptionType }
 205            }));
 206        }
 207    }
 208
 209    private static TagList CreateWorkflowTags(WorkflowExecutionContext context, bool includeExecutionStatus = true, Work
 210    {
 211        var workflow = context.Workflow;
 212        var identity = workflow.Identity;
 213        var workflowSubStatus = workflowSubStatusOverride ?? context.SubStatus;
 214        var workflowStatus = GetWorkflowStatus(context, workflowSubStatusOverride);
 215        var tags = new TagList
 216        {
 217            { WorkflowSystem, SystemName },
 218            { WorkflowDefinitionId, identity.DefinitionId },
 219            { WorkflowDefinitionVersion, identity.Version }
 220        };
 221
 222        if (includeExecutionStatus)
 223        {
 224            tags.Add(WorkflowStatus, workflowStatus.ToString());
 225            tags.Add(WorkflowSubStatus, workflowSubStatus.ToString());
 226        }
 227
 228        AddIfNotNull(ref tags, WorkflowName, workflow.WorkflowMetadata.Name ?? workflow.Name);
 229        AddIfNotNull(ref tags, TenantId, identity.TenantId);
 230        return tags;
 231    }
 232
 233    private static TagList CreateActivityTags(ActivityExecutionContext context, WorkflowActivityStatus activityStatus, b
 234    {
 235        var currentActivity = context.Activity;
 236        var tags = new TagList
 237        {
 238            { WorkflowSystem, SystemName },
 239            { WorkflowDefinitionId, context.WorkflowExecutionContext.Workflow.Identity.DefinitionId },
 240            { ActivityType, currentActivity.Type },
 241            { ActivityVersion, currentActivity.Version },
 242            { ActivityStatus, faulted ? WorkflowActivityStatus.Faulted.ToString() : activityStatus.ToString() },
 243            { ActivityFaulted, faulted }
 244        };
 245
 246        AddIfNotNull(ref tags, TenantId, context.WorkflowExecutionContext.Workflow.Identity.TenantId);
 247        return tags;
 248    }
 249
 250    private static WorkflowInstanceStatus GetWorkflowStatus(WorkflowExecutionContext context, WorkflowInstanceSubStatus?
 251    {
 252        if (workflowSubStatusOverride == null)
 253            return context.Status;
 254
 255        return workflowSubStatusOverride.Value switch
 256        {
 257            WorkflowInstanceSubStatus.Cancelled or WorkflowInstanceSubStatus.Faulted or WorkflowInstanceSubStatus.Finish
 258            _ => WorkflowInstanceStatus.Running
 259        };
 260    }
 261
 262    private static void AddIfNotNull(ref TagList tags, string key, object? value)
 263    {
 264        if (ShouldAddTag(value))
 265            tags.Add(key, value!);
 266    }
 267
 268    private static void AddIfNotNull(DiagnosticsActivity activity, string key, object? value)
 269    {
 270        if (ShouldAddTag(value))
 271            activity.SetTag(key, value);
 272    }
 273
 274    private static bool ShouldAddTag(object? value)
 275    {
 276        return value is not null && (value is not string stringValue || !string.IsNullOrWhiteSpace(stringValue));
 277    }
 278}
 279
 280internal readonly record struct WorkflowInstrumentationScope(DiagnosticsActivity? Activity);
 281
 6612282internal readonly record struct ActivityInstrumentationScope(DiagnosticsActivity? Activity, long StartTimestamp);

Methods/Properties

get_Activity()