Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Prev Previous commit
Next Next commit
move registry to options, create integration using new lifecyle span …
…event
  • Loading branch information
denrase committed Jul 28, 2025
commit 682b7dd47daa2bbf7194327a876708cf189e559e
16 changes: 0 additions & 16 deletions dart/lib/src/hub.dart
Original file line number Diff line number Diff line change
Expand Up @@ -650,22 +650,6 @@ class Hub {

SentryProfilerFactory? _profilerFactory;

@internal
Map<Type, List<Function>> get lifecycleCallbacks =>
_peek().client.lifeCycleRegistry.lifecycleCallbacks;

@internal
void registerSdkLifecycleCallback<T extends SdkLifecycleEvent>(
SdkLifecycleCallback<T> callback) {
_peek().client.lifeCycleRegistry.registerCallback<T>(callback);
}

@internal
void removeSdkLifecycleCallback<T extends SdkLifecycleEvent>(
SdkLifecycleCallback<T> callback) {
_peek().client.lifeCycleRegistry.removeCallback<T>(callback);
}

SentryEvent _assignTraceContext(SentryEvent event) {
// assign trace context
if (event.throwable != null && event.contexts.trace == null) {
Expand Down
17 changes: 0 additions & 17 deletions dart/lib/src/hub_adapter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import 'profiling.dart';
import 'protocol.dart';
import 'protocol/sentry_feedback.dart';
import 'scope.dart';
import 'sdk_lifecycle_hooks.dart';
import 'sentry.dart';
import 'sentry_client.dart';
import 'sentry_options.dart';
Expand Down Expand Up @@ -200,20 +199,4 @@ class HubAdapter implements Hub {

@override
FutureOr<void> captureLog(SentryLog log) => Sentry.currentHub.captureLog(log);

@override
Map<Type, List<Function>> get lifecycleCallbacks =>
Sentry.currentHub.lifecycleCallbacks;

@override
void registerSdkLifecycleCallback<T extends SdkLifecycleEvent>(
SdkLifecycleCallback<T> callback) {
Sentry.currentHub.registerSdkLifecycleCallback(callback);
}

@override
void removeSdkLifecycleCallback<T extends SdkLifecycleEvent>(
SdkLifecycleCallback<T> callback) {
Sentry.currentHub.removeSdkLifecycleCallback(callback);
}
}
2 changes: 1 addition & 1 deletion dart/lib/src/logs_enricher_integration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class LogsEnricherIntegration extends Integration<SentryOptions> {
@override
FutureOr<void> call(Hub hub, SentryOptions options) {
if (options.enableLogs) {
hub.registerSdkLifecycleCallback<OnBeforeCaptureLog>(
options.lifecycleRegistry.registerCallback<OnBeforeCaptureLog>(
(event) async {
final os = getSentryOperatingSystem();

Expand Down
12 changes: 0 additions & 12 deletions dart/lib/src/noop_hub.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import 'profiling.dart';
import 'protocol.dart';
import 'protocol/sentry_feedback.dart';
import 'scope.dart';
import 'sdk_lifecycle_hooks.dart';
import 'sentry_client.dart';
import 'sentry_options.dart';
import 'tracing.dart';
Expand Down Expand Up @@ -145,15 +144,4 @@ class NoOpHub implements Hub {

@override
Scope get scope => Scope(_options);

@override
Map<Type, List<Function>> get lifecycleCallbacks => {};

@override
void registerSdkLifecycleCallback<T extends SdkLifecycleEvent>(
SdkLifecycleCallback<T> callback) {}

@override
void removeSdkLifecycleCallback<T extends SdkLifecycleEvent>(
SdkLifecycleCallback<T> callback) {}
}
7 changes: 0 additions & 7 deletions dart/lib/src/noop_sentry_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import 'hint.dart';
import 'protocol.dart';
import 'protocol/sentry_feedback.dart';
import 'scope.dart';
import 'sdk_lifecycle_hooks.dart';
import 'sentry_client.dart';
import 'sentry_envelope.dart';
import 'sentry_options.dart';
import 'sentry_trace_context_header.dart';

class NoOpSentryClient implements SentryClient {
Expand Down Expand Up @@ -71,9 +69,4 @@ class NoOpSentryClient implements SentryClient {

@override
FutureOr<void> captureLog(SentryLog log, {Scope? scope}) async {}

final _lifeCycleRegistry = SdkLifecycleRegistry(SentryOptions.empty());

@override
SdkLifecycleRegistry get lifeCycleRegistry => _lifeCycleRegistry;
}
7 changes: 7 additions & 0 deletions dart/lib/src/sdk_lifecycle_hooks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,10 @@ class OnBeforeSendEvent extends SdkLifecycleEvent {
final SentryEvent event;
final Hint hint;
}

@internal
class OnSpanStart extends SdkLifecycleEvent {
OnSpanStart(this.span);

final ISentrySpan span;
}
14 changes: 3 additions & 11 deletions dart/lib/src/sentry_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,6 @@ class SentryClient {

static final _emptySentryId = Future.value(SentryId.empty());

late final SdkLifecycleRegistry _lifecycleRegistry;

/// Allows registration and dispatching of callbacks outside of SentryClient
@internal
SdkLifecycleRegistry get lifeCycleRegistry => _lifecycleRegistry;

SentryExceptionFactory get _exceptionFactory => _options.exceptionFactory;
SentryStackTraceFactory get _stackTraceFactory => _options.stackTraceFactory;

Expand Down Expand Up @@ -89,9 +83,7 @@ class SentryClient {

/// Instantiates a client using [SentryOptions]
SentryClient._(this._options)
: _random = _options.sampleRate == null ? null : Random() {
_lifecycleRegistry = SdkLifecycleRegistry(_options);
}
: _random = _options.sampleRate == null ? null : Random();

/// Reports an [event] to Sentry.io.
Future<SentryId> captureEvent(
Expand Down Expand Up @@ -172,7 +164,7 @@ class SentryClient {
}

// Event is fully processed and ready to be sent
await _lifecycleRegistry
await _options.lifecycleRegistry
.dispatchCallback(OnBeforeSendEvent(preparedEvent, hint));

var attachments = List<SentryAttachment>.from(scope?.attachments ?? []);
Expand Down Expand Up @@ -579,7 +571,7 @@ class SentryClient {
}

if (processedLog != null) {
await _lifecycleRegistry
await _options.lifecycleRegistry
.dispatchCallback(OnBeforeCaptureLog(processedLog));
_options.logBatcher.addLog(processedLog);
} else {
Expand Down
3 changes: 3 additions & 0 deletions dart/lib/src/sentry_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,9 @@ class SentryOptions {
@internal
late ClientReportRecorder recorder = NoOpClientReportRecorder();

@internal
late SdkLifecycleRegistry lifecycleRegistry = SdkLifecycleRegistry(this);

/// List of strings/regex controlling to which outgoing requests
/// the SDK will attach tracing headers.
///
Expand Down
6 changes: 6 additions & 0 deletions dart/lib/src/sentry_tracer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class SentryTracer extends ISentrySpan {
collector.onSpanStarted(_rootSpan);
}
}
unawaited(_dispatchOnSpanStart(_rootSpan));
}

@override
Expand Down Expand Up @@ -271,6 +272,7 @@ class SentryTracer extends ISentrySpan {
collector.onSpanStarted(child);
}
}
unawaited(_dispatchOnSpanStart(child));

return child;
}
Expand Down Expand Up @@ -435,4 +437,8 @@ class SentryTracer extends ISentrySpan {
});
}
}

Future<void> _dispatchOnSpanStart(ISentrySpan span) async {
await _hub.options.lifecycleRegistry.dispatchCallback(OnSpanStart(span));
}
}
6 changes: 4 additions & 2 deletions dart/test/sentry_client_lifecycle_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ void main() {
final client = fixture.getSut();
fixture.options.logBatcher = MockLogBatcher();

client.lifeCycleRegistry.registerCallback<OnBeforeCaptureLog>((event) {
fixture.options.lifecycleRegistry
.registerCallback<OnBeforeCaptureLog>((event) {
event.log.attributes['test'] =
SentryLogAttribute.string('test-value');
});
Expand Down Expand Up @@ -74,7 +75,8 @@ void main() {
final client = fixture.getSut();
fixture.options.logBatcher = MockLogBatcher();

client.lifeCycleRegistry.registerCallback<OnBeforeSendEvent>((event) {
fixture.options.lifecycleRegistry
.registerCallback<OnBeforeSendEvent>((event) {
event.event.release = '999';
});

Expand Down
6 changes: 4 additions & 2 deletions dart/test/sentry_client_sdk_lifecycle_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ void main() {
final client = fixture.getSut();
fixture.options.logBatcher = MockLogBatcher();

client.lifeCycleRegistry.registerCallback<OnBeforeCaptureLog>((event) {
fixture.options.lifecycleRegistry
.registerCallback<OnBeforeCaptureLog>((event) {
event.log.attributes['test'] =
SentryLogAttribute.string('test-value');
});
Expand Down Expand Up @@ -74,7 +75,8 @@ void main() {
final client = fixture.getSut();
fixture.options.logBatcher = MockLogBatcher();

client.lifeCycleRegistry.registerCallback<OnBeforeSendEvent>((event) {
fixture.options.lifecycleRegistry
.registerCallback<OnBeforeSendEvent>((event) {
event.event.release = '999';
});

Expand Down
3 changes: 2 additions & 1 deletion dart/test/sentry_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2007,7 +2007,8 @@ void main() {
final client = fixture.getSut();
fixture.options.logBatcher = MockLogBatcher();

client.lifeCycleRegistry.registerCallback<OnBeforeCaptureLog>((event) {
fixture.options.lifecycleRegistry
.registerCallback<OnBeforeCaptureLog>((event) {
event.log.attributes['test'] = SentryLogAttribute.string('test-value');
});

Expand Down
131 changes: 131 additions & 0 deletions dart/test/sentry_tracer_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,137 @@ void main() {
expect(context.sampleRate, '1');
expect(context.sampled, 'true');
});

group('OnSpanStart lifecycle callbacks', () {
test('dispatches OnSpanStart for root span on tracer creation', () async {
final fixture = Fixture();
final capturedSpans = <ISentrySpan>[];

// Register OnSpanStart callback
fixture.hub.options.lifecycleRegistry.registerCallback<OnSpanStart>(
(OnSpanStart event) async {
capturedSpans.add(event.span);
},
);

// Create tracer (this should dispatch OnSpanStart for the root span)
final sut = fixture.getSut();

// Allow async callbacks to complete
await Future.delayed(Duration.zero);

expect(capturedSpans.length, equals(1));
// The root span is a SentrySpan, not the SentryTracer itself
expect(capturedSpans.first, isA<SentrySpan>());
expect(capturedSpans.first.context.operation, equals('op'));
expect(capturedSpans.first.context.description,
isNull); // No description set for root span
expect(
capturedSpans.first.context.traceId, equals(sut.context.traceId));
});

test('dispatches OnSpanStart for child spans', () async {
final fixture = Fixture();
final capturedSpans = <ISentrySpan>[];

// Register OnSpanStart callback
fixture.hub.options.lifecycleRegistry.registerCallback<OnSpanStart>(
(OnSpanStart event) async {
capturedSpans.add(event.span);
},
);

final sut = fixture.getSut();

// Clear the root span callback
capturedSpans.clear();

// Create child spans
sut.startChild('child_op1');
sut.startChild('child_op2', description: 'child description');

// Allow async callbacks to complete
await Future.delayed(Duration.zero);

expect(capturedSpans.length, equals(2));

// Verify first child span
expect(capturedSpans[0].context.operation, equals('child_op1'));
expect(capturedSpans[0].context.description, isNull);
expect(
capturedSpans[0].context.parentSpanId, equals(sut.context.spanId));

// Verify second child span
expect(capturedSpans[1].context.operation, equals('child_op2'));
expect(
capturedSpans[1].context.description, equals('child description'));
expect(
capturedSpans[1].context.parentSpanId, equals(sut.context.spanId));
});

test('dispatches OnSpanStart for nested child spans', () async {
final fixture = Fixture();
final capturedSpans = <ISentrySpan>[];

// Register OnSpanStart callback
fixture.hub.options.lifecycleRegistry.registerCallback<OnSpanStart>(
(OnSpanStart event) async {
capturedSpans.add(event.span);
},
);

final sut = fixture.getSut();
capturedSpans.clear(); // Clear root span

// Create nested spans
final child = sut.startChild('child_op');
await Future.delayed(Duration.zero);

child.startChild('grandchild_op');
await Future.delayed(Duration.zero);

expect(capturedSpans.length, equals(2));

// Verify child span
expect(capturedSpans[0].context.operation, equals('child_op'));
expect(
capturedSpans[0].context.parentSpanId, equals(sut.context.spanId));

// Verify grandchild span
expect(capturedSpans[1].context.operation, equals('grandchild_op'));
expect(capturedSpans[1].context.parentSpanId,
equals(child.context.spanId));
});

test('callbacks are called asynchronously without blocking span creation',
() async {
final fixture = Fixture();
var callbackExecuted = false;

// Register callback that takes some time
fixture.hub.options.lifecycleRegistry.registerCallback<OnSpanStart>(
(OnSpanStart event) async {
await Future.delayed(const Duration(milliseconds: 10));
callbackExecuted = true;
},
);

// Span creation should complete immediately without waiting for callback
final sut = fixture.getSut();
final child = sut.startChild('child_op');

// Callback should not have been executed yet (since it's unawaited)
expect(callbackExecuted, isFalse);

// Wait a bit for the callback to complete
await Future.delayed(const Duration(milliseconds: 20));

// Now callback should have completed
expect(callbackExecuted, isTrue);
expect(sut.context.operation, equals('op'));
expect(child.context.operation, equals('child_op'));
});
});
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class LoadContextsIntegration extends Integration<SentryFlutterOptions> {
}

// ignore: invalid_use_of_internal_member
hub.registerSdkLifecycleCallback<OnBeforeCaptureLog>(
options.lifecycleRegistry.registerCallback<OnBeforeCaptureLog>(
(event) async {
try {
final infos = await _native.loadContexts() ?? {};
Expand Down
Loading
Loading