Thanks for this newly developed package sentry_flutter! However, I see that some exceptions are not caught...
_Platform:_
_IDE:_
_split-debug-info and obfuscate (Flutter Android or iOS) or CanvasKit (Flutter Web):_
_Platform installed with:_
Output of the command flutter doctor -v below:
[✓] Flutter (Channel stable, 1.22.5, on Mac OS X 10.15.7 19H2 darwin-x64, locale zh-Hans-CN)
• Flutter version 1.22.5 at /Users/tom/opt/flutter
• Framework revision 7891006299 (4 weeks ago), 2020-12-10 11:54:40 -0800
• Engine revision ae90085a84
• Dart version 2.10.4
• Pub download mirror https://pub.flutter-io.cn
• Flutter download mirror https://storage.flutter-io.cn
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
• Android SDK at /Users/tom/Library/Android/sdk
• Platform android-30, build-tools 29.0.3
• ANDROID_SDK_ROOT = /Users/tom/Library/Android/sdk
• Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build 1.8.0_212-release-1586-b4-5784211)
• All Android licenses accepted.
[✓] Xcode - develop for iOS and macOS (Xcode 12.3)
• Xcode at /Applications/Xcode.app/Contents/Developer
• Xcode 12.3, Build version 12C33
• CocoaPods version 1.10.0
[✓] Android Studio (version 3.6)
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin version 48.1.1
• Dart plugin version 192.8052
• Java version OpenJDK Runtime Environment (build 1.8.0_212-release-1586-b4-5784211)
[!] IntelliJ IDEA Ultimate Edition (version 2020.3)
• IntelliJ at /Users/tom/Applications/JetBrains Toolbox/IntelliJ IDEA Ultimate.app
✗ Flutter plugin not installed; this adds Flutter specific functionality.
✗ Dart plugin not installed; this adds Dart specific functionality.
• For information about installing plugins, see
https://flutter.dev/intellij-setup/#installing-the-plugins
[✓] VS Code (version 1.52.1)
• VS Code at /Applications/Visual Studio Code.app/Contents
• Flutter extension version 3.18.0
[✓] Proxy Configuration
• HTTP_PROXY is set
• NO_PROXY is 127.0.0.1,localhost
• NO_PROXY contains 127.0.0.1
• NO_PROXY contains localhost
[✓] Connected device (2 available)
• JSN AL00a (mobile) • 7XBNW19215005473 • android-arm64 • Android 10 (API
29)
• iPhone SE (2nd generation) (mobile) • 41640072-3F6C-45F1-A766-86FFCF6BD773 • ios •
com.apple.CoreSimulator.SimRuntime.iOS-14-3 (simulator)
! Error: iPhone has recently restarted. Xcode will continue when iPhone is unlocked. (code -14)
The version of the SDK (See pubspec.lock):
4.0.1
I have the following issue:
The description goes here ...
Steps to reproduce:
Add the following lines into the flutter/example/lib/main.dart. (Actually only 1 core line)
RaisedButton(
child: const Text('Throws in Future.delayed'),
onPressed: () =>
Future.delayed(Duration(milliseconds: 100), () => throw Exception('Throws in Future.delayed')), // <-- THIS IS THE CRUCIAL LINE!
),
RaisedButton(
child: const Text('Throws in Future.then'),
onPressed: () =>
Future.delayed(Duration(milliseconds: 100)).then((_) => throw Exception('Throws in Future.then')),
),
Then press it.
Actual result:
The exception is not caught. This is confirmed by: (1) From the console we see it is "unhandled" exception. (2) not see from Sentry webpage, while other exceptions are happily seen.
Example log:
Restarted application in 713ms.
flutter: [debug] release: [email protected]+3
[VERBOSE-2:ui_dart_state.cc(177)] Unhandled Exception: Exception: Throws in Future.delayed
#0 MainScaffold.build.<anonymous closure>.<anonymous closure> (package:sentry_flutter_example/main.dart:64:69)
#1 new Future.delayed.<anonymous closure> (dart:async/future.dart:326:39)
#2 _rootRun (dart:async/zone.dart:1182:47)
#3 _CustomZone.run (dart:async/zone.dart:1093:19)
#4 _CustomZone.runGuarded (dart:async/zone.dart:997:7)
#5 _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1037:23)
#6 _rootRun (dart:async/zone.dart:1190:13)
#7 _CustomZone.run (dart:async/zone.dart:1093:19)
#8 _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:1021:23)
#9 Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:18:15)
#10 _Timer._runTimers (dart:isolate-patch/timer_impl.dart:397:19)
#11 _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:428:5)
#12 _RawReceivePortImpl._handleMessage (dart:isolate<…>
(and for the Future.then case:)
[VERBOSE-2:ui_dart_state.cc(177)] Unhandled Exception: Exception: Throws in Future.then
#0 MainScaffold.build.<anonymous closure>.<anonymous closure> (package:sentry_flutter_example/main.dart:69:75)
#1 _rootRunUnary (dart:async/zone.dart:1198:47)
#2 _CustomZone.runUnary (dart:async/zone.dart:1100:19)
#3 _FutureListener.handleValue (dart:async/future_impl.dart:143:18)
#4 Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:696:45)
#5 Future._propagateToListeners (dart:async/future_impl.dart:725:32)
#6 Future._complete (dart:async/future_impl.dart:519:7)
#7 new Future.delayed.<anonymous closure> (dart:async/future.dart:323:16)
#8 _rootRun (dart:async/zone.dart:1182:47)
#9 _CustomZone.run (dart:async/zone.dart:1093:19)
#10 _CustomZone.runGuarded (dart:async/zone.dart:997:7)
#11 _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1037:23)
#12 _rootRun (dart:async/zone.dart:1190:13)
#13 _Custom<…>
Expected result:
The exception should be caught.
@fzyzcjy thanks for raising this, See: https://docs.sentry.io/platforms/dart/usage/advanced-usage/#tips-for-catching-errors
Use a catchError block for Futures
https://github.com/getsentry/sentry-dart/blob/main/flutter/example/lib/main.dart#L96-L98
This is how the uncaught handler for Dart/Flutter errors work, unfortunately.
Hmm so you mean this is impossible to be caught globally? :(
From my experience this should be caught by zone error handler in RunZonedGuardedIntegration, or am missing something?
From my experience this should be caught by zone error handler in
RunZonedGuardedIntegration, or am missing something?
I think so too.
The docs suggests that it should work.
@kuhnroyal @ueman I also think so. But after experiment (with adding several lines to the official example code), I see those stacktraces, indicating it does not :(
Thus maybe this is a bug?
we are only able to add an error handler to the current Isolate, which is the "main thread", the Futures have its own Zone/Isolates iirc and you'd need to catch yourself.
I might be wrong, but that's what I experienced when testing it out, also I found similar issues on the Flutter repo.
The official example also has a RunZonedGuarded and they still do catchError.
If you remove catchError and add a breakpoint to the Isolate integration or RunZonedGuarded integration, they are never triggered as well.
Then something is wrong, with 3.0.0 I have a manual zone guard and it triggers.
Then something is wrong, with 3.0.0 I have a manual zone guard and it triggers.
@kuhnroyal with a Future.delayed or microtask? triggered by an onPressed? which passes by the Flutter error thingie.
I just tried this and it gets captures:
ListTile(
title: const Text('Send delayed Flutter error to Sentry'),
onTap: () {
Future.delayed(Duration(seconds: 2), () {
throw ArgumentError('delayed');
});
// _logger.severe('Flutter sample error', ArgumentError('dummy'));
},
),
However I am using Chain.capture() from https://pub.dev/packages/stack_trace instead.
But yea, this also works:
``dart
Future<void> main() async {
runZonedGuarded(() {
Future.delayed(Duration(seconds: 2), () {
throw ArgumentError('dummy');
});
}, (error, _) {
print(error);
});
}
````
Prints:flutter: Invalid argument(s): dummy`
@kuhnroyal So could you reproduce my sample, or does my sample behave differently in your machine? If the former, maybe something related to Chain.capture, or the integration of onTap + Future?
No, I need to clarify:
My first test was with version 3.0.1 and it gets captured in the zone error handler.
My second test was with plain Dart and it also gets captured in the zone error handler.
This leads me to the conclusion that something with the zone setup is broken in 4.x
I have not tried the 4.x in a complex setup yet, but your issue seem to suggest that it does not work.
Do you have any custom integrations enabled that might mess with the RunZonedGuardedIntegration?
@kuhnroyal No custom integrations. I do nothing but:
example folder. edit the code as I have shown in my questionMy second test was with plain Dart and it also gets captured in the zone error handler.
I think that's the difference, when there's Flutter onError involved etc.
I will try to test once more or see if there's any workaround, but I do remember testing this out and it never got triggered when there was runZonedGuarded, Flutter onError and Futures.
probably related to https://github.com/flutter/flutter/issues/48972
I have used this with 3.0.1 for a long time and it always works, the linked issue is new to me, never experienced this.
main() {
Flutter.onError = (FlutterErrorDetails details) async {
// this is not called in release mode
FlutterError.dumpErrorToConsole(details);
...
}
if (!kIsWeb) {
Isolate.current.addErrorListener(RawReceivePort((dynamic pair) async {
....
}).sendPort);
}
runZonedGuarded(() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}, (error, _) {
...
});
}
From some comments there, it seems that the order of WidgetsFlutterBinding.ensureInitialized(); matters.
Basically https://github.com/flutter/flutter/issues/48972#issuecomment-704965782 sums up my experience with this.
WidgetsFlutterBinding.ensureInitialized(); should be called in advance, because we need the bindings initialized in order to read the release version and other things from the Native SDKs.
Will run some tests.
I can confirm that if I add WidgetsFlutterBinding.ensureInitialized(); inside of the runZonedGuarded it works, but, WidgetsFlutterBinding.ensureInitialized(); can't be called before that, otherwise, it doesn't work either.
Calling ensureInitialized before when needed and inside of the runZonedGuarded also does not work.
The thing is, as stated above https://github.com/getsentry/sentry-dart/issues/263#issuecomment-755940675 we do need to call ensureInitialized (before runZonedGuarded is run) so not sure how the solution would be, my first idea would be to really wrap the whole SDK init. in a runZonedGuarded but this looks wrong and we'd kill the option that people would like to have their own runZonedGuarded, so it's unviable I guess.
I also came up with this test after reading https://github.com/flutter/flutter/issues/48972 and https://github.com/flutter/flutter/issues/72351
I will try to research a bit more.
Basically, if the RunZonedGuardedIntegration (or a custom zone integration) is used, most other integrations have to be started from inside this one.
Basically, if the
RunZonedGuardedIntegration(or a custom zone integration) is used, most other integrations have to be started from inside this one.
it's not that simple, it'd still falls to:
we'd kill the option that people would like to have their own runZonedGuarded
Yea thats why I said (or a custom zone integration) :) I use one myself.
Could be flagged somehow or be of a specific type.
a workaround for now, would be to do something like:
Future<void> main() async {
runZonedGuarded(() async {
// init the SDK inside of your own runZonedGuarded, so
// WidgetsFlutterBinding.ensureInitialized() gets called inside of the runZonedGuarded.
await SentryFlutter.init(
(options) {
options.dsn = _exampleDsn;
options.useNativeBreadcrumbTracking();
},
);
// run your own App, usually done by the 'appRunner' Sentry callback.
// do not use the 'appRunner'
runApp(MyApp());
}, (exception, stackTrace) async {
// mimics RunZonedGuardedIntegration capture method
final mechanism = Mechanism(type: 'runZonedGuarded', handled: true);
final throwableMechanism = ThrowableMechanism(mechanism, exception);
final event = SentryEvent(
throwable: throwableMechanism,
level: SentryLevel.fatal,
);
await Sentry.captureEvent(event, stackTrace: stackTrace);
});
}
TLDR:
WidgetsFlutterBinding.ensureInitialized() is the 1st thing to be run when SentryFlutter.init is called.
RunZonedGuardedIntegration has its own runZonedGuarded which executes the AppRunner callback, this is the last integration to be run, after the SDK is fully initialized.
This causes issues as described above, eg Futures are not going thru the uncaught handler from runZonedGuarded.
Maybe it's worth taking a look how the following packages handled this problem:
Well the problem stems from the way integrations are handled, that is pretty unique to this package.
After thinking on this for a while I only see one way unless the Flutter issues get magically solved.
RunZonedGuardedIntegration (to allow custom subclasses)NoZonedGuardedIntegrationRunZonedGuardedIntegration from appRunnerRunZonedGuardedIntegration/NoZonedGuardedIntegration before anything else, call WidgetsFlutterBinding.ensureInitialized() and run all other integration from withinAppRunnerIntegration that is always run last (maybe also allowing custom subclasses)Hi friends, is there any plan of fixing this in this package? This is kind of serious problem :( Today I find my production environment has some exceptions but not caught and not sent to sentry due to this problem. Currently I do this based on the reply above. But this is just a temporary hack, so other users will still suffer from the bug
// Use this as if using SentryFlutter.init
Future<void> hackySentryFlutterInit(FlutterOptionsConfiguration optionsConfiguration, {AppRunner appRunner}) async {
await runZonedGuarded(() async {
// init the SDK inside of your own runZonedGuarded, so
// WidgetsFlutterBinding.ensureInitialized() gets called inside of the runZonedGuarded.
await SentryFlutter.init(optionsConfiguration);
// run your own App, usually done by the 'appRunner' Sentry callback.
// do not use the 'appRunner'
appRunner();
}, (exception, stackTrace) async {
// mimics RunZonedGuardedIntegration capture method
final mechanism = Mechanism(type: 'runZonedGuarded', handled: true);
final throwableMechanism = ThrowableMechanism(mechanism, exception);
final event = SentryEvent(
throwable: throwableMechanism,
level: SentryLevel.fatal,
);
await Sentry.captureEvent(event, stackTrace: stackTrace);
});
}
We'll prioritize a fix for this, but until we get around shipping a new version with a fix, please refer to the work around @marandaneto wrote above:
https://github.com/getsentry/sentry-dart/issues/263#issuecomment-756090046
Oh my god, I just wasted hours on this. For me the issue was that errors thrown in functions called by webview_flutter weren't being caught. Once I finally figured out what the issue was, I ended up creating a fix similar to the comment above, which works in my case too.
Oh my god, I just wasted hours on this. For me the issue was that errors thrown in functions called by webview_flutter weren't being caught. Once I finally figured out what the issue was, I ended up creating a fix similar to the comment above, which works in my case too.
I'm sorry about that.
we'll be looking into it shortly, the workaround works but it should be fixed definitely. trying to ramp up someone on it this week or next week latest, I'm taking care of a few other things atm.
I'm sorry about that.
we'll be looking into it shortly, the workaround works but it should be fixed definitely. trying to ramp up someone on it this week or next week latest, I'm taking care of a few other things atm.
No worries, I didn't mean to blame you guys, I appreciate this new flutter library. It's completely normal that new libraries come with some bugs. I was just a bit frustrated ;) Thanks for looking into it!
sure, thanks for the support & patience :)
The issue is fixed, sorry for taking so long, thanks once more @denrase
See the code snippets using Sentry's 'runZoneGuarded' integration and a custom 'runZonedGuarded'.
With Sentry's 'runZoneGuarded' integration, that's all you need to do:
Future<void> main() async {
await SentryFlutter.init(
(options) {
options.dsn = _exampleDsn;
},
// Init your App.
appRunner: () => runApp(MyApp()),
);
}
Without Sentry's 'runZoneGuarded' integration, you need to init. Sentry within your custom 'runZonedGuarded'
Future<void> main() async {
runZonedGuarded(() async {
// Sentry should be init. within the 'runZonedGuarded' that 'runApp' is run,
// so WidgetsFlutterBinding.ensureInitialized() is called correctly.
// See here that we don't pass the 'appRunner' arg, so you must run 'runApp' yourself.
await SentryFlutter.init(
(options) {
options.dsn = _exampleDsn;
},
);
runApp(MyApp());
}, (exception, stackTrace) async {
await Sentry.captureException(exception, stackTrace: stackTrace);
});
}
In both cases, Future errors will be caught automatically.
I'll be releasing a version shortly
Most helpful comment
https://github.com/getsentry/sentry-dart/releases/tag/4.0.4