< Summary

Information
Class: Elsa.Diagnostics.OpenTelemetry.Extensions.EndpointRouteBuilderExtensions
Assembly: Elsa.Diagnostics.OpenTelemetry
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Diagnostics.OpenTelemetry/Extensions/EndpointRouteBuilderExtensions.cs
Line coverage
76%
Covered lines: 35
Uncovered lines: 11
Coverable lines: 46
Total lines: 114
Line coverage: 76%
Branch coverage
50%
Covered branches: 7
Total branches: 14
Branch coverage: 50%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
MapOpenTelemetryHub(...)0%620%
MapOpenTelemetryHttpProtobufCollector(...)50%2286.66%
MapOpenTelemetryGrpcCollector(...)0%2040%
IngestAsync()100%22100%
ReadBodyAsync()100%44100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Diagnostics.OpenTelemetry/Extensions/EndpointRouteBuilderExtensions.cs

#LineLine coverage
 1using System.Buffers;
 2using Elsa.Diagnostics.OpenTelemetry.Options;
 3using Elsa.Diagnostics.OpenTelemetry.Contracts;
 4using Elsa.Diagnostics.OpenTelemetry.Ingestion;
 5using Elsa.Diagnostics.OpenTelemetry.Ingestion.HttpProtobuf;
 6using Elsa.Diagnostics.OpenTelemetry.Models;
 7using Elsa.Diagnostics.OpenTelemetry.RealTime;
 8using Microsoft.AspNetCore.Http;
 9using Microsoft.AspNetCore.Builder;
 10using Microsoft.AspNetCore.Routing;
 11using Microsoft.Extensions.DependencyInjection;
 12using Microsoft.Extensions.Options;
 13
 14namespace Elsa.Diagnostics.OpenTelemetry.Extensions;
 15
 16public static class EndpointRouteBuilderExtensions
 17{
 18    public const string DefaultHubRoute = "/elsa/hubs/diagnostics/opentelemetry";
 19
 20    public static void MapOpenTelemetryHub(this IEndpointRouteBuilder endpoints)
 21    {
 022        var options = endpoints.ServiceProvider.GetRequiredService<IOptions<OpenTelemetryDiagnosticsOptions>>().Value;
 023        endpoints.MapHub<OpenTelemetryHub>(string.IsNullOrWhiteSpace(options.HubRoute) ? DefaultHubRoute : options.HubRo
 024    }
 25
 26    public static void MapOpenTelemetryHttpProtobufCollector(this IEndpointRouteBuilder endpoints)
 27    {
 628        var options = endpoints.ServiceProvider.GetRequiredService<IOptions<OpenTelemetryDiagnosticsOptions>>().Value;
 629        var basePath = (string.IsNullOrWhiteSpace(options.HttpEndpointPath) ? "/elsa/otlp/v1" : options.HttpEndpointPath
 30
 631        endpoints.MapPost($"{basePath}/traces", static async (HttpContext httpContext, IOpenTelemetryIngestor ingestor, 
 632        {
 1033            return await IngestAsync(httpContext, ingestor, options, payload => OtlpHttpProtobufParser.ParseTraces(paylo
 1234        });
 35
 636        endpoints.MapPost($"{basePath}/metrics", static async (HttpContext httpContext, IOpenTelemetryIngestor ingestor,
 637        {
 038            return await IngestAsync(httpContext, ingestor, options, payload => OtlpHttpProtobufParser.ParseMetrics(payl
 639        });
 40
 641        endpoints.MapPost($"{basePath}/logs", static async (HttpContext httpContext, IOpenTelemetryIngestor ingestor, IO
 642        {
 043            return await IngestAsync(httpContext, ingestor, options, payload => OtlpHttpProtobufParser.ParseLogs(payload
 644        });
 645    }
 46
 47    public static void MapOpenTelemetryGrpcCollector(this IEndpointRouteBuilder endpoints)
 48    {
 049        var options = endpoints.ServiceProvider.GetRequiredService<IOptions<OpenTelemetryDiagnosticsOptions>>().Value;
 50
 051        if (!options.EnableGrpc)
 052            return;
 53
 054        if (string.IsNullOrWhiteSpace(options.GrpcEndpointPath))
 055            throw new InvalidOperationException("OpenTelemetry gRPC ingestion is enabled, but no gRPC endpoint path was 
 56
 57        // The actual gRPC service binding is host-specific. This module exposes shared ingestion
 58        // contracts and accurate collector metadata without forcing every host to reference gRPC.
 059    }
 60
 61    private static async Task<IResult> IngestAsync(
 62        HttpContext httpContext,
 63        IOpenTelemetryIngestor ingestor,
 64        IOptions<OpenTelemetryDiagnosticsOptions> options,
 65        Func<ReadOnlyMemory<byte>, OpenTelemetryBatch> parse,
 66        CancellationToken cancellationToken)
 67    {
 668        if (!OtlpIngestionSecurity.IsAuthorized(httpContext, options.Value))
 169            return Results.Unauthorized();
 70
 71        try
 72        {
 573            var payload = await ReadBodyAsync(httpContext, options.Value.MaxHttpRequestBodySize, cancellationToken);
 474            await ingestor.IngestAsync(parse(payload), cancellationToken);
 375            return Results.Ok();
 76        }
 177        catch (RequestBodyTooLargeException)
 78        {
 179            return Results.StatusCode(StatusCodes.Status413PayloadTooLarge);
 80        }
 181        catch (InvalidDataException)
 82        {
 183            return Results.BadRequest();
 84        }
 685    }
 86
 87    private static async Task<ReadOnlyMemory<byte>> ReadBodyAsync(HttpContext httpContext, long maxBodySize, Cancellatio
 88    {
 589        using var stream = new MemoryStream();
 590        var buffer = ArrayPool<byte>.Shared.Rent(81920);
 591        var totalBytes = 0L;
 92
 93        try
 94        {
 95            int read;
 796            while ((read = await httpContext.Request.Body.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellationToken
 97            {
 398                totalBytes += read;
 399                if (totalBytes > maxBodySize)
 1100                    throw new RequestBodyTooLargeException();
 101
 2102                stream.Write(buffer, 0, read);
 103            }
 4104        }
 105        finally
 106        {
 5107            ArrayPool<byte>.Shared.Return(buffer);
 108        }
 109
 4110        return stream.ToArray();
 4111    }
 112
 113    private sealed class RequestBodyTooLargeException : Exception;
 114}