Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v9]: Flutter Web release health #2794

Open
wants to merge 67 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
469415c
bump to 9.5.0
buenaflor Mar 11, 2025
6c5f7f7
Update CHANGELOG
buenaflor Mar 11, 2025
b584a91
temp
buenaflor Mar 12, 2025
1dc4ffe
temp
buenaflor Mar 12, 2025
e6aa943
temp
buenaflor Mar 12, 2025
5a48913
add sessions to navigator observer
buenaflor Mar 12, 2025
d82c1c6
update
buenaflor Mar 12, 2025
f19e998
update
buenaflor Mar 13, 2025
079f78a
Merge branch 'main' into v9-feat/js-release-health
buenaflor Mar 13, 2025
e4b20e6
add web sessions
buenaflor Mar 13, 2025
b0f0733
update logs and improve adding web sessions code
buenaflor Mar 13, 2025
8b74f4d
add test for beforeSendEvent hook
buenaflor Mar 13, 2025
3296f90
clean up
buenaflor Mar 13, 2025
2348468
Delete flutter/lib/src/event_processor/session_event_processor.dart
buenaflor Mar 13, 2025
869e324
update test
buenaflor Mar 13, 2025
4f20bb9
add test for navigator observer
buenaflor Mar 13, 2025
5faab6d
update group name to web sessions
buenaflor Mar 13, 2025
8a658ef
update naming
buenaflor Mar 13, 2025
4426919
fix
buenaflor Mar 13, 2025
f8e91c2
naming
buenaflor Mar 13, 2025
186dd27
todo
buenaflor Mar 13, 2025
ce0b8c5
naming
buenaflor Mar 13, 2025
14f2f64
first test
buenaflor Mar 14, 2025
4338331
remove unnecessary code
buenaflor Mar 14, 2025
e1ceb32
update to internal and comments
buenaflor Mar 17, 2025
db0fa9b
make getSession nullable
buenaflor Mar 17, 2025
b4d0b7b
do we need this?
buenaflor Mar 17, 2025
fbc5721
update error count
buenaflor Mar 17, 2025
cf7bf9a
use tryCatchSync
buenaflor Mar 17, 2025
8ea8ffc
updateSession status and error can be non-null
buenaflor Mar 17, 2025
12f5bb0
update logger
buenaflor Mar 17, 2025
1417799
update tests
buenaflor Mar 17, 2025
e4dbbee
update test
buenaflor Mar 17, 2025
2416389
clean up
buenaflor Mar 17, 2025
b02d0bc
update
buenaflor Mar 17, 2025
affdb82
fix integration test
buenaflor Mar 17, 2025
4478d8f
cleanup
buenaflor Mar 17, 2025
64b13f8
fix async
buenaflor Mar 18, 2025
39b0584
update CHANGELOG
buenaflor Mar 18, 2025
55bff4a
Merge branch 'main' into v9-feat/js-release-health
buenaflor Mar 18, 2025
0bc50b0
fix internal usage
buenaflor Mar 18, 2025
9d0c67a
fix test
buenaflor Mar 18, 2025
7335da5
clean up web session updater
buenaflor Mar 18, 2025
d0ae7cf
update CHANGELOG
buenaflor Mar 18, 2025
8877646
Merge branch 'main' into v9-feat/js-release-health
buenaflor Mar 18, 2025
92a6c63
update dart comment
buenaflor Mar 18, 2025
bd464a5
Update session updater to check for navigator observer registration
buenaflor Mar 18, 2025
8e609fe
remove commented lines
buenaflor Mar 18, 2025
8396c74
update test structure
buenaflor Mar 18, 2025
082f904
no op tests for native_channel
buenaflor Mar 18, 2025
599dbee
update docs
buenaflor Mar 18, 2025
81bb504
refactor to use integration
buenaflor Mar 19, 2025
df79dec
update naming
buenaflor Mar 20, 2025
50d51e0
update docs
buenaflor Mar 20, 2025
8825e1f
use enable function
buenaflor Mar 20, 2025
c20aff6
update tests
buenaflor Mar 20, 2025
22b3550
update sentry_flutter_test
buenaflor Mar 20, 2025
a933ce1
fix integration test
buenaflor Mar 20, 2025
f9d8b56
fix tests
buenaflor Mar 20, 2025
7bbe0e7
Update flutter/lib/src/integrations/web_session_integration.dart
buenaflor Mar 21, 2025
25b6df3
Merge branch 'main' into v9-feat/js-release-health
buenaflor Mar 21, 2025
75b66b3
update _isEnabled
buenaflor Mar 21, 2025
4f19ef1
remove web check, web session integration is only added for web anyway
buenaflor Mar 21, 2025
97cf12e
implement beforeSendEventObserver
buenaflor Mar 21, 2025
dc195fa
remove unnecessary test
buenaflor Mar 21, 2025
13051e7
formatting
buenaflor Mar 21, 2025
9df9352
cleanup
buenaflor Mar 21, 2025
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- Add support for Flutter Web release health ([#2794](https://github.com/getsentry/sentry-dart/pull/2794))
- Requires using `SentryNavigatorObserver`;
- Bump Native SDK from v0.7.20 to v0.8.2 ([#2761](https://github.com/getsentry/sentry-dart/pull/2761), [#2807](https://github.com/getsentry/sentry-dart/pull/2807))
- [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#082)
- [diff](https://github.com/getsentry/sentry-native/compare/0.7.20...0.8.2)
Expand Down
8 changes: 5 additions & 3 deletions dart/lib/sentry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
/// A pure Dart client for Sentry.io crash reporting.
library;

// ignore: invalid_export_of_internal_element
export 'src/constants.dart';
export 'src/event_processor.dart';
export 'src/exception_cause.dart';
export 'src/exception_cause_extractor.dart';
Expand All @@ -18,12 +20,14 @@ export 'src/hub_adapter.dart';
export 'src/integration.dart';
export 'src/noop_isolate_error_integration.dart'
if (dart.library.io) 'src/isolate_error_integration.dart';
// ignore: invalid_export_of_internal_element
export 'src/observers.dart';
export 'src/performance_collector.dart';
export 'src/runtime_checker.dart';
export 'src/protocol.dart';
export 'src/protocol/sentry_feedback.dart';
export 'src/protocol/sentry_proxy.dart';
export 'src/run_zoned_guarded_integration.dart';
export 'src/runtime_checker.dart';
export 'src/scope.dart';
export 'src/scope_observer.dart';
export 'src/sentry.dart';
Expand All @@ -34,8 +38,6 @@ export 'src/sentry_envelope.dart';
export 'src/sentry_envelope_item.dart';
export 'src/sentry_options.dart';
// ignore: invalid_export_of_internal_element
export 'src/constants.dart';
// ignore: invalid_export_of_internal_element
export 'src/sentry_trace_origins.dart';
export 'src/span_data_convention.dart';
export 'src/spotlight.dart';
Expand Down
18 changes: 18 additions & 0 deletions dart/lib/src/observers.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import 'dart:async';

import 'package:meta/meta.dart';

import '../sentry.dart';

// This file will contain observer definitions that are executed during
// specific points in the SDK such as as right before an event is sent.
// Only for internal use, e.g updating sessions only when an event is fully processed.
// Not to be confused with the public callbacks such as beforeSend.
// These should not mutate the data that is passed through the observer.

/// Called right before an event is sent, after all processing is complete.
/// Should not modify the event at this point.
@internal
abstract class BeforeSendEventObserver {
FutureOr<void> onBeforeSendEvent(SentryEvent event, Hint hint);
}
25 changes: 25 additions & 0 deletions dart/lib/src/sentry_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@
return _emptySentryId;
}

// Event is fully processed and ready to be sent, emit beforeSendEvent observer
await _emitBeforeSendEventObserver(preparedEvent, hint);

var attachments = List<SentryAttachment>.from(scope?.attachments ?? []);
attachments.addAll(hint.attachments);
var screenshot = hint.screenshot;
Expand Down Expand Up @@ -563,4 +566,26 @@
}
return DataCategory.error;
}

FutureOr<void> _emitBeforeSendEventObserver(
SentryEvent event, Hint hint) async {
for (final observer in _options.beforeSendEventObservers) {
try {
final result = observer.onBeforeSendEvent(event, hint);
if (result is Future) {
await result;
}
} catch (exception, stackTrace) {
_options.logger(

Check warning on line 579 in dart/lib/src/sentry_client.dart

View check run for this annotation

Codecov / codecov/patch

dart/lib/src/sentry_client.dart#L579

Added line #L579 was not covered by tests
SentryLevel.error,
'Error while running beforeSendEvent observer',
exception: exception,
stackTrace: stackTrace,
);
if (_options.automatedTestMode) {

Check warning on line 585 in dart/lib/src/sentry_client.dart

View check run for this annotation

Codecov / codecov/patch

dart/lib/src/sentry_client.dart#L585

Added line #L585 was not covered by tests
rethrow;
}
}
}
}
}
20 changes: 20 additions & 0 deletions dart/lib/src/sentry_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,26 @@
/// iOS only supports http proxies, while macOS also supports socks.
SentryProxy? proxy;

final List<BeforeSendEventObserver> _beforeSendEventObserver = [];

@internal
List<BeforeSendEventObserver> get beforeSendEventObservers =>
List.unmodifiable(_beforeSendEventObserver);

/// Adds an observer which is called right before an event is sent.
/// This should not be used to mutate the event.
///
/// Note: this is not triggered for transactions/spans with startTransaction or startChild.
@internal
void addBeforeSendEventObserver(BeforeSendEventObserver observer) {
_beforeSendEventObserver.add(observer);
}

@internal

Check warning on line 516 in dart/lib/src/sentry_options.dart

View check run for this annotation

Codecov / codecov/patch

dart/lib/src/sentry_options.dart#L516

Added line #L516 was not covered by tests
void removeBeforeSendEventObserver(BeforeSendEventObserver observer) {
_beforeSendEventObserver.remove(observer);

Check warning on line 518 in dart/lib/src/sentry_options.dart

View check run for this annotation

Codecov / codecov/patch

dart/lib/src/sentry_options.dart#L518

Added line #L518 was not covered by tests
}

SentryOptions({String? dsn, Platform? platform, RuntimeChecker? checker}) {
this.dsn = dsn;
if (platform != null) {
Expand Down
77 changes: 77 additions & 0 deletions dart/test/sentry_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2197,6 +2197,49 @@ void main() {
await client.captureEvent(fakeEvent, stackTrace: StackTrace.current);
});
});

group('beforeSendEvent observer', () {
late Fixture fixture;

setUp(() {
fixture = Fixture();
});

test('is called with correct event and hint', () async {
final observer = _TestEventObserver();
fixture.options.addBeforeSendEventObserver(observer);

final client = fixture.getSut();
final hint = Hint();
final event = SentryEvent().copyWith(type: 'some random type');

await client.captureEvent(event, hint: hint);

expect(observer.inspectedEvent?.type, 'some random type');
expect(observer.inspectedHint, same(hint));
});

test('is called after all other processors', () async {
final processingOrder = <String>[];
final beforeSend = (SentryEvent event, Hint hint) {
processingOrder.add('beforeSend');
return event;
};

fixture.options
.addBeforeSendEventObserver(_TestOrderObserver(processingOrder));

final client = fixture.getSut(
beforeSend: beforeSend,
eventProcessor: _TestEventProcessor(processingOrder));
final event = SentryEvent();

await client.captureEvent(event);

expect(
processingOrder, ['eventProcessor', 'beforeSend', 'beforeSendEvent']);
});
});
}

Future<SentryEvent> eventFromEnvelope(SentryEnvelope envelope) async {
Expand Down Expand Up @@ -2416,3 +2459,37 @@ class ExceptionWithStackTraceExtractor
return error.stackTrace;
}
}

class _TestEventObserver implements BeforeSendEventObserver {
SentryEvent? inspectedEvent;
Hint? inspectedHint;

@override
FutureOr<void> onBeforeSendEvent(SentryEvent event, Hint hint) {
inspectedEvent = event;
inspectedHint = hint;
}
}

class _TestEventProcessor extends EventProcessor {
_TestEventProcessor(this._processingOrder);

final List<String> _processingOrder;

@override
FutureOr<SentryEvent?> apply(SentryEvent event, Hint hint) {
_processingOrder.add('eventProcessor');
return event;
}
}

class _TestOrderObserver extends BeforeSendEventObserver {
final List<String> _processingOrder;

_TestOrderObserver(this._processingOrder);

@override
FutureOr<void> onBeforeSendEvent(SentryEvent event, Hint hint) {
_processingOrder.add('beforeSendEvent');
}
}
85 changes: 77 additions & 8 deletions flutter/example/integration_test/web_sdk_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,14 @@ void main() {
final client = _getClient()!;
final completer = Completer<List<Object?>>();

int count = 0;
JSFunction beforeEnvelopeCallback = ((JSArray envelope) {
final envelopDart = envelope.dartify() as List<Object?>;
completer.complete(envelopDart);
// the first envelope will be session envelope, we can ignore that
if (count == 1) {
completer.complete(envelopDart);
}
count += 1;
}).toJS;

client.on('beforeEnvelope'.toJS, beforeEnvelopeCallback);
Expand Down Expand Up @@ -155,22 +160,86 @@ void main() {
findsOneWidget,
);
});
});

group('disabled', () {
testWidgets('Sentry JS SDK is not initialized', (tester) async {
testWidgets('send session update if unhandled error', (tester) async {
await restoreFlutterOnErrorAfter(() async {
await SentryFlutter.init((options) {
options.dsn = fakeDsn;
options.autoInitializeNativeSdk = false;
}, appRunner: () async {
await tester.pumpWidget(const app.MyApp());
await tester.pumpWidget(
SentryWidget(child: const app.MyApp()),
);
});
});

expect(globalThis['Sentry'], isNull);
expect(() => _getClient(), throwsA(anything));
final client = _getClient()!;
final completer = Completer<Map<dynamic, dynamic>>();

JSFunction beforeSendSessionCallback = ((JSObject session) {
final sessionDart = session.dartify() as Map<dynamic, dynamic>;
completer.complete(sessionDart);
}).toJS;

client.on('beforeSendSession'.toJS, beforeSendSessionCallback);

final mechanism = Mechanism(type: 'FlutterError', handled: false);
final throwableMechanism =
ThrowableMechanism(mechanism, Exception('test'));
await Sentry.captureException(throwableMechanism);

final session =
await completer.future.timeout(const Duration(seconds: 5));

expect(session['status'], 'crashed');
expect(session['errors'], 1);
});

testWidgets('send session update if handled error and error count = 0',
(tester) async {
await restoreFlutterOnErrorAfter(() async {
await SentryFlutter.init((options) {
options.dsn = fakeDsn;
}, appRunner: () async {
await tester.pumpWidget(
SentryWidget(child: const app.MyApp()),
);
});
});

final client = _getClient()!;
final completer = Completer<Map<dynamic, dynamic>>();

JSFunction beforeSendSessionCallback = ((JSObject session) {
final sessionDart = session.dartify() as Map<dynamic, dynamic>;
completer.complete(sessionDart);
}).toJS;

client.on('beforeSendSession'.toJS, beforeSendSessionCallback);

await Sentry.captureException(Exception('test'));

final session =
await completer.future.timeout(const Duration(seconds: 5));

expect(session['status'], 'ok');
expect(session['errors'], 1);
});
});
});

group('disabled', () {
testWidgets('Sentry JS SDK is not initialized', (tester) async {
await restoreFlutterOnErrorAfter(() async {
await SentryFlutter.init((options) {
options.dsn = fakeDsn;
options.autoInitializeNativeSdk = false;
}, appRunner: () async {
await tester.pumpWidget(const app.MyApp());
});
});

expect(globalThis['Sentry'], isNull);
expect(() => _getClient(), throwsA(anything));
});
});
}
Loading
Loading