From bc8e5b448c4e28a9e9074af6189eeaa523429294 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Mon, 27 Oct 2025 15:44:37 +0100 Subject: [PATCH 1/6] Add App Hang options (not utilized) --- src/sentry/sentry_options.cpp | 9 +++++++++ src/sentry/sentry_options.h | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/src/sentry/sentry_options.cpp b/src/sentry/sentry_options.cpp index 58a535ef..9b19a7e6 100644 --- a/src/sentry/sentry_options.cpp +++ b/src/sentry/sentry_options.cpp @@ -79,6 +79,9 @@ void SentryOptions::_define_project_settings(const Ref &p_options _define_setting("sentry/options/attach_log", p_options->attach_log, false); _define_setting("sentry/options/attach_scene_tree", p_options->attach_scene_tree); + _define_setting("sentry/options/app_hang/tracking", p_options->app_hang_tracking, false); + _define_setting("sentry/options/app_hang/timeout_sec", p_options->app_hang_timeout_sec, false); + _define_setting("sentry/logger/logger_enabled", p_options->logger_enabled); _define_setting("sentry/logger/include_source", p_options->logger_include_source, false); _define_setting("sentry/logger/include_variables", p_options->logger_include_variables, false); @@ -121,6 +124,9 @@ void SentryOptions::_load_project_settings(const Ref &p_options) p_options->attach_log = ProjectSettings::get_singleton()->get_setting("sentry/options/attach_log", p_options->attach_log); p_options->attach_scene_tree = ProjectSettings::get_singleton()->get_setting("sentry/options/attach_scene_tree", p_options->attach_scene_tree); + p_options->app_hang_tracking = ProjectSettings::get_singleton()->get_setting("sentry/options/app_hang/tracking", p_options->app_hang_tracking); + p_options->app_hang_timeout_sec = ProjectSettings::get_singleton()->get_setting("sentry/options/app_hang/timeout_sec", p_options->app_hang_timeout_sec); + p_options->logger_enabled = ProjectSettings::get_singleton()->get_setting("sentry/logger/logger_enabled", p_options->logger_enabled); p_options->logger_include_source = ProjectSettings::get_singleton()->get_setting("sentry/logger/include_source", p_options->logger_include_source); p_options->logger_include_variables = ProjectSettings::get_singleton()->get_setting("sentry/logger/include_variables", p_options->logger_include_variables); @@ -203,6 +209,9 @@ void SentryOptions::_bind_methods() { BIND_PROPERTY(SentryOptions, sentry::make_level_enum_property("screenshot_level"), set_screenshot_level, get_screenshot_level); BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::BOOL, "attach_scene_tree"), set_attach_scene_tree, is_attach_scene_tree_enabled); + BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::BOOL, "app_hang_tracking"), set_app_hang_tracking, is_app_hang_tracking_enabled); + BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::FLOAT, "app_hang_timeout_seconds"), set_app_hang_timeout_sec, get_app_hang_timeout_sec); + BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::BOOL, "logger_enabled"), set_logger_enabled, is_logger_enabled); BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::BOOL, "logger_include_source"), set_logger_include_source, is_logger_include_source_enabled); BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::BOOL, "logger_include_variables"), set_logger_include_variables, is_logger_include_variables_enabled); diff --git a/src/sentry/sentry_options.h b/src/sentry/sentry_options.h index 927eac54..0830e1f3 100644 --- a/src/sentry/sentry_options.h +++ b/src/sentry/sentry_options.h @@ -79,6 +79,9 @@ class SentryOptions : public RefCounted { sentry::Level screenshot_level = sentry::LEVEL_FATAL; bool attach_scene_tree = false; + bool app_hang_tracking = false; + double app_hang_timeout_sec = 5.0; + bool logger_enabled = true; bool logger_include_source = true; bool logger_include_variables = false; @@ -152,6 +155,12 @@ class SentryOptions : public RefCounted { _FORCE_INLINE_ void set_attach_scene_tree(bool p_enable) { attach_scene_tree = p_enable; } _FORCE_INLINE_ bool is_attach_scene_tree_enabled() const { return attach_scene_tree; } + _FORCE_INLINE_ bool is_app_hang_tracking_enabled() const { return app_hang_tracking; } + _FORCE_INLINE_ void set_app_hang_tracking(bool p_enabled) { app_hang_tracking = p_enabled; } + + _FORCE_INLINE_ double get_app_hang_timeout_sec() const { return app_hang_timeout_sec; } + _FORCE_INLINE_ void set_app_hang_timeout_sec(double p_seconds) { app_hang_timeout_sec = p_seconds; } + _FORCE_INLINE_ bool is_logger_enabled() const { return logger_enabled; } _FORCE_INLINE_ void set_logger_enabled(bool p_enabled) { logger_enabled = p_enabled; } From 008b79ffe0f5a5e647b3d2ec2855a4a3b12fb436 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Mon, 27 Oct 2025 15:46:33 +0100 Subject: [PATCH 2/6] Utilize app hang options on Apple --- src/sentry/cocoa/cocoa_sdk.mm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sentry/cocoa/cocoa_sdk.mm b/src/sentry/cocoa/cocoa_sdk.mm index 0efe32f2..3c33fd75 100644 --- a/src/sentry/cocoa/cocoa_sdk.mm +++ b/src/sentry/cocoa/cocoa_sdk.mm @@ -258,10 +258,12 @@ options.dist = string_to_objc(dist); } + options.enableAppHangTracking = SentryOptions::get_singleton()->is_app_hang_tracking_enabled(); + options.appHangTimeoutInterval = SentryOptions::get_singleton()->get_app_hang_timeout_sec(); + // NOTE: This only works for captureMessage(), unfortunately. options.attachStacktrace = false; - options.appHangTimeoutInterval = 5; // 5 seconds options.experimental.enableLogs = SentryOptions::get_singleton()->get_experimental()->get_enable_logs(); options.initialScope = ^(objc::SentryScope *scope) { From 243324e507c73fb4dcdc92e482d7a0c92a0cff5a Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Mon, 27 Oct 2025 16:55:55 +0100 Subject: [PATCH 3/6] Utilize ANR on Android, refactor options and init on Android --- .../godotplugin/SentryAndroidGodotPlugin.kt | 77 +++++++++++-------- .../io/sentry/godotplugin/UtilityFunctions.kt | 8 +- src/sentry/android/android_sdk.cpp | 21 +++-- 3 files changed, 64 insertions(+), 42 deletions(-) diff --git a/android_lib/src/main/java/io/sentry/godotplugin/SentryAndroidGodotPlugin.kt b/android_lib/src/main/java/io/sentry/godotplugin/SentryAndroidGodotPlugin.kt index f2e4ec19..861fcfbb 100644 --- a/android_lib/src/main/java/io/sentry/godotplugin/SentryAndroidGodotPlugin.kt +++ b/android_lib/src/main/java/io/sentry/godotplugin/SentryAndroidGodotPlugin.kt @@ -160,45 +160,56 @@ class SentryAndroidGodotPlugin(godot: Godot) : GodotPlugin(godot) { @UsedByGodot fun init( + optionsData: Dictionary, beforeSendHandlerId: Long, - dsn: String, - debug: Boolean, - release: String, - dist: String, - environment: String, - sampleRate: Float, - maxBreadcrumbs: Int, - enableLogs: Boolean, beforeSendLogHandlerId: Long ) { Log.v(TAG, "Initializing Sentry Android") - SentryAndroid.init(godot.getActivity()!!.applicationContext) { options -> - options.dsn = dsn.ifEmpty { null } - options.isDebug = debug - options.release = release.ifEmpty { null } - options.dist = dist.ifEmpty { null } - options.environment = environment.ifEmpty { null } - options.sampleRate = sampleRate.toDouble() - options.maxBreadcrumbs = maxBreadcrumbs - options.sdkVersion?.name = "sentry.java.android.godot" - options.nativeSdkName = "sentry.native.android.godot" - options.logs.isEnabled = enableLogs - options.beforeSend = - SentryOptions.BeforeSendCallback { event: SentryEvent, hint: Hint -> - Log.v(TAG, "beforeSend: ${event.eventId} isCrashed: ${event.isCrashed}") - val handle: Int = registerEvent(event) - Callable.call(beforeSendHandlerId, "before_send", handle) - eventsByHandle.get()?.remove(handle) // Returns the event or null if it was discarded. - } - if (beforeSendLogHandlerId != 0L) { - options.logs.beforeSend = - SentryOptions.Logs.BeforeSendLogCallback { logEvent -> - val handle: Int = registerLog(logEvent) - Callable.call(beforeSendLogHandlerId, "before_send_log", handle) - logsByHandle.get()?.remove(handle) // Returns the log or null if it was discarded. + + try { + val dsn = optionsData["dsn"] as String + val debug = optionsData["debug"] as Boolean + val release = optionsData["release"] as String + val dist = optionsData["dist"] as String + val environment = optionsData["environment"] as String + val sampleRate = optionsData["sample_rate"] as Double + val maxBreadcrumbs = optionsData["max_breadcrumbs"].toIntOrThrow() + val enableLogs = optionsData["enable_logs"] as Boolean + val appHangTracking = optionsData["app_hang_tracking"] as Boolean + val appHangTimeoutSec = optionsData["app_hang_timeout_sec"] as Double + + SentryAndroid.init(godot.getActivity()!!.applicationContext) { options -> + options.dsn = dsn.ifEmpty { null } + options.isDebug = debug + options.release = release.ifEmpty { null } + options.dist = dist.ifEmpty { null } + options.environment = environment.ifEmpty { null } + options.sampleRate = sampleRate.toDouble() + options.maxBreadcrumbs = maxBreadcrumbs + options.sdkVersion?.name = "sentry.java.android.godot" + options.nativeSdkName = "sentry.native.android.godot" + options.logs.isEnabled = enableLogs + options.isAnrEnabled = appHangTracking + options.anrTimeoutIntervalMillis = (appHangTimeoutSec * 1000.0).toLong() + options.beforeSend = + SentryOptions.BeforeSendCallback { event: SentryEvent, hint: Hint -> + Log.v(TAG, "beforeSend: ${event.eventId} isCrashed: ${event.isCrashed}") + val handle: Int = registerEvent(event) + Callable.call(beforeSendHandlerId, "before_send", handle) + eventsByHandle.get()?.remove(handle) // Returns the event or null if it was discarded. } + if (beforeSendLogHandlerId != 0L) { + options.logs.beforeSend = + SentryOptions.Logs.BeforeSendLogCallback { logEvent -> + val handle: Int = registerLog(logEvent) + Callable.call(beforeSendLogHandlerId, "before_send_log", handle) + logsByHandle.get()?.remove(handle) // Returns the log or null if it was discarded. + } + } } - + } catch (e: Exception) { + Log.e(TAG, "Error initializing Sentry for Android", e) + return } } diff --git a/android_lib/src/main/java/io/sentry/godotplugin/UtilityFunctions.kt b/android_lib/src/main/java/io/sentry/godotplugin/UtilityFunctions.kt index 7de69bc1..b4183949 100644 --- a/android_lib/src/main/java/io/sentry/godotplugin/UtilityFunctions.kt +++ b/android_lib/src/main/java/io/sentry/godotplugin/UtilityFunctions.kt @@ -50,8 +50,14 @@ fun Long.microsecondsToTimestamp(): Date { return Date(millis) } - fun Date.toMicros(): Long { val date: Date = this@toMicros return date.time * 1000 } + +fun Any?.toIntOrThrow(): Int = + when (this) { + is Int -> this + is Long -> this.toInt() + else -> throw IllegalArgumentException("Expected Int or Long, got ${this?.let { it::class } ?: "null"}") + } diff --git a/src/sentry/android/android_sdk.cpp b/src/sentry/android/android_sdk.cpp index e5a47f95..a0d9560b 100644 --- a/src/sentry/android/android_sdk.cpp +++ b/src/sentry/android/android_sdk.cpp @@ -226,16 +226,21 @@ void AndroidSDK::init(const PackedStringArray &p_global_attachments, const Calla is_view_hierarchy ? "event.view_hierarchy" : String()); } + Dictionary optionsData; + optionsData["dsn"] = SentryOptions::get_singleton()->get_dsn(); + optionsData["debug"] = SentryOptions::get_singleton()->is_debug_enabled(); + optionsData["release"] = SentryOptions::get_singleton()->get_release(); + optionsData["dist"] = SentryOptions::get_singleton()->get_dist(); + optionsData["environment"] = SentryOptions::get_singleton()->get_environment(); + optionsData["sample_rate"] = SentryOptions::get_singleton()->get_sample_rate(); + optionsData["max_breadcrumbs"] = SentryOptions::get_singleton()->get_max_breadcrumbs(); + optionsData["enable_logs"] = SentryOptions::get_singleton()->get_experimental()->get_enable_logs(); + optionsData["app_hang_tracking"] = SentryOptions::get_singleton()->is_app_hang_tracking_enabled(); + optionsData["app_hang_timeout_sec"] = SentryOptions::get_singleton()->get_app_hang_timeout_sec(); + android_plugin->call(ANDROID_SN(init), + optionsData, before_send_handler->get_instance_id(), - SentryOptions::get_singleton()->get_dsn(), - SentryOptions::get_singleton()->is_debug_enabled(), - SentryOptions::get_singleton()->get_release(), - SentryOptions::get_singleton()->get_dist(), - SentryOptions::get_singleton()->get_environment(), - SentryOptions::get_singleton()->get_sample_rate(), - SentryOptions::get_singleton()->get_max_breadcrumbs(), - SentryOptions::get_singleton()->get_experimental()->get_enable_logs(), SentryOptions::get_singleton()->get_experimental()->before_send_log.is_valid() ? before_send_log_handler->get_instance_id() : 0); if (is_enabled()) { From 40b95e9eb6304a7fa935b787f4e38c59d032fd29 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Mon, 27 Oct 2025 17:03:36 +0100 Subject: [PATCH 4/6] Class docs and sync property name --- doc_classes/SentryOptions.xml | 6 ++++++ src/sentry/sentry_options.cpp | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/doc_classes/SentryOptions.xml b/doc_classes/SentryOptions.xml index bd7d0636..8e7b5671 100644 --- a/doc_classes/SentryOptions.xml +++ b/doc_classes/SentryOptions.xml @@ -13,6 +13,12 @@ + + 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. + + + 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. + If [code]true[/code], the SDK will attach the Godot log file to the event. diff --git a/src/sentry/sentry_options.cpp b/src/sentry/sentry_options.cpp index 9b19a7e6..4a91a0bf 100644 --- a/src/sentry/sentry_options.cpp +++ b/src/sentry/sentry_options.cpp @@ -210,7 +210,7 @@ void SentryOptions::_bind_methods() { BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::BOOL, "attach_scene_tree"), set_attach_scene_tree, is_attach_scene_tree_enabled); BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::BOOL, "app_hang_tracking"), set_app_hang_tracking, is_app_hang_tracking_enabled); - BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::FLOAT, "app_hang_timeout_seconds"), set_app_hang_timeout_sec, get_app_hang_timeout_sec); + BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::FLOAT, "app_hang_timeout_sec"), set_app_hang_timeout_sec, get_app_hang_timeout_sec); BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::BOOL, "logger_enabled"), set_logger_enabled, is_logger_enabled); BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::BOOL, "logger_include_source"), set_logger_include_source, is_logger_include_source_enabled); From 713553865aac8f31052963b0131241a60b4d93c9 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Mon, 27 Oct 2025 17:11:14 +0100 Subject: [PATCH 5/6] Mention platform support --- doc_classes/SentryOptions.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/doc_classes/SentryOptions.xml b/doc_classes/SentryOptions.xml index 8e7b5671..7d332ec6 100644 --- a/doc_classes/SentryOptions.xml +++ b/doc_classes/SentryOptions.xml @@ -18,6 +18,7 @@ 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. + [b]Note:[/b] This feature is only supported on Android, iOS, and macOS platforms. If [code]true[/code], the SDK will attach the Godot log file to the event. From 1e813cd4cbafb93a31da01157f63046511d77ee9 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Mon, 27 Oct 2025 17:25:34 +0100 Subject: [PATCH 6/6] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b77ba342..3344b7d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Detect when we're inside message logging to prevent SDK print operations through the Godot logger which cause runtime errors. ([#414](https://github.com/getsentry/sentry-godot/pull/414)) - Relax throttling limits on app startup ([#423](https://github.com/getsentry/sentry-godot/pull/423)) - Set app hang timeout to 5s on Apple platforms ([#416](https://github.com/getsentry/sentry-godot/pull/416)) +- Add app hang tracking options and disable this feature by default ([#429](https://github.com/getsentry/sentry-godot/pull/429)) ### Dependencies