diff --git a/CHANGELOG.md b/CHANGELOG.md index d7b69a11..fa0c1af5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,16 @@ ## Unreleased +### Breaking changes + +- Configuration script support and `SentryConfiguration` class are removed. Instead, please use manual initialization with a configuration callback, if you need to set up SDK from code. See [#321](https://github.com/getsentry/sentry-godot/pull/321) for details. +- `enabled` option is renamed to `auto_init` for clarity, and removed from SentryOptions properties (setting it from code has no sense - we auto-initialize very early). +- `disabled_in_editor_play` option is renamed to `skip_auto_init_on_editor_play` for clarity, and removed from SentryOptions properties. + ### Features - Support local variables on Android ([#334](https://github.com/getsentry/sentry-godot/pull/334)) +- Allow initializing manually and shutting down SentrySDK ([#321](https://github.com/getsentry/sentry-godot/pull/321)) ### Other changes 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 841e3020..3b6a2c4f 100644 --- a/android_lib/src/main/java/io/sentry/godotplugin/SentryAndroidGodotPlugin.kt +++ b/android_lib/src/main/java/io/sentry/godotplugin/SentryAndroidGodotPlugin.kt @@ -123,7 +123,7 @@ class SentryAndroidGodotPlugin(godot: Godot) : GodotPlugin(godot) { } @UsedByGodot - fun initialize( + fun init( beforeSendHandlerId: Long, dsn: String, debug: Boolean, @@ -154,6 +154,16 @@ class SentryAndroidGodotPlugin(godot: Godot) : GodotPlugin(godot) { } } + @UsedByGodot + fun close() { + Sentry.close() + } + + @UsedByGodot + fun isEnabled(): Boolean { + return Sentry.isEnabled() + } + @UsedByGodot fun addFileAttachment(path: String, filename: String, contentType: String, attachmentType: String) { val attachment = Attachment( diff --git a/doc_classes/SentryConfiguration.xml b/doc_classes/SentryConfiguration.xml deleted file mode 100644 index 72dc1f26..00000000 --- a/doc_classes/SentryConfiguration.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - Base class for user configuration script in Sentry SDK. - - - Allows configuring Sentry SDK from GDScript. The [method _configure] method is called just before Sentry SDK initializes. - To define a configuration script, create a new script that extends the [SentryConfiguration] class. Then, assign your configuration script in the Project Settings under the [b]Sentry[/b] category. - [b]Important[/b]: If a user configuration script is assigned, SDK initialization will be delayed until [b]ScriptServer[/b] becomes available during game startup. - [codeblock] - extends SentryConfiguration - - func _configure(options: SentryOptions): - if OS.is_debug_build(): - options.environment = "debug" - options.debug = true - options.release = "mygame@1.0.0" - options.before_send = _process_event - - func _process_event(event: SentryEvent) -> SentryEvent: - if event.environment == "debug": - # Discard event if running in a debug build. - return null - if event.message.contains("Bruno"): - # Remove sensitive information from the event. - event.message = event.message.replace("Bruno", "REDACTED") - return event - [/codeblock] - - - - - - - - - Called just before the Sentry SDK initializes. You can override SDK [param options] here and register [member SentryOptions.before_send] callback if needed. - - - - diff --git a/doc_classes/SentryOptions.xml b/doc_classes/SentryOptions.xml index 64332d56..1640596c 100644 --- a/doc_classes/SentryOptions.xml +++ b/doc_classes/SentryOptions.xml @@ -4,7 +4,10 @@ Contains Sentry SDK options. - Defines options for the Sentry SDK. These options can be modified in the Project Settings under the [b]Sentry[/b] category or through a user configuration script. See [SentryConfiguration] for details on creating such a script. + Defines options for the Sentry SDK. These options can be modified in the Project Settings under the [b]Sentry[/b] category or from code through a configuration callback using manual initialization. See [method SentrySDK.init]. + There are two options, that only appear in the project settings: + [b]Auto Init[/b] - Enables automatic initialization of the Sentry SDK when the game starts. + [b]Skip Auto Init on Editor Play[/b] - Prevents automatic initialization when running the game from the editor (by pressing the play button or F5). To learn more, visit [url=https://docs.sentry.io/platforms/godot/configuration/options/]Options documentation[/url]. @@ -29,7 +32,7 @@ [/codeblock] - If assigned, this callback runs before an event is sent to Sentry. It takes [SentryEvent] as a parameter and return either the same event object, with or without modifications, or [code]null[/code] to skip reporting the event. You can assign it in a [SentryConfiguration] script. To check if the event is a crash, use [method SentryEvent.is_crash]. + If assigned, this callback runs before an event is sent to Sentry. It takes [SentryEvent] as a parameter and return either the same event object, with or without modifications, or [code]null[/code] to skip reporting the event. You can assign it in a configuration callback using manual initialization (see [method SentrySDK.init]). To check if the event is a crash, use [method SentryEvent.is_crash]. [codeblock] func _before_send(event: SentryEvent) -> SentryEvent: if event.environment == "editor_dev_run": @@ -49,20 +52,14 @@ Specifies the minimum level of messages to be printed if [member debug] is enabled. - - If [code]true[/code], the SDK will not initialize when you play/run your project from within the Godot editor (using the play button or F5). - The application's distribution. Distributions are used to disambiguate build or deployment variants of the same release of an application. Data Source Name (DSN): Specifies where the SDK should send the events. If this value is not provided, the SDK will try to read it from the [code]SENTRY_DSN[/code] environment variable. If that variable also does not exist, the SDK will just not send any events. - - If [code]false[/code], the SDK will not initialize. This is useful for temporarily disabling the SDK in the Project Settings, or in a [SentryConfiguration] script. - - Environments indicate where an error occurred, such as in a release export, headless server, QA build, or another deployment. The SDK automatically detects Godot-specific environments, such as [code]headless_server[/code] and [code]export_release[/code], but you can also assign it in a [SentryConfiguration] script. + Environments indicate where an error occurred, such as in a release export, headless server, QA build, or another deployment. The SDK automatically detects Godot-specific environments, such as [code]headless_server[/code] and [code]export_release[/code], but you can also assign it in a configuration callback using manual initialization (see [method SentrySDK.init]). To learn more, visit [url=https://docs.sentry.io/platforms/godot/configuration/environments/]Environments documentation[/url]. diff --git a/doc_classes/SentrySDK.xml b/doc_classes/SentrySDK.xml index b3dec615..38510b60 100644 --- a/doc_classes/SentrySDK.xml +++ b/doc_classes/SentrySDK.xml @@ -44,6 +44,12 @@ Captures an event with [param message] and sends it to Sentry, returning the event ID. + + + + Closes the SDK and flushes any pending events to ensure all queued data is sent to Sentry before shutdown. Called automatically when the application exits. + + @@ -62,6 +68,35 @@ Returns the currently set user. See [SentryUser]. + + + + + Initializes the SDK. Called automatically during startup if "Auto Init" is enabled in the project settings. + Manual initialization is only needed when you want to control the exact timing of SDK startup, or when you need to initialize options from code, or use callbacks such as [member SentryOptions.before_send]. Make sure to disable "Auto Init" in the project settings if you intend to manually initialize the SDK. + [b]Note[/b]: The earliest place to initialize Sentry from script is in the [method MainLoop._initialize]. + You can customize the SDK's options by passing a [param configuration_callback] function. Here's a complete example: + [codeblock] + class_name ProjectMainLoop + extends SceneTree + # Tip: Assign ProjectMainLoop as your main loop type in the project settings + # under `application/run/main_loop_type`. + + func _initialize() -> void: + SentrySDK.init(func(options: SentryOptions) -> void: + options.debug = true + options.release = "my-game@1.2.3" + options.environment = "production" + options.before_send = _on_before_send_to_sentry + ) + + func _on_before_send_to_sentry(event: SentryEvent) -> SentryEvent: + # Process event and return either the same event object, + # with or withour modifications, or null to skip reporting the event. + return event + [/codeblock] + + diff --git a/project/example_configuration.gd b/project/example_configuration.gd deleted file mode 100644 index 5cf1f5ba..00000000 --- a/project/example_configuration.gd +++ /dev/null @@ -1,31 +0,0 @@ -extends SentryConfiguration -## Example Sentry configuration script. -## -## Tip: You can assign configuration script in the project settings. - - -## Configure Sentry SDK options -func _configure(options: SentryOptions) -> void: - print("INFO: [example_configuration.gd] Configuring SDK options via GDScript") - - options.debug = true - options.release = "sentry-godot-demo@" + ProjectSettings.get_setting("application/config/version") - options.environment = "demo" - - # Set up event callbacks - options.before_send = _before_send - - # Unit testing hooks (if you're exploring the demo project, just ignore the following line). - load("res://testing_configuration.gd").configure_options(options) - - -## before_send callback example -func _before_send(ev: SentryEvent) -> SentryEvent: - print("INFO: [example_configuration.gd] Processing event: ", ev.id) - if ev.message.contains("Bruno"): - print("INFO: [example_configuration.gd] Removing sensitive information from the event") - ev.message = ev.message.replace("Bruno", "REDACTED") - elif ev.message == "junk": - print("INFO: [example_configuration.gd] Discarding event with message 'junk'") - return null - return ev diff --git a/project/example_configuration.gd.uid b/project/example_configuration.gd.uid deleted file mode 100644 index 23891733..00000000 --- a/project/example_configuration.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://dmavxxyqbqght diff --git a/project/project.godot b/project/project.godot index 88c1a35e..e2cc425e 100644 --- a/project/project.godot +++ b/project/project.godot @@ -13,6 +13,7 @@ config_version=5 config/name="Sentry demo project" config/version="1.0.0-beta.1" run/main_scene="uid://cqiowj0jydds1" +run/main_loop_type="ProjectMainLoop" config/features=PackedStringArray("4.5") run/flush_stdout_on_print=true config/icon="uid://djdyhgbcdue6n" @@ -45,8 +46,8 @@ textures/vram_compression/import_etc2_astc=true [sentry] +options/auto_init=false options/dsn="https://3f1e095cf2e14598a0bd5b4ff324f712@o447951.ingest.us.sentry.io/6680910" -options/configuration_script="uid://dmavxxyqbqght" options/attach_screenshot=true options/attach_scene_tree=true logger/include_variables=true diff --git a/project/project_main_loop.gd b/project/project_main_loop.gd new file mode 100644 index 00000000..b6678e52 --- /dev/null +++ b/project/project_main_loop.gd @@ -0,0 +1,36 @@ +class_name ProjectMainLoop +extends SceneTree +## Example of initializing and configuring Sentry from code. +## +## The earliest place to initialize Sentry in script is in the MainLoop._initialize(). +## Tip: You can assign "ProjectMainLoop" as your main loop class in the project settings +## under `application/run/main_loop_type`. + + +func _initialize() -> void: + SentrySDK.init(func(options: SentryOptions) -> void: + print("INFO: [ProjectMainLoop] Initializing SDK from GDScript") + + options.debug = true + options.release = "sentry-godot-demo@" + ProjectSettings.get_setting("application/config/version") + options.environment = "demo" + + # Set up event callbacks + options.before_send = _on_before_send_to_sentry + ) + + # Post-initialize + # SentrySDK.add_attachment(...) + # ... + + +## before_send example +func _on_before_send_to_sentry(ev: SentryEvent) -> SentryEvent: + print("INFO: [ProjectMainLoop] Processing event: ", ev.id) + if ev.message.contains("Bruno"): + print("INFO: [ProjectMainLoop] Removing sensitive information from the event") + ev.message = ev.message.replace("Bruno", "REDACTED") + elif ev.message == "junk": + print("INFO: [ProjectMainLoop] Discarding event with message 'junk'") + return null + return ev diff --git a/project/project_main_loop.gd.uid b/project/project_main_loop.gd.uid new file mode 100644 index 00000000..14b29ada --- /dev/null +++ b/project/project_main_loop.gd.uid @@ -0,0 +1 @@ +uid://ckyjdnckvfm8b diff --git a/project/test/isolated/test_limit_events_per_frame.gd b/project/test/isolated/test_limit_events_per_frame.gd index 493f38f4..663d5ffc 100644 --- a/project/test/isolated/test_limit_events_per_frame.gd +++ b/project/test/isolated/test_limit_events_per_frame.gd @@ -7,16 +7,15 @@ signal callback_processed var _num_events: int = 0 -static func configure_options(options: SentryOptions) -> void: - # Only one error is allowed to be logged as event per processed frame. - options.logger_limits.events_per_frame = 1 - # Make sure other limits are not interfering. - options.logger_limits.repeated_error_window_ms = 0 - options.logger_limits.throttle_events = 88 - - -func before_test() -> void: - SentrySDK._set_before_send(_before_send) +func before() -> void: + SentrySDK.init(func(options: SentryOptions) -> void: + # Only one error is allowed to be logged as event per processed frame. + options.logger_limits.events_per_frame = 1 + # Make sure other limits are not interfering. + options.logger_limits.repeated_error_window_ms = 0 + options.logger_limits.throttle_events = 88 + options.before_send = _before_send + ) func _before_send(_ev: SentryEvent) -> SentryEvent: @@ -27,10 +26,12 @@ func _before_send(_ev: SentryEvent) -> SentryEvent: ## Only one error should be logged within 1 processed frame. func test_events_per_frame_limit() -> void: - var monitor := monitor_signals(self, false) + monitor_signals(self, false) + push_error("dummy-error") push_error("dummy-error") push_error("dummy-error") - await assert_signal(monitor).is_emitted("callback_processed") + + await assert_signal(self).is_emitted("callback_processed") await get_tree().create_timer(0.1).timeout assert_int(_num_events).is_equal(1) diff --git a/project/test/isolated/test_limit_repeating_error_window.gd b/project/test/isolated/test_limit_repeating_error_window.gd index 2ae788c4..c5d43115 100644 --- a/project/test/isolated/test_limit_repeating_error_window.gd +++ b/project/test/isolated/test_limit_repeating_error_window.gd @@ -7,16 +7,15 @@ signal callback_processed var _num_events: int = 0 -static func configure_options(options: SentryOptions) -> void: - # Ignore duplicate errors within 1 second window. - options.logger_limits.repeated_error_window_ms = 1000 - # Make sure other limits are not interfering. - options.logger_limits.events_per_frame = 88 - options.logger_limits.throttle_events = 88 - - -func before_test() -> void: - SentrySDK._set_before_send(_before_send) +func before() -> void: + SentrySDK.init(func(options: SentryOptions) -> void: + # Ignore duplicate errors within 1 second window. + options.logger_limits.repeated_error_window_ms = 1000 + # Make sure other limits are not interfering. + options.logger_limits.events_per_frame = 88 + options.logger_limits.throttle_events = 88 + options.before_send = _before_send + ) func _before_send(_ev: SentryEvent) -> SentryEvent: diff --git a/project/test/isolated/test_limit_throttling.gd b/project/test/isolated/test_limit_throttling.gd index 182c0806..3739a86c 100644 --- a/project/test/isolated/test_limit_throttling.gd +++ b/project/test/isolated/test_limit_throttling.gd @@ -7,17 +7,16 @@ signal callback_processed var _num_events: int = 0 -static func configure_options(options: SentryOptions) -> void: - # Allow only two errors to be logged as events within 1 second time window. - options.logger_limits.throttle_events = 2 - options.logger_limits.throttle_window_ms = 1000 - # Make sure other limits are not interfering. - options.logger_limits.events_per_frame = 88 - options.logger_limits.repeated_error_window_ms = 0 - - -func before_test() -> void: - SentrySDK._set_before_send(_before_send) +func before() -> void: + SentrySDK.init(func(options: SentryOptions) -> void: + # Allow only two errors to be logged as events within 1 second time window. + options.logger_limits.throttle_events = 2 + options.logger_limits.throttle_window_ms = 1000 + # Make sure other limits are not interfering. + options.logger_limits.events_per_frame = 88 + options.logger_limits.repeated_error_window_ms = 0 + options.before_send = _before_send + ) func _before_send(_ev: SentryEvent) -> SentryEvent: diff --git a/project/test/isolated/test_logger_disabled.gd b/project/test/isolated/test_logger_disabled.gd index 34416f46..2e987230 100644 --- a/project/test/isolated/test_logger_disabled.gd +++ b/project/test/isolated/test_logger_disabled.gd @@ -7,30 +7,30 @@ signal callback_processed var _num_events: int = 0 -static func configure_options(options: SentryOptions) -> void: - options.logger_enabled = false +func before() -> void: + SentrySDK.init(func(options: SentryOptions) -> void: + options.logger_enabled = false - # Make sure other limits are not interfering. - options.logger_limits.events_per_frame = 88 - options.logger_limits.throttle_events = 88 - options.logger_limits.repeated_error_window_ms = 0 - options.logger_limits.throttle_window_ms = 0 + # Make sure other limits are not interfering. + options.logger_limits.events_per_frame = 88 + options.logger_limits.throttle_events = 88 + options.logger_limits.repeated_error_window_ms = 0 + options.logger_limits.throttle_window_ms = 0 - -func before_test() -> void: - SentrySDK._set_before_send(_before_send) + options.before_send = _before_send + ) func _before_send(_ev: SentryEvent) -> SentryEvent: - _num_events += 1 - callback_processed.emit() - return null + _num_events += 1 + callback_processed.emit() + return null func test_event_and_breadcrumb_masks() -> void: - var monitor := monitor_signals(self, false) - push_error("dummy-error") - push_warning("dummy-warning") - await assert_signal(monitor).is_not_emitted("callback_processed") + var monitor := monitor_signals(self, false) + push_error("dummy-error") + push_warning("dummy-warning") + await assert_signal(monitor).is_not_emitted("callback_processed") - assert_int(_num_events).is_equal(0) + assert_int(_num_events).is_equal(0) diff --git a/project/test/isolated/test_logger_with_masks.gd b/project/test/isolated/test_logger_with_masks.gd index 83af8719..0e90dcfa 100644 --- a/project/test/isolated/test_logger_with_masks.gd +++ b/project/test/isolated/test_logger_with_masks.gd @@ -8,35 +8,35 @@ signal callback_processed var _num_events: int = 0 -static func configure_options(options: SentryOptions) -> void: - var mask = SentryOptions.MASK_ERROR | SentryOptions.MASK_SCRIPT | SentryOptions.MASK_SHADER | SentryOptions.MASK_WARNING - options.logger_event_mask = mask - options.logger_breadcrumb_mask = mask +func before() -> void: + SentrySDK.init(func(options: SentryOptions) -> void: + var mask = SentryOptions.MASK_ERROR | SentryOptions.MASK_SCRIPT | SentryOptions.MASK_SHADER | SentryOptions.MASK_WARNING + options.logger_event_mask = mask + options.logger_breadcrumb_mask = mask - # Make sure other limits are not interfering. - options.logger_limits.events_per_frame = 88 - options.logger_limits.throttle_events = 88 - options.logger_limits.repeated_error_window_ms = 0 - options.logger_limits.throttle_window_ms = 0 + # Make sure other limits are not interfering. + options.logger_limits.events_per_frame = 88 + options.logger_limits.throttle_events = 88 + options.logger_limits.repeated_error_window_ms = 0 + options.logger_limits.throttle_window_ms = 0 - -func before_test() -> void: - SentrySDK._set_before_send(_before_send) + options.before_send = _before_send + ) func _before_send(_ev: SentryEvent) -> SentryEvent: - _num_events += 1 - callback_processed.emit() - return null + _num_events += 1 + callback_processed.emit() + return null ## Both events or breadcrumbs should be logged for error and warning. ## TODO: can't verify breadcrumbs yet, maybe later. func test_event_and_breadcrumb_masks() -> void: - var monitor := monitor_signals(self, false) - push_error("dummy-error") - push_warning("dummy-warning") - await assert_signal(monitor).is_emitted("callback_processed") + var monitor := monitor_signals(self, false) + push_error("dummy-error") + push_warning("dummy-warning") + await assert_signal(monitor).is_emitted("callback_processed") - await get_tree().create_timer(0.1).timeout - assert_int(_num_events).is_equal(2) + await get_tree().create_timer(0.1).timeout + assert_int(_num_events).is_equal(2) diff --git a/project/test/isolated/test_logger_with_masks_empty.gd b/project/test/isolated/test_logger_with_masks_empty.gd index 989cfa61..888d9fc0 100644 --- a/project/test/isolated/test_logger_with_masks_empty.gd +++ b/project/test/isolated/test_logger_with_masks_empty.gd @@ -8,28 +8,28 @@ signal callback_processed var _num_events: int = 0 -static func configure_options(options: SentryOptions) -> void: - options.logger_event_mask = 0 - options.logger_breadcrumb_mask = 0 +func before() -> void: + SentrySDK.init(func(options: SentryOptions) -> void: + options.logger_event_mask = 0 + options.logger_breadcrumb_mask = 0 - -func before_test() -> void: - SentrySDK._set_before_send(_before_send) + options.before_send = _before_send + ) func _before_send(_ev: SentryEvent) -> SentryEvent: - _num_events += 1 - callback_processed.emit() - return null + _num_events += 1 + callback_processed.emit() + return null ## No events or breadcrumbs should be logged for errors. ## TODO: can't verify breadcrumbs yet, maybe later. func test_event_and_breadcrumb_masks() -> void: - var monitor := monitor_signals(self, false) - push_error("dummy-error") - push_warning("dummy-warning") - await assert_signal(monitor).is_not_emitted("callback_processed") + var monitor := monitor_signals(self, false) + push_error("dummy-error") + push_warning("dummy-warning") + await assert_signal(monitor).is_not_emitted("callback_processed") - await get_tree().create_timer(0.1).timeout - assert_int(_num_events).is_equal(0) + await get_tree().create_timer(0.1).timeout + assert_int(_num_events).is_equal(0) diff --git a/project/test/isolated/test_options_integrity.gd b/project/test/isolated/test_options_integrity.gd index a64029f4..f099280c 100644 --- a/project/test/isolated/test_options_integrity.gd +++ b/project/test/isolated/test_options_integrity.gd @@ -5,13 +5,13 @@ extends GdUnitTestSuite signal callback_processed -static func configure_options(options: SentryOptions) -> void: - options.release = "1.2.3" - options.environment = "testing" +func before() -> void: + SentrySDK.init(func(options: SentryOptions) -> void: + options.release = "1.2.3" + options.environment = "testing" - -func before_test() -> void: - SentrySDK._set_before_send(_before_send) + options.before_send = _before_send + ) func _before_send(ev: SentryEvent) -> SentryEvent: diff --git a/project/test/isolated/test_sdk_lifecycle.gd b/project/test/isolated/test_sdk_lifecycle.gd new file mode 100644 index 00000000..89df4487 --- /dev/null +++ b/project/test/isolated/test_sdk_lifecycle.gd @@ -0,0 +1,35 @@ +extends GdUnitTestSuite +## Test lifecycle methods. + + +signal callback_processed + + +func _before_send(_ev: SentryEvent) -> SentryEvent: + callback_processed.emit() + return null + + +## Test manual initialization and shutdown of SDK. +func test_sdk_lifecycle() -> void: + monitor_signals(self, false) + + # SDK should be disabled at start. + assert_bool(SentrySDK.is_enabled()).is_false() + + SentrySDK.capture_message("message not captured before SDK is initialized") + await assert_signal(self).is_not_emitted("callback_processed") + + SentrySDK.init(func (options: SentryOptions) -> void: + options.before_send = _before_send + ) + assert_bool(SentrySDK.is_enabled()).is_true() + + SentrySDK.capture_message("message captured when SDK is initialiazed") + await assert_signal(self).is_emitted("callback_processed") + + SentrySDK.close() + assert_bool(SentrySDK.is_enabled()).is_false() + + SentrySDK.capture_message("message not captured when SDK is closed") + await assert_signal(self).is_not_emitted("callback_processed") diff --git a/project/test/isolated/test_sdk_lifecycle.gd.uid b/project/test/isolated/test_sdk_lifecycle.gd.uid new file mode 100644 index 00000000..c66c2e19 --- /dev/null +++ b/project/test/isolated/test_sdk_lifecycle.gd.uid @@ -0,0 +1 @@ +uid://bnpw7sgibliel diff --git a/project/test/suites/test_event.gd b/project/test/suites/test_event.gd index ae6b1d49..dca68a51 100644 --- a/project/test/suites/test_event.gd +++ b/project/test/suites/test_event.gd @@ -2,6 +2,11 @@ extends GdUnitTestSuite ## Basic tests for the SentryEvent class. +func before() -> void: + if not SentrySDK.is_enabled(): + SentrySDK.init() + + ## Test string properties accessors and UTF-8 encoding preservation. @warning_ignore("unused_parameter") func test_string_properties_and_utf8(property: String, test_parameters := [ diff --git a/project/test/suites/test_event_integrity.gd b/project/test/suites/test_event_integrity.gd index eeeadecf..7d26e9f8 100644 --- a/project/test/suites/test_event_integrity.gd +++ b/project/test/suites/test_event_integrity.gd @@ -7,6 +7,11 @@ signal callback_processed var created_id: String +func before() -> void: + if not SentrySDK.is_enabled(): + SentrySDK.init() + + func before_test() -> void: SentrySDK._set_before_send(_before_send) diff --git a/project/test/suites/test_options.gd b/project/test/suites/test_options.gd index 484df4d3..fdf56b5e 100644 --- a/project/test/suites/test_options.gd +++ b/project/test/suites/test_options.gd @@ -12,8 +12,6 @@ func before_test() -> void: ## Test simple bool properties. @warning_ignore("unused_parameter") func test_bool_properties(property: String, test_parameters := [ - ["enabled"], - ["disabled_in_editor_play"], ["debug"], ["attach_log"], ["attach_screenshot"], diff --git a/project/test/suites/test_sdk.gd b/project/test/suites/test_sdk.gd index 5eec51ac..ddbd0f6d 100644 --- a/project/test/suites/test_sdk.gd +++ b/project/test/suites/test_sdk.gd @@ -5,6 +5,11 @@ extends GdUnitTestSuite signal callback_processed +func before() -> void: + if not SentrySDK.is_enabled(): + SentrySDK.init() + + ## SentrySDK.capture_message() should return a non-empty event ID, which must match the ID returned by the get_last_event_id() call. func test_capture_message_id() -> void: var event_id := SentrySDK.capture_message("capture_message_test", SentrySDK.LEVEL_DEBUG) diff --git a/project/testing_configuration.gd b/project/testing_configuration.gd deleted file mode 100644 index 674f4063..00000000 --- a/project/testing_configuration.gd +++ /dev/null @@ -1,19 +0,0 @@ -extends RefCounted - - -## Detects whether an isolated test suite is running and calls its static `configure_options()` method. -## - Since SentryOptions can be configured only once, such test suites must be executed separately. -## - This method is called from "example_configuration.gd". -static func configure_options(options: SentryOptions): - var args: PackedStringArray = OS.get_cmdline_args() - var idx := args.find("-a") - if idx == -1 or args.size() == idx + 1: - return - var path := args[idx + 1] - if not path.ends_with(".gd"): - return - if not FileAccess.file_exists(path): - return - var scr: GDScript = load(path) - if scr.has_method(&"configure_options"): - scr.call(&"configure_options", options) diff --git a/project/testing_configuration.gd.uid b/project/testing_configuration.gd.uid deleted file mode 100644 index 65da1c9f..00000000 --- a/project/testing_configuration.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://dr6g5e14adpqf diff --git a/src/register_types.cpp b/src/register_types.cpp index af509ff0..7297f5a7 100644 --- a/src/register_types.cpp +++ b/src/register_types.cpp @@ -7,7 +7,6 @@ #include "sentry/runtime_config.h" #include "sentry/sentry_attachment.h" #include "sentry/sentry_breadcrumb.h" -#include "sentry/sentry_configuration.h" #include "sentry/sentry_event.h" #include "sentry/sentry_logger.h" #include "sentry/sentry_options.h" @@ -47,7 +46,6 @@ void register_runtime_classes() { GDREGISTER_CLASS(SentryLoggerLimits); GDREGISTER_CLASS(SentryOptions); GDREGISTER_INTERNAL_CLASS(RuntimeConfig); - GDREGISTER_CLASS(SentryConfiguration); GDREGISTER_CLASS(SentryUser); GDREGISTER_CLASS(SentryTimestamp); GDREGISTER_CLASS(SentrySDK); diff --git a/src/sentry/android/android_sdk.cpp b/src/sentry/android/android_sdk.cpp index b39de1a9..159bd265 100644 --- a/src/sentry/android/android_sdk.cpp +++ b/src/sentry/android/android_sdk.cpp @@ -134,10 +134,12 @@ void AndroidSDK::add_attachment(const Ref &p_attachment) { } } -void AndroidSDK::init(const PackedStringArray &p_global_attachments) { +void AndroidSDK::init(const PackedStringArray &p_global_attachments, const Callable &p_configuration_callback) { ERR_FAIL_NULL(android_plugin); - sentry::util::print_debug("Initializing Sentry Android SDK"); + if (p_configuration_callback.is_valid()) { + p_configuration_callback.call(SentryOptions::get_singleton()); + } for (const String &path : p_global_attachments) { bool is_view_hierarchy = path.ends_with(SENTRY_VIEW_HIERARCHY_FN); @@ -148,7 +150,7 @@ void AndroidSDK::init(const PackedStringArray &p_global_attachments) { is_view_hierarchy ? "event.view_hierarchy" : String()); } - android_plugin->call("initialize", + android_plugin->call(ANDROID_SN(init), before_send_handler->get_instance_id(), SentryOptions::get_singleton()->get_dsn(), SentryOptions::get_singleton()->is_debug_enabled(), @@ -159,6 +161,16 @@ void AndroidSDK::init(const PackedStringArray &p_global_attachments) { SentryOptions::get_singleton()->get_max_breadcrumbs()); } +void AndroidSDK::close() { + if (android_plugin != nullptr) { + android_plugin->call(ANDROID_SN(close)); + } +} + +bool AndroidSDK::is_enabled() const { + return android_plugin && android_plugin->call(ANDROID_SN(isEnabled)); +} + AndroidSDK::AndroidSDK() { AndroidStringNames::create_singleton(); @@ -170,6 +182,10 @@ AndroidSDK::AndroidSDK() { } AndroidSDK::~AndroidSDK() { + if (is_enabled()) { + close(); + } + AndroidStringNames::destroy_singleton(); if (before_send_handler != nullptr) { memdelete(before_send_handler); diff --git a/src/sentry/android/android_sdk.h b/src/sentry/android/android_sdk.h index 27a7d225..5dbad09c 100644 --- a/src/sentry/android/android_sdk.h +++ b/src/sentry/android/android_sdk.h @@ -49,7 +49,9 @@ class AndroidSDK : public InternalSDK { virtual void add_attachment(const Ref &p_attachment) override; - virtual void init(const PackedStringArray &p_global_attachments) override; + virtual void init(const PackedStringArray &p_global_attachments, const Callable &p_configuration_callback) override; + virtual void close() override; + virtual bool is_enabled() const override; bool has_android_plugin() const { return android_plugin != nullptr; } diff --git a/src/sentry/android/android_string_names.cpp b/src/sentry/android/android_string_names.cpp index 15655f86..4426aa56 100644 --- a/src/sentry/android/android_string_names.cpp +++ b/src/sentry/android/android_string_names.cpp @@ -15,6 +15,9 @@ void AndroidStringNames::destroy_singleton() { AndroidStringNames::AndroidStringNames() { // API methods. + init = StringName("init"); + close = StringName("close"); + isEnabled = StringName("isEnabled"); setContext = StringName("setContext"); removeContext = StringName("removeContext"); setTag = StringName("setTag"); diff --git a/src/sentry/android/android_string_names.h b/src/sentry/android/android_string_names.h index a9c1344b..7ee3eef1 100644 --- a/src/sentry/android/android_string_names.h +++ b/src/sentry/android/android_string_names.h @@ -27,6 +27,9 @@ class AndroidStringNames { _FORCE_INLINE_ static AndroidStringNames *get_singleton() { return singleton; } // API methods. + StringName init; + StringName close; + StringName isEnabled; StringName setContext; StringName removeContext; StringName setTag; diff --git a/src/sentry/cocoa/cocoa_sdk.h b/src/sentry/cocoa/cocoa_sdk.h index a2fa00bd..6bf2c3b9 100644 --- a/src/sentry/cocoa/cocoa_sdk.h +++ b/src/sentry/cocoa/cocoa_sdk.h @@ -36,8 +36,9 @@ class CocoaSDK : public InternalSDK { virtual void add_attachment(const Ref &p_attachment) override; - virtual void init(const PackedStringArray &p_global_attachments) override; - bool is_enabled() const; + virtual void init(const PackedStringArray &p_global_attachments, const Callable &p_configuration_callback) override; + virtual void close() override; + virtual bool is_enabled() const override; CocoaSDK(); virtual ~CocoaSDK() override; diff --git a/src/sentry/cocoa/cocoa_sdk.mm b/src/sentry/cocoa/cocoa_sdk.mm index e7e128b9..b77e4ed0 100644 --- a/src/sentry/cocoa/cocoa_sdk.mm +++ b/src/sentry/cocoa/cocoa_sdk.mm @@ -141,10 +141,14 @@ }]; } -void CocoaSDK::init(const PackedStringArray &p_global_attachments) { +void CocoaSDK::init(const PackedStringArray &p_global_attachments, const Callable &p_configuration_callback) { [PrivateSentrySDKOnly setSdkName:@"sentry.cocoa.godot"]; [objc::SentrySDK startWithConfigureOptions:^(objc::SentryOptions *options) { + if (p_configuration_callback.is_valid()) { + p_configuration_callback.call(SentryOptions::get_singleton()); + } + options.dsn = string_to_objc(SentryOptions::get_singleton()->get_dsn()); options.debug = SentryOptions::get_singleton()->is_debug_enabled(); options.releaseName = string_to_objc(SentryOptions::get_singleton()->get_release()); @@ -197,6 +201,10 @@ }]; } +void CocoaSDK::close() { + [objc::SentrySDK close]; +} + bool CocoaSDK::is_enabled() const { return [objc::SentrySDK isEnabled]; } @@ -207,7 +215,7 @@ CocoaSDK::~CocoaSDK() { if (is_enabled()) { - [objc::SentrySDK close]; + close(); } } diff --git a/src/sentry/contexts.cpp b/src/sentry/contexts.cpp index 6e1db803..49d58d95 100644 --- a/src/sentry/contexts.cpp +++ b/src/sentry/contexts.cpp @@ -3,7 +3,6 @@ #include "gen/sdk_version.gen.h" #include "sentry/environment.h" #include "sentry/sentry_options.h" -#include "sentry/uuid.h" #include #include diff --git a/src/sentry/disabled/disabled_sdk.h b/src/sentry/disabled/disabled_sdk.h index da755491..84a48ed0 100644 --- a/src/sentry/disabled/disabled_sdk.h +++ b/src/sentry/disabled/disabled_sdk.h @@ -29,7 +29,9 @@ class DisabledSDK : public InternalSDK { virtual void add_attachment(const Ref &p_attachment) override {} - virtual void init(const PackedStringArray &p_global_attachments) override {} + virtual void init(const PackedStringArray &p_global_attachments, const Callable &p_configuration_callback) override {} + virtual void close() override {} + virtual bool is_enabled() const override { return false; } }; } // namespace sentry diff --git a/src/sentry/internal_sdk.h b/src/sentry/internal_sdk.h index 83a50fcd..28b0928a 100644 --- a/src/sentry/internal_sdk.h +++ b/src/sentry/internal_sdk.h @@ -39,7 +39,9 @@ class InternalSDK { virtual void add_attachment(const Ref &p_attachment) = 0; - virtual void init(const PackedStringArray &p_global_attachments) = 0; + virtual void init(const PackedStringArray &p_global_attachments, const Callable &p_configuration_callback) = 0; + virtual void close() = 0; + virtual bool is_enabled() const = 0; virtual ~InternalSDK() = default; }; diff --git a/src/sentry/native/native_sdk.cpp b/src/sentry/native/native_sdk.cpp index 1087a5d8..e385140d 100644 --- a/src/sentry/native/native_sdk.cpp +++ b/src/sentry/native/native_sdk.cpp @@ -266,10 +266,14 @@ void NativeSDK::add_attachment(const Ref &p_attachment) { } } -void NativeSDK::init(const PackedStringArray &p_global_attachments) { +void NativeSDK::init(const PackedStringArray &p_global_attachments, const Callable &p_configuration_callback) { ERR_FAIL_NULL(OS::get_singleton()); ERR_FAIL_NULL(ProjectSettings::get_singleton()); + if (p_configuration_callback.is_valid()) { + p_configuration_callback.call(SentryOptions::get_singleton()); + } + sentry_options_t *options = sentry_options_new(); sentry_options_set_dsn(options, SentryOptions::get_singleton()->get_dsn().utf8()); @@ -335,13 +339,28 @@ void NativeSDK::init(const PackedStringArray &p_global_attachments) { } } +void NativeSDK::close() { + int err = sentry_close(); + initialized = false; + + if (err != 0) { + ERR_PRINT("Sentry: Failed to close native SDK cleanly. Error code: " + itos(err)); + } +} + +bool NativeSDK::is_enabled() const { + return initialized; +} + NativeSDK::NativeSDK() { last_uuid_mutex.instantiate(); last_uuid = sentry_uuid_nil(); } NativeSDK::~NativeSDK() { - sentry_close(); + if (is_enabled()) { + close(); + } } } //namespace sentry::native diff --git a/src/sentry/native/native_sdk.h b/src/sentry/native/native_sdk.h index 3b3b195c..1634b095 100644 --- a/src/sentry/native/native_sdk.h +++ b/src/sentry/native/native_sdk.h @@ -36,7 +36,9 @@ class NativeSDK : public InternalSDK { virtual void add_attachment(const Ref &p_attachment) override; - virtual void init(const PackedStringArray &p_global_attachments) override; + virtual void init(const PackedStringArray &p_global_attachments, const Callable &p_configuration_callback) override; + virtual void close() override; + virtual bool is_enabled() const override; NativeSDK(); virtual ~NativeSDK() override; diff --git a/src/sentry/sentry_configuration.cpp b/src/sentry/sentry_configuration.cpp deleted file mode 100644 index 86987da3..00000000 --- a/src/sentry/sentry_configuration.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "sentry_configuration.h" - -#include "sentry_sdk.h" - -namespace sentry { - -void SentryConfiguration::_call_configure(const Ref &p_options) { - ERR_FAIL_COND(p_options.is_null()); - GDVIRTUAL_CALL(_configure, p_options); - ERR_FAIL_NULL(SentrySDK::get_singleton()); - SentrySDK::get_singleton()->notify_options_configured(); -} - -void SentryConfiguration::_notification(int p_what) { - if (p_what == NOTIFICATION_READY) { - _call_configure(SentryOptions::get_singleton()); - } -} - -void SentryConfiguration::_bind_methods() { - GDVIRTUAL_BIND(_configure, "options"); -} - -} // namespace sentry diff --git a/src/sentry/sentry_configuration.h b/src/sentry/sentry_configuration.h deleted file mode 100644 index 670b77ea..00000000 --- a/src/sentry/sentry_configuration.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef SENTRY_CONFIGURATION_H -#define SENTRY_CONFIGURATION_H - -#include "sentry/sentry_options.h" - -#include -#include - -using namespace godot; - -namespace sentry { - -class SentryConfiguration : public Node { - GDCLASS(SentryConfiguration, Node); - -protected: - static void _bind_methods(); - void _notification(int p_what); - - GDVIRTUAL1(_configure, Ref); - - void _call_configure(const Ref &p_options); - -public: - SentryConfiguration() {} -}; - -} // namespace sentry - -#endif // SENTRY_CONFIGURATION_H diff --git a/src/sentry/sentry_options.cpp b/src/sentry/sentry_options.cpp index 216c0501..28ed824c 100644 --- a/src/sentry/sentry_options.cpp +++ b/src/sentry/sentry_options.cpp @@ -57,14 +57,14 @@ void SentryOptions::_define_project_settings(const Ref &p_options ERR_FAIL_COND(p_options.is_null()); ERR_FAIL_NULL(ProjectSettings::get_singleton()); - _define_setting("sentry/options/enabled", p_options->enabled); - _define_setting("sentry/options/disabled_in_editor_play", p_options->disabled_in_editor_play); + _define_setting("sentry/options/auto_init", p_options->auto_init); + _define_setting("sentry/options/skip_auto_init_on_editor_play", p_options->skip_auto_init_on_editor_play); + _define_setting("sentry/options/dsn", p_options->dsn); _define_setting("sentry/options/release", p_options->release, false); _define_setting("sentry/options/dist", p_options->dist, false); _define_setting(PropertyInfo(Variant::INT, "sentry/options/debug_printing", PROPERTY_HINT_ENUM, "Off,On,Auto"), (int)SentryOptions::DEBUG_DEFAULT); _define_setting(sentry::make_level_enum_property("sentry/options/diagnostic_level"), p_options->diagnostic_level); - _define_setting(PropertyInfo(Variant::STRING, "sentry/options/configuration_script", PROPERTY_HINT_FILE, "*.gd"), p_options->configuration_script, false); _define_setting(PropertyInfo(Variant::FLOAT, "sentry/options/sample_rate", PROPERTY_HINT_RANGE, "0.0,1.0"), p_options->sample_rate, false); _define_setting(PropertyInfo(Variant::INT, "sentry/options/max_breadcrumbs", PROPERTY_HINT_RANGE, "0, 500"), p_options->max_breadcrumbs, false); _define_setting("sentry/options/send_default_pii", p_options->send_default_pii); @@ -92,19 +92,19 @@ void SentryOptions::_load_project_settings(const Ref &p_options) ERR_FAIL_COND(p_options.is_null()); ERR_FAIL_NULL(ProjectSettings::get_singleton()); - p_options->enabled = ProjectSettings::get_singleton()->get_setting("sentry/options/enabled", p_options->enabled); - p_options->disabled_in_editor_play = ProjectSettings::get_singleton()->get_setting("sentry/options/disabled_in_editor_play", p_options->disabled_in_editor_play); + p_options->auto_init = ProjectSettings::get_singleton()->get_setting("sentry/options/auto_init", p_options->auto_init); + p_options->skip_auto_init_on_editor_play = ProjectSettings::get_singleton()->get_setting("sentry/options/skip_auto_init_on_editor_play", p_options->skip_auto_init_on_editor_play); + p_options->dsn = ProjectSettings::get_singleton()->get_setting("sentry/options/dsn", p_options->dsn); p_options->set_release(ProjectSettings::get_singleton()->get_setting("sentry/options/release", p_options->release)); p_options->dist = ProjectSettings::get_singleton()->get_setting("sentry/options/dist", p_options->dist); // DebugMode is only used to represent the debug option in the project settings. - // The user may also set the `debug` option explicitly in a configuration script. + // The user may also set the `debug` option explicitly in a configuration callback. DebugMode mode = (DebugMode)(int)ProjectSettings::get_singleton()->get_setting("sentry/options/debug_printing", (int)SentryOptions::DEBUG_DEFAULT); p_options->_init_debug_option(mode); p_options->diagnostic_level = (sentry::Level)(int)ProjectSettings::get_singleton()->get_setting("sentry/options/diagnostic_level", p_options->diagnostic_level); - p_options->configuration_script = ProjectSettings::get_singleton()->get_setting("sentry/options/configuration_script", p_options->configuration_script); p_options->sample_rate = ProjectSettings::get_singleton()->get_setting("sentry/options/sample_rate", p_options->sample_rate); p_options->max_breadcrumbs = ProjectSettings::get_singleton()->get_setting("sentry/options/max_breadcrumbs", p_options->max_breadcrumbs); p_options->send_default_pii = ProjectSettings::get_singleton()->get_setting("sentry/options/send_default_pii", p_options->send_default_pii); @@ -177,8 +177,6 @@ void SentryOptions::remove_event_processor(const Ref &p_pr } void SentryOptions::_bind_methods() { - BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::BOOL, "enabled"), set_enabled, is_enabled); - BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::BOOL, "disabled_in_editor_play"), set_disabled_in_editor_play, is_disabled_in_editor_play); BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::STRING, "dsn"), set_dsn, get_dsn); BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::STRING, "release"), set_release, get_release); BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::STRING, "dist"), set_dist, get_dist); diff --git a/src/sentry/sentry_options.h b/src/sentry/sentry_options.h index a4648344..10cfae6f 100644 --- a/src/sentry/sentry_options.h +++ b/src/sentry/sentry_options.h @@ -48,8 +48,8 @@ class SentryOptions : public RefCounted { }; static constexpr DebugMode DEBUG_DEFAULT = DebugMode::DEBUG_AUTO; - bool enabled = true; - bool disabled_in_editor_play = false; + bool auto_init = true; + bool skip_auto_init_on_editor_play = false; String dsn = ""; String release = "{app_name}@{app_version}"; String dist = ""; @@ -73,7 +73,6 @@ class SentryOptions : public RefCounted { BitField logger_breadcrumb_mask = int(GodotErrorMask::MASK_ALL); Ref logger_limits; - String configuration_script; Callable before_send; Callable before_capture_screenshot; @@ -92,11 +91,11 @@ class SentryOptions : public RefCounted { static void destroy_singleton(); _FORCE_INLINE_ static Ref get_singleton() { return singleton; } - _FORCE_INLINE_ bool is_enabled() const { return enabled; } - _FORCE_INLINE_ void set_enabled(bool p_enabled) { enabled = p_enabled; } + _FORCE_INLINE_ bool is_auto_init_enabled() const { return auto_init; } + _FORCE_INLINE_ void set_auto_init(bool p_enabled) { auto_init = p_enabled; } - _FORCE_INLINE_ bool is_disabled_in_editor_play() const { return disabled_in_editor_play; } - _FORCE_INLINE_ void set_disabled_in_editor_play(bool p_disabled_in_editor_play) { disabled_in_editor_play = p_disabled_in_editor_play; } + _FORCE_INLINE_ bool should_skip_auto_init_on_editor_play() const { return skip_auto_init_on_editor_play; } + _FORCE_INLINE_ void set_skip_auto_init_on_editor_play(bool p_skip) { skip_auto_init_on_editor_play = p_skip; } _FORCE_INLINE_ String get_dsn() const { return dsn; } _FORCE_INLINE_ void set_dsn(const String &p_dsn) { dsn = p_dsn; } @@ -161,8 +160,6 @@ class SentryOptions : public RefCounted { _FORCE_INLINE_ Ref get_logger_limits() const { return logger_limits; } void set_logger_limits(const Ref &p_limits); - _FORCE_INLINE_ String get_configuration_script() const { return configuration_script; } - _FORCE_INLINE_ Callable get_before_send() const { return before_send; } _FORCE_INLINE_ void set_before_send(const Callable &p_before_send) { before_send = p_before_send; } diff --git a/src/sentry/sentry_sdk.cpp b/src/sentry/sentry_sdk.cpp index 1759c130..f7f668e3 100644 --- a/src/sentry/sentry_sdk.cpp +++ b/src/sentry/sentry_sdk.cpp @@ -118,6 +118,48 @@ void SentrySDK::destroy_singleton() { singleton = nullptr; } +void SentrySDK::init(const Callable &p_configuration_callback) { + ERR_FAIL_COND_MSG(internal_sdk->is_enabled(), "Attempted to initialize SentrySDK that is already initialized"); + +#if SDK_ANDROID + if (OS::get_singleton()->has_feature("editor")) { + ERR_FAIL_MSG("Initializing in Android editor is not supported!"); + return; + } +#endif + + sentry::util::print_debug("Initializing Sentry SDK"); + internal_sdk->init(_get_global_attachments(), p_configuration_callback); + + if (internal_sdk->is_enabled()) { + if (is_auto_initializing) { + // Delay contexts initialization until engine singletons are ready during early initialization. + callable_mp(this, &SentrySDK::_init_contexts).call_deferred(); + } else { + // TODO: move this into sentry::contexts + _init_contexts(); + } + + if (SentryOptions::get_singleton()->is_logger_enabled()) { + if (logger.is_null()) { + logger.instantiate(); + } + OS::get_singleton()->add_logger(logger); + } + } +} + +void SentrySDK::close() { + if (internal_sdk->is_enabled()) { + sentry::util::print_debug("Shutting down Sentry SDK"); + if (logger.is_valid()) { + OS::get_singleton()->remove_logger(logger); + logger.unref(); + } + internal_sdk->close(); + } +} + String SentrySDK::capture_message(const String &p_message, Level p_level) { return internal_sdk->capture_message(p_message, p_level); } @@ -238,63 +280,38 @@ PackedStringArray SentrySDK::_get_global_attachments() { void SentrySDK::_auto_initialize() { sentry::util::print_debug("starting Sentry SDK version " + String(SENTRY_GODOT_SDK_VERSION)); - // Initialize user if it wasn't set explicitly in the configuration script. - if (user.is_null()) { - user.instantiate(); - user->set_id(runtime_config->get_installation_id()); - if (SentryOptions::get_singleton()->is_send_default_pii_enabled()) { - user->infer_ip_address(); - } - } - set_user(user); - bool should_enable = true; - if (!SentryOptions::get_singleton()->is_enabled()) { + if (!SentryOptions::get_singleton()->is_auto_init_enabled()) { should_enable = false; - sentry::util::print_debug("Sentry SDK is disabled in options."); + sentry::util::print_debug("Automatic initialization is disabled in the project settings."); } if (Engine::get_singleton()->is_editor_hint()) { should_enable = false; - sentry::util::print_debug("Sentry SDK is disabled in the editor."); + sentry::util::print_debug("Automatic initialization is disabled in the editor."); } - if (!Engine::get_singleton()->is_editor_hint() && OS::get_singleton()->has_feature("editor") && SentryOptions::get_singleton()->is_disabled_in_editor_play()) { + if (!Engine::get_singleton()->is_editor_hint() && OS::get_singleton()->has_feature("editor") && SentryOptions::get_singleton()->should_skip_auto_init_on_editor_play()) { should_enable = false; - sentry::util::print_debug("Sentry SDK is disabled when project is played from the editor. Tip: This can be changed in the project settings."); + sentry::util::print_debug("Automatic initialization is disabled when project is played from the editor. Tip: This can be changed in the project settings."); } -#if SDK_ANDROID - if (should_enable) { - if (OS::get_singleton()->has_feature("editor")) { - should_enable = false; - } + if (SentryOptions::get_singleton()->get_dsn().is_empty()) { + should_enable = false; + sentry::util::print_debug("Automatic initialization is disabled because no DSN was provided. Tip: You can obtain a DSN from Sentry's dashboard and add it in the project settings."); } -#endif - - enabled = should_enable; - if (!enabled) { - sentry::util::print_info("Sentry SDK is DISABLED! Operations with Sentry SDK will result in no-ops."); + if (!should_enable) { + sentry::util::print_info("Automatic initialization is disabled! Operations with Sentry SDK will result in no-ops."); return; } - internal_sdk->init(_get_global_attachments()); + sentry::util::print_debug("Proceeding with automatic initialization."); - if (SentryOptions::get_singleton()->is_logger_enabled()) { - logger.instantiate(); - OS::get_singleton()->add_logger(logger); - } -} - -void SentrySDK::_check_if_configuration_succeeded() { - if (!configuration_succeeded) { - // Push error and initialize anyway. - ERR_PRINT("Sentry: Configuration via user script failed. Will try to initialize SDK anyway."); - sentry::util::print_error("initializing late because configuration via user script failed"); - _auto_initialize(); - } + is_auto_initializing = true; + init(); + is_auto_initializing = false; } void SentrySDK::_demo_helper_crash_app() { @@ -302,18 +319,21 @@ void SentrySDK::_demo_helper_crash_app() { sentry::util::print_fatal("Crash by access violation ", ptr); // this is going to crash the app } -void SentrySDK::notify_options_configured() { - sentry::util::print_debug("finished configuring options via user script"); - configuration_succeeded = true; - _auto_initialize(); - _init_contexts(); -} - void SentrySDK::prepare_and_auto_initialize() { // Load the runtime configuration from the user's data directory. runtime_config.instantiate(); runtime_config->load_file(OS::get_singleton()->get_user_data_dir() + "/sentry.dat"); + // Initialize user. + if (user.is_null()) { + user.instantiate(); + user->set_id(runtime_config->get_installation_id()); + if (SentryOptions::get_singleton()->is_send_default_pii_enabled()) { + user->infer_ip_address(); + } + } + set_user(user); + // Verify project settings and notify user via errors if there are any issues (deferred). callable_mp_static(_verify_project_settings).call_deferred(); @@ -337,28 +357,7 @@ void SentrySDK::prepare_and_auto_initialize() { SentryOptions::get_singleton()->add_event_processor(memnew(ViewHierarchyProcessor)); } - // Auto-initialize SDK. - if (SentryOptions::get_singleton()->get_configuration_script().is_empty() || Engine::get_singleton()->is_editor_hint()) { - // Early initialization path. - _auto_initialize(); - // Delay contexts initialization until the engine singletons are ready. - callable_mp(this, &SentrySDK::_init_contexts).call_deferred(); - } else { - // Register an autoload singleton, which is a user script extending the - // `SentryConfiguration` class. It will be instantiated and added to the - // scene tree by the engine shortly after ScriptServer is initialized. - // When this happens, the `SentryConfiguration` instance receives - // `NOTIFICATION_READY`, triggering our notification processing code in - // C++, which calls `_configure()` on the user script and then invokes - // `notify_options_configured()` in `SentrySDK`. This, in turn, - // auto-initializes the SDK. - sentry::util::print_debug("waiting for user configuration autoload"); - ERR_FAIL_NULL(ProjectSettings::get_singleton()); - ProjectSettings::get_singleton()->set_setting("autoload/SentryConfigurationScript", - SentryOptions::get_singleton()->get_configuration_script()); - // Ensure issues with the configuration script are detected. - callable_mp(this, &SentrySDK::_check_if_configuration_succeeded).call_deferred(); - } + _auto_initialize(); } void SentrySDK::_notification(int p_what) { @@ -379,6 +378,8 @@ void SentrySDK::_bind_methods() { BIND_ENUM_CONSTANT(LEVEL_ERROR); BIND_ENUM_CONSTANT(LEVEL_FATAL); + ClassDB::bind_method(D_METHOD("init", "configuration_callback"), &SentrySDK::init, DEFVAL(Callable())); + ClassDB::bind_method(D_METHOD("close"), &SentrySDK::close); ClassDB::bind_method(D_METHOD("is_enabled"), &SentrySDK::is_enabled); ClassDB::bind_method(D_METHOD("add_breadcrumb", "breadcrumb"), &SentrySDK::add_breadcrumb); ClassDB::bind_method(D_METHOD("capture_message", "message", "level"), &SentrySDK::capture_message, DEFVAL(LEVEL_INFO)); diff --git a/src/sentry/sentry_sdk.h b/src/sentry/sentry_sdk.h index 90917172..65a44117 100644 --- a/src/sentry/sentry_sdk.h +++ b/src/sentry/sentry_sdk.h @@ -37,13 +37,11 @@ class SentrySDK : public Object { Ref user; Ref user_mutex; Ref logger; - bool enabled = false; - bool configuration_succeeded = false; + bool is_auto_initializing = false; void _init_contexts(); PackedStringArray _get_global_attachments(); void _auto_initialize(); - void _check_if_configuration_succeeded(); void _demo_helper_crash_app(); protected: @@ -58,11 +56,11 @@ class SentrySDK : public Object { _FORCE_INLINE_ std::shared_ptr get_internal_sdk() const { return internal_sdk; } - void notify_options_configured(); - // * Exported API - bool is_enabled() const { return enabled; } + void init(const Callable &p_configuration_callback = Callable()); + void close(); + bool is_enabled() const { return internal_sdk->is_enabled(); } void add_breadcrumb(const Ref &p_breadcrumb);