diff --git a/modules/sentry-native b/modules/sentry-native index 2a3bdfb9..96a344c7 160000 --- a/modules/sentry-native +++ b/modules/sentry-native @@ -1 +1 @@ -Subproject commit 2a3bdfb9e9899574ee2558df2eea39dfa097f103 +Subproject commit 96a344c7344b72cdc6338ae8ef5e1448f3afeaa0 diff --git a/project/test/test_breadcrumb.gd b/project/test/test_breadcrumb.gd new file mode 100644 index 00000000..36097181 --- /dev/null +++ b/project/test/test_breadcrumb.gd @@ -0,0 +1,70 @@ +extends GdUnitTestSuite +## Test SentryBreadcrumb class. + + +signal callback_processed + +var crumb: SentryBreadcrumb + + +func before_test() -> void: + crumb = SentrySDK.create_breadcrumb() + + +func test_breadcrumb_message() -> void: + crumb.message = "test-message" + assert_str(crumb.message).is_equal("test-message") + + +func test_breadcrumb_category() -> void: + crumb.category = "test-category" + assert_str(crumb.category).is_equal("test-category") + + +func test_breadcrumb_level() -> void: + crumb.level = SentrySDK.LEVEL_DEBUG + assert_int(crumb.level).is_equal(SentrySDK.LEVEL_DEBUG) + + +func test_breadcrumb_type() -> void: + crumb.type = "test-type" + assert_str(crumb.type).is_equal("test-type") + + +func test_with_before_breadcrumb() -> void: + SentrySDK._set_before_breadcrumb( + func(b: SentryBreadcrumb): + assert_str(b.message).is_equal("test-message") + assert_str(b.category).is_equal("test-category") + assert_int(b.level).is_equal(SentrySDK.LEVEL_DEBUG) + assert_str(b.type).is_equal("test-type") + assert_dict(b.get_data()).is_equal({"test": "data"}) + assert_str(b.timestamp).is_equal("2025-01-01T20:00:00+00:00") + callback_processed.emit() + return null # discard breadcrumb + ) + + var monitor := monitor_signals(self, false) + + crumb.message = "test-message" + crumb.category = "test-category" + crumb.level = SentrySDK.LEVEL_DEBUG + crumb.type = "test-type" + crumb.set_data({"test": "data"}) + crumb.timestamp = "2025-01-01T20:00:00+00:00" + SentrySDK.capture_breadcrumb(crumb) + + await assert_signal(monitor).is_emitted("callback_processed") + + SentrySDK._unset_before_breadcrumb() + + +func test_breadcrumb_data() -> void: + crumb.set_data({"str": "text", "int": 42}) + assert_dict(crumb.get_data()).is_equal({"str": "text", "int": 42}) + + +func test_breadcrumb_timestamp() -> void: + var ts = Time.get_datetime_string_from_system() + crumb.timestamp = ts + assert_str(crumb.timestamp).is_equal(ts) diff --git a/src/register_types.cpp b/src/register_types.cpp index 4ed188ac..68a5ff1f 100644 --- a/src/register_types.cpp +++ b/src/register_types.cpp @@ -1,6 +1,8 @@ #include "runtime_config.h" -#include "sentry/disabled_event.h" +#include "sentry/disabled/disabled_breadcrumb.h" +#include "sentry/disabled/disabled_event.h" #include "sentry/util.h" +#include "sentry_breadcrumb.h" #include "sentry_configuration.h" #include "sentry_event.h" #include "sentry_logger.h" @@ -13,6 +15,7 @@ #include #ifdef NATIVE_SDK +#include "sentry/native/native_breadcrumb.h" #include "sentry/native/native_event.h" #endif // NATIVE_SDK @@ -54,9 +57,12 @@ void initialize_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(SentrySDK); GDREGISTER_ABSTRACT_CLASS(SentryEvent); GDREGISTER_INTERNAL_CLASS(DisabledEvent); + GDREGISTER_ABSTRACT_CLASS(SentryBreadcrumb); + GDREGISTER_INTERNAL_CLASS(DisabledBreadcrumb); GDREGISTER_INTERNAL_CLASS(SentryLogger); #ifdef NATIVE_SDK GDREGISTER_INTERNAL_CLASS(NativeEvent); + GDREGISTER_INTERNAL_CLASS(NativeBreadcrumb); #endif // NATIVE_SDK SentryOptions::create_singleton(); diff --git a/src/sentry/disabled/disabled_breadcrumb.h b/src/sentry/disabled/disabled_breadcrumb.h new file mode 100644 index 00000000..e9e04a9a --- /dev/null +++ b/src/sentry/disabled/disabled_breadcrumb.h @@ -0,0 +1,41 @@ +#ifndef DISABLED_BREADCRUMB_H +#define DISABLED_BREADCRUMB_H + +#include "sentry/level.h" +#include "sentry_breadcrumb.h" + +class DisabledBreadcrumb : public SentryBreadcrumb { + GDCLASS(DisabledBreadcrumb, SentryBreadcrumb); + +private: + String message; + String category; + sentry::Level level = sentry::Level::LEVEL_INFO; + String type; + Dictionary data; + String timestamp; + +protected: + static void _bind_methods() {} + +public: + virtual void set_message(const String &p_message) override { message = p_message; } + virtual String get_message() const override { return message; } + + virtual void set_category(const String &p_category) override { category = p_category; } + virtual String get_category() const override { return category; } + + virtual void set_level(sentry::Level p_level) override { level = p_level; } + virtual sentry::Level get_level() const override { return level; } + + virtual void set_type(const String &p_type) override { type = p_type; } + virtual String get_type() const override { return type; } + + virtual void set_data(const Dictionary &p_data) override { data = p_data; } + virtual Dictionary get_data() const override { return data; } + + virtual void set_timestamp(const String &p_timestamp) override { timestamp = p_timestamp; } + virtual String get_timestamp() const override { return timestamp; } +}; + +#endif // DISABLED_BREADCRUMB_H diff --git a/src/sentry/disabled_event.h b/src/sentry/disabled/disabled_event.h similarity index 100% rename from src/sentry/disabled_event.h rename to src/sentry/disabled/disabled_event.h diff --git a/src/sentry/disabled_sdk.h b/src/sentry/disabled/disabled_sdk.h similarity index 74% rename from src/sentry/disabled_sdk.h rename to src/sentry/disabled/disabled_sdk.h index 239e8ae3..7f54dbd4 100644 --- a/src/sentry/disabled_sdk.h +++ b/src/sentry/disabled/disabled_sdk.h @@ -1,7 +1,8 @@ #ifndef DISABLED_SDK_H #define DISABLED_SDK_H -#include "sentry/disabled_event.h" +#include "disabled_breadcrumb.h" +#include "disabled_event.h" #include "sentry/internal_sdk.h" namespace sentry { @@ -17,8 +18,10 @@ class DisabledSDK : public InternalSDK { virtual void set_user(const Ref &p_user) override {} virtual void remove_user() override {} - 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 {} + virtual Ref create_breadcrumb() override { return memnew(DisabledBreadcrumb); } + virtual Ref create_breadcrumb(const String &p_message, const String &p_category, Level p_level, + const String &p_type = "default", const Dictionary &p_data = Dictionary()) override { return memnew(DisabledBreadcrumb); } + virtual void capture_breadcrumb(const Ref &p_breadcrumb) override {} 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 ""; } diff --git a/src/sentry/internal_sdk.h b/src/sentry/internal_sdk.h index 79e9637a..ce93a162 100644 --- a/src/sentry/internal_sdk.h +++ b/src/sentry/internal_sdk.h @@ -2,6 +2,7 @@ #define INTERNAL_SDK_H #include "sentry/level.h" +#include "sentry_breadcrumb.h" #include "sentry_event.h" #include "sentry_user.h" @@ -35,8 +36,10 @@ class InternalSDK { virtual void set_user(const Ref &p_user) = 0; virtual void remove_user() = 0; - virtual void add_breadcrumb(const String &p_message, const String &p_category, Level p_level, + virtual Ref create_breadcrumb() = 0; + virtual Ref create_breadcrumb(const String &p_message, const String &p_category, Level p_level, const String &p_type = "default", const Dictionary &p_data = Dictionary()) = 0; + virtual void capture_breadcrumb(const Ref &p_breadcrumb) = 0; // TODO: Consider adding the following function. // virtual void clear_breadcrumbs() = 0; diff --git a/src/sentry/native/native_breadcrumb.cpp b/src/sentry/native/native_breadcrumb.cpp new file mode 100644 index 00000000..1a32a88f --- /dev/null +++ b/src/sentry/native/native_breadcrumb.cpp @@ -0,0 +1,111 @@ +#include "native_breadcrumb.h" +#include "godot_cpp/core/error_macros.hpp" +#include "sentry/native/native_util.h" + +#include + +void NativeBreadcrumb::set_message(const String &p_message) { + sentry::native::sentry_value_set_or_remove_string_by_key(native_crumb, "message", p_message); +} + +String NativeBreadcrumb::get_message() const { + return sentry_value_as_string( + sentry_value_get_by_key(native_crumb, "message")); +} + +void NativeBreadcrumb::set_category(const String &p_category) { + sentry::native::sentry_value_set_or_remove_string_by_key(native_crumb, "category", p_category); +} + +String NativeBreadcrumb::get_category() const { + return sentry_value_as_string( + sentry_value_get_by_key(native_crumb, "category")); +} + +void NativeBreadcrumb::set_level(sentry::Level p_level) { + sentry_value_set_by_key(native_crumb, "level", + sentry_value_new_string(sentry::native::level_to_cstring(p_level))); +} + +sentry::Level NativeBreadcrumb::get_level() const { + sentry_value_t value = sentry_value_get_by_key(native_crumb, "level"); + if (sentry_value_is_null(value)) { + return sentry::Level::LEVEL_ERROR; + } + return sentry::native::cstring_to_level(sentry_value_as_string(value)); +} + +void NativeBreadcrumb::set_type(const String &p_type) { + sentry::native::sentry_value_set_or_remove_string_by_key(native_crumb, "type", p_type); +} + +String NativeBreadcrumb::get_type() const { + return sentry_value_as_string( + sentry_value_get_by_key(native_crumb, "type")); +} + +void NativeBreadcrumb::set_data(const Dictionary &p_data) { + sentry_value_t native_data = sentry::native::variant_to_sentry_value(p_data); + sentry_value_set_by_key(native_crumb, "data", native_data); +} + +Dictionary NativeBreadcrumb::get_data() const { + sentry_value_t data = sentry_value_get_by_key(native_crumb, "data"); + return sentry::native::sentry_value_to_variant(data); +} + +void NativeBreadcrumb::set_timestamp(const String &p_timestamp) { + sentry::native::sentry_value_set_or_remove_string_by_key(native_crumb, "timestamp", p_timestamp); +} + +String NativeBreadcrumb::get_timestamp() const { + sentry_value_t timestamp = sentry_value_get_by_key(native_crumb, "timestamp"); + return sentry_value_as_string(timestamp); +} + +NativeBreadcrumb::NativeBreadcrumb(const String &p_message, const String &p_category, sentry::Level p_level, const String &p_type, const Dictionary &p_data) { + native_crumb = sentry_value_new_object(); + + if (!p_message.is_empty()) { + sentry_value_set_by_key(native_crumb, "message", + sentry_value_new_string(p_message.utf8())); + } + + if (!p_category.is_empty()) { + sentry_value_set_by_key(native_crumb, "category", + sentry_value_new_string(p_category.utf8())); + } + + sentry_value_set_by_key(native_crumb, "level", + sentry_value_new_string(sentry::native::level_to_cstring(p_level))); + + if (!p_type.is_empty()) { + sentry_value_set_by_key(native_crumb, "type", + sentry_value_new_string(p_type.utf8())); + } + + if (!p_data.is_empty()) { + sentry_value_set_by_key(native_crumb, "data", + sentry::native::variant_to_sentry_value(p_data)); + } +} + +NativeBreadcrumb::NativeBreadcrumb(sentry_value_t p_native_crumb) { + if (sentry_value_refcount(p_native_crumb) > 0) { + sentry_value_incref(p_native_crumb); // acquire ownership + native_crumb = p_native_crumb; + } else { + // Shouldn't happen in healthy code. + native_crumb = sentry_value_new_object(); + ERR_PRINT("Sentry: Internal error: Breadcrumb refcount is zero."); + } + native_crumb = p_native_crumb; +} + +NativeBreadcrumb::NativeBreadcrumb() { + native_crumb = sentry_value_new_object(); +} + +NativeBreadcrumb::~NativeBreadcrumb() { + sentry_value_decref(native_crumb); // release ownership +} diff --git a/src/sentry/native/native_breadcrumb.h b/src/sentry/native/native_breadcrumb.h new file mode 100644 index 00000000..8a91c29b --- /dev/null +++ b/src/sentry/native/native_breadcrumb.h @@ -0,0 +1,44 @@ +#ifndef NATIVE_BREADCRUMB_H +#define NATIVE_BREADCRUMB_H + +#include "sentry_breadcrumb.h" + +#include + +class NativeBreadcrumb : public SentryBreadcrumb { + GDCLASS(NativeBreadcrumb, SentryBreadcrumb); + +private: + sentry_value_t native_crumb; + +protected: + static void _bind_methods() {} + +public: + _FORCE_INLINE_ sentry_value_t get_native_value() const { return native_crumb; } + + virtual void set_message(const String &p_message) override; + virtual String get_message() const override; + + virtual void set_category(const String &p_category) override; + virtual String get_category() const override; + + virtual void set_level(sentry::Level p_level) override; + virtual sentry::Level get_level() const override; + + virtual void set_type(const String &p_type) override; + virtual String get_type() const override; + + virtual void set_data(const Dictionary &p_data) override; + virtual Dictionary get_data() const override; + + virtual void set_timestamp(const String &p_timestamp) override; + virtual String get_timestamp() const override; + + NativeBreadcrumb(const String &p_message, const String &p_category, sentry::Level p_level, const String &p_type, const Dictionary &p_data); + NativeBreadcrumb(sentry_value_t p_native_crumb); + NativeBreadcrumb(); + virtual ~NativeBreadcrumb() override; +}; + +#endif // NATIVE_BREADCRUMB_H diff --git a/src/sentry/native/native_event.cpp b/src/sentry/native/native_event.cpp index 66a3c68b..2a73f848 100644 --- a/src/sentry/native/native_event.cpp +++ b/src/sentry/native/native_event.cpp @@ -1,23 +1,10 @@ #include "native_event.h" -#include "godot_cpp/core/error_macros.hpp" #include "sentry/level.h" #include "sentry/native/native_util.h" #include -namespace { - -inline void _sentry_value_set_or_remove_string_by_key(sentry_value_t value, const char *k, const String &v) { - 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); @@ -46,7 +33,8 @@ String NativeEvent::get_message() const { } void NativeEvent::set_timestamp(const String &p_timestamp) { - _sentry_value_set_or_remove_string_by_key(native_event, "timestamp", p_timestamp); + sentry::native::sentry_value_set_or_remove_string_by_key( + native_event, "timestamp", p_timestamp); } String NativeEvent::get_timestamp() const { @@ -76,7 +64,8 @@ sentry::Level NativeEvent::get_level() const { } void NativeEvent::set_logger(const String &p_logger) { - _sentry_value_set_or_remove_string_by_key(native_event, "logger", p_logger); + sentry::native::sentry_value_set_or_remove_string_by_key( + native_event, "logger", p_logger); } String NativeEvent::get_logger() const { @@ -85,7 +74,8 @@ String NativeEvent::get_logger() const { } void NativeEvent::set_release(const String &p_release) { - _sentry_value_set_or_remove_string_by_key(native_event, "release", p_release); + sentry::native::sentry_value_set_or_remove_string_by_key( + native_event, "release", p_release); } String NativeEvent::get_release() const { @@ -94,7 +84,8 @@ String NativeEvent::get_release() const { } void NativeEvent::set_dist(const String &p_dist) { - _sentry_value_set_or_remove_string_by_key(native_event, "dist", p_dist); + sentry::native::sentry_value_set_or_remove_string_by_key( + native_event, "dist", p_dist); } String NativeEvent::get_dist() const { @@ -103,7 +94,8 @@ String NativeEvent::get_dist() const { } void NativeEvent::set_environment(const String &p_environment) { - _sentry_value_set_or_remove_string_by_key(native_event, "environment", p_environment); + sentry::native::sentry_value_set_or_remove_string_by_key( + native_event, "environment", p_environment); } String NativeEvent::get_environment() const { diff --git a/src/sentry/native/native_sdk.cpp b/src/sentry/native/native_sdk.cpp index 9d6c4daf..865a176f 100644 --- a/src/sentry/native/native_sdk.cpp +++ b/src/sentry/native/native_sdk.cpp @@ -3,12 +3,14 @@ #include "sentry.h" #include "sentry/contexts.h" #include "sentry/level.h" +#include "sentry/native/native_breadcrumb.h" #include "sentry/native/native_event.h" #include "sentry/native/native_util.h" #include "sentry/util.h" #include "sentry/util/screenshot.h" #include "sentry_options.h" +#include #include #include #include @@ -71,10 +73,23 @@ inline void _inject_contexts(sentry_value_t p_event) { } } +void test_performance(sentry_value_t ev) { + sentry_value_t contexts = sentry_value_get_by_key(ev, "contexts"); + auto start = std::chrono::high_resolution_clock::now(); + const Dictionary context_dic = sentry::native::sentry_value_to_variant(contexts); + auto end = std::chrono::high_resolution_clock::now(); + std::chrono::duration duration = end - start; + std::cout << "!!!!!!!!!!!!!!!!!!!!! [MEASUREMENTS] Time took: " + << std::chrono::duration_cast(end - start).count() + << " ns" << std::endl; + sentry::util::print_debug(context_dic); +} + sentry_value_t _handle_before_send(sentry_value_t event, void *hint, void *closure) { sentry::util::print_debug("handling before_send"); _save_screenshot(); _inject_contexts(event); + test_performance(event); if (const Callable &before_send = SentryOptions::get_singleton()->get_before_send(); before_send.is_valid()) { Ref event_obj = memnew(NativeEvent(event)); Ref processed = before_send.call(event_obj); @@ -167,13 +182,21 @@ void NativeSDK::remove_user() { sentry_remove_user(); } -void NativeSDK::add_breadcrumb(const String &p_message, const String &p_category, Level p_level, +Ref NativeSDK::create_breadcrumb() { + return memnew(NativeBreadcrumb); +} + +Ref NativeSDK::create_breadcrumb(const String &p_message, const String &p_category, Level p_level, const String &p_type, const Dictionary &p_data) { - sentry_value_t crumb = sentry_value_new_breadcrumb(p_type.utf8().ptr(), p_message.utf8().ptr()); - sentry_value_set_by_key(crumb, "category", sentry_value_new_string(p_category.utf8().ptr())); - sentry_value_set_by_key(crumb, "level", sentry_value_new_string(sentry::level_as_cstring(p_level))); - sentry_value_set_by_key(crumb, "data", sentry::native::variant_to_sentry_value(p_data)); - sentry_add_breadcrumb(crumb); + return memnew(NativeBreadcrumb(p_message, p_category, p_level, p_type, p_data)); +} + +void NativeSDK::capture_breadcrumb(const Ref &p_breadcrumb) { + ERR_FAIL_COND_MSG(p_breadcrumb.is_null(), "Sentry: Can't capture breadcrumb - breadcrumb object is null."); + NativeBreadcrumb *native_crumb = Object::cast_to(p_breadcrumb.ptr()); + ERR_FAIL_NULL(native_crumb); // shouldn't happen + sentry_value_incref(native_crumb->get_native_value()); // keep ownership + sentry_add_breadcrumb(native_crumb->get_native_value()); } String NativeSDK::capture_message(const String &p_message, Level p_level, const String &p_logger) { diff --git a/src/sentry/native/native_sdk.h b/src/sentry/native/native_sdk.h index a35a6076..a1dc7ccd 100644 --- a/src/sentry/native/native_sdk.h +++ b/src/sentry/native/native_sdk.h @@ -23,8 +23,10 @@ class NativeSDK : public InternalSDK { virtual void set_user(const Ref &p_user) override; virtual void remove_user() override; - virtual void add_breadcrumb(const String &p_message, const String &p_category, Level p_level, + virtual Ref create_breadcrumb() override; + virtual Ref create_breadcrumb(const String &p_message, const String &p_category, Level p_level, const String &p_type = "default", const Dictionary &p_data = Dictionary()) override; + virtual void capture_breadcrumb(const Ref &p_breadcrumb) override; virtual String capture_message(const String &p_message, Level p_level = sentry::LEVEL_INFO, const String &p_logger = "") override; virtual String get_last_event_id() override; diff --git a/src/sentry/native/native_util.cpp b/src/sentry/native/native_util.cpp index 481eb3ff..0180c9ec 100644 --- a/src/sentry/native/native_util.cpp +++ b/src/sentry/native/native_util.cpp @@ -1,4 +1,5 @@ #include "native_util.h" +#include "sentry.h" namespace sentry::native { @@ -58,6 +59,48 @@ sentry_value_t variant_to_sentry_value(const Variant &p_variant) { } } +Variant sentry_value_to_variant(sentry_value_t p_value) { + switch (sentry_value_get_type(p_value)) { + case SENTRY_VALUE_TYPE_BOOL: { + return bool(sentry_value_as_int32(p_value)); + } break; + case SENTRY_VALUE_TYPE_INT32: { + return sentry_value_as_int32(p_value); + } break; + case SENTRY_VALUE_TYPE_DOUBLE: { + return sentry_value_as_double(p_value); + } break; + case SENTRY_VALUE_TYPE_STRING: { + return sentry_value_as_string(p_value); + } break; + case SENTRY_VALUE_TYPE_LIST: { + Array array; + for (int i = 0; i < sentry_value_get_length(p_value); i++) { + array.append( + sentry_value_to_variant( + sentry_value_get_by_index(p_value, i))); + } + return array; + } break; + case SENTRY_VALUE_TYPE_OBJECT: { + Dictionary dictionary; + sentry_item_iter_t *it = sentry_value_new_item_iter(p_value); + while (sentry_value_item_iter_valid(it)) { + const char *key = sentry_value_item_iter_get_key(it); + sentry_value_t item_value = sentry_value_item_iter_get_value(it); + dictionary[String{ key }] = sentry_value_to_variant(item_value); + sentry_value_item_iter_next(it); + } + sentry_free(it); + return dictionary; + } break; + case SENTRY_VALUE_TYPE_NULL: + default: { + return Variant(); + } break; + } +} + sentry_value_t strings_to_sentry_list(const PackedStringArray &p_strings) { sentry_value_t sentry_list = sentry_value_new_list(); for (int i = 0; i < p_strings.size(); i++) { diff --git a/src/sentry/native/native_util.h b/src/sentry/native/native_util.h index 51fac3c2..21a2db04 100644 --- a/src/sentry/native/native_util.h +++ b/src/sentry/native/native_util.h @@ -15,6 +15,9 @@ namespace sentry::native { // Convert Godot Variant to sentry_value_t. sentry_value_t variant_to_sentry_value(const Variant &p_variant); +// Convert sentry_value_t to Godot Variant. +Variant sentry_value_to_variant(sentry_value_t p_value); + // Convert PackedStringArray to sentry_value_t (as a list). sentry_value_t strings_to_sentry_list(const PackedStringArray &p_strings); @@ -25,9 +28,18 @@ String make_uuid(); sentry_level_t level_to_native(Level p_level); Level native_to_level(sentry_level_t p_native_level); +// TODO: move this to level.h CharString level_to_cstring(Level p_level); Level cstring_to_level(const CharString &p_cstring); +_FORCE_INLINE_ void sentry_value_set_or_remove_string_by_key(sentry_value_t value, const char *k, const String &v) { + 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())); + } +} + } //namespace sentry::native #endif // NATIVE_UTIL_H diff --git a/src/sentry_breadcrumb.cpp b/src/sentry_breadcrumb.cpp new file mode 100644 index 00000000..9c4801bc --- /dev/null +++ b/src/sentry_breadcrumb.cpp @@ -0,0 +1,15 @@ +#include "sentry_breadcrumb.h" + +#include "sentry/simple_bind.h" +#include "sentry_sdk.h" // Needed for VariantCaster + +void SentryBreadcrumb::_bind_methods() { + BIND_PROPERTY_SIMPLE(SentryBreadcrumb, Variant::STRING, message); + BIND_PROPERTY_SIMPLE(SentryBreadcrumb, Variant::STRING, category); + BIND_PROPERTY(SentryBreadcrumb, sentry::make_level_enum_property("level"), set_level, get_level); + BIND_PROPERTY_SIMPLE(SentryBreadcrumb, Variant::STRING, type); + BIND_PROPERTY_SIMPLE(SentryBreadcrumb, Variant::STRING, timestamp); + + ClassDB::bind_method(D_METHOD("get_data"), &SentryBreadcrumb::get_data); + ClassDB::bind_method(D_METHOD("set_data", "data"), &SentryBreadcrumb::set_data); +} diff --git a/src/sentry_breadcrumb.h b/src/sentry_breadcrumb.h new file mode 100644 index 00000000..861bdd4e --- /dev/null +++ b/src/sentry_breadcrumb.h @@ -0,0 +1,39 @@ +#ifndef SENTRY_BREADCRUMB_H +#define SENTRY_BREADCRUMB_H + +#include "sentry/level.h" + +#include + +using namespace godot; + +// Represents breadcrumbs in the public API. +class SentryBreadcrumb : public RefCounted { + GDCLASS(SentryBreadcrumb, RefCounted); + +protected: + static void _bind_methods(); + +public: + virtual void set_message(const String &p_message) = 0; + virtual String get_message() const = 0; + + virtual void set_category(const String &p_category) = 0; + virtual String get_category() const = 0; + + virtual void set_level(sentry::Level p_level) = 0; + virtual sentry::Level get_level() const = 0; + + virtual void set_type(const String &p_type) = 0; + virtual String get_type() const = 0; + + virtual void set_data(const Dictionary &p_data) = 0; + virtual Dictionary get_data() const = 0; + + virtual void set_timestamp(const String &p_timestamp) = 0; + virtual String get_timestamp() const = 0; + + virtual ~SentryBreadcrumb() = default; +}; + +#endif // SENTRY_BREADCRUMB_H diff --git a/src/sentry_options.cpp b/src/sentry_options.cpp index 7ae90670..4c15cc19 100644 --- a/src/sentry_options.cpp +++ b/src/sentry_options.cpp @@ -173,6 +173,7 @@ void SentryOptions::_bind_methods() { BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::STRING, "before_send"), set_before_send, get_before_send); BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::STRING, "on_crash"), set_on_crash, get_on_crash); + BIND_PROPERTY(SentryOptions, PropertyInfo(Variant::STRING, "before_breadcrumb"), set_before_breadcrumb, get_before_breadcrumb); { using namespace sentry; diff --git a/src/sentry_options.h b/src/sentry_options.h index 89fad7e4..d9a0e615 100644 --- a/src/sentry_options.h +++ b/src/sentry_options.h @@ -70,6 +70,7 @@ class SentryOptions : public RefCounted { String configuration_script; Callable before_send; Callable on_crash; + Callable before_breadcrumb; static void _define_project_settings(const Ref &p_options); static void _load_project_settings(const Ref &p_options); @@ -146,6 +147,9 @@ class SentryOptions : public RefCounted { _FORCE_INLINE_ Callable get_on_crash() const { return on_crash; } _FORCE_INLINE_ void set_on_crash(const Callable &p_on_crash) { on_crash = p_on_crash; } + _FORCE_INLINE_ Callable get_before_breadcrumb() const { return before_breadcrumb; } + _FORCE_INLINE_ void set_before_breadcrumb(const Callable &p_before_breadcrumb) { before_breadcrumb = p_before_breadcrumb; } + SentryOptions(); ~SentryOptions(); }; diff --git a/src/sentry_sdk.cpp b/src/sentry_sdk.cpp index e5ea72f3..a29535bd 100644 --- a/src/sentry_sdk.cpp +++ b/src/sentry_sdk.cpp @@ -2,9 +2,10 @@ #include "gen/sdk_version.gen.h" #include "sentry/contexts.h" -#include "sentry/disabled_sdk.h" +#include "sentry/disabled/disabled_sdk.h" #include "sentry/util.h" #include "sentry/uuid.h" +#include "sentry_breadcrumb.h" #include "sentry_configuration.h" #include @@ -58,7 +59,25 @@ String SentrySDK::capture_message(const String &p_message, Level p_level, const void SentrySDK::add_breadcrumb(const String &p_message, const String &p_category, Level p_level, const String &p_type, const Dictionary &p_data) { - internal_sdk->add_breadcrumb(p_message, p_category, p_level, p_type, p_data); + Ref crumb = internal_sdk->create_breadcrumb(p_message, p_category, p_level, p_type, p_data); + capture_breadcrumb(crumb); +} + +void SentrySDK::capture_breadcrumb(const Ref &p_breadcrumb) { + ERR_FAIL_COND_MSG(p_breadcrumb.is_null(), "Sentry: Can't capture breadcrumb - breadcrumb object is null."); + Ref crumb = p_breadcrumb; + if (SentryOptions::get_singleton()->get_before_breadcrumb().is_valid()) { + Ref processed = SentryOptions::get_singleton()->get_before_breadcrumb().call(crumb); + ERR_FAIL_COND_MSG(processed.is_valid() && processed != crumb, "Sentry: before_breadcrumb callback must return the same breadcrumb object or null."); + if (processed.is_null()) { + // Discard breadcrumb. + sentry::util::print_debug("breadcrumb discarded by before_breadcrumb callback"); + return; + } + sentry::util::print_debug("breadcrumb processed by before_breadcrumb callback"); + crumb = processed; + } + internal_sdk->capture_breadcrumb(crumb); } String SentrySDK::get_last_event_id() const { @@ -179,6 +198,8 @@ void SentrySDK::_bind_methods() { ClassDB::bind_method(D_METHOD("is_enabled"), &SentrySDK::is_enabled); ClassDB::bind_method(D_METHOD("capture_message", "message", "level", "logger"), &SentrySDK::capture_message, DEFVAL(LEVEL_INFO), DEFVAL("")); ClassDB::bind_method(D_METHOD("add_breadcrumb", "message", "category", "level", "type", "data"), &SentrySDK::add_breadcrumb, DEFVAL(LEVEL_INFO), DEFVAL("default"), DEFVAL(Dictionary())); + ClassDB::bind_method(D_METHOD("create_breadcrumb"), &SentrySDK::create_breadcrumb); + ClassDB::bind_method(D_METHOD("capture_breadcrumb", "breadcrumb"), &SentrySDK::capture_breadcrumb); ClassDB::bind_method(D_METHOD("get_last_event_id"), &SentrySDK::get_last_event_id); ClassDB::bind_method(D_METHOD("set_context", "key", "value"), &SentrySDK::set_context); ClassDB::bind_method(D_METHOD("set_tag", "key", "value"), &SentrySDK::set_tag); @@ -194,6 +215,8 @@ void SentrySDK::_bind_methods() { ClassDB::bind_method(D_METHOD("_unset_before_send"), &SentrySDK::unset_before_send); ClassDB::bind_method(D_METHOD("_set_on_crash", "callable"), &SentrySDK::set_on_crash); ClassDB::bind_method(D_METHOD("_unset_on_crash"), &SentrySDK::unset_on_crash); + ClassDB::bind_method(D_METHOD("_set_before_breadcrumb", "callable"), &SentrySDK::set_before_breadcrumb); + ClassDB::bind_method(D_METHOD("_unset_before_breadcrumb"), &SentrySDK::unset_before_breadcrumb); } SentrySDK::SentrySDK() { diff --git a/src/sentry_sdk.h b/src/sentry_sdk.h index 0c23cf13..b73beca2 100644 --- a/src/sentry_sdk.h +++ b/src/sentry_sdk.h @@ -4,6 +4,7 @@ #include "runtime_config.h" #include "sentry/internal_sdk.h" #include "sentry/level.h" +#include "sentry_breadcrumb.h" #include "sentry_event.h" #include "sentry_options.h" @@ -52,6 +53,9 @@ class SentrySDK : public Object { void add_breadcrumb(const String &p_message, const String &p_category, sentry::Level p_level, const String &p_type = "default", const Dictionary &p_data = Dictionary()); + Ref create_breadcrumb() { return internal_sdk->create_breadcrumb(); } + void capture_breadcrumb(const Ref &p_breadcrumb); + void set_context(const String &p_key, const Dictionary &p_value); void set_tag(const String &p_key, const String &p_value); @@ -75,6 +79,9 @@ class SentrySDK : public Object { void set_on_crash(const Callable &p_callable) { SentryOptions::get_singleton()->set_on_crash(p_callable); } void unset_on_crash() { SentryOptions::get_singleton()->set_on_crash(Callable()); } + void set_before_breadcrumb(const Callable &p_callable) { SentryOptions::get_singleton()->set_before_breadcrumb(p_callable); } + void unset_before_breadcrumb() { SentryOptions::get_singleton()->set_before_breadcrumb(Callable()); } + SentrySDK(); ~SentrySDK(); };