From 6bac6eb56251ef3b6253659e1da079d826d3face Mon Sep 17 00:00:00 2001 From: Martin Haintz Date: Tue, 20 Aug 2024 11:27:17 +0200 Subject: [PATCH 01/10] add SentryFlutter.nativeCrash() for Android and iOS --- .../io/sentry/flutter/SentryFlutterPlugin.kt | 5 ++++ flutter/example/lib/main.dart | 12 ++++++++ .../Classes/SentryFlutterPluginApple.swift | 7 +++++ .../lib/src/native/sentry_native_binding.dart | 2 ++ .../lib/src/native/sentry_native_channel.dart | 3 ++ flutter/lib/src/sentry_flutter.dart | 30 ++++++++++++------- 6 files changed, 49 insertions(+), 10 deletions(-) diff --git a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index 4f154a2465..c7c7a60cac 100644 --- a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -74,6 +74,7 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { "removeTag" -> removeTag(call.argument("key"), result) "loadContexts" -> loadContexts(result) "displayRefreshRate" -> displayRefreshRate(result) + "nativeCrash" -> crash(result) else -> result.notImplemented() } } @@ -466,4 +467,8 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { ) result.success(serializedScope) } + + private fun crash(result: Result) { + error("This is a fatal error caused by SentryFlutter.nativeCrash.") + } } diff --git a/flutter/example/lib/main.dart b/flutter/example/lib/main.dart index 82bcd0b3c8..7c11d17cca 100644 --- a/flutter/example/lib/main.dart +++ b/flutter/example/lib/main.dart @@ -758,6 +758,12 @@ class AndroidExample extends StatelessWidget { }, child: const Text('Platform exception'), ), + ElevatedButton( + onPressed: () async { + SentryFlutter.nativeCrash(); + }, + child: const Text('Sentry.nativeCrash'), + ), ]); } } @@ -870,6 +876,12 @@ class CocoaExample extends StatelessWidget { }, child: const Text('Objective-C SEGFAULT'), ), + ElevatedButton( + onPressed: () async { + SentryFlutter.nativeCrash(); + }, + child: const Text('Sentry.nativeCrash'), + ), ], ); } diff --git a/flutter/ios/Classes/SentryFlutterPluginApple.swift b/flutter/ios/Classes/SentryFlutterPluginApple.swift index 35249ef5d1..c2024f3779 100644 --- a/flutter/ios/Classes/SentryFlutterPluginApple.swift +++ b/flutter/ios/Classes/SentryFlutterPluginApple.swift @@ -174,6 +174,9 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { case "resumeAppHangTracking": resumeAppHangTracking(result) + case "nativeCrash": + crash() + default: result(FlutterMethodNotImplemented) } @@ -729,6 +732,10 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { SentrySDK.resumeAppHangTracking() result("") } + + private func crash() { + SentrySDK.crash() + } } // swiftlint:enable function_body_length diff --git a/flutter/lib/src/native/sentry_native_binding.dart b/flutter/lib/src/native/sentry_native_binding.dart index 002790fc32..168326fcc9 100644 --- a/flutter/lib/src/native/sentry_native_binding.dart +++ b/flutter/lib/src/native/sentry_native_binding.dart @@ -57,4 +57,6 @@ abstract class SentryNativeBinding { Future pauseAppHangTracking(); Future resumeAppHangTracking(); + + Future nativeCrash(); } diff --git a/flutter/lib/src/native/sentry_native_channel.dart b/flutter/lib/src/native/sentry_native_channel.dart index 623111bb9e..97245870db 100644 --- a/flutter/lib/src/native/sentry_native_channel.dart +++ b/flutter/lib/src/native/sentry_native_channel.dart @@ -194,4 +194,7 @@ class SentryNativeChannel @override Future resumeAppHangTracking() => _channel.invokeMethod('resumeAppHangTracking'); + + @override + Future nativeCrash() => _channel.invokeMethod('nativeCrash'); } diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index c688f9862b..93dcda82f1 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:ui'; +import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart'; @@ -249,11 +250,7 @@ mixin SentryFlutter { /// Only for iOS and macOS. static Future 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"); return Future.value(); } return _native!.pauseAppHangTracking(); @@ -263,11 +260,7 @@ mixin SentryFlutter { /// Only for iOS and macOS static Future 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.value(); } return _native!.resumeAppHangTracking(); @@ -280,4 +273,21 @@ mixin SentryFlutter { static set native(SentryNativeBinding? value) => _native = value; static SentryNativeBinding? _native; + + static Future nativeCrash() { + const _channel = MethodChannel('example.flutter.sentry.io'); + if (_native == null) { + _logNativeIntegrationNotAvailable("nativeCrash"); + return Future.value(); + } + return _native!.nativeCrash(); + } + + 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.', + ); + } } From d3d87c09c9d03738d1859647949fe990541961cf Mon Sep 17 00:00:00 2001 From: Martin Haintz Date: Tue, 20 Aug 2024 11:33:20 +0200 Subject: [PATCH 02/10] add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 672ed7fce9..b6340bea27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- Add `SentryFlutter.nativeCrash()` using MethodChannels for Android and iOS ([#2239](https://github.com/getsentry/sentry-dart/pull/2239)) - Add `ignoreRoutes` parameter to `SentryNavigatorObserver`. ([#2218](https://github.com/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. From 38d0ea5401a3ea3156c5684698bafc9802d51284 Mon Sep 17 00:00:00 2001 From: Martin Haintz Date: Tue, 20 Aug 2024 11:44:13 +0200 Subject: [PATCH 03/10] remove unused variable --- flutter/lib/src/sentry_flutter.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index 93dcda82f1..eff502ba7e 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -275,7 +275,6 @@ mixin SentryFlutter { static SentryNativeBinding? _native; static Future nativeCrash() { - const _channel = MethodChannel('example.flutter.sentry.io'); if (_native == null) { _logNativeIntegrationNotAvailable("nativeCrash"); return Future.value(); From f71ceb8afde531cb7adcadf0a4fc95c54d2f232b Mon Sep 17 00:00:00 2001 From: Martin Haintz Date: Tue, 20 Aug 2024 12:11:34 +0200 Subject: [PATCH 04/10] improved kotlin implementation --- .../main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt | 6 +++++- flutter/lib/src/sentry_flutter.dart | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index c7c7a60cac..c3b496659f 100644 --- a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -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 @@ -469,6 +470,9 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { } private fun crash(result: Result) { - error("This is a fatal error caused by SentryFlutter.nativeCrash.") + val exception = RuntimeException("FlutterSentry Native Integration: Sample RuntimeException") + val mainThread = Looper.getMainLooper().thread + mainThread.uncaughtExceptionHandler.uncaughtException(mainThread, exception) + mainThread.join(500) } } diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index eff502ba7e..c99268fe00 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:ui'; -import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart'; From aeb534e6cc0e99d11b19e6b1793a8c4425bc2645 Mon Sep 17 00:00:00 2001 From: Martin Haintz Date: Tue, 20 Aug 2024 12:44:44 +0200 Subject: [PATCH 05/10] fix kotlin analysis warnings --- .../main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index c3b496659f..fcd43dde52 100644 --- a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -75,7 +75,7 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { "removeTag" -> removeTag(call.argument("key"), result) "loadContexts" -> loadContexts(result) "displayRefreshRate" -> displayRefreshRate(result) - "nativeCrash" -> crash(result) + "nativeCrash" -> crash() else -> result.notImplemented() } } @@ -469,10 +469,11 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { result.success(serializedScope) } - private fun crash(result: Result) { + private fun crash() { val exception = RuntimeException("FlutterSentry Native Integration: Sample RuntimeException") val mainThread = Looper.getMainLooper().thread mainThread.uncaughtExceptionHandler.uncaughtException(mainThread, exception) - mainThread.join(500) + private const val waitTime = 500 + mainThread.join(waitTime) } } From c6164ac26d2c67f85a7d20cf3a1638a277513086 Mon Sep 17 00:00:00 2001 From: Martin Haintz Date: Tue, 20 Aug 2024 13:24:01 +0200 Subject: [PATCH 06/10] Update CHANGELOG.md Co-authored-by: Giancarlo Buenaflor --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6340bea27..ccdca3529d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features - Add `SentryFlutter.nativeCrash()` using MethodChannels for Android and iOS ([#2239](https://github.com/getsentry/sentry-dart/pull/2239)) + - This can be used to test if native crash reporting works - Add `ignoreRoutes` parameter to `SentryNavigatorObserver`. ([#2218](https://github.com/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. From ad729e256a40eb2e61ed295167442cdf34c79d53 Mon Sep 17 00:00:00 2001 From: Martin Haintz Date: Tue, 20 Aug 2024 14:17:50 +0200 Subject: [PATCH 07/10] fix kotlin linter errors --- .../io/sentry/flutter/SentryFlutterPlugin.kt | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index fcd43dde52..42098e4c36 100644 --- a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -50,8 +50,8 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { sentryFlutter = SentryFlutter( - androidSdk = androidSdk, - nativeSdk = nativeSdk, + androidSdk = ANDROID_SDK, + nativeSdk = NATIVE_SDK, ) } @@ -415,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 } } @@ -441,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) } @@ -451,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) { @@ -469,11 +477,4 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { result.success(serializedScope) } - private fun crash() { - val exception = RuntimeException("FlutterSentry Native Integration: Sample RuntimeException") - val mainThread = Looper.getMainLooper().thread - mainThread.uncaughtExceptionHandler.uncaughtException(mainThread, exception) - private const val waitTime = 500 - mainThread.join(waitTime) - } } From 980d9bde9194d8c09b71fec7339f4cccc26b682f Mon Sep 17 00:00:00 2001 From: Martin Haintz Date: Tue, 20 Aug 2024 14:19:50 +0200 Subject: [PATCH 08/10] remove whitespace --- .../src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index 42098e4c36..5bb1296a24 100644 --- a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -476,5 +476,4 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { ) result.success(serializedScope) } - } From 59341c425fc32028241eb2a5785bf238fdddfaea Mon Sep 17 00:00:00 2001 From: Martin Haintz Date: Tue, 20 Aug 2024 14:42:31 +0200 Subject: [PATCH 09/10] add Description for nativeCrash --- flutter/lib/src/sentry_flutter.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index c99268fe00..6cd503bb14 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -273,6 +273,9 @@ mixin SentryFlutter { 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 and iOS static Future nativeCrash() { if (_native == null) { _logNativeIntegrationNotAvailable("nativeCrash"); From 22c210ee39133f18bfe4cbec42557f6abc1324a3 Mon Sep 17 00:00:00 2001 From: Martin Haintz Date: Wed, 21 Aug 2024 13:50:48 +0200 Subject: [PATCH 10/10] add test for nativeCrash --- flutter/lib/src/sentry_flutter.dart | 2 +- flutter/test/sentry_native_channel_test.dart | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index 6cd503bb14..3ff835284c 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -275,7 +275,7 @@ mixin SentryFlutter { /// 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 and iOS + /// Only for Android, iOS and macOS static Future nativeCrash() { if (_native == null) { _logNativeIntegrationNotAvailable("nativeCrash"); diff --git a/flutter/test/sentry_native_channel_test.dart b/flutter/test/sentry_native_channel_test.dart index 5ad5eb2b3f..fb339b4682 100644 --- a/flutter/test/sentry_native_channel_test.dart +++ b/flutter/test/sentry_native_channel_test.dart @@ -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')); + }); }); } }