Skip to content
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

Improving error handling support for web and some improvements for sentry handler #32

Merged
merged 15 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
60 changes: 44 additions & 16 deletions lib/core/catcher_2.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,21 @@ class Catcher2 implements ReportModeAction {

void _configure(GlobalKey<NavigatorState>? 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 '
Expand Down Expand Up @@ -170,6 +173,7 @@ class Catcher2 implements ReportModeAction {
}

Future<void> _setupErrorHooks() async {
// FlutterError.onError catches SYNCHRONOUS errors for all platforms
FlutterError.onError = (details) async {
await _reportError(
details.exception,
Expand All @@ -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<dynamic>;
Expand All @@ -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<Future<void>>(
() 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);
},
),
);
}
}

Expand All @@ -216,7 +244,7 @@ class Catcher2 implements ReportModeAction {
}
}

void _initWidgetsBinding() {
void _initWidgetsBindingIfNeeded() {
if (ensureInitialized) {
WidgetsFlutterBinding.ensureInitialized();
}
Expand Down
26 changes: 19 additions & 7 deletions lib/handlers/sentry_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -21,6 +23,8 @@ class SentryHandler extends ReportHandler {

/// Sentry Client instance
final SentryClient sentryClient;
final String serverName;
final String loggerName;

/// User data
SentryUser? userContext;
Expand Down Expand Up @@ -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');
}
Expand All @@ -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) {
Expand All @@ -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 = '?';
Expand All @@ -111,12 +123,12 @@ class SentryHandler extends ReportHandler {

SentryEvent buildEvent(Report report, Map<String, dynamic> 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: '',
Expand Down
3 changes: 3 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -15,6 +17,7 @@ screenshots:
path: screenshots/3.png
- description: 'catcher_2 in action with dialogs'
path: screenshots/6.png

topics:
- analysis
- errors
Expand Down