Skip to content

Commit e8dfbe7

Browse files
authored
feat: Add app hang tracking options (#429)
Adds related options, and disables this feature by default.
1 parent c4d4741 commit e8dfbe7

File tree

8 files changed

+93
-43
lines changed

8 files changed

+93
-43
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- 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))
1515
- Relax throttling limits on app startup ([#423](https://github.com/getsentry/sentry-godot/pull/423))
1616
- Set app hang timeout to 5s on Apple platforms ([#416](https://github.com/getsentry/sentry-godot/pull/416))
17+
- Add app hang tracking options and disable this feature by default ([#429](https://github.com/getsentry/sentry-godot/pull/429))
1718

1819
### Dependencies
1920

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

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -160,45 +160,56 @@ class SentryAndroidGodotPlugin(godot: Godot) : GodotPlugin(godot) {
160160

161161
@UsedByGodot
162162
fun init(
163+
optionsData: Dictionary,
163164
beforeSendHandlerId: Long,
164-
dsn: String,
165-
debug: Boolean,
166-
release: String,
167-
dist: String,
168-
environment: String,
169-
sampleRate: Float,
170-
maxBreadcrumbs: Int,
171-
enableLogs: Boolean,
172165
beforeSendLogHandlerId: Long
173166
) {
174167
Log.v(TAG, "Initializing Sentry Android")
175-
SentryAndroid.init(godot.getActivity()!!.applicationContext) { options ->
176-
options.dsn = dsn.ifEmpty { null }
177-
options.isDebug = debug
178-
options.release = release.ifEmpty { null }
179-
options.dist = dist.ifEmpty { null }
180-
options.environment = environment.ifEmpty { null }
181-
options.sampleRate = sampleRate.toDouble()
182-
options.maxBreadcrumbs = maxBreadcrumbs
183-
options.sdkVersion?.name = "sentry.java.android.godot"
184-
options.nativeSdkName = "sentry.native.android.godot"
185-
options.logs.isEnabled = enableLogs
186-
options.beforeSend =
187-
SentryOptions.BeforeSendCallback { event: SentryEvent, hint: Hint ->
188-
Log.v(TAG, "beforeSend: ${event.eventId} isCrashed: ${event.isCrashed}")
189-
val handle: Int = registerEvent(event)
190-
Callable.call(beforeSendHandlerId, "before_send", handle)
191-
eventsByHandle.get()?.remove(handle) // Returns the event or null if it was discarded.
192-
}
193-
if (beforeSendLogHandlerId != 0L) {
194-
options.logs.beforeSend =
195-
SentryOptions.Logs.BeforeSendLogCallback { logEvent ->
196-
val handle: Int = registerLog(logEvent)
197-
Callable.call(beforeSendLogHandlerId, "before_send_log", handle)
198-
logsByHandle.get()?.remove(handle) // Returns the log or null if it was discarded.
168+
169+
try {
170+
val dsn = optionsData["dsn"] as String
171+
val debug = optionsData["debug"] as Boolean
172+
val release = optionsData["release"] as String
173+
val dist = optionsData["dist"] as String
174+
val environment = optionsData["environment"] as String
175+
val sampleRate = optionsData["sample_rate"] as Double
176+
val maxBreadcrumbs = optionsData["max_breadcrumbs"].toIntOrThrow()
177+
val enableLogs = optionsData["enable_logs"] as Boolean
178+
val appHangTracking = optionsData["app_hang_tracking"] as Boolean
179+
val appHangTimeoutSec = optionsData["app_hang_timeout_sec"] as Double
180+
181+
SentryAndroid.init(godot.getActivity()!!.applicationContext) { options ->
182+
options.dsn = dsn.ifEmpty { null }
183+
options.isDebug = debug
184+
options.release = release.ifEmpty { null }
185+
options.dist = dist.ifEmpty { null }
186+
options.environment = environment.ifEmpty { null }
187+
options.sampleRate = sampleRate.toDouble()
188+
options.maxBreadcrumbs = maxBreadcrumbs
189+
options.sdkVersion?.name = "sentry.java.android.godot"
190+
options.nativeSdkName = "sentry.native.android.godot"
191+
options.logs.isEnabled = enableLogs
192+
options.isAnrEnabled = appHangTracking
193+
options.anrTimeoutIntervalMillis = (appHangTimeoutSec * 1000.0).toLong()
194+
options.beforeSend =
195+
SentryOptions.BeforeSendCallback { event: SentryEvent, hint: Hint ->
196+
Log.v(TAG, "beforeSend: ${event.eventId} isCrashed: ${event.isCrashed}")
197+
val handle: Int = registerEvent(event)
198+
Callable.call(beforeSendHandlerId, "before_send", handle)
199+
eventsByHandle.get()?.remove(handle) // Returns the event or null if it was discarded.
199200
}
201+
if (beforeSendLogHandlerId != 0L) {
202+
options.logs.beforeSend =
203+
SentryOptions.Logs.BeforeSendLogCallback { logEvent ->
204+
val handle: Int = registerLog(logEvent)
205+
Callable.call(beforeSendLogHandlerId, "before_send_log", handle)
206+
logsByHandle.get()?.remove(handle) // Returns the log or null if it was discarded.
207+
}
208+
}
200209
}
201-
210+
} catch (e: Exception) {
211+
Log.e(TAG, "Error initializing Sentry for Android", e)
212+
return
202213
}
203214
}
204215

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,14 @@ fun Long.microsecondsToTimestamp(): Date {
5050
return Date(millis)
5151
}
5252

53-
5453
fun Date.toMicros(): Long {
5554
val date: Date = this@toMicros
5655
return date.time * 1000
5756
}
57+
58+
fun Any?.toIntOrThrow(): Int =
59+
when (this) {
60+
is Int -> this
61+
is Long -> this.toInt()
62+
else -> throw IllegalArgumentException("Expected Int or Long, got ${this?.let { it::class } ?: "null"}")
63+
}

doc_classes/SentryOptions.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@
1313
<tutorials>
1414
</tutorials>
1515
<members>
16+
<member name="app_hang_timeout_sec" type="float" setter="set_app_hang_timeout_sec" getter="get_app_hang_timeout_sec" default="5.0">
17+
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.
18+
</member>
19+
<member name="app_hang_tracking" type="bool" setter="set_app_hang_tracking" getter="is_app_hang_tracking_enabled" default="false">
20+
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.
22+
</member>
1623
<member name="attach_log" type="bool" setter="set_attach_log" getter="is_attach_log_enabled" default="true">
1724
If [code]true[/code], the SDK will attach the Godot log file to the event.
1825
</member>

src/sentry/android/android_sdk.cpp

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -226,16 +226,21 @@ void AndroidSDK::init(const PackedStringArray &p_global_attachments, const Calla
226226
is_view_hierarchy ? "event.view_hierarchy" : String());
227227
}
228228

229+
Dictionary optionsData;
230+
optionsData["dsn"] = SentryOptions::get_singleton()->get_dsn();
231+
optionsData["debug"] = SentryOptions::get_singleton()->is_debug_enabled();
232+
optionsData["release"] = SentryOptions::get_singleton()->get_release();
233+
optionsData["dist"] = SentryOptions::get_singleton()->get_dist();
234+
optionsData["environment"] = SentryOptions::get_singleton()->get_environment();
235+
optionsData["sample_rate"] = SentryOptions::get_singleton()->get_sample_rate();
236+
optionsData["max_breadcrumbs"] = SentryOptions::get_singleton()->get_max_breadcrumbs();
237+
optionsData["enable_logs"] = SentryOptions::get_singleton()->get_experimental()->get_enable_logs();
238+
optionsData["app_hang_tracking"] = SentryOptions::get_singleton()->is_app_hang_tracking_enabled();
239+
optionsData["app_hang_timeout_sec"] = SentryOptions::get_singleton()->get_app_hang_timeout_sec();
240+
229241
android_plugin->call(ANDROID_SN(init),
242+
optionsData,
230243
before_send_handler->get_instance_id(),
231-
SentryOptions::get_singleton()->get_dsn(),
232-
SentryOptions::get_singleton()->is_debug_enabled(),
233-
SentryOptions::get_singleton()->get_release(),
234-
SentryOptions::get_singleton()->get_dist(),
235-
SentryOptions::get_singleton()->get_environment(),
236-
SentryOptions::get_singleton()->get_sample_rate(),
237-
SentryOptions::get_singleton()->get_max_breadcrumbs(),
238-
SentryOptions::get_singleton()->get_experimental()->get_enable_logs(),
239244
SentryOptions::get_singleton()->get_experimental()->before_send_log.is_valid() ? before_send_log_handler->get_instance_id() : 0);
240245

241246
if (is_enabled()) {

src/sentry/cocoa/cocoa_sdk.mm

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,10 +258,12 @@
258258
options.dist = string_to_objc(dist);
259259
}
260260

261+
options.enableAppHangTracking = SentryOptions::get_singleton()->is_app_hang_tracking_enabled();
262+
options.appHangTimeoutInterval = SentryOptions::get_singleton()->get_app_hang_timeout_sec();
263+
261264
// NOTE: This only works for captureMessage(), unfortunately.
262265
options.attachStacktrace = false;
263266

264-
options.appHangTimeoutInterval = 5; // 5 seconds
265267
options.experimental.enableLogs = SentryOptions::get_singleton()->get_experimental()->get_enable_logs();
266268

267269
options.initialScope = ^(objc::SentryScope *scope) {

src/sentry/sentry_options.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ void SentryOptions::_define_project_settings(const Ref<SentryOptions> &p_options
7979
_define_setting("sentry/options/attach_log", p_options->attach_log, false);
8080
_define_setting("sentry/options/attach_scene_tree", p_options->attach_scene_tree);
8181

82+
_define_setting("sentry/options/app_hang/tracking", p_options->app_hang_tracking, false);
83+
_define_setting("sentry/options/app_hang/timeout_sec", p_options->app_hang_timeout_sec, false);
84+
8285
_define_setting("sentry/logger/logger_enabled", p_options->logger_enabled);
8386
_define_setting("sentry/logger/include_source", p_options->logger_include_source, false);
8487
_define_setting("sentry/logger/include_variables", p_options->logger_include_variables, false);
@@ -121,6 +124,9 @@ void SentryOptions::_load_project_settings(const Ref<SentryOptions> &p_options)
121124
p_options->attach_log = ProjectSettings::get_singleton()->get_setting("sentry/options/attach_log", p_options->attach_log);
122125
p_options->attach_scene_tree = ProjectSettings::get_singleton()->get_setting("sentry/options/attach_scene_tree", p_options->attach_scene_tree);
123126

127+
p_options->app_hang_tracking = ProjectSettings::get_singleton()->get_setting("sentry/options/app_hang/tracking", p_options->app_hang_tracking);
128+
p_options->app_hang_timeout_sec = ProjectSettings::get_singleton()->get_setting("sentry/options/app_hang/timeout_sec", p_options->app_hang_timeout_sec);
129+
124130
p_options->logger_enabled = ProjectSettings::get_singleton()->get_setting("sentry/logger/logger_enabled", p_options->logger_enabled);
125131
p_options->logger_include_source = ProjectSettings::get_singleton()->get_setting("sentry/logger/include_source", p_options->logger_include_source);
126132
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() {
203209
BIND_PROPERTY(SentryOptions, sentry::make_level_enum_property("screenshot_level"), set_screenshot_level, get_screenshot_level);
204210
BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::BOOL, "attach_scene_tree"), set_attach_scene_tree, is_attach_scene_tree_enabled);
205211

212+
BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::BOOL, "app_hang_tracking"), set_app_hang_tracking, is_app_hang_tracking_enabled);
213+
BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::FLOAT, "app_hang_timeout_sec"), set_app_hang_timeout_sec, get_app_hang_timeout_sec);
214+
206215
BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::BOOL, "logger_enabled"), set_logger_enabled, is_logger_enabled);
207216
BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::BOOL, "logger_include_source"), set_logger_include_source, is_logger_include_source_enabled);
208217
BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::BOOL, "logger_include_variables"), set_logger_include_variables, is_logger_include_variables_enabled);

src/sentry/sentry_options.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ class SentryOptions : public RefCounted {
7979
sentry::Level screenshot_level = sentry::LEVEL_FATAL;
8080
bool attach_scene_tree = false;
8181

82+
bool app_hang_tracking = false;
83+
double app_hang_timeout_sec = 5.0;
84+
8285
bool logger_enabled = true;
8386
bool logger_include_source = true;
8487
bool logger_include_variables = false;
@@ -152,6 +155,12 @@ class SentryOptions : public RefCounted {
152155
_FORCE_INLINE_ void set_attach_scene_tree(bool p_enable) { attach_scene_tree = p_enable; }
153156
_FORCE_INLINE_ bool is_attach_scene_tree_enabled() const { return attach_scene_tree; }
154157

158+
_FORCE_INLINE_ bool is_app_hang_tracking_enabled() const { return app_hang_tracking; }
159+
_FORCE_INLINE_ void set_app_hang_tracking(bool p_enabled) { app_hang_tracking = p_enabled; }
160+
161+
_FORCE_INLINE_ double get_app_hang_timeout_sec() const { return app_hang_timeout_sec; }
162+
_FORCE_INLINE_ void set_app_hang_timeout_sec(double p_seconds) { app_hang_timeout_sec = p_seconds; }
163+
155164
_FORCE_INLINE_ bool is_logger_enabled() const { return logger_enabled; }
156165
_FORCE_INLINE_ void set_logger_enabled(bool p_enabled) { logger_enabled = p_enabled; }
157166

0 commit comments

Comments
 (0)