LCOV - code coverage report
Current view: top level - lib/src/trace - span.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 98.8 % 161 159
Test Date: 2026-08-27 23:42:02 Functions: - 0 0

            Line data    Source code
       1              : // Copyright The OpenTelemetry Authors
       2              : // SPDX-License-Identifier: Apache-2.0
       3              : 
       4              : library;
       5              : 
       6              : import 'package:dartastic_opentelemetry_api/dartastic_opentelemetry_api.dart';
       7              : import 'package:meta/meta.dart';
       8              : 
       9              : import '../resource/resource.dart';
      10              : import 'tracer.dart';
      11              : 
      12              : part 'span_create.dart';
      13              : 
      14              : /// SDK implementation of the APISpan interface.
      15              : ///
      16              : /// A Span represents a single operation within a trace. Spans can be nested
      17              : /// to form a trace tree. Each trace contains a root span, which typically
      18              : /// describes the entire operation and, optionally, one or more sub-spans
      19              : /// for its sub-operations.
      20              : ///
      21              : /// This implementation delegates most functionality to the API Span implementation
      22              : /// while adding SDK-specific behaviors like span processor notification.
      23              : ///
      24              : /// Note: Per [OTEP 0265: Event Vision](https://github.com/open-telemetry/opentelemetry-specification/blob/main/oteps/0265-event-vision.md)
      25              : /// and [OTEP 4430: Span Event API deprecation plan](https://github.com/open-telemetry/opentelemetry-specification/blob/main/oteps/4430-span-event-api-deprecation-plan.md),
      26              : /// span events are planned for deprecation in favor of log-based events
      27              : /// emitted via the Logs API; SDKs will provide options to render log-based
      28              : /// events as span events for compatibility.
      29              : ///
      30              : /// More information:
      31              : /// https://opentelemetry.io/docs/specs/otel/trace/sdk/
      32              : class Span implements APISpan {
      33              :   final APISpan _delegate;
      34              :   final Tracer _sdkTracer;
      35              : 
      36              :   /// The recording state decided by the sampler at creation time.
      37              :   ///
      38              :   /// Per the Trace SDK spec (ShouldSample), a DROP decision creates a span
      39              :   /// with `IsRecording == false`; the delegate APISpan does not carry this
      40              :   /// state, so the SDK span holds it.
      41              :   final bool _isRecording;
      42              : 
      43              :   /// Private constructor for creating Span instances.
      44              :   ///
      45              :   /// @param delegate The API Span implementation to delegate to
      46              :   /// @param sdkTracer The SDK Tracer that created this Span
      47              :   /// @param isRecording Whether this span records data (sampling decision)
      48           59 :   Span._(APISpan delegate, Tracer sdkTracer, {bool isRecording = true})
      49              :       : _delegate = delegate,
      50              :         _sdkTracer = sdkTracer,
      51              :         _isRecording = isRecording {
      52           59 :     if (OTelLog.isDebug()) {
      53          162 :       OTelLog.debug('SDKSpan: Created new span with name ${delegate.name}');
      54              :     }
      55              :   }
      56              : 
      57              :   /// Gets the resource associated with this span's tracer.
      58              :   ///
      59              :   /// @return The resource associated with this span
      60          126 :   Resource? get resource => _sdkTracer.resource;
      61              : 
      62           53 :   @override
      63              :   void end({DateTime? endTime, SpanStatusCode? spanStatus}) {
      64           53 :     if (OTelLog.isDebug()) {
      65           48 :       OTelLog.debug(
      66          192 :         'SDKSpan: Starting to end span ${spanContext.spanId} with name $name',
      67              :       );
      68              :     }
      69              : 
      70              :     if (spanStatus != null) {
      71            1 :       setStatus(spanStatus);
      72              :     }
      73              : 
      74              :     try {
      75           53 :       if (OTelLog.isDebug()) {
      76          144 :         OTelLog.debug('SDKSpan: Calling delegate.end() for span $name');
      77              :       }
      78              :       // `spanStatus` is deliberately NOT forwarded: setStatus() above already
      79              :       // applied it to the same delegate, and the API's end(spanStatus:) is
      80              :       // deprecated because the spec's End operation takes only a timestamp.
      81          106 :       _delegate.end(endTime: endTime);
      82           53 :       if (OTelLog.isDebug()) {
      83          144 :         OTelLog.debug('SDKSpan: Delegate.end() completed for span $name');
      84              :       }
      85              : 
      86              :       // Notify span processors that this span has ended. Per the Trace
      87              :       // SDK spec (Sampling), span processors MUST receive only spans
      88              :       // with IsRecording == true — a dropped span ends silently.
      89           53 :       if (!_isRecording) {
      90            5 :         if (OTelLog.isDebug()) {
      91            5 :           OTelLog.debug(
      92           10 :             'SDKSpan: Span $name is not recording; skipping processor onEnd',
      93              :           );
      94              :         }
      95              :         return;
      96              :       }
      97          106 :       final provider = _sdkTracer.provider;
      98           53 :       if (OTelLog.isDebug()) {
      99           48 :         OTelLog.debug(
     100          144 :           'SDKSpan: Notifying ${provider.spanProcessors.length} span processors',
     101              :         );
     102              :       }
     103           99 :       for (final processor in provider.spanProcessors) {
     104              :         try {
     105           46 :           if (OTelLog.isDebug()) {
     106           45 :             OTelLog.debug(
     107           90 :               'SDKSpan: Calling onEnd for processor ${processor.runtimeType}',
     108              :             );
     109              :           }
     110           46 :           processor.onEnd(this);
     111           46 :           if (OTelLog.isDebug()) {
     112           45 :             OTelLog.debug(
     113           90 :               'SDKSpan: Successfully called onEnd for processor ${processor.runtimeType}',
     114              :             );
     115              :           }
     116              :         } catch (e, stackTrace) {
     117            1 :           if (OTelLog.isError()) {
     118            1 :             OTelLog.error(
     119            2 :               'SDKSpan: Error calling onEnd for processor ${processor.runtimeType}: $e',
     120              :             );
     121            2 :             OTelLog.error('Stack trace: $stackTrace');
     122              :           }
     123              :         }
     124              :       }
     125              :     } catch (e, stackTrace) {
     126            0 :       if (OTelLog.isError()) OTelLog.error('SDKSpan: Error during end(): $e');
     127            0 :       if (OTelLog.isError()) OTelLog.error('Stack trace: $stackTrace');
     128              :       rethrow;
     129              :     }
     130              :   }
     131              : 
     132              :   // All mutators below are gated on the sampling decision: when a span is
     133              :   // not recording, "all this data is discarded right away" and mutation
     134              :   // attempts are no-ops (Trace API spec, IsRecording). The delegate only
     135              :   // guards against mutation after end, so the gate lives here.
     136              : 
     137            2 :   @override
     138              :   set attributes(Attributes newAttributes) {
     139            2 :     if (!_isRecording) return;
     140            2 :     _delegate.attributes = newAttributes;
     141              :   }
     142              : 
     143            5 :   @override
     144              :   void addAttributes(Attributes attributes) {
     145            5 :     if (!_isRecording) return;
     146            8 :     _delegate.addAttributes(attributes);
     147              :   }
     148              : 
     149            3 :   @override
     150              :   void addEvent(SpanEvent spanEvent) {
     151            3 :     if (!_isRecording) return;
     152            6 :     _delegate.addEvent(spanEvent);
     153              :   }
     154              : 
     155            7 :   @override
     156              :   void addEventNow(String name, [Attributes? attributes]) {
     157            7 :     if (!_isRecording) return;
     158           12 :     _delegate.addEventNow(name, attributes);
     159              :   }
     160              : 
     161            2 :   @override
     162              :   void addEvents(Map<String, Attributes?> spanEvents) {
     163            2 :     if (!_isRecording) return;
     164            2 :     _delegate.addEvents(spanEvents);
     165              :   }
     166              : 
     167            4 :   @override
     168              :   void addLink(SpanContext spanContext, [Attributes? attributes]) {
     169            4 :     if (!_isRecording) return;
     170            8 :     _delegate.addLink(spanContext, attributes);
     171              :   }
     172              : 
     173            3 :   @override
     174              :   void addSpanLink(SpanLink spanLink) {
     175            3 :     if (!_isRecording) return;
     176            4 :     _delegate.addSpanLink(spanLink);
     177              :   }
     178              : 
     179           50 :   @override
     180          100 :   DateTime? get endTime => _delegate.endTime;
     181              : 
     182            9 :   @override
     183           18 :   bool get isEnded => _delegate.isEnded;
     184              : 
     185            8 :   @override
     186           24 :   bool get isRecording => _isRecording && _delegate.isRecording;
     187              : 
     188           44 :   @override
     189           88 :   SpanKind get kind => _delegate.kind;
     190              : 
     191           54 :   @override
     192          108 :   String get name => _delegate.name;
     193              : 
     194           43 :   @override
     195           86 :   APISpan? get parentSpan => _delegate.parentSpan;
     196              : 
     197            9 :   @override
     198              :   void recordException(
     199              :     Object exception, {
     200              :     StackTrace? stackTrace,
     201              :     Attributes? attributes,
     202              :     bool? escaped,
     203              :   }) {
     204            9 :     if (!_isRecording) return;
     205           16 :     _delegate.recordException(
     206              :       exception,
     207              :       stackTrace: stackTrace,
     208              :       attributes: attributes,
     209              :       escaped: escaped,
     210              :     );
     211              :   }
     212              : 
     213            4 :   @override
     214              :   void setBoolAttribute(String name, bool value) {
     215            4 :     if (!_isRecording) return;
     216            6 :     _delegate.setBoolAttribute(name, value);
     217              :   }
     218              : 
     219            2 :   @override
     220              :   void setBoolListAttribute(String name, List<bool> value) {
     221            2 :     if (!_isRecording) return;
     222            4 :     _delegate.setBoolListAttribute(name, value);
     223              :   }
     224              : 
     225            4 :   @override
     226              :   void setDoubleAttribute(String name, double value) {
     227            4 :     if (!_isRecording) return;
     228            6 :     _delegate.setDoubleAttribute(name, value);
     229              :   }
     230              : 
     231            2 :   @override
     232              :   void setDoubleListAttribute(String name, List<double> value) {
     233            2 :     if (!_isRecording) return;
     234            4 :     _delegate.setDoubleListAttribute(name, value);
     235              :   }
     236              : 
     237            6 :   @override
     238              :   void setIntAttribute(String name, int value) {
     239            6 :     if (!_isRecording) return;
     240           10 :     _delegate.setIntAttribute(name, value);
     241              :   }
     242              : 
     243            2 :   @override
     244              :   void setIntListAttribute(String name, List<int> value) {
     245            2 :     if (!_isRecording) return;
     246            4 :     _delegate.setIntListAttribute(name, value);
     247              :   }
     248              : 
     249           11 :   @override
     250              :   void setStatus(SpanStatusCode statusCode, [String? description]) {
     251           11 :     if (!_isRecording) return;
     252           20 :     _delegate.setStatus(statusCode, description);
     253           10 :     if (OTelLog.isDebug()) {
     254           10 :       OTelLog.debug(
     255           30 :         'SDKSpan: Set status to $statusCode for span ${spanContext.spanId}',
     256              :       );
     257              :     }
     258              :   }
     259              : 
     260            6 :   @override
     261              :   void setStringAttribute<T>(String name, String value) {
     262            6 :     if (!_isRecording) return;
     263           10 :     _delegate.setStringAttribute<T>(name, value);
     264              :   }
     265              : 
     266            2 :   @override
     267              :   void setStringListAttribute<T>(String name, List<String> value) {
     268            2 :     if (!_isRecording) return;
     269            4 :     _delegate.setStringListAttribute<T>(name, value);
     270              :   }
     271              : 
     272            2 :   @override
     273              :   void setDateTimeAsStringAttribute(String name, DateTime value) {
     274            2 :     if (!_isRecording) return;
     275            4 :     _delegate.setDateTimeAsStringAttribute(name, value);
     276              :   }
     277              : 
     278           57 :   @override
     279          114 :   SpanContext get spanContext => _delegate.spanContext;
     280              : 
     281           47 :   @override
     282           94 :   List<SpanEvent>? get spanEvents => _delegate.spanEvents;
     283              : 
     284            3 :   @override
     285            6 :   SpanId get spanId => _delegate.spanId;
     286              : 
     287           45 :   @override
     288           90 :   List<SpanLink>? get spanLinks => _delegate.spanLinks;
     289              : 
     290           45 :   @override
     291           90 :   DateTime get startTime => _delegate.startTime;
     292              : 
     293           47 :   @override
     294           94 :   SpanStatusCode get status => _delegate.status;
     295              : 
     296           46 :   @override
     297           92 :   String? get statusDescription => _delegate.statusDescription;
     298              : 
     299            3 :   @override
     300              :   void updateName(String name) {
     301            3 :     if (!_isRecording) return;
     302            4 :     _delegate.updateName(name);
     303              : 
     304            4 :     final provider = _sdkTracer.provider;
     305            4 :     for (final processor in provider.spanProcessors) {
     306            2 :       processor.onNameUpdate(this, name);
     307              :     }
     308              :   }
     309              : 
     310           43 :   @override
     311              :   InstrumentationScope get instrumentationScope =>
     312           86 :       _delegate.instrumentationScope;
     313              : 
     314            6 :   @override
     315           12 :   SpanContext? get parentSpanContext => _delegate.parentSpanContext;
     316              : 
     317           36 :   @override
     318              :   String toString() {
     319              :     final indent = '  ';
     320           36 :     final buffer = StringBuffer()
     321           36 :       ..writeln('Span {')
     322          108 :       ..writeln('$indent name: $name,')
     323          108 :       ..writeln('$indent spanContext: $spanContext,')
     324          108 :       ..writeln('$indent kind: $kind,')
     325          118 :       ..writeln('$indent parentSpan: ${parentSpan?.spanContext ?? "none"},')
     326          108 :       ..writeln('$indent instrumentationScope: $instrumentationScope,')
     327          108 :       ..writeln('$indent startTime: $startTime,')
     328          108 :       ..writeln('$indent endTime: $endTime,')
     329          108 :       ..writeln('$indent status: $status,')
     330          108 :       ..writeln('$indent statusDescription: $statusDescription,')
     331          108 :       ..writeln('$indent attributes: $attributes,');
     332              : 
     333              :     // Span Events
     334           43 :     if (spanEvents?.isNotEmpty ?? false) {
     335           14 :       buffer.writeln('$indent spanEvents: [');
     336           21 :       for (final e in spanEvents!) {
     337           14 :         buffer.writeln('$indent$indent$e,');
     338              :       }
     339           14 :       buffer.writeln('$indent ],');
     340              :     } else {
     341           70 :       buffer.writeln('$indent spanEvents: [],');
     342              :     }
     343              : 
     344              :     // Span Links
     345           41 :     if (spanLinks?.isNotEmpty ?? false) {
     346           10 :       buffer.writeln('$indent spanLinks: [');
     347           15 :       for (final l in spanLinks!) {
     348           10 :         buffer.writeln('$indent$indent$l,');
     349              :       }
     350           10 :       buffer.writeln('$indent ]');
     351              :     } else {
     352           70 :       buffer.writeln('$indent spanLinks: []');
     353              :     }
     354              : 
     355           36 :     buffer.writeln('}');
     356           36 :     return buffer.toString();
     357              :   }
     358              : 
     359              :   /// Returns whether this span context is valid
     360              :   /// A span context is valid when it has a non-zero traceId and a non-zero spanId.
     361            9 :   @override
     362           18 :   bool get isValid => spanContext.isValid;
     363              : 
     364           46 :   @visibleForTesting
     365              :   @override
     366              :   // ignore: invalid_use_of_visible_for_testing_member
     367           92 :   Attributes get attributes => _delegate.attributes;
     368              : 
     369              :   // This check is always true because the method is part of the interface implementation
     370              :   // and the delegate is already an APISpan.
     371              :   /// Checks if this object is an instance of the specified type.
     372              :   ///
     373              :   /// This method is used for type checking and compatibility with the API Span implementation.
     374              :   /// It returns true if the specified type is APISpan or the exact runtime type of this object.
     375              :   ///
     376              :   /// @param type The type to check against
     377              :   /// @return true if this object is an instance of the specified type, false otherwise
     378            4 :   bool isInstanceOf(Type type) => type == APISpan || runtimeType == type;
     379              : }
        

Generated by: LCOV version 2.0-1