Skip to content
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 6 additions & 0 deletions package-dev/Plugins/iOS/SentryNativeBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ void SentryNativeBridgeOptionsSetInt(const void *options, const char *name, int3
dictOptions[[NSString stringWithUTF8String:name]] = [NSNumber numberWithInt:value];
}

void SentryNativeBridgeOptionsSetDouble(const void *options, const char *name, double value)
{
NSMutableDictionary *dictOptions = (__bridge NSMutableDictionary *)options;
dictOptions[[NSString stringWithUTF8String:name]] = [NSNumber numberWithDouble:value];
}

void SentryNativeBridgeOptionsAddFailedRequestStatusCodeRange(const void *options, int32_t min, int32_t max)
{
NSMutableDictionary *dictOptions = (__bridge NSMutableDictionary *)options;
Expand Down
1 change: 1 addition & 0 deletions package-dev/Plugins/iOS/SentryNativeBridgeNoOp.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
void *_Nullable SentryNativeBridgeOptionsNew() { return nil; }
void SentryNativeBridgeOptionsSetString(void *options, const char *name, const char *value) { }
void SentryNativeBridgeOptionsSetInt(void *options, const char *name, int32_t value) { }
void SentryNativeBridgeOptionsSetDouble(void *options, const char *name, double value) { }
void SentryNativeBridgeOptionsAddFailedRequestStatusCodeRange(void *options, int32_t min, int32_t max) { }
int SentryNativeBridgeStartWithOptions(void *options) { return 0; }

Expand Down
6 changes: 6 additions & 0 deletions package-dev/Plugins/macOS/SentryNativeBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ void SentryNativeBridgeOptionsSetInt(const void *options, const char *name, int3
dictOptions[[NSString stringWithUTF8String:name]] = [NSNumber numberWithInt:value];
}

void SentryNativeBridgeOptionsSetDouble(const void *options, const char *name, double value)
{
NSMutableDictionary *dictOptions = (__bridge NSMutableDictionary *)options;
dictOptions[[NSString stringWithUTF8String:name]] = [NSNumber numberWithDouble:value];
}

void SentryNativeBridgeOptionsAddFailedRequestStatusCodeRange(const void *options, int32_t min, int32_t max)
{
NSMutableDictionary *dictOptions = (__bridge NSMutableDictionary *)options;
Expand Down
3 changes: 2 additions & 1 deletion src/Sentry.Unity.Editor.iOS/NativeOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)},
Expand Down
19 changes: 11 additions & 8 deletions src/Sentry.Unity.Editor/ConfigurationWindow/AdvancedTab.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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. Currently limited to iOS." +
"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();
Expand Down
9 changes: 9 additions & 0 deletions src/Sentry.Unity.iOS/SentryCocoaBridgeProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Comment thread
bitsandfoxes marked this conversation as resolved.

Logger?.LogDebug("Setting EnableWatchdogTerminationTracking: {0}", options.IosWatchdogTerminationIntegrationEnabled);
OptionsSetInt(cOptions, "enableWatchdogTerminationTracking", options.IosWatchdogTerminationIntegrationEnabled ? 1 : 0);

Expand Down Expand Up @@ -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);
Comment thread
bitsandfoxes marked this conversation as resolved.
Comment thread
cursor[bot] marked this conversation as resolved.

[DllImport("__Internal", EntryPoint = "SentryNativeBridgeOptionsAddFailedRequestStatusCodeRange")]
private static extern void OptionsAddFailedRequestStatusCodeRange(IntPtr options, int min, int max);

Expand Down
7 changes: 7 additions & 0 deletions src/Sentry.Unity.iOS/SentryNativeCocoa.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ internal static void Configure(SentryUnityOptions options, RuntimePlatform platf
}

options.ScopeObserver = new NativeScopeObserver("iOS", options);

if (options.EnableAppHangTracking)
{
Logger?.LogDebug("Disabling the C# ANR watchdog - sentry-cocoa handles app hang detection.");
options.DisableAnrIntegration();
}

Comment thread
bitsandfoxes marked this conversation as resolved.
}
else
{
Expand Down
2 changes: 2 additions & 0 deletions src/Sentry.Unity/ScriptableSentryUnityOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -201,6 +202,7 @@ internal SentryUnityOptions ToSentryUnityOptions(
DiagnosticLevel = DiagnosticLevel,
CaptureLogErrorEvents = CaptureLogErrorEvents,
AnrTimeout = TimeSpan.FromMilliseconds(AnrTimeout),
EnableAppHangTracking = EnableAppHangTracking,
CaptureFailedRequests = CaptureFailedRequests,
FilterBadGatewayExceptions = FilterBadGatewayExceptions,
IosNativeSupportEnabled = IosNativeSupportEnabled,
Expand Down
7 changes: 7 additions & 0 deletions src/Sentry.Unity/SentryUnityOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,13 @@ public sealed class SentryUnityOptions : SentryOptions
/// </summary>
public bool IosWatchdogTerminationIntegrationEnabled { get; set; } = false;

/// <summary>
/// Enables native app hang (ANR) detection. This currently only targets iOS.
/// When enabled, the platform's native SDK monitors the Unity thread for hangs.
/// This replaces the older C# watchdog.
/// </summary>
public bool EnableAppHangTracking { get; set; } = true;

/// <summary>
/// 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
Expand Down
40 changes: 40 additions & 0 deletions test/Sentry.Unity.Editor.iOS.Tests/NativeOptionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
Loading