Skip to content

Feat/dart default integrations #187

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

Merged
merged 33 commits into from
Nov 28, 2020
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
95b2f92
move isolateErrorIntegration and runZonedGuardedIntegration to sentry…
rxlabz Nov 25, 2020
df76b33
add isolateErrorIntegration and runZonedGuardedIntegration to default…
rxlabz Nov 25, 2020
22bf3cf
sentry-dart README
rxlabz Nov 25, 2020
df9df2c
changelog
rxlabz Nov 25, 2020
34151dd
changelog
rxlabz Nov 25, 2020
b165efb
Update dart/README.md
rxlabz Nov 25, 2020
71ce158
changelog
rxlabz Nov 25, 2020
8423efa
fix iOSPlatformCheck for FLutter Web
rxlabz Nov 25, 2020
d65c61e
feedback
rxlabz Nov 25, 2020
2275f3a
make the Sentry.init appRunner argument optional
rxlabz Nov 26, 2020
abe6c67
Update CHANGELOG.md
rxlabz Nov 26, 2020
3c07c9f
Merge branch 'main' into feat/dart-default-integrations
rxlabz Nov 26, 2020
44706b2
Update dart/lib/src/sentry.dart
rxlabz Nov 27, 2020
3658395
refacto rename callback to appRunner
rxlabz Nov 27, 2020
b2ba775
fix tests
rxlabz Nov 27, 2020
37d6371
SentryFlutter.init : runZoneGuardedIntegration is optional
rxlabz Nov 27, 2020
513898b
Merge remote-tracking branch 'origin/main' into feat/dart-default-int…
rxlabz Nov 27, 2020
bfddf96
SentryFlutter.init positional optional arguments
rxlabz Nov 27, 2020
986d95d
Sentry.init : add a initialIntegrations param
rxlabz Nov 27, 2020
d852ee6
AppRunner returns FutureOr<void>
rxlabz Nov 27, 2020
262d91e
Update flutter/README.md
rxlabz Nov 27, 2020
26c0f9d
dart example README
rxlabz Nov 27, 2020
e74acb9
dart example README
rxlabz Nov 27, 2020
dfc7a64
READMEs
rxlabz Nov 27, 2020
ad6130d
doc and README
rxlabz Nov 27, 2020
0f42f6a
rename callback to appRunner in tests
rxlabz Nov 27, 2020
57dfc45
Update dart/lib/src/sentry.dart
rxlabz Nov 27, 2020
c083456
Merge branch 'feat/dart-default-integrations' of github.com:getsentry…
rxlabz Nov 27, 2020
96c2f95
docs
rxlabz Nov 27, 2020
ee21afa
dart example : remove exit
rxlabz Nov 27, 2020
a33841b
doc safari stacktrace fails
rxlabz Nov 27, 2020
61a6f11
flutter example : duplicated button
rxlabz Nov 27, 2020
b4dc147
fix conflict
marandaneto Nov 28, 2020
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
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@

- Fix: StackTrace frames with 'package' uri.scheme are inApp by default #185
- Enhancement: add loadContextsIntegration tests
- StackTrace factory : package are inApp by default
- Fix: missing app's stack traces for Flutter errors
- Enhancement: add isolateErrorIntegration and runZonedGuardedIntegration to default integrations in sentry-dart

### Breaking changes

- `Sentry.init` and `SentryFlutter.init` have an optional callback argument which runs the host app after Sentry initialization.
- add loadContextsIntegration tests
- Ref: add missing docs and move sentry web plugin to the inner src folder
- Ref: Remove deprecated classes (Flutter Plugin for Android) and cleaning up #186
Expand Down
44 changes: 41 additions & 3 deletions dart/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,48 @@ import 'dart:async';
import 'package:sentry/sentry.dart';

Future<void> main() async {
await Sentry.init((options) {
options.dsn = 'https://[email protected]/add-your-dsn-here';
});
await Sentry.init(
(options) {
options.dsn = 'https://[email protected]/add-your-dsn-here';
},
initApp, // Init your App.
);
}

void initApp() {
try {
aMethodThatMightFail();
} catch (exception, stackTrace) {
await Sentry.captureException(
exception,
stackTrace: stackTrace,
);
}
}

void aMethodThatMightFail() {
throw null;
}
```

Or, if you don't want to run your app in its own error zone [runZonedGuarded] :

```dart
import 'dart:async';
import 'package:sentry/sentry.dart';

Future<void> main() async {
await Sentry.init(
(options) {
options.dsn = 'https://[email protected]/add-your-dsn-here';
},
);

// Init your App.
initApp();
}

void initApp() {
try {
aMethodThatMightFail();
} catch (exception, stackTrace) {
Expand Down
14 changes: 11 additions & 3 deletions dart/example/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

import 'dart:async';
import 'dart:io';

import 'package:sentry/sentry.dart';

Expand All @@ -17,9 +18,12 @@ Future<void> main() async {
SentryEvent processTagEvent(SentryEvent event, Object hint) =>
event..tags.addAll({'page-locale': 'en-us'});

await Sentry.init((options) => options
..dsn = dsn
..addEventProcessor(processTagEvent));
await Sentry.init(
(options) => options
..dsn = dsn
..addEventProcessor(processTagEvent),
runApp,
);

Sentry.addBreadcrumb(
Breadcrumb(
Expand Down Expand Up @@ -48,7 +52,9 @@ Future<void> main() async {
..setTag('build', '579')
..setExtra('company-name', 'Dart Inc');
});
}

void runApp() async {
print('\nReporting a complete event example: ');

// Sends a full Sentry event payload to show the different parts of the UI.
Expand Down Expand Up @@ -82,6 +88,8 @@ Future<void> main() async {
} finally {
await Sentry.close();
}

exit(0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this if we await the events to be sent?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, without it the dart process seems to never end

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dart does not end the process or it gets a deadlock somewhere in our SDK? worth checking

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems to be caused by the isolateErrorIntegration

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ I removed the exit call

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but the problem still exists or what? just to know if you found out or not

Copy link
Contributor Author

@rxlabz rxlabz Nov 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, the problem still exists. it's caused by the isolateErrorIntegration, but didn't found how to fix it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

Future<void> loadConfig() async {
Expand Down
33 changes: 18 additions & 15 deletions dart/example_web/web/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,15 @@ const dsn =
'https://[email protected]/5428562';

Future<void> main() async {
querySelector('#output').text = 'Your Dart app is running.';

querySelector('#btEvent')
.onClick
.listen((event) => captureCompleteExampleEvent());
querySelector('#btMessage').onClick.listen((event) => captureMessage());
querySelector('#btException').onClick.listen((event) => captureException());

await initSentry();
}

Future<void> initSentry() async {
SentryEvent processTagEvent(SentryEvent event, Object hint) =>
event..tags.addAll({'page-locale': 'en-us'});

await Sentry.init((options) => options
..dsn = dsn
..addEventProcessor(processTagEvent));
await Sentry.init(
(options) => options
..dsn = dsn
..addEventProcessor(processTagEvent),
runApp,
);

Sentry.addBreadcrumb(
Breadcrumb(
Expand Down Expand Up @@ -59,6 +50,18 @@ Future<void> initSentry() async {
});
}

void runApp() {
print('runApp');

querySelector('#output').text = 'Your Dart app is running.';

querySelector('#btEvent')
.onClick
.listen((event) => captureCompleteExampleEvent());
querySelector('#btMessage').onClick.listen((event) => captureMessage());
querySelector('#btException').onClick.listen((event) => captureException());
}

void captureMessage() async {
print('Capturing Message : ');
final sentryId = await Sentry.captureMessage(
Expand Down
7 changes: 5 additions & 2 deletions dart/lib/sentry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
// found in the LICENSE file.

/// A pure Dart client for Sentry.io crash reporting.
export 'src/default_integrations.dart';
export 'src/hub.dart';
export 'src/noop_isolate_error_integration.dart'
if (dart.library.io) 'src/isolate_error_integration.dart';
export 'src/protocol.dart';
export 'src/scope.dart';
export 'src/sentry.dart';
export 'src/sentry_client.dart';
export 'src/hub.dart';
export 'src/sentry_options.dart';
export 'src/transport/transport.dart';
// useful for integrations
export 'src/throwable_mechanism.dart';
export 'src/transport/transport.dart';
33 changes: 33 additions & 0 deletions dart/lib/src/default_integrations.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import 'dart:async';

import 'hub.dart';
import 'protocol.dart';
import 'sentry.dart';
import 'sentry_options.dart';
import 'throwable_mechanism.dart';

/// integration that capture errors on the runZonedGuarded error handler
Integration runZonedGuardedIntegration(
AppRunner appRunner,
) {
void integration(Hub hub, SentryOptions options) {
runZonedGuarded(() async {
await appRunner();
}, (exception, stackTrace) async {
// runZonedGuarded doesn't crash the App.
const mechanism = Mechanism(type: 'runZonedGuarded', handled: true);
final throwableMechanism = ThrowableMechanism(mechanism, exception);

final event = SentryEvent(
throwable: throwableMechanism,
level: SentryLevel.fatal,
);

await hub.captureEvent(event, stackTrace: stackTrace);
});

options.sdk.addIntegration('runZonedGuardedIntegration');
}

return integration;
}
51 changes: 51 additions & 0 deletions dart/lib/src/isolate_error_integration.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import 'dart:isolate';

import 'hub.dart';
import 'protocol.dart';
import 'sentry_options.dart';
import 'throwable_mechanism.dart';

/// integration that capture errors on the current Isolate Error handler
/// which is the main thread.
void isolateErrorIntegration(Hub hub, SentryOptions options) {
final receivePort = _createPort(hub, options);

Isolate.current.addErrorListener(receivePort.sendPort);

options.sdk.addIntegration('isolateErrorIntegration');
}

RawReceivePort _createPort(Hub hub, SentryOptions options) {
return RawReceivePort(
(dynamic error) async {
await handleIsolateError(hub, options, error);
},
);
}

/// Parse and raise an event out of the Isolate error.
/// Visible for testing.
Future<void> handleIsolateError(
Hub hub,
SentryOptions options,
dynamic error,
) async {
options.logger(SentryLevel.debug, 'Capture from IsolateError $error');

// https://api.dartlang.org/stable/2.7.0/dart-isolate/Isolate/addErrorListener.html
// error is a list of 2 elements
if (error is List<dynamic> && error.length == 2) {
final dynamic throwable = error.first;
final dynamic stackTrace = error.last;

// Isolate errors don't crash the App.
const mechanism = Mechanism(type: 'isolateError', handled: true);
final throwableMechanism = ThrowableMechanism(mechanism, throwable);
final event = SentryEvent(
throwable: throwableMechanism,
level: SentryLevel.fatal,
);

await hub.captureEvent(event, stackTrace: stackTrace);
}
}
5 changes: 5 additions & 0 deletions dart/lib/src/noop_isolate_error_integration.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import 'hub.dart';
import 'sentry_options.dart';

// noop web integration : isolate doesnt' work in browser
void isolateErrorIntegration(Hub hub, SentryOptions options) {}
56 changes: 55 additions & 1 deletion dart/lib/src/sentry.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import 'dart:async';

import 'default_integrations.dart';
import 'hub.dart';
import 'hub_adapter.dart';
import 'noop_hub.dart';
import 'noop_isolate_error_integration.dart'
if (dart.library.io) 'isolate_error_integration.dart';
import 'protocol.dart';
import 'sentry_client.dart';
import 'sentry_options.dart';
import 'utils.dart';

/// Configuration options callback
typedef OptionsConfiguration = FutureOr<void> Function(SentryOptions);

// run main app callback
typedef AppRunner = FutureOr<void> Function();

/// Sentry SDK main entry point
class Sentry {
static Hub _hub = NoOpHub();
Expand All @@ -20,11 +27,20 @@ class Sentry {
static Hub get currentHub => _hub;

/// Initializes the SDK
static Future<void> init(OptionsConfiguration optionsConfiguration) async {
/// passing a [AppRunner] callback allows to run the app within its own error zone (`runZonedGuarded`)
/// https://api.dart.dev/stable/2.10.4/dart-async/runZonedGuarded.html
static Future<void> init(
OptionsConfiguration optionsConfiguration, [
AppRunner appRunner,
List<Integration> initialIntegrations,
]) async {
if (optionsConfiguration == null) {
throw ArgumentError('OptionsConfiguration is required.');
}

final options = SentryOptions();
await _initDefaultValues(options, appRunner, initialIntegrations);

await optionsConfiguration(options);

if (options == null) {
Expand All @@ -34,6 +50,44 @@ class Sentry {
await _init(options);
}

static Future<void> _initDefaultValues(
SentryOptions options,
AppRunner appRunner,
List<Integration> initialIntegrations,
) async {
// if no environment is set, we set 'production' by default, but if we know it's
// a non-release build, or the SENTRY_ENVIRONMENT is set, we read from it.
if (const bool.hasEnvironment('SENTRY_ENVIRONMENT') || !isReleaseMode) {
options.environment = const String.fromEnvironment(
'SENTRY_ENVIRONMENT',
defaultValue: 'debug',
);
}

// if the SENTRY_DSN is set, we read from it.
options.dsn = const bool.hasEnvironment('SENTRY_DSN')
? const String.fromEnvironment('SENTRY_DSN')
: options.dsn;

if (initialIntegrations != null && initialIntegrations.isNotEmpty) {
initialIntegrations
.forEach((integration) => options.addIntegration(integration));
}

// Throws when running on the browser
if (!isWeb) {
// catch any errors that may occur within the entry function, main()
// in the ‘root zone’ where all Dart programs start
options.addIntegration(isolateErrorIntegration);
}

// finally the runZonedGuarded, catch any errors in Dart code running
// ‘outside’ the Flutter framework
if (appRunner != null) {
options.addIntegration(runZonedGuardedIntegration(appRunner));
}
}

/// Initializes the SDK
static Future<void> _init(SentryOptions options) async {
if (isEnabled) {
Expand Down
4 changes: 4 additions & 0 deletions dart/lib/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ String formatDateAsIso8601WithMillisPrecision(DateTime date) {

/// helper to detect a browser context
const isWeb = identical(0, 0.0);

/// helper to detect if app is in release mode
const isReleaseMode =
bool.fromEnvironment('dart.vm.product', defaultValue: false);
Loading