LCOV - code coverage report
Current view: top level - lib/src/trace/export/otlp - span_transformer.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 99.4 % 157 156
Test Date: 2026-10-11 16:35:14 Functions: - 0 0

            Line data    Source code
       1              : // Copyright The OpenTelemetry Authors
       2              : // SPDX-License-Identifier: Apache-2.0
       3              : 
       4              : import 'package:dartastic_opentelemetry_api/dartastic_opentelemetry_api.dart';
       5              : import 'package:fixnum/fixnum.dart';
       6              : 
       7              : import '../../../../proto/opentelemetry_proto_dart.dart' as proto;
       8              : import '../../../otel.dart';
       9              : import '../../span.dart';
      10              : 
      11              : /// Transforms internal span representation to OTLP format
      12              : class OtlpSpanTransformer {
      13              :   /// Convert a list of spans to OTLP ExportTraceServiceRequest
      14           43 :   static proto.ExportTraceServiceRequest transformSpans(List<Span> spans) {
      15           43 :     final exportTraceServiceRequest = proto.ExportTraceServiceRequest();
      16           43 :     if (spans.isEmpty) return exportTraceServiceRequest;
      17              : 
      18              :     // Group spans by their resource first
      19           43 :     final resourceGroups = <String, List<Span>>{};
      20              : 
      21           86 :     for (final span in spans) {
      22           43 :       final resource = span.resource;
      23              :       final key = resource != null
      24           86 :           ? _getResourceServiceName(resource.attributes)
      25              :           : 'default-service';
      26          172 :       resourceGroups.putIfAbsent(key, () => []).add(span);
      27              :     }
      28              : 
      29              :     // Process each resource group
      30           86 :     for (final resourceEntry in resourceGroups.entries) {
      31           43 :       final spanList = resourceEntry.value;
      32           43 :       if (spanList.isEmpty) continue;
      33              : 
      34              :       // Extract resource attributes from the span's resource
      35           86 :       final resource = spanList.first.resource;
      36           43 :       final resourceAttrs = resource?.attributes ?? OTel.createAttributes();
      37              : 
      38           43 :       if (OTelLog.isDebug()) {
      39           42 :         OTelLog.debug('Extracting resource attributes for export:');
      40          126 :         resourceAttrs.toList().forEach((attr) {
      41          126 :           if (attr.key == Service.serviceName.key) {
      42          168 :             OTelLog.debug('  ${attr.key}: ${attr.value}');
      43              :           }
      44              :         });
      45              :       }
      46              : 
      47           43 :       if (OTelLog.isDebug()) {
      48           42 :         OTelLog.debug('Extracting resource attributes for export:');
      49          126 :         resourceAttrs.toList().forEach((attr) {
      50          126 :           if (attr.key == Service.serviceName.key) {
      51          168 :             OTelLog.debug('  ${attr.key}: ${attr.value}');
      52              :           }
      53              :         });
      54              :       }
      55              : 
      56              :       // Create resource
      57           43 :       final protoResource = proto.Resource()
      58          129 :         ..attributes.addAll(transformAttributeMap(resourceAttrs));
      59              : 
      60              :       // Group spans by instrumentation scope
      61           43 :       final scopeGroups = <String, List<Span>>{};
      62           86 :       for (final span in spanList) {
      63           43 :         final scopeKey = _instrumentationKey(span);
      64          172 :         scopeGroups.putIfAbsent(scopeKey, () => []).add(span);
      65              :       }
      66              : 
      67              :       // Create ResourceSpans
      68           86 :       final resourceSpan = proto.ResourceSpans()..resource = protoResource;
      69              : 
      70              :       // Process each instrumentation scope group
      71           86 :       for (final scopeEntry in scopeGroups.entries) {
      72           43 :         final scopeSpanList = scopeEntry.value;
      73           43 :         if (scopeSpanList.isEmpty) continue;
      74              : 
      75              :         // Get instrumentation scope information from the first span
      76           86 :         final scope = scopeSpanList.first.instrumentationScope;
      77          129 :         final otlpScope = proto.InstrumentationScope()..name = scope.name;
      78           43 :         if (scope.version != null) {
      79           50 :           otlpScope.version = scope.version!;
      80              :         }
      81              : 
      82              :         // Transform all spans in this scope to OTLP format
      83           43 :         final otlpSpans = <proto.Span>[];
      84           86 :         for (final span in scopeSpanList) {
      85           86 :           otlpSpans.add(transformSpan(span));
      86              :         }
      87              : 
      88              :         // Create ScopeSpans
      89           43 :         final otlpScopeSpans = proto.ScopeSpans()
      90           43 :           ..scope = otlpScope
      91           86 :           ..spans.addAll(otlpSpans);
      92              : 
      93           86 :         resourceSpan.scopeSpans.add(otlpScopeSpans);
      94              :       }
      95              : 
      96           86 :       exportTraceServiceRequest.resourceSpans.add(resourceSpan);
      97              :     }
      98              : 
      99              :     return exportTraceServiceRequest;
     100              :   }
     101              : 
     102              :   /// Get service name from resource attributes
     103           43 :   static String _getResourceServiceName(Attributes attributes) {
     104           86 :     for (final attr in attributes.toList()) {
     105          129 :       if (attr.key == Service.serviceName.key) {
     106           86 :         return attr.value.toString();
     107              :       }
     108              :     }
     109              :     return 'default-service';
     110              :   }
     111              : 
     112              :   /// Creates a key for grouping spans by instrumentation scope
     113           43 :   static String _instrumentationKey(Span span) {
     114           43 :     final scope = span.instrumentationScope;
     115          129 :     return '${scope.name}:${scope.version ?? ''}';
     116              :   }
     117              : 
     118              :   /// Convert a single span to OTLP Span
     119           43 :   static proto.Span transformSpan(Span span) {
     120           43 :     if (OTelLog.isDebug()) {
     121          126 :       OTelLog.debug('Transforming span: ${span.name}');
     122              :     }
     123           43 :     final context = span.spanContext;
     124              : 
     125           43 :     final otlpSpan = proto.Span()
     126          129 :       ..traceId = context.traceId.bytes
     127          129 :       ..spanId = context.spanId.bytes
     128           86 :       ..name = span.name
     129          129 :       ..kind = transformSpanKind(span.kind)
     130          215 :       ..startTimeUnixNano = Int64(span.startTime.microsecondsSinceEpoch * 1000);
     131              : 
     132           43 :     if (span.endTime != null) {
     133           76 :       otlpSpan.endTimeUnixNano = Int64(
     134          114 :         span.endTime!.microsecondsSinceEpoch * 1000,
     135              :       );
     136              :     }
     137              : 
     138              :     // First check if we have a parent span
     139           43 :     final parentSpan = span.parentSpan;
     140           22 :     if (parentSpan != null && parentSpan.spanContext.isValid) {
     141           11 :       if (OTelLog.isDebug()) {
     142           33 :         OTelLog.debug('Setting parentSpanId from parentSpan for ${span.name}');
     143              :       }
     144           44 :       otlpSpan.parentSpanId = parentSpan.spanContext.spanId.bytes;
     145              :     }
     146              :     // If we don't have a parent span but have a parent span ID in our context
     147          129 :     else if (context.parentSpanId != null && context.parentSpanId!.isValid) {
     148            1 :       if (OTelLog.isDebug()) {
     149            3 :         OTelLog.debug('Setting parentSpanId from context for ${span.name}');
     150              :       }
     151            3 :       otlpSpan.parentSpanId = context.parentSpanId!.bytes;
     152              :     }
     153              : 
     154              :     // Add attributes
     155           43 :     final attrs = span.attributes;
     156          129 :     otlpSpan.attributes.addAll(transformAttributeMap(attrs));
     157              : 
     158              :     // Add events
     159           43 :     final events = span.spanEvents;
     160            8 :     if (events != null && events.isNotEmpty) {
     161           24 :       otlpSpan.events.addAll(transformEvents(events));
     162              :     }
     163              : 
     164              :     // Add links
     165           43 :     final links = span.spanLinks;
     166            6 :     if (links != null && links.isNotEmpty) {
     167           18 :       otlpSpan.links.addAll(transformLinks(links));
     168              :     }
     169              : 
     170              :     // Add status
     171           43 :     final status = span.status;
     172          129 :     otlpSpan.status = transformStatus(status, span.statusDescription);
     173              : 
     174              :     return otlpSpan;
     175              :   }
     176              : 
     177              :   /// Convert span status to OTLP Status
     178           43 :   static proto.Status transformStatus(
     179              :     SpanStatusCode status,
     180              :     String? description,
     181              :   ) {
     182           43 :     final otlpStatus = proto.Status();
     183              : 
     184              :     switch (status) {
     185           43 :       case SpanStatusCode.Ok:
     186            3 :         otlpStatus.code = proto.Status_StatusCode.STATUS_CODE_OK;
     187              :         break;
     188           40 :       case SpanStatusCode.Error:
     189            6 :         otlpStatus.code = proto.Status_StatusCode.STATUS_CODE_ERROR;
     190              :         // TODO The OTel spec requires the description for error statuses
     191              :         if (description != null) {
     192            6 :           otlpStatus.message = description;
     193              :         }
     194              :         break;
     195           40 :       case SpanStatusCode.Unset:
     196           40 :         otlpStatus.code = proto.Status_StatusCode.STATUS_CODE_UNSET;
     197              :         break;
     198              :     }
     199              : 
     200              :     return otlpStatus;
     201              :   }
     202              : 
     203              :   /// Convert span kind to OTLP SpanKind
     204           43 :   static proto.Span_SpanKind transformSpanKind(SpanKind kind) {
     205              :     switch (kind) {
     206           43 :       case SpanKind.internal:
     207              :         return proto.Span_SpanKind.SPAN_KIND_INTERNAL;
     208            3 :       case SpanKind.server:
     209              :         return proto.Span_SpanKind.SPAN_KIND_SERVER;
     210            3 :       case SpanKind.client:
     211              :         return proto.Span_SpanKind.SPAN_KIND_CLIENT;
     212            1 :       case SpanKind.producer:
     213              :         return proto.Span_SpanKind.SPAN_KIND_PRODUCER;
     214            1 :       case SpanKind.consumer:
     215              :         return proto.Span_SpanKind.SPAN_KIND_CONSUMER;
     216              :     }
     217              :   }
     218              : 
     219              :   /// Convert events to OTLP Event list
     220            8 :   static List<proto.Span_Event> transformEvents(List<SpanEvent> events) {
     221           16 :     return events.map((event) {
     222            8 :       final spanEvent = proto.Span_Event()
     223           40 :         ..timeUnixNano = Int64(event.timestamp.microsecondsSinceEpoch * 1000)
     224           16 :         ..name = event.name;
     225              : 
     226            8 :       if (event.attributes != null) {
     227           32 :         spanEvent.attributes.addAll(transformAttributeMap(event.attributes!));
     228              :       }
     229              : 
     230              :       return spanEvent;
     231            8 :     }).toList();
     232              :   }
     233              : 
     234              :   /// Convert links to OTLP Link list
     235            6 :   static List<proto.Span_Link> transformLinks(List<dynamic> links) {
     236           12 :     return links.map((link) {
     237            6 :       final spanLink = proto.Span_Link();
     238              : 
     239            6 :       if (link is SpanLink) {
     240            6 :         final spanContext = link.spanContext;
     241              :         spanLink
     242           18 :           ..traceId = spanContext.traceId.bytes
     243           18 :           ..spanId = spanContext.spanId.bytes;
     244              : 
     245           24 :         spanLink.attributes.addAll(transformAttributeMap(link.attributes));
     246              :       }
     247              : 
     248              :       return spanLink;
     249            6 :     }).toList();
     250              :   }
     251              : 
     252              :   /// Convert attribute map to OTLP KeyValue list
     253           43 :   static List<proto.KeyValue> transformAttributeMap(Attributes attributes) {
     254           43 :     final result = <proto.KeyValue>[];
     255              : 
     256          129 :     attributes.toList().forEach((attr) {
     257           43 :       final keyValue = proto.KeyValue()
     258           86 :         ..key = attr.key
     259           86 :         ..value = _transformAttributeValue(attr);
     260              : 
     261           43 :       result.add(keyValue);
     262              :     });
     263              : 
     264              :     return result;
     265              :   }
     266              : 
     267              :   /// Convert AttributeValue to OTLP AnyValue
     268           43 :   static proto.AnyValue _transformAttributeValue(Attribute attr) {
     269           43 :     final anyValue = proto.AnyValue();
     270              : 
     271           86 :     if (attr.value is String) {
     272           86 :       anyValue.stringValue = attr.value as String;
     273           22 :     } else if (attr.value is bool) {
     274           14 :       anyValue.boolValue = attr.value as bool;
     275           22 :     } else if (attr.value is int) {
     276           30 :       anyValue.intValue = Int64(attr.value as int);
     277           12 :     } else if (attr.value is double) {
     278            8 :       anyValue.doubleValue = attr.value as double;
     279            6 :     } else if (attr.value is List<String>) {
     280            3 :       final arrayValue = proto.ArrayValue();
     281            6 :       arrayValue.values.addAll(
     282            6 :         (attr.value as List<String>).map(
     283            9 :           (v) => proto.AnyValue()..stringValue = v,
     284              :         ),
     285              :       );
     286            3 :       anyValue.arrayValue = arrayValue;
     287            6 :     } else if (attr.value is List<bool>) {
     288            2 :       final arrayValue = proto.ArrayValue();
     289            4 :       arrayValue.values.addAll(
     290           10 :         (attr.value as List<bool>).map((v) => proto.AnyValue()..boolValue = v),
     291              :       );
     292            2 :       anyValue.arrayValue = arrayValue;
     293            6 :     } else if (attr.value is List<int>) {
     294            3 :       final arrayValue = proto.ArrayValue();
     295            6 :       arrayValue.values.addAll(
     296            6 :         (attr.value as List<int>).map(
     297           12 :           (v) => proto.AnyValue()..intValue = Int64(v),
     298              :         ),
     299              :       );
     300            3 :       anyValue.arrayValue = arrayValue;
     301            4 :     } else if (attr.value is List<double>) {
     302            2 :       final arrayValue = proto.ArrayValue();
     303            4 :       arrayValue.values.addAll(
     304            4 :         (attr.value as List<double>).map(
     305            6 :           (v) => proto.AnyValue()..doubleValue = v,
     306              :         ),
     307              :       );
     308            2 :       anyValue.arrayValue = arrayValue;
     309              :     } else {
     310              :       // For any other type, convert to string
     311            0 :       anyValue.stringValue = attr.value.toString();
     312              :     }
     313              : 
     314              :     return anyValue;
     315              :   }
     316              : }
        

Generated by: LCOV version 2.0-1