Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Event hooks & configuration via GDScript #60

Merged
merged 23 commits into from
Jan 29, 2025
Merged

Conversation

limbonaut
Copy link
Collaborator

@limbonaut limbonaut commented Jan 14, 2025

This PR introduces configuration via GDScript and adds the user-facing event hooks before_send and on_crash, as outlined in https://develop.sentry.dev/sdk/expected-features/#before-send-hook and https://docs.sentry.io/platforms/native/configuration/filtering/.

It introduces a new public API class called SentryConfiguration which users can extend in a script, and assign the script file in options to configure the SDK during initialization. However, due to the way scripting is initialized in the Godot Engine, this comes with a trade-off: a slightly later initialization. If a configuration script is assigned, initialization must be delayed until ScriptServer is ready to load and run the user script.

Resolves #47
Resolves #48

@limbonaut limbonaut added the enhancement New feature or request label Jan 14, 2025
Copy link
Contributor

github-actions bot commented Jan 14, 2025

Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against 8b41d9a

@limbonaut limbonaut changed the title feat: Event hooks feat: Event hooks & configuration via GDScript Jan 22, 2025
Comment on lines -138 to -150
#ifdef NATIVE_SDK
internal_sdk = std::make_shared<NativeSDK>();
#else
// Unsupported platform
sentry::util::print_debug("This is an unsupported platform. Disabling Sentry SDK...");
enabled = false;
#endif
}

if (!enabled) {
sentry::util::print_debug("Sentry SDK is DISABLED! Operations with Sentry SDK will result in no-ops.");
internal_sdk = std::make_shared<DisabledSDK>();
return;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI this code moved to _intialize()

@limbonaut limbonaut marked this pull request as ready for review January 23, 2025 20:58
Copy link

@bitsandfoxes bitsandfoxes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is amazing stuff. The changelog reads as if the SDK initializes at different points depending on whether the configure callback has been implemented. I don't quite follow the chain of notifications to get from init to configure back to actual init with the actual options. Would unifying the initialization to just the one right after the script server runs simplify this?

func _configure(options: SentryOptions) -> void:
print("[example_configuration.gd] Configuring SDK options via GDScript")
options.debug = true

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could extend the sample here and provide some more options. I.e. setting the release and/or the environment. To give an idea what's there.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea, although, we can't change environment until #66 is merged. Will keep it in mind.
I've updated the example configuration with setting release attribute in 46c122e


## before_send callback example
func _before_send(ev: SentryEvent) -> SentryEvent:
print("[example_configuration.gd] Processing event: ", ev.id)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the more important uses of this callback is to allow users filtering of events. I.e. return null for dropping events entirely. Based on the native implementation it looks like this should be supported. Can we update the sample with some filtering?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can, although, this would apply to everything in the demo project, including our unit tests, so it should be something harmless.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the example with filtering in 9a49f11

Comment on lines +114 to +121
if (sentry_value_refcount(p_native_event) > 0) {
sentry_value_incref(p_native_event); // acquire ownership
native_event = p_native_event;
} else {
// Shouldn't happen in healthy code.
native_event = sentry_value_new_event();
ERR_PRINT("Sentry: Internal error: Event refcount is zero.");
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this change coming from?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I adjusted how ownership is handled in the NativeEvent class, just so we don't have to preclude constructor call with incref every time.

}

if (!enabled) {
sentry::util::print_debug("Sentry SDK is DISABLED! Operations with Sentry SDK will result in no-ops.");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not quite following the whole initialization. Is logging set up prior to initialization? Does this show up 100% of the time with the SDK disabled? The way to get rid of this would be to increase the logging verbosity I guess?

Copy link
Collaborator Author

@limbonaut limbonaut Jan 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are commenting on this specific if-branch, then this message would appear in stdout if options.debug is ON and SDK is disabled either manually via options or because it is unsupported platform.

I'm not sure what you mean by logging here. Godot doesn't employ the logger concept, with verbosity levels and such. There is print, push_error, and push_warning in GDScript. But we're not adding this message to such channels, and similar to sentry_native, it is simply printed to stdout (for consistency’s sake). Our error logger will eventually process errors and warnings, but will not deal with prints.

@limbonaut
Copy link
Collaborator Author

limbonaut commented Jan 28, 2025

Thanks for reviewing @bitsandfoxes. It's a best-effort solution among very few options. Let me explain.

The changelog reads as if the SDK initializes at different points depending on whether the configure callback has been implemented.

Correct, more on that below.

Would unifying the initialization to just the one right after the script server runs simplify this?

We don't have an initialization opportunity at that point. Here are our options:

  1. Before the ScriptServer is initialized -- no user configuration is possible. Several opportunities to hook up here. We use this method if the user configuration script is not supplied.
  2. Via autoload singleton, for which we need a user script. If we don't have a script - we can't use this option. This is our current approach if the user provides a script. Otherwise, we default to early initialization, and potentially very early initialization when Initialize SDK as early as possible #39 gets implemented. This step happens shortly after ScriptServer and the scene tree are initialized, but before the main scene is loaded and added to the tree.
  3. Initialize late, during the main loop's first iteration - that's kinda late for us. Hence, I went with 1 and 2 combined.

These are the options I could find to tap into initialization, and I don't see any other way aside from introducing upstream changes.

I don't quite follow the chain of notifications to get from init to configure back to actual init with the actual options.

  1. We're registering an autoload singleton (aka user script that extends our SentryConfiguration class) in the project settings. Only a script can be registered as an autoload singleton. This step takes place in the SentrySDK constructor.
  2. Autoload singleton script is loaded and added to the scene tree by the engine.
  3. The SentryConfiguration instance (user script must extend this class) receives NOTIFICATION_READY when added to the scene tree. In C++, our notification processing code in SentryConfiguration calls _configure() on the user script and then calls notify_options_configured() in SentrySDK.
  4. _notify_options_configured() finally initializes the internal SDK.
  5. We also have a safety catch (implemented via option 3), just in case the user script fails for whatever reason, to notify the user about the script problem and initialize anyway at that point.

UPDATE:
Added explanation in 8b41d9a

@bitsandfoxes bitsandfoxes merged commit 623b874 into main Jan 29, 2025
16 checks passed
@bitsandfoxes bitsandfoxes deleted the feat/event-hooks branch January 29, 2025 14:36
limbonaut added a commit that referenced this pull request Jan 29, 2025
Add SentryOptions.environment property. Environment is auto-detected for user convenience, and it will be possible to assign it after #60 is merged.

Also, change auto-detected environment names to prioritize development environments.

Resolves GH-64
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

OptionConfiguration - customize options via code User-facing event hooks
2 participants