From 7e0b073f32fd9604d49a9033f374d1baa410e69f Mon Sep 17 00:00:00 2001 From: Vishesh Handa Date: Mon, 30 Mar 2020 11:20:04 +0200 Subject: [PATCH] Move error reporting to its own file * Avoid passing Flutter errors to its Zone handler, this doesn't always catch the errors * Catch the current isolate's errors. I haven't been able to test this out, but lets see. --- lib/error_reporting.dart | 115 +++++++++++++++++++++++++++++++++++++++ lib/main.dart | 39 ++++--------- 2 files changed, 126 insertions(+), 28 deletions(-) create mode 100644 lib/error_reporting.dart diff --git a/lib/error_reporting.dart b/lib/error_reporting.dart new file mode 100644 index 00000000..191e7af4 --- /dev/null +++ b/lib/error_reporting.dart @@ -0,0 +1,115 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:device_info/device_info.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_crashlytics/flutter_crashlytics.dart'; +import 'package:gitjournal/app.dart'; +import 'package:gitjournal/settings.dart'; +import 'package:package_info/package_info.dart'; +import 'package:sentry/sentry.dart'; + +SentryClient _sentryClient; +Future _initSentry() async { + return SentryClient( + dsn: 'https://35f34dbec289435fbe16483faacf49a5@sentry.io/5168082', + environmentAttributes: await _environmentEvent, + ); +} + +Future getSentryClient() async { + return _sentryClient ??= await _initSentry(); +} + +Future get _environmentEvent async { + final packageInfo = await PackageInfo.fromPlatform(); + final deviceInfoPlugin = DeviceInfoPlugin(); + OperatingSystem os; + Device device; + if (Platform.isAndroid) { + final androidInfo = await deviceInfoPlugin.androidInfo; + os = OperatingSystem( + name: 'android', + version: androidInfo.version.release, + ); + device = Device( + model: androidInfo.model, + manufacturer: androidInfo.manufacturer, + modelId: androidInfo.product, + ); + } else if (Platform.isIOS) { + final iosInfo = await deviceInfoPlugin.iosInfo; + os = OperatingSystem( + name: iosInfo.systemName, + version: iosInfo.systemVersion, + ); + device = Device( + model: iosInfo.utsname.machine, + family: iosInfo.model, + manufacturer: 'Apple', + ); + } + final environment = Event( + release: '${packageInfo.version} (${packageInfo.buildNumber})', + contexts: Contexts( + operatingSystem: os, + device: device, + app: App( + name: packageInfo.appName, + version: packageInfo.version, + build: packageInfo.buildNumber, + ), + ), + ); + return environment; +} + +void flutterOnErrorHandler(FlutterErrorDetails details) { + if (reportCrashes == true) { + // vHanda: This doesn't always call our zone error handler, why? + // Zone.current.handleUncaughtError(details.exception, details.stack); + reportError(details.exception, details.stack); + } else { + FlutterError.dumpErrorToConsole(details); + } +} + +bool get reportCrashes => _reportCrashes ??= _initReportCrashes(); +bool _reportCrashes; +bool _initReportCrashes() { + return !JournalApp.isInDebugMode && Settings.instance.collectCrashReports; +} + +Future getCrashlyticsClient() async { + return _crashlytics ??= await _initCrashlytics(); +} + +FlutterCrashlytics _crashlytics; +Future _initCrashlytics() async { + await FlutterCrashlytics().initialize(); + return FlutterCrashlytics(); +} + +Future reportError(Object error, StackTrace stackTrace) async { + if (reportCrashes) { + try { + final sentry = await getSentryClient(); + sentry.captureException( + exception: error, + stackTrace: stackTrace, + ); + } catch (e) { + print("Failed to report with Sentry: $e"); + } + + try { + final crashlytics = await getCrashlyticsClient(); + crashlytics.reportCrash(error, stackTrace, forceCrash: false); + } catch (e) { + print("Failed to report with Crashlytics: $e"); + } + } + + print("Uncaught Exception: $error"); + print(stackTrace); +} diff --git a/lib/main.dart b/lib/main.dart index bfe208d0..8cd7a045 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,10 +1,10 @@ import 'dart:async'; +import 'dart:isolate'; import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart' as foundation; -import 'package:flutter_crashlytics/flutter_crashlytics.dart'; -import 'package:sentry/sentry.dart'; +import 'package:gitjournal/error_reporting.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:gitjournal/app.dart'; @@ -17,35 +17,18 @@ void main() async { Settings.instance.load(pref); JournalApp.isInDebugMode = foundation.kDebugMode; - var reportCrashes = - !JournalApp.isInDebugMode && Settings.instance.collectCrashReports; + FlutterError.onError = flutterOnErrorHandler; - FlutterError.onError = (FlutterErrorDetails details) { - if (!reportCrashes) { - FlutterError.dumpErrorToConsole(details); - } else { - Zone.current.handleUncaughtError(details.exception, details.stack); - } - }; + Isolate.current.addErrorListener(RawReceivePort((dynamic pair) async { + var isolateError = pair as List; + assert(isolateError.length == 2); + assert(isolateError.first.runtimeType == Error); + assert(isolateError.last.runtimeType == StackTrace); - print("Report Crashes: $reportCrashes"); - if (reportCrashes) { - await FlutterCrashlytics().initialize(); - } - - var sentry = SentryClient( - dsn: 'https://35f34dbec289435fbe16483faacf49a5@sentry.io/5168082', - ); + await reportError(isolateError.first, isolateError.last); + }).sendPort); runZoned>(() async { await JournalApp.main(pref); - }, onError: (Object error, StackTrace stackTrace) async { - print("Uncaught Exception: " + error.toString()); - print(stackTrace); - FlutterCrashlytics().reportCrash(error, stackTrace, forceCrash: false); - sentry.captureException( - exception: error, - stackTrace: stackTrace, - ); - }); + }, onError: reportError); }