Skip to content

Conversation

@ioannisj
Copy link
Contributor

💡 Motivation and Context

Part of PostHog/posthog#38845

Base is currently #212. Will rebase once merged

Adds autocapture for Flutter framework and Dart platform unhandled exceptions

💚 How did you test it?

  • Unit tests: TODO

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

@ioannisj
Copy link
Contributor Author

Still need to address some feedback on #212, so just looking for a general review on the autocapture approach/config for now 🙏

Comment on lines +146 to +153
/// Enable automatic capture of unhandled exceptions
///
/// When enabled, PostHog will automatically capture:
/// - Flutter framework errors (FlutterError.onError)
/// - Dart runtime errors (PlatformDispatcher.onError)
///
/// Default: false
var captureUnhandledExceptions = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its confusing having this one and the other 2 more granular configs
i'd prefer to keep just the 2 granular ones

/// Controls whether `PlatformDispatcher.onError errors` are captured.
///
/// Default: true
var captureDartErrors = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should call this one something closer to the listener, eg capturePlatformDispatcherErrors and mention its the Dart errors in the comment docs

Comment on lines +171 to +187
/// Callback function to determine if an exception should be captured
///
/// Return true to capture the exception, false to ignore it.
/// If null, all exceptions will be captured (default behavior).
///
/// Example:
/// ```dart
/// shouldCaptureException: (error) {
/// // Don't capture test exceptions
/// if (error is TestException) return false;
/// // Don't capture StateError exceptions
/// if (error is StateError) return false;
/// // Capture all other exceptions
/// return true;
/// }
/// ```
bool Function(dynamic error)? shouldCaptureException;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should implement #155 instead of having another callback for the very same thing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah agreed. This was more off your comment in one of our calls that it would be good idea to check directly on error type before capturing an exception. Will remove

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should still do that, and we can do it via the before send as well, its not a blocker for this PR tho.
if we add this one now, its hard to remove later

/// ```
bool Function(dynamic error)? shouldCaptureException;

// We could skip for now, but we will need this on the native layer soon
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd remove this comment, adding to toMap wont hurt

/// - All other packages are inApp unless in inAppExcludes
var inAppByDefault = true;

/// Enable automatic capture of unhandled exceptions
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import '../posthog_flutter_platform_interface.dart';

/// Handles automatic capture of Flutter and Dart exceptions
class PostHogErrorTrackingAutoCaptureIntegration {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could create the Integration interface here and do it similar to android and ios, with the install, uninstall and setup methods

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipped since this is now just 1 integration, but I agree that the next integration to be added should follow that pattern?

);

if (config.captureUnhandledExceptions) {
_instance!.start();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avoid the use of !

Comment on lines +80 to +89
// Restore original handlers
if (_originalFlutterErrorHandler != null) {
FlutterError.onError = _originalFlutterErrorHandler;
_originalFlutterErrorHandler = null;
}

if (_originalPlatformErrorHandler != null) {
PlatformDispatcher.instance.onError = _originalPlatformErrorHandler;
_originalPlatformErrorHandler = null;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should restore to the default value regardless if they are null or not
https://github.com/PostHog/posthog-android/blob/3b565baef8b46510ca515a13f211e245edf1b4c6/posthog/src/main/java/com/posthog/errortracking/PostHogErrorTrackingAutoCaptureIntegration.kt#L65
otherwise, if _originalPlatformErrorHandler is null, we will never unhook our own handlers

_captureException(
error: details.exception,
stackTrace: details.stack,
handled: false,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that was the plan from our discussion in #212. Will do

}

void _posthogFlutterErrorHandler(FlutterErrorDetails details) {
// don't capture silent errors (could maybe be a config?)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

void _posthogFlutterErrorHandler(FlutterErrorDetails details) {
// don't capture silent errors (could maybe be a config?)
if (!details.silent) {
_captureException(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error: details.exception,
stackTrace: details.stack,
handled: false,
context: details.context?.toString(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see https://github.com/PostHog/posthog-flutter/pull/214/files#r2459538837
we should not just toString, check how we stringify the metadata, etc

Comment on lines +115 to +127
// Call the original handler
if (_originalFlutterErrorHandler != null) {
try {
_originalFlutterErrorHandler!(details);
} catch (e) {
// Pretty sure we should be doing this to avoid infinite loops
debugPrint(
'PostHog: Error in original FlutterError.onError handler: $e');
}
} else {
// If no original handler, use the default behavior (default is to dump to console)
FlutterError.presentError(details);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will swallow the error and modify the apps behaviour, we should just call the _originalFlutterErrorHandler and le do it what it should do
we should wrap in try catch calling our own handler if needed

/// Platform error handler for Dart runtime errors
void _setupPlatformErrorHandler() {
// prevent circular calls
if (PlatformDispatcher.instance.onError == _posthogPlatformErrorHandler) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was added not a long ago so i am not sure this is available from our min version which is now 3.22
if this was added after 3.22, we have 2 options
either increase the min version or do this https://github.com/getsentry/sentry-dart/blob/a69a51fd1695dd93024be80a50ad05dd990b2b82/packages/flutter/lib/src/utils/platform_dispatcher_wrapper.dart#L49
which is figure out at runtime if its available or not and only set the callback if its available at runtime

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iirc PlatformDispatcher.instance.onError does not work on flutter web, so it should be a no op
flutter/flutter#100277

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the alternative for web and older versions before having PlatformDispatcher.instance.onError is https://api.flutter.dev/flutter/dart-async/runZonedGuarded.html
https://github.com/getsentry/sentry-dart/blob/a69a51fd1695dd93024be80a50ad05dd990b2b82/packages/dart/lib/src/sentry_run_zoned_guarded.dart#L12
i'd avoid using runZonedGuarded if possible and only support the other things going forward

Comment on lines +148 to +157
// Call the original handler
if (_originalPlatformErrorHandler != null) {
try {
return _originalPlatformErrorHandler!(error, stackTrace);
} catch (e) {
debugPrint(
'PostHog: Error in original PlatformDispatcher.onError handler: $e');
return true; // Consider the error handled
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

Future<void> _captureException({
required dynamic error,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

always use Object or Object? if nullable, check the whole PR about this

}) {
return _posthog.captureException(
error: error,
stackTrace: stackTrace ?? StackTrace.current,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do not use StackTrace.current here, you need to figure this out inside of the captureException so you can set syntheetic or not

@@ -1,5 +1,6 @@
import 'package:meta/meta.dart';

import 'exceptions/posthog_error_tracking_autocapture_integration.dart';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets make the folder called errortracking which is the name of the product, and focus on this name for naming our things instead of just exceptions

return _posthog.setup(config);
}

void installFlutterIntegrations(PostHogConfig config) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be private

}
}

void uninstallFlutterIntegrations() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

@marandaneto
Copy link
Member

missing the Isolate autocapture
see https://github.com/getsentry/sentry-dart/blob/a69a51fd1695dd93024be80a50ad05dd990b2b82/packages/dart/lib/src/isolate_error_integration.dart#L8
this is also IO only (not web), so you have to do dynamic imports

error: error,
stackTrace: stackTrace,
handled: false,
context: 'Platform error');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants