Skip to content
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
12aee4d
Add support
buenaflor Jan 14, 2026
5c3b095
Add test
buenaflor Jan 14, 2026
023c89e
Merge branch 'feat/span-first' into feat/span/frames-tracking-support
buenaflor Jan 16, 2026
e9f9b74
Merge branch 'feat/span-first' into feat/span/frames-tracking-support
buenaflor Feb 3, 2026
4133f4f
Update test
buenaflor Jan 14, 2026
8ea21f8
Update doc
buenaflor Feb 3, 2026
1a33052
Update to OnProcessSpan
buenaflor Feb 3, 2026
74dc1f9
Update
buenaflor Feb 3, 2026
49eae68
Update
buenaflor Feb 3, 2026
3c9ad4a
Set noop datetime to 0
buenaflor Feb 3, 2026
6d5613b
Use switch for traceLifecycle
buenaflor Feb 3, 2026
9aa55a4
Merge branch 'feat/span-first' into feat/span/frames-tracking-support
buenaflor Feb 4, 2026
e97ec07
Deprecate collectors
buenaflor Feb 4, 2026
8c954bd
Review
buenaflor Feb 4, 2026
3e9b6c4
Review
buenaflor Feb 4, 2026
b499167
Review
buenaflor Feb 4, 2026
1c14c1c
Review
buenaflor Feb 4, 2026
bb0fe39
Review
buenaflor Feb 4, 2026
4f088cb
Review
buenaflor Feb 4, 2026
4a6eac3
Review
buenaflor Feb 4, 2026
084fabd
Analyze
buenaflor Feb 4, 2026
7444114
Update to unified collector
buenaflor Feb 5, 2026
1d5083e
Analyze
buenaflor Feb 5, 2026
2c27e38
Update
buenaflor Feb 5, 2026
62e81d8
Update
buenaflor Feb 5, 2026
1ea10a9
Update
buenaflor Feb 5, 2026
703a66f
Fix frames tracking
buenaflor Feb 5, 2026
1b084cd
Update finished bool
buenaflor Feb 5, 2026
f80fd0d
Update
buenaflor Feb 5, 2026
9413892
Update
buenaflor Feb 5, 2026
514e7b5
Fix race condition for finish
buenaflor Feb 5, 2026
b66742e
Update
buenaflor Feb 5, 2026
6d6b381
Update finish condition
buenaflor Feb 5, 2026
ced147d
Fix analyze
buenaflor Feb 5, 2026
8741446
Update
buenaflor Feb 6, 2026
0cc7c26
Review
buenaflor Feb 6, 2026
c55cfa6
Fix tracer bug
buenaflor Feb 6, 2026
5799275
Review
buenaflor Feb 6, 2026
c6d4abe
Simplify
buenaflor Feb 6, 2026
a0fcebc
Fix test
buenaflor Feb 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/dart/lib/sentry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export 'src/sentry_envelope.dart';
export 'src/sentry_envelope_item.dart';
export 'src/sentry_options.dart';
export 'src/telemetry/sentry_trace_lifecycle.dart';
export 'src/telemetry/span/sentry_span_v2.dart';
// ignore: invalid_export_of_internal_element
export 'src/sentry_trace_origins.dart';
export 'src/span_data_convention.dart';
Expand Down
13 changes: 13 additions & 0 deletions packages/dart/lib/src/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,19 @@ abstract class SemanticAttributesConstants {
/// The device family (e.g., "iOS", "Android").
static const deviceFamily = 'device.family';

/// The number of total frames rendered during the lifetime of the span.
static const framesTotal = 'frames.total';

/// The number of slow frames rendered during the lifetime of the span.
static const framesSlow = 'frames.slow';

/// The number of frozen frames rendered during the lifetime of the span.
static const framesFrozen = 'frames.frozen';

/// The sum of all delayed frame durations in seconds during the lifetime of the span.
/// For more information see [frames delay](https://develop.sentry.dev/sdk/performance/frames-delay/).
static const framesDelay = 'frames.delay';

/// The HTTP request method (e.g., "GET", "POST").
static const httpRequestMethod = 'http.request.method';

Expand Down
3 changes: 3 additions & 0 deletions packages/dart/lib/src/hub.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import 'telemetry/span/sentry_span_sampling_context.dart';
import 'transport/data_category.dart';
import 'utils/internal_logger.dart';
import 'utils/stacktrace_utils.dart';

Check warning on line 15 in packages/dart/lib/src/hub.dart

View workflow job for this annotation

GitHub Actions / analyze / analyze

Unused import: 'utils/stacktrace_utils.dart'.

Try removing the import directive. See https://dart.dev/diagnostics/unused_import to learn more about this problem.

/// Configures the scope through the callback.
typedef ScopeCallback = FutureOr<void> Function(Scope);
Expand Down Expand Up @@ -713,6 +714,8 @@
scope.setActiveSpan(span);
}

_options.lifecycleRegistry.dispatchCallback(OnSpanStartV2(span));

return span;
}

Expand Down
81 changes: 46 additions & 35 deletions packages/dart/lib/src/protocol/sentry_span.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class SentrySpan extends ISentrySpan {
late final DateTime _startTimestamp;
final Hub _hub;

bool _isFinished = false;
bool _isFinishing = false;
bool _isRootSpan = false;

bool get isRootSpan => _isRootSpan;
Expand Down Expand Up @@ -51,50 +53,59 @@ class SentrySpan extends ISentrySpan {
@override
Future<void> finish(
{SpanStatus? status, DateTime? endTimestamp, Hint? hint}) async {
if (finished) {
// Prevent concurrent or duplicate finish() calls
if (_isFinished || _isFinishing) {
return;
}

if (status != null) {
_status = status;
}
_isFinishing = true;

if (endTimestamp == null) {
endTimestamp = _hub.options.clock();
} else if (endTimestamp.isBefore(_startTimestamp)) {
_hub.options.log(
SentryLevel.warning,
'End timestamp ($endTimestamp) cannot be before start timestamp ($_startTimestamp)',
);
endTimestamp = _hub.options.clock();
} else {
endTimestamp = endTimestamp.toUtc();
}
try {
if (status != null) {
_status = status;
}

for (final collector in _hub.options.performanceCollectors) {
if (collector is PerformanceContinuousCollector) {
await collector.onSpanFinished(this, endTimestamp);
if (endTimestamp == null) {
endTimestamp = _hub.options.clock();
} else if (endTimestamp.isBefore(_startTimestamp)) {
_hub.options.log(
SentryLevel.warning,
'End timestamp ($endTimestamp) cannot be before start timestamp ($_startTimestamp)',
);
endTimestamp = _hub.options.clock();
} else {
endTimestamp = endTimestamp.toUtc();
}
}

// Dispatch OnSpanFinish lifecycle event
final callback =
_hub.options.lifecycleRegistry.dispatchCallback(OnSpanFinish(this));
if (callback is Future) {
await callback;
}
_endTimestamp = endTimestamp;

// ignore: deprecated_member_use_from_same_package
for (final collector in _hub.options.performanceCollectors) {
if (collector is PerformanceContinuousCollector) {
await collector.onSpanFinished(this, endTimestamp);
}
}

// Dispatch OnSpanFinish lifecycle event
final callback =
_hub.options.lifecycleRegistry.dispatchCallback(OnSpanFinish(this));
if (callback is Future) {
await callback;
}

// associate error
if (_throwable != null) {
_hub.setSpanContext(_throwable, this, _tracer.name);
}

// The finished flag depends on the _endTimestamp
// If we set this earlier then finished is true and then we cannot use setData etc...
_endTimestamp = endTimestamp;
_isFinished = true;

// associate error
if (_throwable != null) {
_hub.setSpanContext(_throwable, this, _tracer.name);
await _finishedCallback?.call(endTimestamp: _endTimestamp, hint: hint);
return super
.finish(status: status, endTimestamp: _endTimestamp, hint: hint);
} finally {
_isFinishing = false;
}
await _finishedCallback?.call(endTimestamp: _endTimestamp, hint: hint);
return super
.finish(status: status, endTimestamp: _endTimestamp, hint: hint);
}

@override
Expand Down Expand Up @@ -207,7 +218,7 @@ class SentrySpan extends ISentrySpan {
}

@override
bool get finished => _endTimestamp != null;
bool get finished => _isFinished;

@override
dynamic get throwable => _throwable;
Expand Down
13 changes: 12 additions & 1 deletion packages/dart/lib/src/sdk_lifecycle_hooks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,16 @@ class OnSpanFinish extends SdkLifecycleEvent {
final ISentrySpan span;
}

/// Dispatched when span is ready for processing (before default enrichment).
/// Dispatched when a sampled span is started.
@internal
class OnSpanStartV2 extends SdkLifecycleEvent {
OnSpanStartV2(this.span);

final SentrySpanV2 span;
}

/// Dispatched when a span has been captured and is ready for processing (before default enrichment)
/// and before it's being added to the telemetry processor.
///
/// This is useful for integrations to hook into e.g for enriching with attributes.
@internal
Expand All @@ -107,6 +116,8 @@ class OnProcessSpan extends SdkLifecycleEvent {
OnProcessSpan(this.span);
}

/// Dispatched when a metric has been captured and is ready for processing (before default enrichment)
/// and before it's being added to the telemetry processor.
@internal
class OnProcessMetric extends SdkLifecycleEvent {
final SentryMetric metric;
Expand Down
2 changes: 2 additions & 0 deletions packages/dart/lib/src/sentry_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -657,10 +657,12 @@ class SentryOptions {
return tracesSampleRate != null || tracesSampler != null;
}

@Deprecated('Will be removed in the next major v10')
List<PerformanceCollector> get performanceCollectors =>
_performanceCollectors;
final List<PerformanceCollector> _performanceCollectors = [];

@Deprecated('Will be removed in the next major v10')
void addPerformanceCollector(PerformanceCollector collector) {
_performanceCollectors.add(collector);
}
Expand Down
2 changes: 2 additions & 0 deletions packages/dart/lib/src/sentry_tracer.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// ignore_for_file: deprecated_member_use_from_same_package

import 'dart:async';

import 'package:meta/meta.dart';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ final class NoOpSentrySpanV2 implements SentrySpanV2 {
@override
SentrySpanV2? get parentSpan => null;

@override
DateTime get startTimestamp => DateTime.fromMillisecondsSinceEpoch(0);

@override
DateTime? get endTimestamp => null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ final class RecordingSentrySpanV2 implements SentrySpanV2 {
@override
set status(SentrySpanStatusV2 value) => _status = value;

@override
DateTime get startTimestamp => _startTimestamp;

@override
DateTime? get endTimestamp => _endTimestamp;

Expand Down
5 changes: 5 additions & 0 deletions packages/dart/lib/src/telemetry/span/sentry_span_v2.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ sealed class SentrySpanV2 {
/// Sets the status of this span.
set status(SentrySpanStatusV2 status);

/// The start timestamp of this span.
DateTime get startTimestamp;

/// The end timestamp of this span.
///
/// Returns null if the span has not ended yet.
DateTime? get endTimestamp;

/// Whether this span has ended.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ final class UnsetSentrySpanV2 implements SentrySpanV2 {
@override
SentrySpanV2? get parentSpan => _throw();

@override
DateTime get startTimestamp => _throw();

@override
DateTime? get endTimestamp => _throw();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ abstract class InstrumentationSpan {
Future<void> finish({SpanStatus? status, DateTime? endTimestamp});
SentryTraceHeader toSentryTrace();
SentryBaggageHeader? toBaggageHeader();

/// Returns true if this span is a no-op span that doesn't record data.
bool get isNoop;

/// The start timestamp of this span.
DateTime get startTimestamp;
}

/// [InstrumentationSpan] implementation wrapping [ISentrySpan].
Expand Down Expand Up @@ -65,6 +71,22 @@ class LegacyInstrumentationSpan implements InstrumentationSpan {

@override
SentryBaggageHeader? toBaggageHeader() => _span.toBaggageHeader();

@override
bool get isNoop => _span is NoOpSentrySpan;

@override
DateTime get startTimestamp => _span.startTimestamp;

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is LegacyInstrumentationSpan &&
runtimeType == other.runtimeType &&
identical(_span, other._span);

@override
int get hashCode => _span.hashCode;
}

@internal
Expand Down Expand Up @@ -180,4 +202,20 @@ class StreamingInstrumentationSpan implements InstrumentationSpan {
}
return SentrySpanStatusV2.error;
}

@override
bool get isNoop => _span is! RecordingSentrySpanV2;

@override
DateTime get startTimestamp => _span.startTimestamp;

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is StreamingInstrumentationSpan &&
runtimeType == other.runtimeType &&
identical(_span, other._span);

@override
int get hashCode => _span.hashCode;
}
Loading
Loading