< Summary

Information
Class: Elsa.Diagnostics.OpenTelemetry.Ingestion.HttpProtobuf.OtlpHttpProtobufParser
Assembly: Elsa.Diagnostics.OpenTelemetry
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Diagnostics.OpenTelemetry/Ingestion/HttpProtobuf/OtlpHttpProtobufParser.cs
Line coverage
76%
Covered lines: 321
Uncovered lines: 98
Coverable lines: 419
Total lines: 701
Line coverage: 76.6%
Branch coverage
66%
Covered branches: 213
Total branches: 322
Branch coverage: 66.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
ParseTraces(...)100%66100%
ParseMetrics(...)100%88100%
ParseLogs(...)100%66100%
ParseResourceSpans(...)100%88100%
ParseScopeSpans(...)100%66100%
ParseSpan(...)86.11%403685.41%
ParseSpanEvent(...)0%156120%
ParseSpanLink(...)0%210140%
ParseSpanStatus(...)66.66%131280%
ParseResourceMetrics(...)100%88100%
ParseScopeMetrics(...)100%88100%
ParseMetric(...)73.33%433075.75%
ParseNumberDataPoints(...)100%66100%
ParseNumberDataPoint(...)92.85%141493.33%
ParseHistogramDataPoints(...)0%4260%
ParseHistogramDataPoint(...)0%210140%
ParseResourceLogs(...)100%88100%
ParseScopeLogs(...)100%66100%
ParseLogRecord(...)89.65%292996.87%
ParseResourceAttributes(...)100%66100%
AddAttribute(...)100%1212100%
ParseAnyValue(...)16.66%591850%
CreateResource(...)100%44100%
CreateBatch(...)100%11100%
CreateTrace(...)50%66100%
GetAttribute(...)100%22100%
FromUnixNanos(...)100%1160%
ToHex(...)100%11100%
SpanKindName(...)25%19844.44%
SeverityName(...)0%156120%
.ctor(...)100%11100%
get_Number()100%11100%
get_WireType()100%11100%
get_Varint()100%11100%
get_Bytes()100%11100%
get_DoubleValue()100%11100%
StringValue()100%11100%
.ctor(...)100%11100%
TryReadField(...)55.55%10979.16%
ReadVarint()66.66%6683.33%
EnsureAvailable(...)100%22100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Diagnostics.OpenTelemetry/Ingestion/HttpProtobuf/OtlpHttpProtobufParser.cs

#LineLine coverage
 1using System.Buffers.Binary;
 2using System.Globalization;
 3using System.Text;
 4using Elsa.Diagnostics.OpenTelemetry.Models;
 5
 6namespace Elsa.Diagnostics.OpenTelemetry.Ingestion.HttpProtobuf;
 7
 8internal static class OtlpHttpProtobufParser
 9{
 10    public static OpenTelemetryBatch ParseTraces(ReadOnlySpan<byte> payload)
 11    {
 512        var resources = new List<TelemetryResource>();
 513        var spans = new List<TelemetrySpan>();
 514        var reader = new ProtobufReader(payload);
 15
 716        while (reader.TryReadField(out var field))
 17        {
 218            if (field.Number != 1 || field.WireType != ProtobufWireType.LengthDelimited)
 19                continue;
 20
 221            var resourceSpans = ParseResourceSpans(field.Bytes);
 222            resources.Add(resourceSpans.Resource);
 223            spans.AddRange(resourceSpans.Spans);
 224        }
 25
 426        return CreateBatch(resources, spans, [], [], []);
 27    }
 28
 29    public static OpenTelemetryBatch ParseMetrics(ReadOnlySpan<byte> payload)
 30    {
 131        var resources = new List<TelemetryResource>();
 132        var instruments = new Dictionary<string, MetricInstrument>(StringComparer.OrdinalIgnoreCase);
 133        var points = new List<MetricPoint>();
 134        var reader = new ProtobufReader(payload);
 35
 236        while (reader.TryReadField(out var field))
 37        {
 138            if (field.Number != 1 || field.WireType != ProtobufWireType.LengthDelimited)
 39                continue;
 40
 141            var resourceMetrics = ParseResourceMetrics(field.Bytes);
 142            resources.Add(resourceMetrics.Resource);
 43
 444            foreach (var instrument in resourceMetrics.Instruments)
 145                instruments[instrument.Id] = instrument;
 46
 147            points.AddRange(resourceMetrics.Points);
 148        }
 49
 150        return CreateBatch(resources, [], instruments.Values.ToList(), points, []);
 51    }
 52
 53    public static OpenTelemetryBatch ParseLogs(ReadOnlySpan<byte> payload)
 54    {
 155        var resources = new List<TelemetryResource>();
 156        var logs = new List<OtlpLogRecord>();
 157        var reader = new ProtobufReader(payload);
 58
 259        while (reader.TryReadField(out var field))
 60        {
 161            if (field.Number != 1 || field.WireType != ProtobufWireType.LengthDelimited)
 62                continue;
 63
 164            var resourceLogs = ParseResourceLogs(field.Bytes);
 165            resources.Add(resourceLogs.Resource);
 166            logs.AddRange(resourceLogs.Logs);
 167        }
 68
 169        return CreateBatch(resources, [], [], [], logs);
 70    }
 71
 72    private static (TelemetryResource Resource, List<TelemetrySpan> Spans) ParseResourceSpans(ReadOnlySpan<byte> payload
 73    {
 274        var resource = CreateResource(new Dictionary<string, string?>());
 275        var spans = new List<TelemetrySpan>();
 276        var reader = new ProtobufReader(payload);
 77
 678        while (reader.TryReadField(out var field))
 79        {
 480            if (field.WireType != ProtobufWireType.LengthDelimited)
 81                continue;
 82
 483            if (field.Number == 1)
 284                resource = CreateResource(ParseResourceAttributes(field.Bytes));
 285            else if (field.Number == 2)
 286                spans.AddRange(ParseScopeSpans(field.Bytes, resource.Id));
 287        }
 88
 289        return (resource, spans);
 90    }
 91
 92    private static List<TelemetrySpan> ParseScopeSpans(ReadOnlySpan<byte> payload, string resourceId)
 93    {
 294        var spans = new List<TelemetrySpan>();
 295        var reader = new ProtobufReader(payload);
 96
 597        while (reader.TryReadField(out var field))
 98        {
 399            if (field.Number == 2 && field.WireType == ProtobufWireType.LengthDelimited)
 3100                spans.Add(ParseSpan(field.Bytes, resourceId));
 3101        }
 102
 2103        return spans;
 104    }
 105
 106    private static TelemetrySpan ParseSpan(ReadOnlySpan<byte> payload, string resourceId)
 107    {
 3108        var traceId = "";
 3109        var spanId = "";
 3110        string? parentSpanId = null;
 3111        var name = "";
 3112        var kind = "unspecified";
 3113        var start = DateTimeOffset.UnixEpoch;
 3114        var end = DateTimeOffset.UnixEpoch;
 3115        var status = SpanStatus.Unset;
 3116        string? statusDescription = null;
 3117        var attributes = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
 3118        var events = new List<TelemetrySpanEvent>();
 3119        var links = new List<TelemetrySpanLink>();
 3120        var reader = new ProtobufReader(payload);
 121
 31122        while (reader.TryReadField(out var field))
 123        {
 28124            switch (field.Number)
 125            {
 3126                case 1 when field.WireType == ProtobufWireType.LengthDelimited:
 3127                    traceId = ToHex(field.Bytes);
 3128                    break;
 3129                case 2 when field.WireType == ProtobufWireType.LengthDelimited:
 3130                    spanId = ToHex(field.Bytes);
 3131                    break;
 1132                case 4 when field.WireType == ProtobufWireType.LengthDelimited:
 1133                    parentSpanId = ToHex(field.Bytes);
 1134                    break;
 3135                case 5 when field.WireType == ProtobufWireType.LengthDelimited:
 3136                    name = field.StringValue();
 3137                    break;
 138                case 6:
 3139                    kind = SpanKindName(field.Varint);
 3140                    break;
 141                case 7:
 3142                    start = FromUnixNanos(field.Varint);
 3143                    break;
 144                case 8:
 3145                    end = FromUnixNanos(field.Varint);
 3146                    break;
 6147                case 9 when field.WireType == ProtobufWireType.LengthDelimited:
 6148                    AddAttribute(attributes, field.Bytes);
 6149                    break;
 0150                case 11 when field.WireType == ProtobufWireType.LengthDelimited:
 0151                    events.Add(ParseSpanEvent(field.Bytes));
 0152                    break;
 0153                case 12 when field.WireType == ProtobufWireType.LengthDelimited:
 0154                    links.Add(ParseSpanLink(field.Bytes));
 0155                    break;
 3156                case 15 when field.WireType == ProtobufWireType.LengthDelimited:
 3157                    (status, statusDescription) = ParseSpanStatus(field.Bytes);
 3158                    break;
 159            }
 160        }
 161
 3162        if (end < start)
 0163            end = start;
 164
 3165        return new TelemetrySpan($"{traceId}:{spanId}", traceId, spanId, parentSpanId, resourceId, name, kind, start, en
 166    }
 167
 168    private static TelemetrySpanEvent ParseSpanEvent(ReadOnlySpan<byte> payload)
 169    {
 0170        var timestamp = DateTimeOffset.UnixEpoch;
 0171        var name = "";
 0172        var attributes = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
 0173        var reader = new ProtobufReader(payload);
 174
 0175        while (reader.TryReadField(out var field))
 176        {
 0177            if (field.Number == 1)
 0178                timestamp = FromUnixNanos(field.Varint);
 0179            else if (field.Number == 2 && field.WireType == ProtobufWireType.LengthDelimited)
 0180                name = field.StringValue();
 0181            else if (field.Number == 3 && field.WireType == ProtobufWireType.LengthDelimited)
 0182                AddAttribute(attributes, field.Bytes);
 0183        }
 184
 0185        return new TelemetrySpanEvent(name, timestamp, attributes);
 186    }
 187
 188    private static TelemetrySpanLink ParseSpanLink(ReadOnlySpan<byte> payload)
 189    {
 0190        var traceId = "";
 0191        var spanId = "";
 0192        var attributes = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
 0193        var reader = new ProtobufReader(payload);
 194
 0195        while (reader.TryReadField(out var field))
 196        {
 0197            if (field.Number == 1 && field.WireType == ProtobufWireType.LengthDelimited)
 0198                traceId = ToHex(field.Bytes);
 0199            else if (field.Number == 2 && field.WireType == ProtobufWireType.LengthDelimited)
 0200                spanId = ToHex(field.Bytes);
 0201            else if (field.Number == 3 && field.WireType == ProtobufWireType.LengthDelimited)
 0202                AddAttribute(attributes, field.Bytes);
 0203        }
 204
 0205        return new TelemetrySpanLink(traceId, spanId, attributes);
 206    }
 207
 208    private static (SpanStatus Status, string? Description) ParseSpanStatus(ReadOnlySpan<byte> payload)
 209    {
 3210        var status = SpanStatus.Unset;
 3211        string? description = null;
 3212        var reader = new ProtobufReader(payload);
 213
 6214        while (reader.TryReadField(out var field))
 215        {
 3216            if (field.Number == 2 && field.WireType == ProtobufWireType.LengthDelimited)
 0217                description = field.StringValue();
 3218            else if (field.Number == 3)
 3219                status = field.Varint switch
 3220                {
 2221                    1 => SpanStatus.Ok,
 1222                    2 => SpanStatus.Error,
 0223                    _ => SpanStatus.Unset
 3224                };
 3225        }
 226
 3227        return (status, description);
 228    }
 229
 230    private static (TelemetryResource Resource, List<MetricInstrument> Instruments, List<MetricPoint> Points) ParseResou
 231    {
 1232        var resource = CreateResource(new Dictionary<string, string?>());
 1233        var instruments = new List<MetricInstrument>();
 1234        var points = new List<MetricPoint>();
 1235        var reader = new ProtobufReader(payload);
 236
 3237        while (reader.TryReadField(out var field))
 238        {
 2239            if (field.WireType != ProtobufWireType.LengthDelimited)
 240                continue;
 241
 2242            if (field.Number == 1)
 1243                resource = CreateResource(ParseResourceAttributes(field.Bytes));
 1244            else if (field.Number == 2)
 1245                ParseScopeMetrics(field.Bytes, resource.Id, instruments, points);
 1246        }
 247
 1248        return (resource, instruments, points);
 249    }
 250
 251    private static void ParseScopeMetrics(ReadOnlySpan<byte> payload, string resourceId, ICollection<MetricInstrument> i
 252    {
 1253        var reader = new ProtobufReader(payload);
 254
 2255        while (reader.TryReadField(out var field))
 256        {
 1257            if (field.Number != 2 || field.WireType != ProtobufWireType.LengthDelimited)
 258                continue;
 259
 1260            var metric = ParseMetric(field.Bytes, resourceId);
 1261            instruments.Add(metric.Instrument);
 262
 4263            foreach (var point in metric.Points)
 1264                points.Add(point);
 265        }
 1266    }
 267
 268    private static (MetricInstrument Instrument, List<MetricPoint> Points) ParseMetric(ReadOnlySpan<byte> payload, strin
 269    {
 1270        var name = "";
 1271        string? description = null;
 1272        string? unit = null;
 1273        var kind = MetricKind.Gauge;
 1274        var points = new List<MetricPoint>();
 1275        var reader = new ProtobufReader(payload);
 276
 5277        while (reader.TryReadField(out var field))
 278        {
 4279            switch (field.Number)
 280            {
 1281                case 1 when field.WireType == ProtobufWireType.LengthDelimited:
 1282                    name = field.StringValue();
 1283                    break;
 1284                case 2 when field.WireType == ProtobufWireType.LengthDelimited:
 1285                    description = field.StringValue();
 1286                    break;
 1287                case 3 when field.WireType == ProtobufWireType.LengthDelimited:
 1288                    unit = field.StringValue();
 1289                    break;
 1290                case 5 when field.WireType == ProtobufWireType.LengthDelimited:
 1291                    kind = MetricKind.Gauge;
 2292                    points.AddRange(ParseNumberDataPoints(field.Bytes, resourceId, () => $"{resourceId}:{name}:gauge"));
 1293                    break;
 0294                case 7 when field.WireType == ProtobufWireType.LengthDelimited:
 0295                    kind = MetricKind.Sum;
 0296                    points.AddRange(ParseNumberDataPoints(field.Bytes, resourceId, () => $"{resourceId}:{name}:sum"));
 0297                    break;
 0298                case 9 when field.WireType == ProtobufWireType.LengthDelimited:
 0299                    kind = MetricKind.Histogram;
 0300                    points.AddRange(ParseHistogramDataPoints(field.Bytes, resourceId, () => $"{resourceId}:{name}:histog
 0301                    break;
 302            }
 303        }
 304
 1305        var instrumentId = $"{resourceId}:{name}:{kind.ToString().ToLowerInvariant()}";
 1306        var instrument = new MetricInstrument(instrumentId, resourceId, name, unit, description, kind, new Dictionary<st
 2307        points = points.Select(x => x with { InstrumentId = instrumentId, InstrumentName = name }).ToList();
 1308        return (instrument, points);
 309    }
 310
 311    private static List<MetricPoint> ParseNumberDataPoints(ReadOnlySpan<byte> payload, string resourceId, Func<string> i
 312    {
 1313        var points = new List<MetricPoint>();
 1314        var reader = new ProtobufReader(payload);
 315
 2316        while (reader.TryReadField(out var field))
 317        {
 1318            if (field.Number == 1 && field.WireType == ProtobufWireType.LengthDelimited)
 1319                points.Add(ParseNumberDataPoint(field.Bytes, resourceId, instrumentIdFactory()));
 1320        }
 321
 1322        return points;
 323    }
 324
 325    private static MetricPoint ParseNumberDataPoint(ReadOnlySpan<byte> payload, string resourceId, string instrumentId)
 326    {
 1327        var timestamp = DateTimeOffset.UnixEpoch;
 1328        double? value = null;
 1329        var attributes = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
 1330        var reader = new ProtobufReader(payload);
 331
 4332        while (reader.TryReadField(out var field))
 333        {
 3334            if (field.Number == 3)
 1335                timestamp = FromUnixNanos(field.Varint);
 2336            else if (field.Number == 4 && field.WireType == ProtobufWireType.Fixed64)
 1337                value = field.DoubleValue;
 1338            else if (field.Number == 6)
 0339                value = (long)field.Varint;
 1340            else if (field.Number == 7 && field.WireType == ProtobufWireType.LengthDelimited)
 1341                AddAttribute(attributes, field.Bytes);
 1342        }
 343
 1344        return new MetricPoint(Guid.NewGuid().ToString("N"), instrumentId, "", resourceId, timestamp, value, null, null,
 345    }
 346
 347    private static List<MetricPoint> ParseHistogramDataPoints(ReadOnlySpan<byte> payload, string resourceId, Func<string
 348    {
 0349        var points = new List<MetricPoint>();
 0350        var reader = new ProtobufReader(payload);
 351
 0352        while (reader.TryReadField(out var field))
 353        {
 0354            if (field.Number == 1 && field.WireType == ProtobufWireType.LengthDelimited)
 0355                points.Add(ParseHistogramDataPoint(field.Bytes, resourceId, instrumentIdFactory()));
 0356        }
 357
 0358        return points;
 359    }
 360
 361    private static MetricPoint ParseHistogramDataPoint(ReadOnlySpan<byte> payload, string resourceId, string instrumentI
 362    {
 0363        var timestamp = DateTimeOffset.UnixEpoch;
 0364        long? count = null;
 0365        double? sum = null;
 0366        var attributes = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
 0367        var reader = new ProtobufReader(payload);
 368
 0369        while (reader.TryReadField(out var field))
 370        {
 0371            if (field.Number == 3)
 0372                timestamp = FromUnixNanos(field.Varint);
 0373            else if (field.Number == 4)
 0374                count = (long)field.Varint;
 0375            else if (field.Number == 5 && field.WireType == ProtobufWireType.Fixed64)
 0376                sum = field.DoubleValue;
 0377            else if (field.Number == 9 && field.WireType == ProtobufWireType.LengthDelimited)
 0378                AddAttribute(attributes, field.Bytes);
 0379        }
 380
 0381        return new MetricPoint(Guid.NewGuid().ToString("N"), instrumentId, "", resourceId, timestamp, null, sum, count, 
 382    }
 383
 384    private static (TelemetryResource Resource, List<OtlpLogRecord> Logs) ParseResourceLogs(ReadOnlySpan<byte> payload)
 385    {
 1386        var resource = CreateResource(new Dictionary<string, string?>());
 1387        var logs = new List<OtlpLogRecord>();
 1388        var reader = new ProtobufReader(payload);
 389
 3390        while (reader.TryReadField(out var field))
 391        {
 2392            if (field.WireType != ProtobufWireType.LengthDelimited)
 393                continue;
 394
 2395            if (field.Number == 1)
 1396                resource = CreateResource(ParseResourceAttributes(field.Bytes));
 1397            else if (field.Number == 2)
 1398                logs.AddRange(ParseScopeLogs(field.Bytes, resource.Id));
 1399        }
 400
 1401        return (resource, logs);
 402    }
 403
 404    private static List<OtlpLogRecord> ParseScopeLogs(ReadOnlySpan<byte> payload, string resourceId)
 405    {
 1406        var logs = new List<OtlpLogRecord>();
 1407        var reader = new ProtobufReader(payload);
 408
 2409        while (reader.TryReadField(out var field))
 410        {
 1411            if (field.Number == 2 && field.WireType == ProtobufWireType.LengthDelimited)
 1412                logs.Add(ParseLogRecord(field.Bytes, resourceId));
 1413        }
 414
 1415        return logs;
 416    }
 417
 418    private static OtlpLogRecord ParseLogRecord(ReadOnlySpan<byte> payload, string resourceId)
 419    {
 1420        var timestamp = DateTimeOffset.UnixEpoch;
 1421        var severityText = "";
 1422        int? severityNumber = null;
 1423        var body = "";
 1424        string? traceId = null;
 1425        string? spanId = null;
 1426        var attributes = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
 1427        var reader = new ProtobufReader(payload);
 428
 8429        while (reader.TryReadField(out var field))
 430        {
 7431            switch (field.Number)
 432            {
 433                case 1:
 1434                    timestamp = FromUnixNanos(field.Varint);
 1435                    break;
 436                case 2:
 1437                    severityNumber = (int)field.Varint;
 1438                    break;
 1439                case 3 when field.WireType == ProtobufWireType.LengthDelimited:
 1440                    severityText = field.StringValue();
 1441                    break;
 1442                case 5 when field.WireType == ProtobufWireType.LengthDelimited:
 1443                    body = ParseAnyValue(field.Bytes) ?? "";
 1444                    break;
 1445                case 6 when field.WireType == ProtobufWireType.LengthDelimited:
 1446                    AddAttribute(attributes, field.Bytes);
 1447                    break;
 1448                case 9 when field.WireType == ProtobufWireType.LengthDelimited:
 1449                    traceId = ToHex(field.Bytes);
 1450                    break;
 1451                case 10 when field.WireType == ProtobufWireType.LengthDelimited:
 1452                    spanId = ToHex(field.Bytes);
 1453                    break;
 454            }
 455        }
 456
 1457        if (string.IsNullOrWhiteSpace(severityText) && severityNumber != null)
 0458            severityText = SeverityName(severityNumber.Value);
 459
 1460        return new OtlpLogRecord(Guid.NewGuid().ToString("N"), resourceId, timestamp, severityText, severityNumber, body
 461    }
 462
 463    private static Dictionary<string, string?> ParseResourceAttributes(ReadOnlySpan<byte> payload)
 464    {
 4465        var attributes = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
 4466        var reader = new ProtobufReader(payload);
 467
 16468        while (reader.TryReadField(out var field))
 469        {
 12470            if (field.Number == 1 && field.WireType == ProtobufWireType.LengthDelimited)
 12471                AddAttribute(attributes, field.Bytes);
 12472        }
 473
 4474        return attributes;
 475    }
 476
 477    private static void AddAttribute(IDictionary<string, string?> attributes, ReadOnlySpan<byte> payload)
 478    {
 20479        string? key = null;
 20480        string? value = null;
 20481        var reader = new ProtobufReader(payload);
 482
 60483        while (reader.TryReadField(out var field))
 484        {
 40485            if (field.Number == 1 && field.WireType == ProtobufWireType.LengthDelimited)
 20486                key = field.StringValue();
 20487            else if (field.Number == 2 && field.WireType == ProtobufWireType.LengthDelimited)
 20488                value = ParseAnyValue(field.Bytes);
 20489        }
 490
 20491        if (!string.IsNullOrWhiteSpace(key))
 20492            attributes[key] = value;
 20493    }
 494
 495    private static string? ParseAnyValue(ReadOnlySpan<byte> payload)
 496    {
 21497        var reader = new ProtobufReader(payload);
 498
 21499        while (reader.TryReadField(out var field))
 500        {
 21501            return field.Number switch
 21502            {
 42503                1 when field.WireType == ProtobufWireType.LengthDelimited => field.StringValue(),
 0504                2 => field.Varint != 0 ? "true" : "false",
 0505                3 => ((long)field.Varint).ToString(CultureInfo.InvariantCulture),
 0506                4 when field.WireType == ProtobufWireType.Fixed64 => field.DoubleValue.ToString(CultureInfo.InvariantCul
 0507                7 when field.WireType == ProtobufWireType.LengthDelimited => ToHex(field.Bytes),
 0508                _ => null
 21509            };
 510        }
 511
 0512        return null;
 513    }
 514
 515    private static TelemetryResource CreateResource(IDictionary<string, string?> attributes)
 516    {
 8517        var serviceName = GetAttribute(attributes, "service.name") ?? "unknown_service";
 8518        var instanceId = GetAttribute(attributes, "service.instance.id");
 8519        var resourceId = string.IsNullOrWhiteSpace(instanceId) ? serviceName : $"{serviceName}:{instanceId}";
 8520        return new TelemetryResource(resourceId, serviceName, instanceId, GetAttribute(attributes, "telemetry.sdk.langua
 521    }
 522
 523    private static OpenTelemetryBatch CreateBatch(
 524        IReadOnlyCollection<TelemetryResource> resources,
 525        IReadOnlyCollection<TelemetrySpan> spans,
 526        IReadOnlyCollection<MetricInstrument> instruments,
 527        IReadOnlyCollection<MetricPoint> points,
 528        IReadOnlyCollection<OtlpLogRecord> logs)
 529    {
 6530        var traces = spans
 3531            .GroupBy(x => x.TraceId, StringComparer.OrdinalIgnoreCase)
 6532            .Select(CreateTrace)
 6533            .ToList();
 534
 10535        return new OpenTelemetryBatch(resources.DistinctBy(x => x.Id).ToList(), traces, spans.ToList(), instruments.ToLi
 536    }
 537
 538    private static TelemetryTrace CreateTrace(IGrouping<string, TelemetrySpan> spans)
 539    {
 4540        var orderedSpans = spans.OrderBy(x => x.StartTime).ToList();
 4541        var root = orderedSpans.FirstOrDefault(x => string.IsNullOrWhiteSpace(x.ParentSpanId)) ?? orderedSpans[0];
 5542        var start = orderedSpans.Min(x => x.StartTime);
 5543        var end = orderedSpans.Max(x => x.EndTime);
 6544        var status = orderedSpans.Any(x => x.Status == SpanStatus.Error) ? SpanStatus.Error : orderedSpans.Any(x => x.St
 2545        var workflowInstanceIds = orderedSpans
 3546            .Select(x => GetAttribute(x.Attributes, "workflow.instance.id"))
 3547            .Where(x => !string.IsNullOrWhiteSpace(x))
 3548            .Select(x => x!)
 2549            .Distinct(StringComparer.OrdinalIgnoreCase)
 2550            .ToList();
 551
 5552        return new TelemetryTrace(spans.Key, root.SpanId, root.Name, start, end, end - start, status, orderedSpans.Selec
 553    }
 554
 27555    private static string? GetAttribute(IDictionary<string, string?> attributes, string key) => attributes.TryGetValue(k
 556
 557    private static DateTimeOffset FromUnixNanos(ulong value)
 558    {
 559        try
 560        {
 8561            var ticks = checked((long)(value / 100));
 8562            return DateTimeOffset.UnixEpoch.AddTicks(ticks);
 563        }
 0564        catch (Exception e) when (e is OverflowException or ArgumentOutOfRangeException)
 565        {
 0566            throw new InvalidDataException("The OTLP timestamp is outside the supported range.", e);
 567        }
 8568    }
 569
 570    private static string ToHex(ReadOnlySpan<byte> bytes)
 571    {
 9572        return Convert.ToHexString(bytes).ToLowerInvariant();
 573    }
 574
 3575    private static string SpanKindName(ulong value) => value switch
 3576    {
 3577        1 => "internal",
 0578        2 => "server",
 0579        3 => "client",
 0580        4 => "producer",
 0581        5 => "consumer",
 0582        _ => "unspecified"
 3583    };
 584
 0585    private static string SeverityName(int value) => value switch
 0586    {
 0587        >= 21 => "Fatal",
 0588        >= 17 => "Error",
 0589        >= 13 => "Warning",
 0590        >= 9 => "Information",
 0591        >= 5 => "Debug",
 0592        >= 1 => "Trace",
 0593        _ => "Unspecified"
 0594    };
 595
 596    private enum ProtobufWireType
 597    {
 598        Varint = 0,
 599        Fixed64 = 1,
 600        LengthDelimited = 2,
 601        Fixed32 = 5
 602    }
 603
 604    private readonly ref struct ProtobufField
 605    {
 606        public ProtobufField(int number, ProtobufWireType wireType, ulong varint, ReadOnlySpan<byte> bytes, double doubl
 607        {
 136608            Number = number;
 136609            WireType = wireType;
 136610            Varint = varint;
 136611            Bytes = bytes;
 136612            DoubleValue = doubleValue;
 136613        }
 614
 167615        public int Number { get; }
 121616        public ProtobufWireType WireType { get; }
 15617        public ulong Varint { get; }
 120618        public ReadOnlySpan<byte> Bytes { get; }
 1619        public double DoubleValue { get; }
 620
 48621        public string StringValue() => Encoding.UTF8.GetString(Bytes);
 622    }
 623
 624    private ref struct ProtobufReader
 625    {
 626        private ReadOnlySpan<byte> _remaining;
 627
 628        public ProtobufReader(ReadOnlySpan<byte> payload)
 629        {
 70630            _remaining = payload;
 70631        }
 632
 633        public bool TryReadField(out ProtobufField field)
 634        {
 185635            field = default;
 636
 185637            if (_remaining.IsEmpty)
 48638                return false;
 639
 137640            var tag = ReadVarint();
 137641            var number = (int)(tag >> 3);
 137642            var wireType = (ProtobufWireType)(tag & 7);
 643
 644            switch (wireType)
 645            {
 646                case ProtobufWireType.Varint:
 15647                    field = new ProtobufField(number, wireType, ReadVarint(), default, default);
 15648                    return true;
 649                case ProtobufWireType.Fixed64:
 1650                    EnsureAvailable(8);
 1651                    var fixed64 = BinaryPrimitives.ReadUInt64LittleEndian(_remaining[..8]);
 1652                    _remaining = _remaining[8..];
 1653                    field = new ProtobufField(number, wireType, fixed64, default, BitConverter.Int64BitsToDouble((long)f
 1654                    return true;
 655                case ProtobufWireType.LengthDelimited:
 121656                    var length = checked((int)ReadVarint());
 121657                    EnsureAvailable(length);
 120658                    var bytes = _remaining[..length];
 120659                    _remaining = _remaining[length..];
 120660                    field = new ProtobufField(number, wireType, default, bytes, default);
 120661                    return true;
 662                case ProtobufWireType.Fixed32:
 0663                    EnsureAvailable(4);
 0664                    _remaining = _remaining[4..];
 0665                    field = new ProtobufField(number, wireType, default, default, default);
 0666                    return true;
 667                default:
 0668                    throw new InvalidDataException($"Unsupported protobuf wire type '{wireType}'.");
 669            }
 670        }
 671
 672        private ulong ReadVarint()
 673        {
 273674            ulong value = 0;
 273675            var shift = 0;
 676
 688677            for (var i = 0; i < 10; i++)
 678            {
 344679                if (_remaining.IsEmpty)
 0680                    throw new InvalidDataException("Unexpected end of protobuf payload.");
 681
 344682                var b = _remaining[0];
 344683                _remaining = _remaining[1..];
 344684                value |= (ulong)(b & 0x7f) << shift;
 685
 344686                if ((b & 0x80) == 0)
 273687                    return value;
 688
 71689                shift += 7;
 690            }
 691
 0692            throw new InvalidDataException("Invalid protobuf varint.");
 693        }
 694
 695        private readonly void EnsureAvailable(int byteCount)
 696        {
 122697            if (_remaining.Length < byteCount)
 1698                throw new InvalidDataException("Unexpected end of protobuf payload.");
 121699        }
 700    }
 701}

Methods/Properties

ParseTraces(System.ReadOnlySpan`1<System.Byte>)
ParseMetrics(System.ReadOnlySpan`1<System.Byte>)
ParseLogs(System.ReadOnlySpan`1<System.Byte>)
ParseResourceSpans(System.ReadOnlySpan`1<System.Byte>)
ParseScopeSpans(System.ReadOnlySpan`1<System.Byte>,System.String)
ParseSpan(System.ReadOnlySpan`1<System.Byte>,System.String)
ParseSpanEvent(System.ReadOnlySpan`1<System.Byte>)
ParseSpanLink(System.ReadOnlySpan`1<System.Byte>)
ParseSpanStatus(System.ReadOnlySpan`1<System.Byte>)
ParseResourceMetrics(System.ReadOnlySpan`1<System.Byte>)
ParseScopeMetrics(System.ReadOnlySpan`1<System.Byte>,System.String,System.Collections.Generic.ICollection`1<Elsa.Diagnostics.OpenTelemetry.Models.MetricInstrument>,System.Collections.Generic.ICollection`1<Elsa.Diagnostics.OpenTelemetry.Models.MetricPoint>)
ParseMetric(System.ReadOnlySpan`1<System.Byte>,System.String)
ParseNumberDataPoints(System.ReadOnlySpan`1<System.Byte>,System.String,System.Func`1<System.String>)
ParseNumberDataPoint(System.ReadOnlySpan`1<System.Byte>,System.String,System.String)
ParseHistogramDataPoints(System.ReadOnlySpan`1<System.Byte>,System.String,System.Func`1<System.String>)
ParseHistogramDataPoint(System.ReadOnlySpan`1<System.Byte>,System.String,System.String)
ParseResourceLogs(System.ReadOnlySpan`1<System.Byte>)
ParseScopeLogs(System.ReadOnlySpan`1<System.Byte>,System.String)
ParseLogRecord(System.ReadOnlySpan`1<System.Byte>,System.String)
ParseResourceAttributes(System.ReadOnlySpan`1<System.Byte>)
AddAttribute(System.Collections.Generic.IDictionary`2<System.String,System.String>,System.ReadOnlySpan`1<System.Byte>)
ParseAnyValue(System.ReadOnlySpan`1<System.Byte>)
CreateResource(System.Collections.Generic.IDictionary`2<System.String,System.String>)
CreateBatch(System.Collections.Generic.IReadOnlyCollection`1<Elsa.Diagnostics.OpenTelemetry.Models.TelemetryResource>,System.Collections.Generic.IReadOnlyCollection`1<Elsa.Diagnostics.OpenTelemetry.Models.TelemetrySpan>,System.Collections.Generic.IReadOnlyCollection`1<Elsa.Diagnostics.OpenTelemetry.Models.MetricInstrument>,System.Collections.Generic.IReadOnlyCollection`1<Elsa.Diagnostics.OpenTelemetry.Models.MetricPoint>,System.Collections.Generic.IReadOnlyCollection`1<Elsa.Diagnostics.OpenTelemetry.Models.OtlpLogRecord>)
CreateTrace(System.Linq.IGrouping`2<System.String,Elsa.Diagnostics.OpenTelemetry.Models.TelemetrySpan>)
GetAttribute(System.Collections.Generic.IDictionary`2<System.String,System.String>,System.String)
FromUnixNanos(System.UInt64)
ToHex(System.ReadOnlySpan`1<System.Byte>)
SpanKindName(System.UInt64)
SeverityName(System.Int32)
.ctor(System.Int32,Elsa.Diagnostics.OpenTelemetry.Ingestion.HttpProtobuf.OtlpHttpProtobufParser/ProtobufWireType,System.UInt64,System.ReadOnlySpan`1<System.Byte>,System.Double)
get_Number()
get_WireType()
get_Varint()
get_Bytes()
get_DoubleValue()
StringValue()
.ctor(System.ReadOnlySpan`1<System.Byte>)
TryReadField(Elsa.Diagnostics.OpenTelemetry.Ingestion.HttpProtobuf.OtlpHttpProtobufParser/ProtobufField&)
ReadVarint()
EnsureAvailable(System.Int32)