diff --git a/lib/core/catcher_2.dart b/lib/core/catcher_2.dart index 9a94c31..d63055d 100644 --- a/lib/core/catcher_2.dart +++ b/lib/core/catcher_2.dart @@ -79,18 +79,21 @@ class Catcher2 implements ReportModeAction { void _configure(GlobalKey? navigatorKey) { _instance = this; - _initWidgetsBinding(); _configureNavigatorKey(navigatorKey); _setupCurrentConfig(); _configureLogger(); _setupReportModeActionInReportMode(); _setupScreenshotManager(); + _setupErrorHooks(); + + _initWidgetBindingAndRunApp(); + + // Loading device and application info requires that the widgets binding is + // initialized so we need to run it after we init WidgetsFlutterBinding. _loadDeviceInfo(); _loadApplicationInfo(); - _setupErrorHooks(); - if (_currentConfig.handlers.isEmpty) { _logger.warning( 'Handlers list is empty. Configure at least one handler to ' @@ -170,6 +173,7 @@ class Catcher2 implements ReportModeAction { } Future _setupErrorHooks() async { + // FlutterError.onError catches SYNCHRONOUS errors for all platforms FlutterError.onError = (details) async { await _reportError( details.exception, @@ -178,14 +182,20 @@ class Catcher2 implements ReportModeAction { ); _currentConfig.onFlutterError?.call(details); }; - PlatformDispatcher.instance.onError = (error, stack) { - _reportError(error, stack); - _currentConfig.onPlatformError?.call(error, stack); - return true; - }; + + // PlatformDispatcher.instance.onError catches ASYNCHRONOUS errors, but it + // does not work for web, most likely due to this issue: + // https://github.com/flutter/flutter/issues/100277 + if (!kIsWeb) { + PlatformDispatcher.instance.onError = (error, stack) { + _reportError(error, stack); + _currentConfig.onPlatformError?.call(error, stack); + return true; + }; + } /// Web doesn't have Isolate error listener support - if (!ApplicationProfileManager.isWeb()) { + if (!kIsWeb) { Isolate.current.addErrorListener( RawReceivePort((pair) async { final isolateError = pair as List; @@ -195,14 +205,32 @@ class Catcher2 implements ReportModeAction { ); }).sendPort, ); + } + } + + void _initWidgetBindingAndRunApp() { + if (!kIsWeb) { + // This isn't web, we can just run the app, no need for runZoneGuarded + // since async errors are caught by PlatformDispatcher.instance.onError. + _initWidgetsBindingIfNeeded(); _runApp(); } else { - // Due to https://github.com/flutter/flutter/issues/100277 - // this is still needed... As soon as proper error catching support - // for Web is implemented, this branch should be removed and just - // _runApp should be called, the same as in the other branch - // without the Isolate[...] stuff. - runZonedGuarded(_runApp, _reportError); + // We are in a web environment so we need runZoneGuarded to catch async + // exceptions. + unawaited( + runZonedGuarded>( + () async { + // It is important that we run init widgets binding inside the + // runZonedGuarded call to be able to catch the async execeptions. + _initWidgetsBindingIfNeeded(); + _runApp(); + }, + (error, stack) { + _reportError(error, stack); + _currentConfig.onPlatformError?.call(error, stack); + }, + ), + ); } } @@ -216,7 +244,7 @@ class Catcher2 implements ReportModeAction { } } - void _initWidgetsBinding() { + void _initWidgetsBindingIfNeeded() { if (ensureInitialized) { WidgetsFlutterBinding.ensureInitialized(); } diff --git a/lib/handlers/sentry_handler.dart b/lib/handlers/sentry_handler.dart index 855f6d6..aac6ac0 100644 --- a/lib/handlers/sentry_handler.dart +++ b/lib/handlers/sentry_handler.dart @@ -10,6 +10,8 @@ import 'package:sentry/sentry.dart'; class SentryHandler extends ReportHandler { SentryHandler( this.sentryClient, { + this.serverName = 'Catcher 2', + this.loggerName = 'Catcher 2', this.userContext, this.enableDeviceParameters = true, this.enableApplicationParameters = true, @@ -21,6 +23,8 @@ class SentryHandler extends ReportHandler { /// Sentry Client instance final SentryClient sentryClient; + final String serverName; + final String loggerName; /// User data SentryUser? userContext; @@ -66,11 +70,12 @@ class SentryHandler extends ReportHandler { // and the code relies on File from dart:io that does not work in web // either because we do not have access to the file system in web. SentryAttachment? screenshotAttachment; + File? screenshotFile; try { if (report.screenshot != null && !kIsWeb) { final screenshotPath = report.screenshot!.path; - final file = File(screenshotPath); - final bytes = await file.readAsBytes(); + screenshotFile = File(screenshotPath); + final bytes = await screenshotFile.readAsBytes(); screenshotAttachment = SentryAttachment.fromScreenshotData(bytes); _printLog('Created screenshot attachment'); } @@ -86,6 +91,12 @@ class SentryHandler extends ReportHandler { : null, ); + if (screenshotFile != null) { + // Cleanup screenshot file after submission to save space on device. + await screenshotFile.delete(); + _printLog('Screenshot file removed from device (cleanup)'); + } + _printLog('Logged to sentry!'); return true; } catch (exception, stackTrace) { @@ -98,10 +109,11 @@ class SentryHandler extends ReportHandler { var applicationVersion = ''; final applicationParameters = report.applicationParameters; if (applicationParameters.containsKey('appName')) { - applicationVersion += (applicationParameters['appName'] as String?)!; + applicationVersion += + (applicationParameters['appName'] as String?)!.toLowerCase(); } if (applicationParameters.containsKey('version')) { - applicationVersion += " ${applicationParameters["version"]}"; + applicationVersion += "@${applicationParameters["version"]}"; } if (applicationVersion.isEmpty) { applicationVersion = '?'; @@ -111,12 +123,12 @@ class SentryHandler extends ReportHandler { SentryEvent buildEvent(Report report, Map tags) => SentryEvent( - logger: 'Catcher 2', - serverName: 'Catcher 2', + logger: loggerName, + serverName: serverName, release: customRelease ?? _getApplicationVersion(report), environment: customEnvironment ?? (report.applicationParameters['environment'] as String?), - message: const SentryMessage('Error handled by Catcher 2'), + message: SentryMessage(report.error.toString()), throwable: report.error, level: SentryLevel.error, culprit: '', diff --git a/pubspec.yaml b/pubspec.yaml index 72adc37..2ea40d9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,8 +4,10 @@ version: 1.3.0 homepage: https://github.com/ThexXTURBOXx/catcher_2 repository: https://github.com/ThexXTURBOXx/catcher_2 issue_tracker: https://github.com/ThexXTURBOXx/catcher_2/issues + funding: - https://github.com/sponsors/ThexXTURBOXx + screenshots: - description: 'The catcher_2 logo' path: screenshots/logo.png @@ -15,6 +17,7 @@ screenshots: path: screenshots/3.png - description: 'catcher_2 in action with dialogs' path: screenshots/6.png + topics: - analysis - errors