LCOV - code coverage report
Current view: top level - src/trace - tracer.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 75.3 % 146 110
Test Date: 2025-11-15 13:23:01 Functions: - 0 0

            Line data    Source code
       1              : // Licensed under the Apache License, Version 2.0
       2              : // Copyright 2025, Michael Bushe, All rights reserved.
       3              : 
       4              : library;
       5              : 
       6              : import 'package:dartastic_opentelemetry/src/trace/sampling/sampler.dart';
       7              : import 'package:dartastic_opentelemetry/src/trace/tracer_provider.dart';
       8              : import 'package:dartastic_opentelemetry_api/dartastic_opentelemetry_api.dart';
       9              : 
      10              : import '../otel.dart';
      11              : import '../resource/resource.dart';
      12              : import 'span.dart';
      13              : 
      14              : part 'tracer_create.dart';
      15              : 
      16              : /// SDK implementation of the APITracer interface.
      17              : ///
      18              : /// A Tracer is responsible for creating and managing spans. Each Tracer
      19              : /// is associated with a specific instrumentation scope and can create
      20              : /// spans that represent operations within that scope.
      21              : ///
      22              : /// This implementation delegates some functionality to the API Tracer
      23              : /// implementation while adding SDK-specific behaviors like sampling and
      24              : /// span processor notification.
      25              : ///
      26              : /// Note: Per [OTEP 0265](https://opentelemetry.io/docs/specs/semconv/general/events/),
      27              : /// span events are being deprecated and will be replaced by the Logging API in future versions.
      28              : ///
      29              : /// More information:
      30              : /// https://opentelemetry.io/docs/specs/otel/trace/sdk/
      31              : class Tracer implements APITracer {
      32              :   final TracerProvider _provider;
      33              :   final APITracer _delegate;
      34              :   final Sampler? _sampler;
      35              :   bool _enabled = true;
      36              : 
      37              :   /// Gets the sampler associated with this tracer.
      38              :   /// If no sampler was specified for this tracer, uses the provider's sampler.
      39          108 :   Sampler? get sampler => _sampler ?? _provider.sampler;
      40              : 
      41              :   /// Private constructor for creating Tracer instances.
      42              :   ///
      43              :   /// @param provider The TracerProvider that created this Tracer
      44              :   /// @param delegate The API Tracer implementation to delegate to
      45              :   /// @param sampler Optional custom sampler for this Tracer
      46           29 :   Tracer._({
      47              :     required TracerProvider provider,
      48              :     required APITracer delegate,
      49              :     Sampler? sampler,
      50              :   })  : _provider = provider,
      51              :         _delegate = delegate,
      52              :         _sampler = sampler;
      53              : 
      54            1 :   @override
      55            2 :   String get name => _delegate.name;
      56              : 
      57            0 :   @override
      58            0 :   String? get schemaUrl => _delegate.schemaUrl;
      59              : 
      60            0 :   @override
      61            0 :   String? get version => _delegate.version;
      62              : 
      63            0 :   @override
      64            0 :   Attributes? get attributes => _delegate.attributes;
      65              : 
      66            0 :   @override
      67            0 :   set attributes(Attributes? attributes) => _delegate.attributes = attributes;
      68              : 
      69            1 :   @override
      70            1 :   bool get enabled => _enabled;
      71              : 
      72            3 :   @override
      73            6 :   APISpan? get currentSpan => _delegate.currentSpan;
      74              : 
      75              :   /// Sets whether this tracer is enabled.
      76              :   ///
      77              :   /// When disabled, the tracer will still create spans, but they may not be
      78              :   /// recorded or exported.
      79            0 :   set enabled(bool enable) => _enabled = enable;
      80              : 
      81              :   /// Gets the provider that created this tracer.
      82           44 :   TracerProvider get provider => _provider;
      83              : 
      84              :   /// Gets the resource associated with this tracer's provider.
      85           57 :   Resource? get resource => _provider.resource;
      86              : 
      87            3 :   @override
      88              :   T withSpan<T>(APISpan span, T Function() fn) {
      89            3 :     if (OTelLog.isDebug()) {
      90            3 :       OTelLog.debug(
      91           12 :           'Tracer: withSpan called with span ${span.name}, spanId: ${span.spanContext.spanId}');
      92              :     }
      93            3 :     final originalContext = Context.current;
      94              :     try {
      95            6 :       Context.current = originalContext.setCurrentSpan(span);
      96            3 :       if (OTelLog.isDebug()) {
      97            9 :         OTelLog.debug('Tracer: Context set with span ${span.name}');
      98              :       }
      99            3 :       final result = fn();
     100            3 :       if (OTelLog.isDebug()) {
     101            3 :         OTelLog.debug(
     102            6 :             'Tracer: Function completed in withSpan for ${span.name}');
     103              :       }
     104              :       return result;
     105              :     } catch (e, stackTrace) {
     106            0 :       if (OTelLog.isError()) {
     107            0 :         OTelLog.error('Tracer: Exception in withSpan for ${span.name}: $e');
     108              :       }
     109            0 :       if (span is Span) {
     110            0 :         span.recordException(e, stackTrace: stackTrace);
     111            0 :         span.setStatus(SpanStatusCode.Error, e.toString());
     112              :       }
     113              :       rethrow;
     114              :     } finally {
     115            3 :       Context.current = originalContext;
     116            3 :       if (OTelLog.isDebug()) {
     117            9 :         OTelLog.debug('Tracer: withSpan completed for span ${span.name}');
     118              :       }
     119              :       // Check span validity but don't automatically end it
     120            3 :       if (!span.isValid) {
     121            0 :         if (OTelLog.isDebug()) {
     122            0 :           OTelLog.debug(
     123            0 :               'Tracer: Warning - span ${span.name} is invalid after withSpan operation');
     124              :         }
     125              :       }
     126              :     }
     127              :   }
     128              : 
     129            3 :   @override
     130              :   Future<T> withSpanAsync<T>(APISpan span, Future<T> Function() fn) async {
     131            3 :     if (OTelLog.isDebug()) {
     132            3 :       OTelLog.debug(
     133           12 :           'Tracer: withSpanAsync called with span ${span.name}, spanId: ${span.spanContext.spanId}');
     134              :     }
     135            3 :     final originalContext = Context.current;
     136              :     try {
     137            6 :       Context.current = originalContext.setCurrentSpan(span);
     138            3 :       if (OTelLog.isDebug()) {
     139            3 :         OTelLog.debug(
     140            6 :             'Tracer: Context set with span ${span.name} for async operation');
     141              :       }
     142            3 :       return await fn();
     143              :     } catch (e, stackTrace) {
     144            0 :       if (OTelLog.isError()) {
     145            0 :         OTelLog.error(
     146            0 :             'Tracer: Exception in withSpanAsync for ${span.name}: $e');
     147              :       }
     148            0 :       if (span is Span) {
     149            0 :         span.recordException(e, stackTrace: stackTrace);
     150            0 :         span.setStatus(SpanStatusCode.Error, e.toString());
     151              :       }
     152              :       rethrow;
     153              :     } finally {
     154            3 :       Context.current = originalContext;
     155            3 :       if (OTelLog.isDebug()) {
     156            9 :         OTelLog.debug('Tracer: withSpanAsync completed for span ${span.name}');
     157              :       }
     158              :       // Check span validity but don't automatically end it
     159            3 :       if (!span.isValid) {
     160            0 :         if (OTelLog.isDebug()) {
     161            0 :           OTelLog.debug(
     162            0 :               'Tracer: Warning - span ${span.name} is invalid after withSpanAsync operation');
     163              :         }
     164              :       }
     165              :     }
     166              :   }
     167              : 
     168              :   /// Starts a span with the given context instead of the current context
     169              :   /// Sets the current span in the given context
     170            0 :   APISpan startSpanWithContext({
     171              :     required String name,
     172              :     required Context context,
     173              :     SpanKind kind = SpanKind.internal,
     174              :     Attributes? attributes,
     175              :   }) {
     176            0 :     final span = startSpan(
     177              :       name,
     178              :       context: context,
     179              :       kind: kind,
     180              :       attributes: attributes,
     181              :     );
     182              : 
     183              :     // Set the span in the context we were given
     184            0 :     final updatedContext = context.setCurrentSpan(span);
     185              : 
     186              :     // Also update the current global context if needed
     187            0 :     if (Context.current == context) {
     188            0 :       Context.current = updatedContext;
     189              :     }
     190              : 
     191              :     return span;
     192              :   }
     193              : 
     194            1 :   @override
     195              :   Span createSpan({
     196              :     required String name,
     197              :     SpanContext? spanContext,
     198              :     APISpan? parentSpan,
     199              :     SpanKind kind = SpanKind.internal,
     200              :     Attributes? attributes,
     201              :     List<SpanLink>? links,
     202              :     List<SpanEvent>? spanEvents,
     203              :     DateTime? startTime,
     204              :     bool? isRecording = true,
     205              :     Context? context,
     206              :   }) {
     207            1 :     if (OTelLog.isDebug()) {
     208            2 :       OTelLog.debug('Tracer: Creating span with name: $name, kind: $kind');
     209              :     }
     210              : 
     211            2 :     final APISpan delegateSpan = _delegate.createSpan(
     212              :       name: name,
     213              :       spanContext: spanContext,
     214              :       parentSpan: parentSpan,
     215              :       kind: kind,
     216              :       attributes: attributes,
     217              :       links: links,
     218              :       startTime: startTime,
     219              :       spanEvents: spanEvents,
     220              :       isRecording: isRecording,
     221              :       context: context,
     222              :     );
     223              : 
     224            1 :     return SDKSpanCreate.create(
     225              :       delegateSpan: delegateSpan,
     226              :       sdkTracer: this,
     227              :     );
     228              :   }
     229              : 
     230           27 :   @override
     231              :   Span startSpan(
     232              :     String name, {
     233              :     Context? context,
     234              :     SpanContext? spanContext,
     235              :     APISpan? parentSpan,
     236              :     SpanKind kind = SpanKind.internal,
     237              :     Attributes? attributes,
     238              :     List<SpanLink>? links,
     239              :     bool? isRecording = true,
     240              :   }) {
     241           27 :     if (OTelLog.isDebug()) {
     242           54 :       OTelLog.debug('Tracer: Starting span with name: $name, kind: $kind');
     243              :     }
     244              : 
     245              :     // Get parent context from either the passed context or parent span
     246              :     SpanContext? parentContext;
     247              :     APISpan? effectiveParentSpan = parentSpan;
     248           27 :     final effectiveContext = context ?? Context.current;
     249              : 
     250           54 :     if (effectiveContext != Context.root) {
     251              :       // If an explicit context was provided, check for a span
     252           20 :       if (effectiveContext.span != null) {
     253              :         // Use the span from the context as parent (if no explicit parent span)
     254           19 :         effectiveParentSpan ??= effectiveContext.span;
     255              :       }
     256              :       // Always check for span context in the context
     257           20 :       parentContext = effectiveContext.spanContext;
     258              :     }
     259              : 
     260              :     // If no parentContext from context but we have a parentSpan, use its context
     261              :     if (parentContext == null && effectiveParentSpan != null) {
     262            0 :       parentContext = effectiveParentSpan.spanContext;
     263              :     }
     264              : 
     265              :     // Determine the trace ID to use
     266              :     TraceId traceId;
     267            4 :     if (spanContext != null && spanContext.traceId.isValid) {
     268              :       // Use provided span context's trace ID if valid
     269            2 :       traceId = spanContext.traceId;
     270              : 
     271              :       // Validate it against parent if both exist and are valid
     272            1 :       if (parentContext != null && parentContext.isValid) {
     273            2 :         if (parentContext.traceId != traceId) {
     274            2 :           throw ArgumentError(
     275              :               'Cannot create span with different trace ID than parent. '
     276            1 :               'Parent trace ID: ${parentContext.traceId}, '
     277              :               'Provided trace ID: $traceId');
     278              :         }
     279              :       }
     280           20 :     } else if (parentContext != null && parentContext.isValid) {
     281              :       // Inherit from parent if available
     282           20 :       traceId = parentContext.traceId;
     283              :     } else {
     284              :       // Generate new trace ID for root span
     285           27 :       traceId = OTel.traceId();
     286              :     }
     287              : 
     288              :     // Determine the parent span ID
     289              :     SpanId? parentSpanId;
     290              :     if (effectiveParentSpan != null &&
     291           40 :         effectiveParentSpan.spanContext.isValid) {
     292              :       // Use effective parent span's span ID
     293           40 :       parentSpanId = effectiveParentSpan.spanContext.spanId;
     294            2 :     } else if (parentContext != null && parentContext.isValid) {
     295              :       // Use parent context's span ID
     296            2 :       parentSpanId = parentContext.spanId;
     297              :     }
     298              : 
     299              :     // Inherit trace flags from parent if available
     300              :     TraceFlags? traceFlags;
     301           20 :     if (parentContext != null && parentContext.isValid) {
     302           20 :       traceFlags = parentContext.traceFlags;
     303              :     }
     304              : 
     305           27 :     if (OTelLog.isDebug()) {
     306              :       if (parentSpanId != null) {
     307           20 :         OTelLog.debug(
     308           20 :             'Creating child span: traceId=$traceId, parentSpanId=$parentSpanId');
     309              :       } else {
     310           54 :         OTelLog.debug('Creating root span: traceId=$traceId');
     311              :       }
     312              :     }
     313              : 
     314              :     // Apply sampling decision if we have a sampler
     315              :     bool shouldRecord = true;
     316           27 :     if (sampler != null) {
     317           54 :       final samplingResult = sampler!.shouldSample(
     318              :         parentContext: effectiveContext,
     319           27 :         traceId: traceId.toString(),
     320              :         name: name,
     321              :         spanKind: kind,
     322              :         attributes: attributes,
     323              :         links: links,
     324              :       );
     325              : 
     326              :       // Update the isRecording flag based on the sampling decision
     327           54 :       shouldRecord = samplingResult.decision != SamplingDecision.drop;
     328              : 
     329              :       // Update trace flags based on sampling decision
     330              :       if (traceFlags == null) {
     331           27 :         traceFlags = OTel.traceFlags(
     332              :             shouldRecord ? TraceFlags.SAMPLED_FLAG : TraceFlags.NONE_FLAG);
     333           20 :       } else if (shouldRecord && !traceFlags.isSampled) {
     334              :         // Upgrade to sampled if necessary
     335            2 :         traceFlags = OTel.traceFlags(TraceFlags.SAMPLED_FLAG);
     336            2 :       } else if (!shouldRecord && traceFlags.isSampled) {
     337              :         // Downgrade to not sampled if necessary
     338            2 :         traceFlags = OTel.traceFlags(TraceFlags.NONE_FLAG);
     339              :       }
     340              : 
     341              :       // Add sampler attributes if provided
     342           27 :       if (samplingResult.attributes != null) {
     343              :         if (attributes == null) {
     344            0 :           attributes = samplingResult.attributes;
     345              :         } else {
     346              :           attributes =
     347            0 :               attributes.copyWithAttributes(samplingResult.attributes!);
     348              :         }
     349              :       }
     350              : 
     351           27 :       if (OTelLog.isDebug()) {
     352           27 :         OTelLog.debug(
     353           54 :             'Sampling decision for span $name: ${samplingResult.decision}');
     354              :       }
     355              :     }
     356              : 
     357              :     // Always create a new span context with a new span ID
     358              :     // For root spans, ensure we set an invalid parent span ID (zeros)
     359           27 :     final newSpanContext = OTel.spanContext(
     360              :       traceId: traceId,
     361           27 :       spanId: OTel.spanId(), // Always generate a new span ID
     362              :       parentSpanId: parentSpanId ??
     363           27 :           OTel.spanIdInvalid(), // Use invalid span ID for root spans
     364              :       traceFlags: traceFlags,
     365              :     );
     366              : 
     367              :     // Create the delegate span with our newly created span context
     368           54 :     final APISpan delegateSpan = _delegate.startSpan(name,
     369              :         context: effectiveContext,
     370              :         spanContext: newSpanContext,
     371              :         parentSpan: effectiveParentSpan,
     372              :         kind: kind,
     373              :         attributes: attributes,
     374              :         links: links,
     375              :         isRecording: isRecording ?? shouldRecord);
     376              : 
     377              :     // Wrap it in our SDK span which will handle processing
     378           27 :     final sdkSpan = SDKSpanCreate.create(
     379              :       delegateSpan: delegateSpan,
     380              :       sdkTracer: this,
     381              :     );
     382              : 
     383              :     // Notify processors
     384           78 :     for (final processor in _provider.spanProcessors) {
     385           24 :       processor.onStart(sdkSpan, context);
     386              :     }
     387              : 
     388              :     return sdkSpan;
     389              :   }
     390              : 
     391              :   /// Convenience method that starts a span and runs function fn
     392              :   /// on error, records the exception and sets status to SpanStatusCode.Error
     393              :   /// ends the span, always
     394            4 :   T recordSpan<T>({
     395              :     required String name,
     396              :     required T Function() fn,
     397              :     SpanKind kind = SpanKind.internal,
     398              :     Attributes? attributes,
     399              :   }) {
     400            4 :     final span = startSpan(name, kind: kind, attributes: attributes);
     401              :     try {
     402            4 :       return fn();
     403              :     } catch (e, stackTrace) {
     404            2 :       span.recordException(e, stackTrace: stackTrace);
     405            4 :       span.setStatus(SpanStatusCode.Error, e.toString());
     406              :       rethrow;
     407              :     } finally {
     408            4 :       span.end();
     409              :     }
     410              :   }
     411              : 
     412              :   /// Convenience method thatstarts a span and runs function fn and
     413              :   /// awaits its future
     414              :   /// on error, records the exception and sets status to SpanStatusCode.Error
     415              :   /// ends the span, always
     416            2 :   Future<T> recordSpanAsync<T>({
     417              :     required String name,
     418              :     required Future<T> Function() fn,
     419              :     SpanKind kind = SpanKind.internal,
     420              :     Attributes? attributes,
     421              :   }) async {
     422            2 :     final span = startSpan(name, kind: kind, attributes: attributes);
     423              :     try {
     424            2 :       return await fn();
     425              :     } catch (e, stackTrace) {
     426            0 :       span.recordException(e, stackTrace: stackTrace);
     427            0 :       span.setStatus(SpanStatusCode.Error, e.toString());
     428              :       rethrow;
     429              :     } finally {
     430            2 :       span.end();
     431              :     }
     432              :   }
     433              : 
     434              :   /// TODO - needs better doc.  Is recordSpan superfluous?
     435            2 :   T startActiveSpan<T>({
     436              :     required String name,
     437              :     required T Function(APISpan span) fn,
     438              :     SpanKind kind = SpanKind.internal,
     439              :     Attributes? attributes,
     440              :   }) {
     441            2 :     final span = startSpan(name, kind: kind, attributes: attributes);
     442              :     try {
     443              :       // Use our own withSpan to ensure proper context propagation
     444            6 :       return withSpan(span, () => fn(span));
     445              :     } finally {
     446            2 :       span.end();
     447              :     }
     448              :   }
     449              : 
     450              :   /// Same as startActiveSpan but awaits the future
     451            2 :   Future<T> startActiveSpanAsync<T>({
     452              :     required String name,
     453              :     required Future<T> Function(APISpan span) fn,
     454              :     SpanKind kind = SpanKind.internal,
     455              :     Attributes? attributes,
     456              :   }) async {
     457            2 :     final span = startSpan(name, kind: kind, attributes: attributes);
     458              :     try {
     459              :       // Use our own withSpanAsync to ensure proper context propagation
     460            6 :       return await withSpanAsync(span, () => fn(span));
     461              :     } finally {
     462            2 :       span.end();
     463              :     }
     464              :   }
     465              : }
        

Generated by: LCOV version 2.0-1