< 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 : IAsiBackboneGovernanceEmitter
 14{
 215    private static readonly ActivitySource ActivitySource = new(OpenTelemetryGovernanceInstrumentation.ActivitySourceNam
 216    private static readonly Meter Meter = new(OpenTelemetryGovernanceInstrumentation.MeterName);
 217    private static readonly Counter<long> EmissionsCounter = Meter.CreateCounter<long>(
 218        OpenTelemetryGovernanceInstrumentation.EmissionsCounterName,
 219        description: "Counts AsiBackbone governance emission attempts accepted by the OpenTelemetry diagnostics provider
 220    private static readonly Counter<long> EmissionFailuresCounter = Meter.CreateCounter<long>(
 221        OpenTelemetryGovernanceInstrumentation.EmissionFailuresCounterName,
 222        description: "Counts AsiBackbone governance emission failures normalized by the OpenTelemetry diagnostics provid
 223    private static readonly Histogram<double> EmissionLatencyHistogram = Meter.CreateHistogram<double>(
 224        OpenTelemetryGovernanceInstrumentation.EmissionLatencyHistogramName,
 225        unit: "ms",
 226        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()
 434        : this(new OpenTelemetryGovernanceEmitterOptions())
 35    {
 436    }
 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>
 3242    public OpenTelemetryGovernanceEmitter(OpenTelemetryGovernanceEmitterOptions options)
 43    {
 3244        ArgumentNullException.ThrowIfNull(options);
 3245        options.Validate();
 46
 3247        this.options = options;
 3248    }
 49
 50    /// <inheritdoc />
 51    public async ValueTask<GovernanceEmissionResult> EmitAsync(
 52        GovernanceEmissionEnvelope envelope,
 53        CancellationToken cancellationToken = default)
 54    {
 2855        ArgumentNullException.ThrowIfNull(envelope);
 2856        cancellationToken.ThrowIfCancellationRequested();
 57
 2858        var stopwatch = Stopwatch.StartNew();
 59
 60        try
 61        {
 2862            if (options.BeforeEmitAsync is not null)
 63            {
 264                await options.BeforeEmitAsync(envelope, cancellationToken).ConfigureAwait(false);
 65            }
 66
 2667            cancellationToken.ThrowIfCancellationRequested();
 2668            double latencyMs = stopwatch.Elapsed.TotalMilliseconds;
 2669            string eventName = OpenTelemetryGovernanceEventNameMapper.GetEventName(
 2670                envelope.EventType,
 2671                envelope.EmitterStatus);
 2672            string providerName = options.ProviderName.Trim();
 73
 2674            if (options.EmitActivityEvents)
 75            {
 476                EmitActivity(envelope, eventName, providerName, options.DefaultActivityName, latencyMs);
 77            }
 78
 2679            if (options.EmitMetrics)
 80            {
 481                EmitDeliveredMetrics(envelope, providerName, latencyMs);
 82            }
 83
 2684            return GovernanceEmissionResult.Delivered(
 2685                providerName,
 2686                envelope.EnvelopeId,
 2687                new Dictionary<string, string>(StringComparer.Ordinal)
 2688                {
 2689                    ["opentelemetry.activity_source"] = OpenTelemetryGovernanceInstrumentation.ActivitySourceName,
 2690                    ["opentelemetry.meter"] = OpenTelemetryGovernanceInstrumentation.MeterName,
 2691                    ["opentelemetry.event_name"] = eventName,
 2692                    ["opentelemetry.emission_latency_ms"] = latencyMs.ToString("0.###", System.Globalization.CultureInfo
 2693                });
 94        }
 095        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
 96        {
 097            throw;
 98        }
 299        catch (Exception ex)
 100        {
 2101            double latencyMs = stopwatch.Elapsed.TotalMilliseconds;
 2102            var error = GovernanceEmissionError.Create(
 2103                "opentelemetry.emission.exception",
 2104                $"OpenTelemetry governance emission failed with {ex.GetType().Name}.",
 2105                isRetryable: true,
 2106                providerName: options.ProviderName,
 2107                providerErrorCode: ex.GetType().FullName);
 108
 2109            if (options.EmitMetrics)
 110            {
 2111                EmitFailureMetrics(envelope, error, latencyMs);
 112            }
 113
 2114            return GovernanceEmissionResult.RetryableFailure(
 2115                error,
 2116                providerName: options.ProviderName,
 2117                metadata: new Dictionary<string, string>(StringComparer.Ordinal)
 2118                {
 2119                    ["opentelemetry.activity_source"] = OpenTelemetryGovernanceInstrumentation.ActivitySourceName,
 2120                    ["opentelemetry.meter"] = OpenTelemetryGovernanceInstrumentation.MeterName,
 2121                    ["opentelemetry.emission_latency_ms"] = latencyMs.ToString("0.###", System.Globalization.CultureInfo
 2122                });
 123        }
 28124    }
 125
 126    private static void EmitActivity(
 127        GovernanceEmissionEnvelope envelope,
 128        string eventName,
 129        string providerName,
 130        string defaultActivityName,
 131        double latencyMs)
 132    {
 4133        ActivityTagsCollection tags = BuildTags(envelope, providerName, GovernanceEmissionStatus.Delivered.ToString(), l
 4134        string activityName = string.IsNullOrWhiteSpace(envelope.OperationName)
 4135            ? defaultActivityName
 4136            : envelope.OperationName;
 137
 4138        using Activity? activity = ActivitySource.StartActivity(activityName, ActivityKind.Internal);
 139
 4140        if (activity is null)
 141        {
 2142            return;
 143        }
 144
 108145        foreach (KeyValuePair<string, object?> tag in tags)
 146        {
 52147            _ = activity.SetTag(tag.Key, tag.Value);
 148        }
 149
 2150        _ = activity.AddEvent(new ActivityEvent(eventName, envelope.OccurredUtc, tags));
 6151    }
 152
 153    private static ActivityTagsCollection BuildTags(
 154        GovernanceEmissionEnvelope envelope,
 155        string providerName,
 156        string result,
 157        double latencyMs)
 158    {
 4159        ActivityTagsCollection tags = [];
 160
 4161        AddTag(tags, OpenTelemetryGovernanceAttributes.EnvelopeId, envelope.EnvelopeId);
 4162        AddTag(tags, OpenTelemetryGovernanceAttributes.SchemaVersion, envelope.SchemaVersion);
 4163        AddTag(tags, OpenTelemetryGovernanceAttributes.EventType, envelope.EventType.ToString());
 4164        AddTag(tags, OpenTelemetryGovernanceAttributes.EventId, envelope.EventId);
 4165        AddTag(tags, OpenTelemetryGovernanceAttributes.CorrelationId, envelope.CorrelationId);
 4166        AddTag(tags, OpenTelemetryGovernanceAttributes.AuditResidueId, envelope.AuditResidueId);
 4167        AddTag(tags, OpenTelemetryGovernanceAttributes.TraceId, envelope.TraceId);
 4168        AddTag(tags, OpenTelemetryGovernanceAttributes.SpanId, envelope.SpanId);
 4169        AddTag(tags, OpenTelemetryGovernanceAttributes.ParentSpanId, envelope.ParentSpanId);
 4170        AddTag(tags, OpenTelemetryGovernanceAttributes.DecisionOutcome, envelope.Outcome);
 4171        AddTag(tags, OpenTelemetryGovernanceAttributes.DecisionStage, envelope.DecisionStage);
 4172        AddTag(tags, OpenTelemetryGovernanceAttributes.PolicyVersion, envelope.PolicyVersion);
 4173        AddTag(tags, OpenTelemetryGovernanceAttributes.PolicyHash, envelope.PolicyHash);
 4174        AddTag(tags, OpenTelemetryGovernanceAttributes.LifecycleStage, envelope.LifecycleStage?.ToString());
 4175        AddTag(tags, OpenTelemetryGovernanceAttributes.LifecycleStageSequence, envelope.LifecycleStageSequence);
 4176        AddTag(tags, OpenTelemetryGovernanceAttributes.GatewayExecutionId, envelope.GatewayExecutionId);
 4177        AddTag(tags, OpenTelemetryGovernanceAttributes.OutboxSequence, envelope.OutboxSequence);
 4178        AddTag(tags, OpenTelemetryGovernanceAttributes.EmitterProvider, providerName);
 4179        AddTag(tags, OpenTelemetryGovernanceAttributes.EmitterStatus, envelope.EmitterStatus);
 4180        AddTag(tags, OpenTelemetryGovernanceAttributes.EmitterResult, result);
 4181        AddTag(tags, OpenTelemetryGovernanceAttributes.EmissionLatencyMs, latencyMs);
 182
 4183        if (envelope.Payload is not null)
 184        {
 4185            AddTag(tags, OpenTelemetryGovernanceAttributes.PayloadType, envelope.Payload.PayloadType);
 4186            AddTag(tags, OpenTelemetryGovernanceAttributes.PayloadSchemaVersion, envelope.Payload.SchemaVersion);
 4187            AddTag(tags, OpenTelemetryGovernanceAttributes.PayloadContentType, envelope.Payload.ContentType);
 4188            AddTag(tags, OpenTelemetryGovernanceAttributes.PayloadContentHash, envelope.Payload.ContentHash);
 4189            AddTag(tags, OpenTelemetryGovernanceAttributes.PayloadSizeBytes, envelope.Payload.SizeBytes);
 190        }
 191
 4192        return tags;
 193    }
 194
 195    private static void EmitDeliveredMetrics(
 196        GovernanceEmissionEnvelope envelope,
 197        string providerName,
 198        double latencyMs)
 199    {
 4200        TagList tags = BuildMetricTags(envelope, providerName, GovernanceEmissionStatus.Delivered.ToString());
 4201        EmissionsCounter.Add(1, tags);
 4202        EmissionLatencyHistogram.Record(latencyMs, tags);
 4203    }
 204
 205    private static void EmitFailureMetrics(
 206        GovernanceEmissionEnvelope envelope,
 207        GovernanceEmissionError error,
 208        double latencyMs)
 209    {
 2210        TagList tags = BuildMetricTags(envelope, error.ProviderName ?? OpenTelemetryGovernanceInstrumentation.ProviderNa
 2211        tags.Add(OpenTelemetryGovernanceAttributes.MetricFailureCode, error.Code);
 2212        tags.Add(OpenTelemetryGovernanceAttributes.MetricRetryable, error.IsRetryable);
 213
 2214        EmissionFailuresCounter.Add(1, tags);
 2215        EmissionLatencyHistogram.Record(latencyMs, tags);
 2216    }
 217
 218    private static TagList BuildMetricTags(
 219        GovernanceEmissionEnvelope envelope,
 220        string providerName,
 221        string result)
 222    {
 6223        TagList tags = new()
 6224        {
 6225            { OpenTelemetryGovernanceAttributes.MetricEventType, envelope.EventType.ToString() },
 6226            { OpenTelemetryGovernanceAttributes.MetricResult, result },
 6227            { OpenTelemetryGovernanceAttributes.MetricProvider, providerName }
 6228        };
 229
 6230        return tags;
 231    }
 232
 233    private static void AddTag(ActivityTagsCollection tags, string key, string? value)
 234    {
 88235        if (!string.IsNullOrWhiteSpace(value))
 236        {
 88237            tags.Add(key, value);
 238        }
 88239    }
 240
 241    private static void AddTag(ActivityTagsCollection tags, string key, long? value)
 242    {
 8243        if (value.HasValue)
 244        {
 8245            tags.Add(key, value.Value);
 246        }
 8247    }
 248
 249    private static void AddTag(ActivityTagsCollection tags, string key, int? value)
 250    {
 4251        if (value.HasValue)
 252        {
 4253            tags.Add(key, value.Value);
 254        }
 4255    }
 256
 257    private static void AddTag(ActivityTagsCollection tags, string key, double value)
 258    {
 4259        tags.Add(key, value);
 4260    }
 261}