< Summary

Information
Class: AsiBackbone.OpenTelemetry.OpenTelemetryGovernanceEmitter
Assembly: AsiBackbone.OpenTelemetry
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.OpenTelemetry/OpenTelemetryGovernanceEmitter.cs
Line coverage
98%
Covered lines: 132
Uncovered lines: 2
Coverable lines: 134
Total lines: 261
Line coverage: 98.5%
Branch coverage
73%
Covered branches: 19
Total branches: 26
Branch coverage: 73%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor()100%11100%
.ctor(...)100%11100%
EmitAsync()100%8895.74%
EmitActivity(...)83.33%66100%
BuildTags(...)50%44100%
EmitDeliveredMetrics(...)100%11100%
EmitFailureMetrics(...)50%22100%
BuildMetricTags(...)100%11100%
AddTag(...)50%22100%
AddTag(...)50%22100%
AddTag(...)50%22100%
AddTag(...)100%11100%

File(s)

/home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.OpenTelemetry/OpenTelemetryGovernanceEmitter.cs

#LineLine coverage
 1using System.Diagnostics;
 2using System.Diagnostics.Metrics;
 3using AsiBackbone.Core.Emissions;
 4
 5namespace AsiBackbone.OpenTelemetry;
 6
 7/// <summary>
 8/// Emits provider-neutral governance envelopes through OpenTelemetry-friendly .NET diagnostics primitives.
 9/// </summary>
 10/// <remarks>
 11/// This emitter records activity events, activity tags, and low-cardinality metrics. It does not configure exporters or
 12/// </remarks>
 13public sealed class OpenTelemetryGovernanceEmitter : IGovernanceEmitter
 14{
 115    private static readonly ActivitySource ActivitySource = new(OpenTelemetryGovernanceInstrumentation.ActivitySourceNam
 116    private static readonly Meter Meter = new(OpenTelemetryGovernanceInstrumentation.MeterName);
 117    private static readonly Counter<long> EmissionsCounter = Meter.CreateCounter<long>(
 118        OpenTelemetryGovernanceInstrumentation.EmissionsCounterName,
 119        description: "Counts AsiBackbone governance emission attempts accepted by the OpenTelemetry diagnostics provider
 120    private static readonly Counter<long> EmissionFailuresCounter = Meter.CreateCounter<long>(
 121        OpenTelemetryGovernanceInstrumentation.EmissionFailuresCounterName,
 122        description: "Counts AsiBackbone governance emission failures normalized by the OpenTelemetry diagnostics provid
 123    private static readonly Histogram<double> EmissionLatencyHistogram = Meter.CreateHistogram<double>(
 124        OpenTelemetryGovernanceInstrumentation.EmissionLatencyHistogramName,
 125        unit: "ms",
 126        description: "Measures local OpenTelemetry governance emission latency in milliseconds.");
 27
 28    private readonly OpenTelemetryGovernanceEmitterOptions options;
 29
 30    /// <summary>
 31    /// Initializes a new instance of the <see cref="OpenTelemetryGovernanceEmitter" /> class using default options.
 32    /// </summary>
 33    public OpenTelemetryGovernanceEmitter()
 234        : this(new OpenTelemetryGovernanceEmitterOptions())
 35    {
 236    }
 37
 38    /// <summary>
 39    /// Initializes a new instance of the <see cref="OpenTelemetryGovernanceEmitter" /> class using host-owned options.
 40    /// </summary>
 41    /// <param name="options">The OpenTelemetry governance emitter options.</param>
 1642    public OpenTelemetryGovernanceEmitter(OpenTelemetryGovernanceEmitterOptions options)
 43    {
 1644        ArgumentNullException.ThrowIfNull(options);
 1645        options.Validate();
 46
 1647        this.options = options;
 1648    }
 49
 50    /// <inheritdoc />
 51    public async ValueTask<GovernanceEmissionResult> EmitAsync(
 52        GovernanceEmissionEnvelope envelope,
 53        CancellationToken cancellationToken = default)
 54    {
 1455        ArgumentNullException.ThrowIfNull(envelope);
 1456        cancellationToken.ThrowIfCancellationRequested();
 57
 1458        var stopwatch = Stopwatch.StartNew();
 59
 60        try
 61        {
 1462            if (options.BeforeEmitAsync is not null)
 63            {
 164                await options.BeforeEmitAsync(envelope, cancellationToken).ConfigureAwait(false);
 65            }
 66
 1367            cancellationToken.ThrowIfCancellationRequested();
 1368            double latencyMs = stopwatch.Elapsed.TotalMilliseconds;
 1369            string eventName = OpenTelemetryGovernanceEventNameMapper.GetEventName(
 1370                envelope.EventType,
 1371                envelope.EmitterStatus);
 1372            string providerName = options.ProviderName.Trim();
 73
 1374            if (options.EmitActivityEvents)
 75            {
 276                EmitActivity(envelope, eventName, providerName, options.DefaultActivityName, latencyMs);
 77            }
 78
 1379            if (options.EmitMetrics)
 80            {
 281                EmitDeliveredMetrics(envelope, providerName, latencyMs);
 82            }
 83
 1384            return GovernanceEmissionResult.Delivered(
 1385                providerName,
 1386                envelope.EnvelopeId,
 1387                new Dictionary<string, string>(StringComparer.Ordinal)
 1388                {
 1389                    ["opentelemetry.activity_source"] = OpenTelemetryGovernanceInstrumentation.ActivitySourceName,
 1390                    ["opentelemetry.meter"] = OpenTelemetryGovernanceInstrumentation.MeterName,
 1391                    ["opentelemetry.event_name"] = eventName,
 1392                    ["opentelemetry.emission_latency_ms"] = latencyMs.ToString("0.###", System.Globalization.CultureInfo
 1393                });
 94        }
 095        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
 96        {
 097            throw;
 98        }
 199        catch (Exception ex)
 100        {
 1101            double latencyMs = stopwatch.Elapsed.TotalMilliseconds;
 1102            var error = GovernanceEmissionError.Create(
 1103                "opentelemetry.emission.exception",
 1104                $"OpenTelemetry governance emission failed with {ex.GetType().Name}.",
 1105                isRetryable: true,
 1106                providerName: options.ProviderName,
 1107                providerErrorCode: ex.GetType().FullName);
 108
 1109            if (options.EmitMetrics)
 110            {
 1111                EmitFailureMetrics(envelope, error, latencyMs);
 112            }
 113
 1114            return GovernanceEmissionResult.RetryableFailure(
 1115                error,
 1116                providerName: options.ProviderName,
 1117                metadata: new Dictionary<string, string>(StringComparer.Ordinal)
 1118                {
 1119                    ["opentelemetry.activity_source"] = OpenTelemetryGovernanceInstrumentation.ActivitySourceName,
 1120                    ["opentelemetry.meter"] = OpenTelemetryGovernanceInstrumentation.MeterName,
 1121                    ["opentelemetry.emission_latency_ms"] = latencyMs.ToString("0.###", System.Globalization.CultureInfo
 1122                });
 123        }
 14124    }
 125
 126    private static void EmitActivity(
 127        GovernanceEmissionEnvelope envelope,
 128        string eventName,
 129        string providerName,
 130        string defaultActivityName,
 131        double latencyMs)
 132    {
 2133        ActivityTagsCollection tags = BuildTags(envelope, providerName, GovernanceEmissionStatus.Delivered.ToString(), l
 2134        string activityName = string.IsNullOrWhiteSpace(envelope.OperationName)
 2135            ? defaultActivityName
 2136            : envelope.OperationName;
 137
 2138        using Activity? activity = ActivitySource.StartActivity(activityName, ActivityKind.Internal);
 139
 2140        if (activity is null)
 141        {
 1142            return;
 143        }
 144
 54145        foreach (KeyValuePair<string, object?> tag in tags)
 146        {
 26147            _ = activity.SetTag(tag.Key, tag.Value);
 148        }
 149
 1150        _ = activity.AddEvent(new ActivityEvent(eventName, envelope.OccurredUtc, tags));
 3151    }
 152
 153    private static ActivityTagsCollection BuildTags(
 154        GovernanceEmissionEnvelope envelope,
 155        string providerName,
 156        string result,
 157        double latencyMs)
 158    {
 2159        ActivityTagsCollection tags = [];
 160
 2161        AddTag(tags, OpenTelemetryGovernanceAttributes.EnvelopeId, envelope.EnvelopeId);
 2162        AddTag(tags, OpenTelemetryGovernanceAttributes.SchemaVersion, envelope.SchemaVersion);
 2163        AddTag(tags, OpenTelemetryGovernanceAttributes.EventType, envelope.EventType.ToString());
 2164        AddTag(tags, OpenTelemetryGovernanceAttributes.EventId, envelope.EventId);
 2165        AddTag(tags, OpenTelemetryGovernanceAttributes.CorrelationId, envelope.CorrelationId);
 2166        AddTag(tags, OpenTelemetryGovernanceAttributes.DecisionReceiptId, envelope.DecisionReceiptId);
 2167        AddTag(tags, OpenTelemetryGovernanceAttributes.TraceId, envelope.TraceId);
 2168        AddTag(tags, OpenTelemetryGovernanceAttributes.SpanId, envelope.SpanId);
 2169        AddTag(tags, OpenTelemetryGovernanceAttributes.ParentSpanId, envelope.ParentSpanId);
 2170        AddTag(tags, OpenTelemetryGovernanceAttributes.DecisionOutcome, envelope.Outcome);
 2171        AddTag(tags, OpenTelemetryGovernanceAttributes.DecisionStage, envelope.DecisionStage);
 2172        AddTag(tags, OpenTelemetryGovernanceAttributes.PolicyVersion, envelope.PolicyVersion);
 2173        AddTag(tags, OpenTelemetryGovernanceAttributes.PolicyHash, envelope.PolicyHash);
 2174        AddTag(tags, OpenTelemetryGovernanceAttributes.LifecycleStage, envelope.LifecycleStage?.ToString());
 2175        AddTag(tags, OpenTelemetryGovernanceAttributes.LifecycleStageSequence, envelope.LifecycleStageSequence);
 2176        AddTag(tags, OpenTelemetryGovernanceAttributes.GatewayExecutionId, envelope.GatewayExecutionId);
 2177        AddTag(tags, OpenTelemetryGovernanceAttributes.OutboxSequence, envelope.OutboxSequence);
 2178        AddTag(tags, OpenTelemetryGovernanceAttributes.EmitterProvider, providerName);
 2179        AddTag(tags, OpenTelemetryGovernanceAttributes.EmitterStatus, envelope.EmitterStatus);
 2180        AddTag(tags, OpenTelemetryGovernanceAttributes.EmitterResult, result);
 2181        AddTag(tags, OpenTelemetryGovernanceAttributes.EmissionLatencyMs, latencyMs);
 182
 2183        if (envelope.Payload is not null)
 184        {
 2185            AddTag(tags, OpenTelemetryGovernanceAttributes.PayloadType, envelope.Payload.PayloadType);
 2186            AddTag(tags, OpenTelemetryGovernanceAttributes.PayloadSchemaVersion, envelope.Payload.SchemaVersion);
 2187            AddTag(tags, OpenTelemetryGovernanceAttributes.PayloadContentType, envelope.Payload.ContentType);
 2188            AddTag(tags, OpenTelemetryGovernanceAttributes.PayloadContentHash, envelope.Payload.ContentHash);
 2189            AddTag(tags, OpenTelemetryGovernanceAttributes.PayloadSizeBytes, envelope.Payload.SizeBytes);
 190        }
 191
 2192        return tags;
 193    }
 194
 195    private static void EmitDeliveredMetrics(
 196        GovernanceEmissionEnvelope envelope,
 197        string providerName,
 198        double latencyMs)
 199    {
 2200        TagList tags = BuildMetricTags(envelope, providerName, GovernanceEmissionStatus.Delivered.ToString());
 2201        EmissionsCounter.Add(1, tags);
 2202        EmissionLatencyHistogram.Record(latencyMs, tags);
 2203    }
 204
 205    private static void EmitFailureMetrics(
 206        GovernanceEmissionEnvelope envelope,
 207        GovernanceEmissionError error,
 208        double latencyMs)
 209    {
 1210        TagList tags = BuildMetricTags(envelope, error.ProviderName ?? OpenTelemetryGovernanceInstrumentation.ProviderNa
 1211        tags.Add(OpenTelemetryGovernanceAttributes.MetricFailureCode, error.Code);
 1212        tags.Add(OpenTelemetryGovernanceAttributes.MetricRetryable, error.IsRetryable);
 213
 1214        EmissionFailuresCounter.Add(1, tags);
 1215        EmissionLatencyHistogram.Record(latencyMs, tags);
 1216    }
 217
 218    private static TagList BuildMetricTags(
 219        GovernanceEmissionEnvelope envelope,
 220        string providerName,
 221        string result)
 222    {
 3223        TagList tags = new()
 3224        {
 3225            { OpenTelemetryGovernanceAttributes.MetricEventType, envelope.EventType.ToString() },
 3226            { OpenTelemetryGovernanceAttributes.MetricResult, result },
 3227            { OpenTelemetryGovernanceAttributes.MetricProvider, providerName }
 3228        };
 229
 3230        return tags;
 231    }
 232
 233    private static void AddTag(ActivityTagsCollection tags, string key, string? value)
 234    {
 44235        if (!string.IsNullOrWhiteSpace(value))
 236        {
 44237            tags.Add(key, value);
 238        }
 44239    }
 240
 241    private static void AddTag(ActivityTagsCollection tags, string key, long? value)
 242    {
 4243        if (value.HasValue)
 244        {
 4245            tags.Add(key, value.Value);
 246        }
 4247    }
 248
 249    private static void AddTag(ActivityTagsCollection tags, string key, int? value)
 250    {
 2251        if (value.HasValue)
 252        {
 2253            tags.Add(key, value.Value);
 254        }
 2255    }
 256
 257    private static void AddTag(ActivityTagsCollection tags, string key, double value)
 258    {
 2259        tags.Add(key, value);
 2260    }
 261}