Skip to content

feat: SentryBreadcrumb class and before_breadcrumb hook #83

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

Draft
wants to merge 34 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
688739a
Add SentryBreadcrumb abstract class
limbonaut Jan 31, 2025
d9ac3df
Add DisabledBreadcrumb
limbonaut Jan 31, 2025
39c5a41
Implement NativeBreadcrumb
limbonaut Jan 31, 2025
866ad3d
Register those classes
limbonaut Jan 31, 2025
8aa0473
Create breadcrumb internal method
limbonaut Jan 31, 2025
352230e
Fix gdextension compilation
limbonaut Jan 31, 2025
683a403
New method SentrySDK.create_breadcrumb()
limbonaut Jan 31, 2025
4c06dff
Add `before_breadcrumb` option
limbonaut Jan 31, 2025
ee4d23c
Process breadcrumbs in before_breadcrumb
limbonaut Jan 31, 2025
667bc00
NativeSDK.capture_breadcrumb() implementation
limbonaut Jan 31, 2025
6019de1
Add hidden set_before_breadcrumb methods
limbonaut Jan 31, 2025
a828d48
Fix native include not guarded
limbonaut Jan 31, 2025
bfec7d5
Public methods to create and capture breadcrumbs
limbonaut Jan 31, 2025
95073a4
Basic tests
limbonaut Jan 31, 2025
7b272fd
Test before_breadcrumb callback
limbonaut Jan 31, 2025
ee9d823
spaces -> tabs
limbonaut Jan 31, 2025
c98f138
Fix before_breadcrumb not called with capture_breadcrumb()
limbonaut Jan 31, 2025
2dfbbf3
Fix test failing while monitoring signal
limbonaut Jan 31, 2025
9f481d7
Implement `SentryBreadcrumb.data`
limbonaut Feb 4, 2025
9e01f27
Expose data as methods instead of a property
limbonaut Feb 4, 2025
46c923c
Bump `sentry-native` branch
limbonaut Feb 4, 2025
2fae26e
Merge branch 'main' into feat/breadcrumb-hook
limbonaut Feb 4, 2025
97ccead
Bump `sentry-native` branch
limbonaut Feb 4, 2025
3afc98e
Remove unnecessary include
limbonaut Feb 4, 2025
d2d2931
Add `timestamp` property
limbonaut Feb 4, 2025
383ad8f
Adjust test
limbonaut Feb 4, 2025
89a73dd
Refactor NativeEvent to use new common function
limbonaut Feb 4, 2025
b55d8bc
Use sentry value iterators
limbonaut Feb 6, 2025
206a041
Merge branch 'main' into feat/breadcrumb-hook
limbonaut Feb 6, 2025
ba3ca57
Bump sentry-native branch
limbonaut Feb 7, 2025
687ad56
Merge branch 'main' into feat/breadcrumb-hook
limbonaut Feb 14, 2025
9c59998
Merge branch 'main' into feat/breadcrumb-hook
limbonaut Feb 28, 2025
8838745
Merge branch 'main' into feat/breadcrumb-hook
limbonaut Feb 28, 2025
969bda1
Merge branch 'main' into feat/breadcrumb-hook
limbonaut Feb 28, 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
70 changes: 70 additions & 0 deletions project/test/test_breadcrumb.gd
Original file line number Diff line number Diff line change
@@ -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)
8 changes: 7 additions & 1 deletion src/register_types.cpp
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -13,6 +15,7 @@
#include <godot_cpp/classes/window.hpp>

#ifdef NATIVE_SDK
#include "sentry/native/native_breadcrumb.h"
#include "sentry/native/native_event.h"
#endif // NATIVE_SDK

Expand Down Expand Up @@ -53,9 +56,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();
Expand Down
41 changes: 41 additions & 0 deletions src/sentry/disabled/disabled_breadcrumb.h
Original file line number Diff line number Diff line change
@@ -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
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -17,8 +18,10 @@ class DisabledSDK : public InternalSDK {
virtual void set_user(const Ref<SentryUser> &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<SentryBreadcrumb> create_breadcrumb() override { return memnew(DisabledBreadcrumb); }
virtual Ref<SentryBreadcrumb> 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<SentryBreadcrumb> &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 ""; }
Expand Down
5 changes: 4 additions & 1 deletion 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_breadcrumb.h"
#include "sentry_event.h"
#include "sentry_user.h"

Expand Down Expand Up @@ -35,8 +36,10 @@ class InternalSDK {
virtual void set_user(const Ref<SentryUser> &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<SentryBreadcrumb> create_breadcrumb() = 0;
virtual Ref<SentryBreadcrumb> 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<SentryBreadcrumb> &p_breadcrumb) = 0;
// TODO: Consider adding the following function.
// virtual void clear_breadcrumbs() = 0;

Expand Down
111 changes: 111 additions & 0 deletions src/sentry/native/native_breadcrumb.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#include "native_breadcrumb.h"
#include "godot_cpp/core/error_macros.hpp"
#include "sentry/native/native_util.h"

#include <sentry.h>

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
}
44 changes: 44 additions & 0 deletions src/sentry/native/native_breadcrumb.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#ifndef NATIVE_BREADCRUMB_H
#define NATIVE_BREADCRUMB_H

#include "sentry_breadcrumb.h"

#include <sentry.h>

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
Loading