diff --git a/CHANGELOG.md b/CHANGELOG.md index f8e3d3c11..96ef13050 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ ### Features -- Added `AndroidNativeAnrEnabled` (default `true`) to enable ANR detection through `sentry-java` SDK. The native ANR integration monitors the Android UI thread. On API ≥ 30 this uses [ANR v2](https://docs.sentry.io/platforms/android/configuration/app-not-respond/) via `ApplicationExitInfo` to report OS-detected ANRs from prior runs; on API < 30 it falls back to an in-process watchdog. This is complementary to the Unity SDK's C# watchdog, which monitors the Unity player loop. ([#2671](https://github.com/getsentry/sentry-unity/pull/2671)) +- Added `EnableAppHangTracking` (default `true`) to enable app hang (ANR) detection through the `sentry-cocoa` SDK on iOS and macOS. When enabled, sentry-cocoa monitors the main thread and replaces the Unity SDK's C# watchdog on these platforms, allowing Sentry to show a stack trace for the hang event ([#2679](https://github.com/getsentry/sentry-unity/pull/2679)) +- Added `AndroidNativeAnrEnabled` (default `true`) to enable ANR detection through the `sentry-java` SDK. The native ANR integration monitors the Android UI thread. On API ≥ 30 this uses [ANR v2](https://docs.sentry.io/platforms/android/configuration/app-not-respond/) via `ApplicationExitInfo` to report OS-detected ANRs from prior runs; on API < 30 it falls back to an in-process watchdog. This is complementary to the Unity SDK's C# watchdog, which monitors the Unity player loop. ([#2671](https://github.com/getsentry/sentry-unity/pull/2671)) ### Dependencies diff --git a/src/Sentry.Unity.Editor.iOS/NativeOptions.cs b/src/Sentry.Unity.Editor.iOS/NativeOptions.cs index 6a0752d60..3ad945c29 100644 --- a/src/Sentry.Unity.Editor.iOS/NativeOptions.cs +++ b/src/Sentry.Unity.Editor.iOS/NativeOptions.cs @@ -30,7 +30,8 @@ internal static string Generate(SentryUnityOptions options) @""maxBreadcrumbs"": @{options.MaxBreadcrumbs}, @""maxCacheItems"": @{options.MaxCacheItems}, @""enableAutoSessionTracking"": @NO, - @""enableAppHangTracking"": @NO, + @""enableAppHangTracking"": @{ToObjCString(options.EnableAppHangTracking)}, + @""appHangTimeoutInterval"": @{options.AnrTimeout.TotalSeconds.ToString(System.Globalization.CultureInfo.InvariantCulture)}, @""enableCaptureFailedRequests"": @{ToObjCString(options.CaptureFailedRequests)}, @""failedRequestStatusCodes"" : @[{failedRequestStatusCodesArray}], @""sendDefaultPii"" : @{ToObjCString(options.SendDefaultPii)}, diff --git a/src/Sentry.Unity.Editor/ConfigurationWindow/AdvancedTab.cs b/src/Sentry.Unity.Editor/ConfigurationWindow/AdvancedTab.cs index 56d0c2e9e..028b4ef72 100644 --- a/src/Sentry.Unity.Editor/ConfigurationWindow/AdvancedTab.cs +++ b/src/Sentry.Unity.Editor/ConfigurationWindow/AdvancedTab.cs @@ -39,21 +39,24 @@ internal static void Display(ScriptableSentryUnityOptions options, SentryCliOpti EditorGUILayout.Space(); { - options.AnrDetectionEnabled = EditorGUILayout.BeginToggleGroup( - new GUIContent("ANR Detection", "Whether the SDK should report 'Application Not " + - "Responding' events."), + options.AnrDetectionEnabled = EditorGUILayout.Toggle( + new GUIContent("C# Watchdog", "Whether the SDK should run the C# main-thread watchdog " + + "to report 'Application Not Responding' events."), options.AnrDetectionEnabled); - EditorGUI.indentLevel++; + + options.EnableAppHangTracking = EditorGUILayout.Toggle( + new GUIContent("App Hang Tracking", + "Enables app hang (ANR) detection via the native SDK. " + + "When enabled, the native SDK monitors the main thread and the Unity SDK's C# ANR " + + "watchdog is skipped to avoid duplicate reports."), + options.EnableAppHangTracking); options.AnrTimeout = EditorGUILayout.IntField( - new GUIContent("Timeout [ms]", + new GUIContent("App Hang Timeout [ms]", "The duration in [ms] for how long the game has to be unresponsive " + "before an ANR event is reported.\nDefault: 5000ms"), options.AnrTimeout); options.AnrTimeout = Math.Max(0, options.AnrTimeout); - - EditorGUI.indentLevel--; - EditorGUILayout.EndToggleGroup(); } EditorGUILayout.Space(); diff --git a/src/Sentry.Unity.iOS/SentryCocoaBridgeProxy.cs b/src/Sentry.Unity.iOS/SentryCocoaBridgeProxy.cs index 9d36d8d6a..a3d6c1080 100644 --- a/src/Sentry.Unity.iOS/SentryCocoaBridgeProxy.cs +++ b/src/Sentry.Unity.iOS/SentryCocoaBridgeProxy.cs @@ -66,6 +66,12 @@ public static bool Init(SentryUnityOptions options) // See https://github.com/getsentry/sentry-unity/issues/1658 OptionsSetInt(cOptions, "enableNetworkBreadcrumbs", 0); + Logger?.LogDebug("Setting EnableAppHangTracking: {0}", options.EnableAppHangTracking); + OptionsSetInt(cOptions, "enableAppHangTracking", options.EnableAppHangTracking ? 1 : 0); + + Logger?.LogDebug("Setting AppHangTimeoutInterval: {0}s", options.AnrTimeout.TotalSeconds); + OptionsSetDouble(cOptions, "appHangTimeoutInterval", options.AnrTimeout.TotalSeconds); + Logger?.LogDebug("Setting EnableWatchdogTerminationTracking: {0}", options.IosWatchdogTerminationIntegrationEnabled); OptionsSetInt(cOptions, "enableWatchdogTerminationTracking", options.IosWatchdogTerminationIntegrationEnabled ? 1 : 0); @@ -103,6 +109,9 @@ public static bool Init(SentryUnityOptions options) [DllImport("__Internal", EntryPoint = "SentryNativeBridgeOptionsSetInt")] private static extern void OptionsSetInt(IntPtr options, string name, int value); + [DllImport("__Internal", EntryPoint = "SentryNativeBridgeOptionsSetDouble")] + private static extern void OptionsSetDouble(IntPtr options, string name, double value); + [DllImport("__Internal", EntryPoint = "SentryNativeBridgeOptionsAddFailedRequestStatusCodeRange")] private static extern void OptionsAddFailedRequestStatusCodeRange(IntPtr options, int min, int max); diff --git a/src/Sentry.Unity.iOS/SentryNativeCocoa.cs b/src/Sentry.Unity.iOS/SentryNativeCocoa.cs index 89aa52f04..98d29de0c 100644 --- a/src/Sentry.Unity.iOS/SentryNativeCocoa.cs +++ b/src/Sentry.Unity.iOS/SentryNativeCocoa.cs @@ -54,6 +54,12 @@ internal static void Configure(SentryUnityOptions options, RuntimePlatform platf options.ScopeObserver = new NativeScopeObserver("macOS", options); } + if (options.EnableAppHangTracking) + { + Logger?.LogDebug("Disabling the C# ANR watchdog - sentry-cocoa handles app hang detection."); + options.DisableAnrIntegration(); + } + SentryCocoaBridgeProxy.SetSdkName(); // Since we're not building the SDK we have to overwrite the name here options.NativeContextWriter = new NativeContextWriter(); diff --git a/src/Sentry.Unity/ScriptableSentryUnityOptions.cs b/src/Sentry.Unity/ScriptableSentryUnityOptions.cs index e357b6724..5bb07e44f 100644 --- a/src/Sentry.Unity/ScriptableSentryUnityOptions.cs +++ b/src/Sentry.Unity/ScriptableSentryUnityOptions.cs @@ -108,6 +108,7 @@ public static string GetConfigPath(string? notDefaultConfigName = null) [field: SerializeField] public bool AnrDetectionEnabled { get; set; } = true; [field: SerializeField] public int AnrTimeout { get; set; } = (int)TimeSpan.FromSeconds(5).TotalMilliseconds; + [field: SerializeField] public bool EnableAppHangTracking { get; set; } = true; [field: SerializeField] public bool CaptureFailedRequests { get; set; } = true; @@ -201,6 +202,7 @@ internal SentryUnityOptions ToSentryUnityOptions( DiagnosticLevel = DiagnosticLevel, CaptureLogErrorEvents = CaptureLogErrorEvents, AnrTimeout = TimeSpan.FromMilliseconds(AnrTimeout), + EnableAppHangTracking = EnableAppHangTracking, CaptureFailedRequests = CaptureFailedRequests, FilterBadGatewayExceptions = FilterBadGatewayExceptions, IosNativeSupportEnabled = IosNativeSupportEnabled, diff --git a/src/Sentry.Unity/SentryUnityOptions.cs b/src/Sentry.Unity/SentryUnityOptions.cs index 20da43cb0..23870254c 100644 --- a/src/Sentry.Unity/SentryUnityOptions.cs +++ b/src/Sentry.Unity/SentryUnityOptions.cs @@ -185,6 +185,18 @@ public sealed class SentryUnityOptions : SentryOptions /// public bool IosWatchdogTerminationIntegrationEnabled { get; set; } = false; + /// + /// Enables app hang (ANR) detection on iOS and macOS through the native (sentry-cocoa) SDK. + /// When enabled, sentry-cocoa monitors the main thread for hangs and the Unity SDK's + /// C# ANR watchdog is skipped to avoid duplicate reports. + /// + /// + /// sentry-cocoa observes the platform run loop directly, which yields more accurate + /// app-hang detection than the Unity-side watchdog. Disable this only if you want + /// to fall back to the C# watchdog. + /// + public bool EnableAppHangTracking { get; set; } = true; + /// /// Whether the SDK should initialize the native SDK before the game starts. This bakes the options at build-time into /// the generated Xcode project. Modifying the options at runtime will not affect the options used to initialize diff --git a/test/Sentry.Unity.Editor.iOS.Tests/NativeOptionsTests.cs b/test/Sentry.Unity.Editor.iOS.Tests/NativeOptionsTests.cs index 71aecac29..87ad6e0c7 100644 --- a/test/Sentry.Unity.Editor.iOS.Tests/NativeOptionsTests.cs +++ b/test/Sentry.Unity.Editor.iOS.Tests/NativeOptionsTests.cs @@ -58,6 +58,46 @@ public void CreateOptionsFile_NewSentryOptions_ContainsSdkNameSetting() File.Delete(testOptionsFileName); } + [Test] + public void CreateOptionsFile_EnableAppHangTracking_SetsYes() + { + const string testOptionsFileName = "testOptions.m"; + + NativeOptions.CreateFile(testOptionsFileName, new SentryUnityOptions { EnableAppHangTracking = true }); + + var nativeOptions = File.ReadAllText(testOptionsFileName); + StringAssert.Contains("@\"enableAppHangTracking\": @YES", nativeOptions); + + File.Delete(testOptionsFileName); + } + + [Test] + public void CreateOptionsFile_AppHangTrackingDisabled_SetsNo() + { + const string testOptionsFileName = "testOptions.m"; + + NativeOptions.CreateFile(testOptionsFileName, new SentryUnityOptions { EnableAppHangTracking = false }); + + var nativeOptions = File.ReadAllText(testOptionsFileName); + StringAssert.Contains("@\"enableAppHangTracking\": @NO", nativeOptions); + + File.Delete(testOptionsFileName); + } + + [Test] + public void CreateOptionsFile_AnrTimeout_WrittenAsSeconds() + { + const string testOptionsFileName = "testOptions.m"; + + NativeOptions.CreateFile(testOptionsFileName, + new SentryUnityOptions { AnrTimeout = System.TimeSpan.FromMilliseconds(7500) }); + + var nativeOptions = File.ReadAllText(testOptionsFileName); + StringAssert.Contains("@\"appHangTimeoutInterval\": @7.5", nativeOptions); + + File.Delete(testOptionsFileName); + } + [Test] public void CreateOptionsFile_FilterBadGatewayEnabled_AddsFiltering() {