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

Generated by: LCOV version 2.0-1