< Summary

Information
Class: IceRpc.Telemetry.TelemetryMiddleware
Assembly: IceRpc.Telemetry
File(s): /home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc.Telemetry/TelemetryMiddleware.cs
Tag: 275_13775359185
Line coverage
95%
Covered lines: 45
Uncovered lines: 2
Coverable lines: 47
Total lines: 94
Line coverage: 95.7%
Branch coverage
75%
Covered branches: 6
Total branches: 8
Branch coverage: 75%
Method coverage
100%
Covered methods: 3
Total methods: 3
Method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
DispatchAsync()66.66%6.06688.23%
RestoreActivityContext(...)100%22100%

File(s)

/home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc.Telemetry/TelemetryMiddleware.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using IceRpc.Extensions.DependencyInjection;
 4using System.Buffers;
 5using System.Diagnostics;
 6using ZeroC.Slice;
 7
 8namespace IceRpc.Telemetry;
 9
 10/// <summary>A middleware that starts an <see cref="Activity" /> per request, following
 11/// <see href="https://opentelemetry.io/">OpenTelemetry</see> conventions. The middleware restores the parent invocation
 12/// activity from the request <see cref="RequestFieldKey.TraceContext" /> field before starting the dispatch activity.
 13/// </summary>
 14/// <remarks>The activities are only created for requests using the icerpc protocol.</remarks>
 15/// <seealso cref="TelemetryRouterExtensions" />
 16/// <seealso cref="TelemetryDispatcherBuilderExtensions"/>
 17public class TelemetryMiddleware : IDispatcher
 18{
 19    private readonly IDispatcher _next;
 20    private readonly ActivitySource _activitySource;
 21
 22    /// <summary>Constructs a telemetry middleware.</summary>
 23    /// <param name="next">The next dispatcher in the dispatch pipeline.</param>
 24    /// <param name="activitySource">The <see cref="ActivitySource" /> is used to start the request activity.</param>
 325    public TelemetryMiddleware(IDispatcher next, ActivitySource activitySource)
 326    {
 327        _next = next;
 328        _activitySource = activitySource;
 329    }
 30
 31    /// <inheritdoc/>
 32    public async ValueTask<OutgoingResponse> DispatchAsync(IncomingRequest request, CancellationToken cancellationToken)
 333    {
 334        if (request.Protocol.HasFields)
 335        {
 336            string name = $"{request.Path}/{request.Operation}";
 337            using Activity activity = _activitySource.CreateActivity(name, ActivityKind.Server) ?? new Activity(name);
 338            activity.AddTag("rpc.system", "icerpc");
 339            activity.AddTag("rpc.service", request.Path);
 340            activity.AddTag("rpc.method", request.Operation);
 341            if (request.Fields.TryGetValue(RequestFieldKey.TraceContext, out ReadOnlySequence<byte> buffer))
 242            {
 243                RestoreActivityContext(buffer, activity);
 144            }
 245            activity.Start();
 246            return await _next.DispatchAsync(request, cancellationToken).ConfigureAwait(false);
 47        }
 48        else
 049        {
 050            return await _next.DispatchAsync(request, cancellationToken).ConfigureAwait(false);
 51        }
 252    }
 53
 54    internal static void RestoreActivityContext(ReadOnlySequence<byte> buffer, Activity activity)
 355    {
 356        var decoder = new SliceDecoder(buffer, SliceEncoding.Slice2);
 57
 58        // Read W3C traceparent binary encoding (1 byte version, 16 bytes trace-ID, 8 bytes span-ID,
 59        // 1 byte flags) https://www.w3.org/TR/trace-context/#traceparent-header-field-values
 60
 361        byte traceIdVersion = decoder.DecodeUInt8();
 62
 263        using IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent(16);
 264        Span<byte> traceIdSpan = memoryOwner.Memory.Span[0..16];
 265        decoder.CopyTo(traceIdSpan);
 266        var traceId = ActivityTraceId.CreateFromBytes(traceIdSpan);
 67
 268        Span<byte> spanIdSpan = memoryOwner.Memory.Span[0..8];
 269        decoder.CopyTo(spanIdSpan);
 270        var spanId = ActivitySpanId.CreateFromBytes(spanIdSpan);
 71
 272        var traceFlags = (ActivityTraceFlags)decoder.DecodeUInt8();
 73
 274        activity.SetParentId(traceId, spanId, traceFlags);
 75
 76        // Read TraceState encoded as a string
 277        activity.TraceStateString = decoder.DecodeString();
 78
 279        IEnumerable<(string Key, string Value)> baggage = decoder.DecodeSequence(
 280            (ref SliceDecoder decoder) =>
 281            {
 282                string key = decoder.DecodeString();
 283                string value = decoder.DecodeString();
 284                return (key, value);
 485            });
 86
 87        // Restore in reverse order to keep the order in witch the peer add baggage entries,
 88        // this is important when there are duplicate keys.
 1089        foreach ((string key, string value) in baggage.Reverse())
 290        {
 291            activity.AddBaggage(key, value);
 292        }
 493    }
 94}