Skip to content

Commit 7062e3f

Browse files
authored
ref(anr): Add Android-specific ANR detection options (#749)
1 parent 88f6a49 commit 7062e3f

14 files changed

Lines changed: 190 additions & 26 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
## Unreleased
44

5+
### Breaking changes
6+
7+
- Add Android-specific ANR (Application Not Responding) detection options ([#749](https://github.com/getsentry/sentry-godot/pull/749))
8+
- ANR detection is now enabled by default; set `SentryOptions.android.enable_anr_detection` to `false` to opt out
9+
- ANR detection is now separate from the App Hang Tracking options (`app_hang_tracking`, `app_hang_timeout_sec`), which now apply to Apple platforms only
10+
- Configure these through `SentryOptions.android`, or in the **Project Settings** under **Sentry > Android > Application Not Responding**
11+
512
### Dependencies
613

714
- Bump Sentry JavaScript from v10.55.0 to v10.57.0 ([#743](https://github.com/getsentry/sentry-godot/pull/743), [#754](https://github.com/getsentry/sentry-godot/pull/754))

android_lib/src/main/java/io/sentry/godotplugin/SentryAndroidGodotPlugin.kt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,10 @@ class SentryAndroidGodotPlugin(godot: Godot) : GodotPlugin(godot) {
182182
val maxBreadcrumbs = optionsData["max_breadcrumbs"].toIntOrThrow()
183183
val enableLogs = optionsData["enable_logs"] as Boolean
184184
val enableMetrics = optionsData["enable_metrics"] as Boolean
185-
val appHangTracking = optionsData["app_hang_tracking"] as Boolean
186-
val appHangTimeoutSec = optionsData["app_hang_timeout_sec"] as Double
187-
val shutdownTimeoutMs = optionsData["shutdown_timeout_ms"].toIntOrThrow()
185+
val enableAnrDetection = optionsData["enable_anr_detection"] as Boolean
186+
val anrTimeoutIntervalMs = optionsData["anr_timeout_interval_ms"].toLongOrThrow()
187+
val attachAnrThreadDump = optionsData["attach_anr_thread_dump"] as Boolean
188+
val shutdownTimeoutMs = optionsData["shutdown_timeout_ms"].toLongOrThrow()
188189

189190
SentryAndroid.init(godot.getActivity()!!.applicationContext) { options ->
190191
options.dsn = dsn.ifEmpty { null }
@@ -198,9 +199,10 @@ class SentryAndroidGodotPlugin(godot: Godot) : GodotPlugin(godot) {
198199
options.nativeSdkName = "sentry.native.android.godot"
199200
options.logs.isEnabled = enableLogs
200201
options.metrics.isEnabled = enableMetrics
201-
options.isAnrEnabled = appHangTracking
202-
options.anrTimeoutIntervalMillis = (appHangTimeoutSec * 1000.0).toLong()
203-
options.shutdownTimeoutMillis = shutdownTimeoutMs.toLong()
202+
options.isAnrEnabled = enableAnrDetection
203+
options.anrTimeoutIntervalMillis = anrTimeoutIntervalMs
204+
options.isAttachAnrThreadDump = attachAnrThreadDump
205+
options.shutdownTimeoutMillis = shutdownTimeoutMs
204206
options.isTombstoneEnabled = true
205207
options.beforeSend =
206208
SentryOptions.BeforeSendCallback { event: SentryEvent, hint: Hint ->

android_lib/src/main/java/io/sentry/godotplugin/UtilityFunctions.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@ fun Any?.toIntOrThrow(): Int =
6262
else -> throw IllegalArgumentException("Expected Int or Long, got ${this?.let { it::class } ?: "null"}")
6363
}
6464

65+
fun Any?.toLongOrThrow(): Long =
66+
when (this) {
67+
is Int -> this.toLong()
68+
is Long -> this
69+
else -> throw IllegalArgumentException("Expected Int or Long, got ${this?.let { it::class } ?: "null"}")
70+
}
71+
6572
fun Any?.toLongOrNull(): Long? =
6673
when (this) {
6774
is Long -> this
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<class name="SentryAndroidOptions" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/godotengine/godot/master/doc/class.xsd">
3+
<brief_description>
4+
Android-specific options for Sentry SDK.
5+
</brief_description>
6+
<description>
7+
Contains configuration options that apply only when the project runs on Android, such as ANR (Application Not Responding) detection. Access this configuration through [member SentryOptions.android].
8+
See also [SentryOptions].
9+
</description>
10+
<tutorials>
11+
</tutorials>
12+
<members>
13+
<member name="anr_timeout_interval_ms" type="int" setter="set_anr_timeout_interval_ms" getter="get_anr_timeout_interval_ms" default="5000">
14+
Specifies how long, in milliseconds, the main thread must stay blocked before the SDK reports an ANR.
15+
Applies only when [member enable_anr_detection] is enabled, and only to the V1 implementation used on Android versions before 11. On Android 11 and later, the operating system determines when the application stops responding, so this value has no effect.
16+
</member>
17+
<member name="attach_anr_thread_dump" type="bool" setter="set_attach_anr_thread_dump" getter="get_attach_anr_thread_dump" default="false">
18+
If [code]true[/code], attaches the operating system's thread dump to the ANR event as a plain-text attachment, adding detail for investigating where the application became unresponsive.
19+
Applies only when [member enable_anr_detection] is enabled, and only to the V2 implementation used on Android 11 and later.
20+
</member>
21+
<member name="enable_anr_detection" type="bool" setter="set_enable_anr_detection" getter="get_enable_anr_detection" default="true">
22+
If [code]true[/code], detects and reports ANR (Application Not Responding) errors. The SDK monitors the main thread for unresponsiveness and reports an event when an ANR occurs.
23+
Android 11 and later use the system-based V2 implementation, while earlier versions use the watchdog-based V1 implementation. See [member anr_timeout_interval_ms] and [member attach_anr_thread_dump] for options specific to each implementation. On Apple platforms, [member SentryOptions.app_hang_tracking] configures the equivalent app hang detection.
24+
To learn more, visit [url=https://docs.sentry.io/platforms/android/configuration/app-not-respond/]Application Not Responding documentation[/url].
25+
</member>
26+
</members>
27+
</class>

doc_classes/SentryOptions.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@
1313
<tutorials>
1414
</tutorials>
1515
<members>
16+
<member name="android" type="SentryAndroidOptions" setter="" getter="get_android">
17+
Configures Android-specific options, such as ANR (Application Not Responding) detection.
18+
</member>
1619
<member name="app_hang_timeout_sec" type="float" setter="set_app_hang_timeout_sec" getter="get_app_hang_timeout_sec" default="5.0">
1720
Specifies the timeout duration in seconds after which the application is considered to have hanged. When [member app_hang_tracking] is enabled, if the main thread is blocked for longer than this duration, it will be reported as an application hang event to Sentry.
1821
</member>
1922
<member name="app_hang_tracking" type="bool" setter="set_app_hang_tracking" getter="is_app_hang_tracking_enabled" default="false">
2023
If [code]true[/code], enables automatic detection and reporting of application hangs. The SDK will monitor the main thread and report hang events when it becomes unresponsive for longer than the duration specified in [member app_hang_timeout_sec]. This helps identify performance issues where the application becomes frozen or unresponsive.
21-
[b]Note:[/b] This feature is only supported on Android, iOS, and macOS platforms.
24+
[b]Note:[/b] This feature applies to iOS and macOS only. On Android, [member android] configures ANR (Application Not Responding) detection instead.
2225
</member>
2326
<member name="attach_log" type="bool" setter="set_attach_log" getter="is_attach_log_enabled" default="true">
2427
If [code]true[/code], the SDK will attach the Godot log file to the event.

src/register_types.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ using namespace sentry;
7272
void register_runtime_classes() {
7373
GDREGISTER_CLASS(SentryLoggerLimits);
7474
GDREGISTER_CLASS(SentryExperimental);
75+
GDREGISTER_CLASS(SentryAndroidOptions);
7576
GDREGISTER_CLASS(SentryOptions);
7677
GDREGISTER_INTERNAL_CLASS(RuntimeConfig);
7778
GDREGISTER_CLASS(SentryUser);

src/sentry/android/android_sdk.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,8 +350,9 @@ void AndroidSDK::init() {
350350
optionsData["max_breadcrumbs"] = SENTRY_OPTIONS()->get_max_breadcrumbs();
351351
optionsData["enable_logs"] = SENTRY_OPTIONS()->get_enable_logs();
352352
optionsData["enable_metrics"] = SENTRY_OPTIONS()->get_experimental()->get_enable_metrics();
353-
optionsData["app_hang_tracking"] = SENTRY_OPTIONS()->is_app_hang_tracking_enabled();
354-
optionsData["app_hang_timeout_sec"] = SENTRY_OPTIONS()->get_app_hang_timeout_sec();
353+
optionsData["enable_anr_detection"] = SENTRY_OPTIONS()->get_android()->get_enable_anr_detection();
354+
optionsData["anr_timeout_interval_ms"] = SENTRY_OPTIONS()->get_android()->get_anr_timeout_interval_ms();
355+
optionsData["attach_anr_thread_dump"] = SENTRY_OPTIONS()->get_android()->get_attach_anr_thread_dump();
355356
optionsData["shutdown_timeout_ms"] = SENTRY_OPTIONS()->get_shutdown_timeout_ms();
356357

357358
android_plugin->call(ANDROID_SN(init),

src/sentry/dotnet/csharp_interop.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,11 @@ struct NativeOptions {
145145

146146
// Experimental
147147
uint8_t enable_metrics;
148+
149+
// Android
150+
uint8_t android_enable_anr_detection;
151+
int32_t android_anr_timeout_interval_ms;
152+
uint8_t android_attach_anr_thread_dump;
148153
};
149154

150155
// Generic array handle for returning native-allocated arrays across the interop boundary.
@@ -200,6 +205,10 @@ struct ManagedOptions {
200205
int32_t logger_log_mask;
201206

202207
uint8_t enable_metrics;
208+
209+
uint8_t android_enable_anr_detection;
210+
int32_t android_anr_timeout_interval_ms;
211+
uint8_t android_attach_anr_thread_dump;
203212
};
204213

205214
struct NativeTraceContext {
@@ -242,6 +251,9 @@ static void _apply_managed_options(const ManagedOptions &data, Ref<SentryOptions
242251
options->set_logger_breadcrumb_mask(data.logger_breadcrumb_mask);
243252
options->set_logger_log_mask(data.logger_log_mask);
244253
options->get_experimental()->set_enable_metrics(data.enable_metrics);
254+
options->get_android()->set_enable_anr_detection(data.android_enable_anr_detection);
255+
options->get_android()->set_anr_timeout_interval_ms(data.android_anr_timeout_interval_ms);
256+
options->get_android()->set_attach_anr_thread_dump(data.android_attach_anr_thread_dump);
245257
}
246258

247259
void _populate_options_data(NativeOptions &r_data, const Ref<SentryOptions> &options) {
@@ -276,6 +288,9 @@ void _populate_options_data(NativeOptions &r_data, const Ref<SentryOptions> &opt
276288
r_data.logger_breadcrumb_mask = options->get_logger_breadcrumb_mask();
277289
r_data.logger_log_mask = options->get_logger_log_mask();
278290
r_data.enable_metrics = options->get_experimental()->get_enable_metrics();
291+
r_data.android_enable_anr_detection = options->get_android()->get_enable_anr_detection();
292+
r_data.android_anr_timeout_interval_ms = options->get_android()->get_anr_timeout_interval_ms();
293+
r_data.android_attach_anr_thread_dump = options->get_android()->get_attach_anr_thread_dump();
279294
}
280295

281296
// *** Functions called from C#

src/sentry/dotnet/managed/Sentry.Godot/Interop/NativeBridge.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ private struct NativeOptions
114114
public int logger_breadcrumb_mask;
115115
public int logger_log_mask;
116116
public byte enable_metrics;
117+
public byte android_enable_anr_detection;
118+
public int android_anr_timeout_interval_ms;
119+
public byte android_attach_anr_thread_dump;
117120
}
118121

119122
// Must match layout of ManagedOptions in csharp_interop.cpp.
@@ -149,6 +152,9 @@ private unsafe struct ManagedOptions
149152
public int logger_breadcrumb_mask;
150153
public int logger_log_mask;
151154
public byte enable_metrics;
155+
public byte android_enable_anr_detection;
156+
public int android_anr_timeout_interval_ms;
157+
public byte android_attach_anr_thread_dump;
152158
}
153159

154160
[StructLayout(LayoutKind.Sequential)]
@@ -477,6 +483,9 @@ private static void ApplyNativeOptions(NativeOptions data, SentryGodotOptions op
477483
opts.LoggerBreadcrumbMask = (SentryGodotOptions.GodotLoggerEventMask)data.logger_breadcrumb_mask;
478484
opts.LoggerLogMask = (SentryGodotOptions.GodotLoggerEventMask)data.logger_log_mask;
479485
opts.EnableMetrics = data.enable_metrics != 0;
486+
opts.Android.EnableAnrDetection = data.android_enable_anr_detection != 0;
487+
opts.Android.AnrTimeoutInterval = TimeSpan.FromMilliseconds(data.android_anr_timeout_interval_ms);
488+
opts.Android.AttachAnrThreadDump = data.android_attach_anr_thread_dump != 0;
480489
}
481490

482491
public static void ApplyNativeOptions(SentryGodotOptions opts)
@@ -765,6 +774,9 @@ public static unsafe void InitNativeSdk(SentryGodotOptions opts)
765774
logger_breadcrumb_mask = (int)opts.LoggerBreadcrumbMask,
766775
logger_log_mask = (int)opts.LoggerLogMask,
767776
enable_metrics = (byte)(opts.EnableMetrics ? 1 : 0),
777+
android_enable_anr_detection = (byte)(opts.Android.EnableAnrDetection ? 1 : 0),
778+
android_anr_timeout_interval_ms = (int)opts.Android.AnrTimeoutInterval.TotalMilliseconds,
779+
android_attach_anr_thread_dump = (byte)(opts.Android.AttachAnrThreadDump ? 1 : 0),
768780
};
769781
csharp_interop_sdk_init(managed);
770782
}

src/sentry/dotnet/managed/Sentry.Godot/SentryGodotOptions.cs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ public SentryGodotOptions()
5050
/// when it becomes unresponsive for longer than <see cref="AppHangTimeout"/>.
5151
/// </summary>
5252
/// <remarks>
53-
/// Only supported on Android, iOS, and macOS.
53+
/// This feature applies to iOS and macOS only. On Android, <see cref="Android"/> configures
54+
/// ANR (Application Not Responding) detection instead.
5455
/// </remarks>
5556
public bool AppHangTracking { get; set; } = false;
5657

@@ -137,6 +138,11 @@ public enum GodotLoggerEventMask
137138
/// </remarks>
138139
public SentryLoggerLimits LoggerLimits { get; set; } = new SentryLoggerLimits();
139140

141+
/// <summary>
142+
/// Configures Android-specific options, such as ANR (Application Not Responding) detection.
143+
/// </summary>
144+
public SentryAndroidOptions Android { get; set; } = new SentryAndroidOptions();
145+
140146
private readonly List<SentryAttachment> _defaultAttachments = [];
141147

142148
internal IReadOnlyList<SentryAttachment> DefaultAttachments => _defaultAttachments;
@@ -229,3 +235,44 @@ public sealed class SentryLoggerLimits
229235
/// </summary>
230236
public TimeSpan ThrottleWindow { get; set; } = TimeSpan.FromMilliseconds(10000);
231237
}
238+
239+
/// <summary>
240+
/// Contains configuration options that apply only when the project runs on Android, such as ANR (Application Not
241+
/// Responding) detection. Access this configuration through <see cref="SentryGodotOptions.Android"/>.
242+
/// </summary>
243+
/// <seealso cref="SentryGodotOptions"/>
244+
public sealed class SentryAndroidOptions
245+
{
246+
/// <summary>
247+
/// Enables detection and reporting of ANR (Application Not Responding) errors. The SDK monitors the main thread
248+
/// for unresponsiveness and reports an event when an ANR occurs.
249+
/// </summary>
250+
/// <remarks>
251+
/// Android 11 and later use the system-based V2 implementation, while earlier versions use the watchdog-based V1
252+
/// implementation. See <see cref="AnrTimeoutInterval"/> and <see cref="AttachAnrThreadDump"/> for options specific
253+
/// to each implementation. On Apple platforms, <see cref="SentryGodotOptions.AppHangTracking"/> configures the
254+
/// equivalent app hang detection.
255+
/// To learn more, visit <see href="https://docs.sentry.io/platforms/android/configuration/app-not-respond/">Application Not Responding documentation</see>.
256+
/// </remarks>
257+
public bool EnableAnrDetection { get; set; } = true;
258+
259+
/// <summary>
260+
/// Specifies how long the main thread must stay blocked before the SDK reports an ANR.
261+
/// </summary>
262+
/// <remarks>
263+
/// Applies only when <see cref="EnableAnrDetection"/> is enabled, and only to the V1 implementation used on Android
264+
/// versions before 11. On Android 11 and later, the operating system determines when the application stops responding, so
265+
/// this value has no effect.
266+
/// </remarks>
267+
public TimeSpan AnrTimeoutInterval { get; set; } = TimeSpan.FromMilliseconds(5000);
268+
269+
/// <summary>
270+
/// Attaches the operating system's thread dump to the ANR event as a plain-text attachment, adding detail for
271+
/// investigating where the application became unresponsive.
272+
/// </summary>
273+
/// <remarks>
274+
/// Applies only when <see cref="EnableAnrDetection"/> is enabled, and only to the V2 implementation used on Android
275+
/// 11 and later.
276+
/// </remarks>
277+
public bool AttachAnrThreadDump { get; set; } = false;
278+
}

0 commit comments

Comments
 (0)