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

Provide a way to cause an example native crash from Flutter #2239

Merged
merged 10 commits into from
Aug 21, 2024
Merged
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Features

- Add `SentryFlutter.nativeCrash()` using MethodChannels for Android and iOS ([#2239](https:/getsentry/sentry-dart/pull/2239))
martinhaintz marked this conversation as resolved.
Show resolved Hide resolved
- This can be used to test if native crash reporting works
- Add `ignoreRoutes` parameter to `SentryNavigatorObserver`. ([#2218](https:/getsentry/sentry-dart/pull/2218))
- This will ignore the Routes and prevent the Route from being pushed to the Sentry server.
- Ignored routes will also create no TTID and TTFD spans.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.sentry.flutter
import android.app.Activity
import android.content.Context
import android.os.Build
import android.os.Looper
import android.util.Log
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
Expand Down Expand Up @@ -49,8 +50,8 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {

sentryFlutter =
SentryFlutter(
androidSdk = androidSdk,
nativeSdk = nativeSdk,
androidSdk = ANDROID_SDK,
nativeSdk = NATIVE_SDK,
)
}

Expand All @@ -74,6 +75,7 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
"removeTag" -> removeTag(call.argument("key"), result)
"loadContexts" -> loadContexts(result)
"displayRefreshRate" -> displayRefreshRate(result)
"nativeCrash" -> crash()
else -> result.notImplemented()
}
}
Expand Down Expand Up @@ -413,16 +415,17 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
}

companion object {
private const val FLUTTER_SDK = "sentry.dart.flutter"
private const val ANDROID_SDK = "sentry.java.android.flutter"
private const val NATIVE_SDK = "sentry.native.android.flutter"
private const val NATIVE_CRASH_WAIT_TIME = 500L

private const val flutterSdk = "sentry.dart.flutter"
private const val androidSdk = "sentry.java.android.flutter"
private const val nativeSdk = "sentry.native.android.flutter"
private fun setEventOriginTag(event: SentryEvent) {
event.sdk?.let {
when (it.name) {
flutterSdk -> setEventEnvironmentTag(event, "flutter", "dart")
androidSdk -> setEventEnvironmentTag(event, environment = "java")
nativeSdk -> setEventEnvironmentTag(event, environment = "native")
FLUTTER_SDK -> setEventEnvironmentTag(event, "flutter", "dart")
ANDROID_SDK -> setEventEnvironmentTag(event, environment = "java")
NATIVE_SDK -> setEventEnvironmentTag(event, environment = "native")
else -> return
}
}
Expand All @@ -439,7 +442,7 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {

private fun addPackages(event: SentryEvent, sdk: SdkVersion?) {
event.sdk?.let {
if (it.name == flutterSdk) {
if (it.name == FLUTTER_SDK) {
sdk?.packageSet?.forEach { sentryPackage ->
it.addPackage(sentryPackage.name, sentryPackage.version)
}
Expand All @@ -449,6 +452,13 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
}
}
}

private fun crash() {
val exception = RuntimeException("FlutterSentry Native Integration: Sample RuntimeException")
val mainThread = Looper.getMainLooper().thread
mainThread.uncaughtExceptionHandler.uncaughtException(mainThread, exception)
mainThread.join(NATIVE_CRASH_WAIT_TIME)
}
}

private fun loadContexts(result: Result) {
Expand Down
12 changes: 12 additions & 0 deletions flutter/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,12 @@ class AndroidExample extends StatelessWidget {
},
child: const Text('Platform exception'),
),
ElevatedButton(
onPressed: () async {
SentryFlutter.nativeCrash();
},
child: const Text('Sentry.nativeCrash'),
),
]);
}
}
Expand Down Expand Up @@ -870,6 +876,12 @@ class CocoaExample extends StatelessWidget {
},
child: const Text('Objective-C SEGFAULT'),
),
ElevatedButton(
onPressed: () async {
SentryFlutter.nativeCrash();
},
child: const Text('Sentry.nativeCrash'),
),
],
);
}
Expand Down
7 changes: 7 additions & 0 deletions flutter/ios/Classes/SentryFlutterPluginApple.swift
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin {
case "resumeAppHangTracking":
resumeAppHangTracking(result)

case "nativeCrash":
crash()

default:
result(FlutterMethodNotImplemented)
}
Expand Down Expand Up @@ -729,6 +732,10 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin {
SentrySDK.resumeAppHangTracking()
result("")
}

private func crash() {
SentrySDK.crash()
}
}

// swiftlint:enable function_body_length
Expand Down
2 changes: 2 additions & 0 deletions flutter/lib/src/native/sentry_native_binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,6 @@ abstract class SentryNativeBinding {
Future<void> pauseAppHangTracking();

Future<void> resumeAppHangTracking();

Future<void> nativeCrash();
}
3 changes: 3 additions & 0 deletions flutter/lib/src/native/sentry_native_channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -194,4 +194,7 @@ class SentryNativeChannel
@override
Future<void> resumeAppHangTracking() =>
_channel.invokeMethod('resumeAppHangTracking');

@override
Future<void> nativeCrash() => _channel.invokeMethod('nativeCrash');
}
31 changes: 21 additions & 10 deletions flutter/lib/src/sentry_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -249,11 +249,7 @@
/// Only for iOS and macOS.
static Future<void> pauseAppHangTracking() {
if (_native == null) {
// ignore: invalid_use_of_internal_member
Sentry.currentHub.options.logger(
SentryLevel.debug,
'Native integration is not available. Make sure SentryFlutter is initialized before accessing the pauseAppHangTracking API.',
);
_logNativeIntegrationNotAvailable("pauseAppHangTracking");
martinhaintz marked this conversation as resolved.
Show resolved Hide resolved
return Future<void>.value();
}
return _native!.pauseAppHangTracking();
Expand All @@ -263,11 +259,7 @@
/// Only for iOS and macOS
static Future<void> resumeAppHangTracking() {
if (_native == null) {
// ignore: invalid_use_of_internal_member
Sentry.currentHub.options.logger(
SentryLevel.debug,
'Native integration is not available. Make sure SentryFlutter is initialized before accessing the resumeAppHangTracking API.',
);
_logNativeIntegrationNotAvailable("resumeAppHangTracking");
return Future<void>.value();
}
return _native!.resumeAppHangTracking();
Expand All @@ -280,4 +272,23 @@
static set native(SentryNativeBinding? value) => _native = value;

static SentryNativeBinding? _native;

/// Use `nativeCrash()` to crash the native implementation and test/debug the crash reporting for native code.
/// This should not be used in production code.
/// Only for Android, iOS and macOS
static Future<void> nativeCrash() {

Check warning on line 279 in flutter/lib/src/sentry_flutter.dart

View check run for this annotation

Codecov / codecov/patch

flutter/lib/src/sentry_flutter.dart#L279

Added line #L279 was not covered by tests
martinhaintz marked this conversation as resolved.
Show resolved Hide resolved
if (_native == null) {
_logNativeIntegrationNotAvailable("nativeCrash");
return Future<void>.value();

Check warning on line 282 in flutter/lib/src/sentry_flutter.dart

View check run for this annotation

Codecov / codecov/patch

flutter/lib/src/sentry_flutter.dart#L281-L282

Added lines #L281 - L282 were not covered by tests
}
return _native!.nativeCrash();

Check warning on line 284 in flutter/lib/src/sentry_flutter.dart

View check run for this annotation

Codecov / codecov/patch

flutter/lib/src/sentry_flutter.dart#L284

Added line #L284 was not covered by tests
}

static void _logNativeIntegrationNotAvailable(String methodName) {
// ignore: invalid_use_of_internal_member
Sentry.currentHub.options.logger(
SentryLevel.debug,
'Native integration is not available. Make sure SentryFlutter is initialized before accessing the $methodName API.',
);
}
}
9 changes: 9 additions & 0 deletions flutter/test/sentry_native_channel_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,15 @@ void main() {

verify(channel.invokeMethod('resumeAppHangTracking'));
});

test('nativeCrash', () async {
when(channel.invokeMethod('nativeCrash'))
.thenAnswer((_) => Future.value());

await sut.nativeCrash();

verify(channel.invokeMethod('nativeCrash'));
});
});
}
}
Loading