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.
This commit is contained in:
Vishesh Handa
2020-03-30 11:20:04 +02:00
parent 8c89f8a1b0
commit 7e0b073f32
2 changed files with 126 additions and 28 deletions

115
lib/error_reporting.dart Normal file
View File

@ -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<SentryClient> _initSentry() async {
return SentryClient(
dsn: 'https://35f34dbec289435fbe16483faacf49a5@sentry.io/5168082',
environmentAttributes: await _environmentEvent,
);
}
Future<SentryClient> getSentryClient() async {
return _sentryClient ??= await _initSentry();
}
Future<Event> 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<FlutterCrashlytics> getCrashlyticsClient() async {
return _crashlytics ??= await _initCrashlytics();
}
FlutterCrashlytics _crashlytics;
Future<FlutterCrashlytics> _initCrashlytics() async {
await FlutterCrashlytics().initialize();
return FlutterCrashlytics();
}
Future<void> 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);
}

View File

@ -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<dynamic>;
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<Future<void>>(() 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);
}