Skip to content
New issue

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

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

Already on GitHub? # to your account

feat: Event objects #51

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
85bc598
Add `SentryEvent` base class & native implemention
limbonaut Dec 23, 2024
94539dd
Implement `SentrySDK.capture_event(event)`
limbonaut Dec 23, 2024
a74b9fc
Add test for `capture_event()`
limbonaut Dec 23, 2024
966850d
Add `SentryEvent.message` property
limbonaut Dec 23, 2024
b1cd501
Refactor code
limbonaut Dec 23, 2024
9893d33
Add unit tests for SentryEvent
limbonaut Dec 23, 2024
cab54aa
Fix issues discovered in testing
limbonaut Dec 23, 2024
4619a1b
Add doc comments for tests
limbonaut Dec 23, 2024
8b6c784
Harden the code
limbonaut Jan 2, 2025
14f392a
Add DisabledEvent class
limbonaut Jan 2, 2025
12beaef
Fix compilation error
limbonaut Jan 2, 2025
dae8091
Revise native event ownership
limbonaut Jan 2, 2025
3adcdd9
Add `SentryEvent.platform` property
limbonaut Jan 2, 2025
3dd697b
Add `SentryEvent.logger` property
limbonaut Jan 2, 2025
5a48235
Add `SentryEvent.release` property
limbonaut Jan 2, 2025
6e6a16d
Add `SentryEvent.dist` property
limbonaut Jan 2, 2025
17bd246
Add `SentryEvent.environment` property
limbonaut Jan 2, 2025
718663f
Fix accessing message
limbonaut Jan 3, 2025
4f72880
Fix native event id
limbonaut Jan 3, 2025
0f83a6a
Add `SentrySDK.create_message_event()`
limbonaut Jan 3, 2025
8055c6d
Missing level
limbonaut Jan 3, 2025
a7a296a
Add comment
limbonaut Jan 3, 2025
ebba28a
Merge remote-tracking branch 'upstream/main' into feat/event-objects
limbonaut Jan 8, 2025
ec07866
Update changelog
limbonaut Jan 8, 2025
8e3ac2c
Remove `create_message_event` method
limbonaut Jan 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions project/test/test_event.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
class_name TestEvent
extends GdUnitTestSuite


## SentryEvent.id should not be empty on event creation.
func test_event_id() -> void:
var event := SentrySDK.create_event()
assert_str(event.id).is_not_empty()


## SentryEvent.message should be set to the specified value, and should be empty on event creation.
func test_event_message() -> void:
var event := SentrySDK.create_event()
assert_str(event.message).is_empty()
event.message = "Hello, World!"
assert_str(event.message).is_equal("Hello, World!")


## SentryEvent.level should be set to the specified value.
func test_event_level() -> void:
var event := SentrySDK.create_event()
for l in [SentrySDK.LEVEL_DEBUG, SentrySDK.LEVEL_INFO, SentrySDK.LEVEL_WARNING, SentrySDK.LEVEL_ERROR, SentrySDK.LEVEL_FATAL]:
event.level = l
assert_int(event.level).is_equal(l)


## SentryEvent.timestamp should not be empty on event creation, and setter should update it.
func test_event_timestamp() -> void:
var event := SentrySDK.create_event()
assert_str(event.timestamp).is_not_empty()
var ts = Time.get_datetime_string_from_system()
event.timestamp = ts
assert_str(event.timestamp).is_equal(ts)


## SentryEvent.platform should not be empty.
func test_event_platform() -> void:
var event := SentrySDK.create_event()
assert_str(event.platform).is_not_empty()


## SentryEvent.logger should be set to the specified value, and empty on event creation.
func test_event_logger() -> void:
var event := SentrySDK.create_event()
assert_str(event.logger).is_empty()
event.logger = "custom-logger"
assert_str(event.logger).is_equal("custom-logger")


## SentryEvent.release should be set to the specified value, and empty on event creation.
func test_event_release() -> void:
var event := SentrySDK.create_event()
assert_str(event.release).is_empty()
event.release = "custom-release"
assert_str(event.release).is_equal("custom-release")


## SentryEvent.dist should be set to the specified value, and empty on event creation.
func test_event_dist() -> void:
var event := SentrySDK.create_event()
assert_str(event.dist).is_empty()
event.dist = "custom-dist"
assert_str(event.dist).is_equal("custom-dist")


## SentryEvent.environment should be set to the specified value, and empty on event creation.
func test_event_environment() -> void:
var event := SentrySDK.create_event()
assert_str(event.environment).is_empty()
event.environment = "custom-environment"
assert_str(event.environment).is_equal("custom-environment")


## SentrySDK.capture_event() should return a non-empty event ID, which must match the ID returned by the get_last_event_id() call.
func test_capture_event() -> void:
var event := SentrySDK.create_event()
var event_id := SentrySDK.capture_event(event)
assert_str(event_id).is_not_empty()
assert_str(event_id).is_equal(event.id)
assert_str(SentrySDK.get_last_event_id()).is_not_empty()
assert_str(event_id).is_equal(SentrySDK.get_last_event_id())


## SentrySDK.create_message_event() should return a SentryEvent instance with the specified message and level, and should be able to be captured.
func test_create_message_event_and_capture() -> void:
var event := SentrySDK.create_message_event("Test message", SentrySDK.LEVEL_DEBUG, "custom-logger")
assert_str(event.message).is_equal("Test message")
assert_str(event.id).is_not_empty()
assert_int(event.level).is_equal(SentrySDK.LEVEL_DEBUG)
assert_str(event.logger).is_equal("custom-logger")
var event_id := SentrySDK.capture_event(event)
assert_str(event_id).is_not_empty()
assert_str(event_id).is_equal(event.id)
assert_str(event_id).is_equal(SentrySDK.get_last_event_id())
6 changes: 6 additions & 0 deletions src/register_types.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
#include "runtime_config.h"
#include "sentry/disabled_event.h"
#include "sentry/native/native_event.h"
#include "sentry_event.h"
#include "sentry_logger.h"
#include "sentry_options.h"
#include "sentry_sdk.h"
Expand Down Expand Up @@ -38,6 +41,9 @@ void initialize_module(ModuleInitializationLevel p_level) {
GDREGISTER_INTERNAL_CLASS(RuntimeConfig);
GDREGISTER_CLASS(SentryUser);
GDREGISTER_CLASS(SentrySDK);
GDREGISTER_ABSTRACT_CLASS(SentryEvent);
GDREGISTER_INTERNAL_CLASS(NativeEvent);
GDREGISTER_INTERNAL_CLASS(DisabledEvent);
SentrySDK *sentry_singleton = memnew(SentrySDK);
Engine::get_singleton()->register_singleton("SentrySDK", SentrySDK::get_singleton());

Expand Down
49 changes: 49 additions & 0 deletions src/sentry/disabled_event.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#ifndef DISABLED_EVENT_H
#define DISABLED_EVENT_H

#include "sentry_event.h"

// Event class that is used when the SDK is disabled.

Choose a reason for hiding this comment

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

Where does the need for a DisabledEvent come from? Can't we use the SentryEvent and still have the SDK disabled?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

SentryEvent is abstract and cannot be instantiated, and it's supposed to be implemented by NativeEvent class and such (for each internal SDK used). The reason behind DisabledEvent is to return a non-null value, so that it doesn't break scripts which may not be checking if SentrySDK is enabled. I could put this logic into SentryEvent, but it would add some bloated state to each event. I.e. SentryEvent is basically an interface.

class DisabledEvent : public SentryEvent {
GDCLASS(DisabledEvent, SentryEvent);

private:
String message;
String timestamp;
String logger;
sentry::Level level = sentry::Level::LEVEL_INFO;
String release;
String dist;
String environment;

protected:
static void _bind_methods() {}

public:
virtual String get_id() const override { return ""; }

virtual void set_message(const String &p_message) override { message = p_message; }
virtual String get_message() const override { return message; }

virtual void set_timestamp(const String &p_timestamp) override { timestamp = p_timestamp; }
virtual String get_timestamp() const override { return timestamp; }

virtual String get_platform() const override { return ""; }

virtual void set_level(sentry::Level p_level) override { level = p_level; }
virtual sentry::Level get_level() const override { return level; }

virtual void set_logger(const String &p_logger) override { logger = p_logger; }
virtual String get_logger() const override { return logger; }

virtual void set_release(const String &p_release) override { release = p_release; }
virtual String get_release() const override { return release; }

virtual void set_dist(const String &p_dist) override { dist = p_dist; }
virtual String get_dist() const override { return dist; }

virtual void set_environment(const String &p_environment) override { environment = p_environment; }
virtual String get_environment() const override { return environment; }
};

#endif // DISABLED_EVENT_H
9 changes: 6 additions & 3 deletions src/sentry/disabled_sdk.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef DISABLED_SDK_H
#define DISABLED_SDK_H

#include "sentry/disabled_event.h"
#include "sentry/internal_sdk.h"

namespace sentry {
Expand All @@ -18,13 +19,15 @@ class DisabledSDK : public InternalSDK {

virtual void add_breadcrumb(const String &p_message, const String &p_category, Level p_level,
const String &p_type = "default", const Dictionary &p_data = Dictionary()) override {}
// TODO: Consider adding the following function.
// virtual void clear_breadcrumbs() = 0;

virtual String capture_message(const String &p_message, Level p_level, const String &p_logger = "") override { return ""; }
virtual String capture_message(const String &p_message, Level p_level = sentry::LEVEL_INFO, const String &p_logger = "") override { return ""; }
virtual String get_last_event_id() override { return ""; }
virtual String capture_error(const String &p_type, const String &p_value, Level p_level, const Vector<StackFrame> &p_frames) override { return ""; }

virtual Ref<SentryEvent> create_event() override { return memnew(DisabledEvent); }
virtual Ref<SentryEvent> create_message_event(const String &p_message, Level p_level = sentry::LEVEL_INFO, const String &p_logger = "") override { return memnew(DisabledEvent); };
virtual String capture_event(const Ref<SentryEvent> &p_event) override { return ""; }

virtual void initialize() override {}
};

Expand Down
5 changes: 5 additions & 0 deletions src/sentry/internal_sdk.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define INTERNAL_SDK_H

#include "sentry/level.h"
#include "sentry_event.h"
#include "sentry_user.h"

#include <godot_cpp/variant/dictionary.hpp>
Expand Down Expand Up @@ -44,6 +45,10 @@ class InternalSDK {
virtual String get_last_event_id() = 0;
virtual String capture_error(const String &p_type, const String &p_value, Level p_level, const Vector<StackFrame> &p_frames) = 0;

virtual Ref<SentryEvent> create_event() = 0;
virtual Ref<SentryEvent> create_message_event(const String &p_message, sentry::Level p_level, const String &p_logger = "") = 0;
virtual String capture_event(const Ref<SentryEvent> &p_event) = 0;

virtual void initialize() = 0;
virtual ~InternalSDK() = default;
};
Expand Down
6 changes: 5 additions & 1 deletion src/sentry/level.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace sentry {

CharString level_as_cstring(Level level) {
godot::CharString level_as_cstring(Level level) {
switch (level) {
case Level::LEVEL_DEBUG:
return "debug";
Expand All @@ -19,4 +19,8 @@ CharString level_as_cstring(Level level) {
}
}

godot::PropertyInfo make_level_enum_property(const godot::String &p_name) {
return godot::PropertyInfo(godot::Variant::INT, p_name, godot::PROPERTY_HINT_ENUM, "Debug,Info,Warning,Error,Fatal");
}

} // namespace sentry
11 changes: 7 additions & 4 deletions src/sentry/level.h
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
#ifndef SENTRY_LEVEL_H
#define SENTRY_LEVEL_H

#include <godot_cpp/variant/char_string.hpp>

using namespace godot;
#include <godot_cpp/core/property_info.hpp>
#include <godot_cpp/variant/string.hpp>

namespace sentry {

// Represents the severity of events or breadcrumbs.
// In the public API, it is exposed as SentrySDK.Level enum.
// And as such, VariantCaster<SentrySDK::Level> is defined in sentry_sdk.h.
enum Level {
LEVEL_DEBUG = 0,
LEVEL_INFO = 1,
Expand All @@ -15,7 +17,8 @@ enum Level {
LEVEL_FATAL = 4
};

CharString level_as_cstring(Level level);
godot::CharString level_as_cstring(Level level);
godot::PropertyInfo make_level_enum_property(const godot::String &p_name);

} // namespace sentry

Expand Down
114 changes: 114 additions & 0 deletions src/sentry/native/native_event.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#include "native_event.h"

#include "sentry/level.h"
#include "sentry/native/native_util.h"

#include <sentry.h>

namespace {

inline void _sentry_value_set_or_remove_string_by_key(sentry_value_t value, const char *k, const String &v) {

Choose a reason for hiding this comment

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

So we end up with shortcuts and instead of event.remove_release() we want users to use event.set_release("")?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not sure if I understand the question. This is a helper method. It sets a string value on a sentry_value object, unless the string is empty, in which case it removes such key. Hence, the name of the method.
So we don't store such values:
os.name = ""
Instead, we remove the os.name key-value from the object.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh, I think I see what you mean. We have a pair of setter/getter for each property of SentryEvent.
event.release is how we access it in GDScript. event.release = "some_release" is how we set it. So in order to remove the value, we would be doing event.release = ""

Copy link
Collaborator Author

@limbonaut limbonaut Jan 10, 2025

Choose a reason for hiding this comment

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

@bitsandfoxes Do you think we need explicit methods for removing event properties?

if (v.is_empty()) {
sentry_value_remove_by_key(value, k);
} else {
sentry_value_set_by_key(value, k, sentry_value_new_string(v.utf8()));
}
}

} // unnamed namespace

String NativeEvent::get_id() const {
sentry_value_t id = sentry_value_get_by_key(native_event, "event_id");
return sentry_value_as_string(id);
}

void NativeEvent::set_message(const String &p_message) {
if (p_message.is_empty()) {
sentry_value_remove_by_key(native_event, "message");
} else {
sentry_value_t message = sentry_value_get_by_key(native_event, "message");
if (sentry_value_is_null(message)) {
message = sentry_value_new_object();
sentry_value_set_by_key(native_event, "message", message);
}
sentry_value_set_by_key(message, "formatted", sentry_value_new_string(p_message.utf8()));
}
}

String NativeEvent::get_message() const {
sentry_value_t message = sentry_value_get_by_key(native_event, "message");
sentry_value_t formatted = sentry_value_get_by_key(message, "formatted");
return sentry_value_as_string(formatted);
}

void NativeEvent::set_timestamp(const String &p_timestamp) {
_sentry_value_set_or_remove_string_by_key(native_event, "timestamp", p_timestamp);
}

String NativeEvent::get_timestamp() const {
sentry_value_t timestamp = sentry_value_get_by_key(native_event, "timestamp");
return sentry_value_as_string(timestamp);
}

String NativeEvent::get_platform() const {
sentry_value_t platform = sentry_value_get_by_key(native_event, "platform");
return sentry_value_as_string(platform);
}

void NativeEvent::set_level(sentry::Level p_level) {
sentry_value_set_by_key(native_event, "level",
sentry_value_new_string(sentry::native::level_to_cstring(p_level)));
}

sentry::Level NativeEvent::get_level() const {
sentry_value_t value = sentry_value_get_by_key(native_event, "level");
return sentry::native::cstring_to_level(sentry_value_as_string(value));
}

void NativeEvent::set_logger(const String &p_logger) {
_sentry_value_set_or_remove_string_by_key(native_event, "logger", p_logger);
}

String NativeEvent::get_logger() const {
sentry_value_t logger = sentry_value_get_by_key(native_event, "logger");
return sentry_value_as_string(logger);
}

void NativeEvent::set_release(const String &p_release) {
_sentry_value_set_or_remove_string_by_key(native_event, "release", p_release);
}

String NativeEvent::get_release() const {
sentry_value_t release = sentry_value_get_by_key(native_event, "release");
return sentry_value_as_string(release);
}

void NativeEvent::set_dist(const String &p_dist) {
_sentry_value_set_or_remove_string_by_key(native_event, "dist", p_dist);
}

String NativeEvent::get_dist() const {
sentry_value_t dist = sentry_value_get_by_key(native_event, "dist");
return sentry_value_as_string(dist);
}

void NativeEvent::set_environment(const String &p_environment) {
_sentry_value_set_or_remove_string_by_key(native_event, "environment", p_environment);
}

String NativeEvent::get_environment() const {
sentry_value_t environment = sentry_value_get_by_key(native_event, "environment");
return sentry_value_as_string(environment);
}

NativeEvent::NativeEvent(sentry_value_t p_native_event) {
native_event = p_native_event;
}

NativeEvent::NativeEvent() :
NativeEvent(sentry_value_new_event()) {
}

NativeEvent::~NativeEvent() {
sentry_value_decref(native_event);
}
Loading
Loading