Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
b236071
Add SentryBreadcrumb abstract class
limbonaut Jan 31, 2025
fd8cb4a
Add DisabledBreadcrumb
limbonaut Jan 31, 2025
554fc69
Implement NativeBreadcrumb
limbonaut Jan 31, 2025
487a2db
Register those classes
limbonaut Jan 31, 2025
3766f13
Create breadcrumb internal method
limbonaut Jan 31, 2025
0625ecc
Fix gdextension compilation
limbonaut Jan 31, 2025
38744ff
New method SentrySDK.create_breadcrumb()
limbonaut Jan 31, 2025
3863e4e
Fix simple_bind location
limbonaut Aug 29, 2025
986008f
Fix DisabledBreadcrumb
limbonaut Aug 29, 2025
c446743
Add static factory methods for SentryBreadcrumb
limbonaut Aug 29, 2025
00b3e0b
Fix native include
limbonaut Aug 29, 2025
9d4bf93
Use sentry::native namespace
limbonaut Aug 29, 2025
c46f14e
Breadcrumb in Cocoa
limbonaut Aug 29, 2025
3cf405d
Adjust logger to use new API calls
limbonaut Aug 29, 2025
4ab6179
Add native implementation
limbonaut Aug 29, 2025
2166dea
Define get_native_breadcrumb()
limbonaut Aug 29, 2025
055c6d4
Remove breadcrumb data getter
limbonaut Aug 29, 2025
f2234a3
Add SentryBreadcrumb.user() static factory method
limbonaut Aug 29, 2025
ed8c391
Add basic tests
limbonaut Aug 29, 2025
82e2ce1
Add Android implementation
limbonaut Aug 29, 2025
b188fa7
Update test_breadcrumb.gd
limbonaut Aug 29, 2025
4402963
Fix default breadcrumb level
limbonaut Sep 1, 2025
b327f69
Comment out "category" test
limbonaut Sep 1, 2025
6c63ee3
Fix potential memory leak in NativeBreadcrumb constructor
limbonaut Sep 1, 2025
27b007b
Fix native issues
limbonaut Sep 1, 2025
c3b7fe0
Clarify comment
limbonaut Sep 1, 2025
eb8247a
Update tests
limbonaut Sep 1, 2025
ec0fe7d
Add class docs
limbonaut Sep 1, 2025
d49ee64
Remove helpers, and leave a single factory method that takes a message
limbonaut Sep 1, 2025
3355ade
Remove SentrySDK.create_breadcrumb
limbonaut Sep 1, 2025
a53b143
Update test_breadcrumb.gd
limbonaut Sep 1, 2025
1b33aa3
Default to empty message in factory method, and actually set it
limbonaut Sep 1, 2025
7d933d0
Use new API in logger
limbonaut Sep 1, 2025
82a5f9c
Update docs
limbonaut Sep 1, 2025
579afe0
Update demo
limbonaut Sep 1, 2025
b706b07
Fix wrong link
limbonaut Sep 1, 2025
8284b3d
Add get_timestamp() with native implementation
limbonaut Sep 1, 2025
2a8b56b
Add cocoa get_timestamp()
limbonaut Sep 1, 2025
f2c7695
Add android breadcrumb implementation
limbonaut Sep 1, 2025
edafb3a
Update SentryBreadcrumb.xml
limbonaut Sep 1, 2025
0f7fa17
Set error type and category on error breadcrumbs
limbonaut Sep 1, 2025
888c713
Test for get_timestamp()
limbonaut Sep 1, 2025
a632acc
Fix missing message on Android
limbonaut Sep 1, 2025
f0492ec
Fix native breadcrumb creation
limbonaut Sep 1, 2025
205fc57
Update CHANGELOG.md
limbonaut Sep 1, 2025
2b4113c
Fix arg name
limbonaut Sep 1, 2025
c7171b3
Update test
limbonaut Sep 1, 2025
a7936a4
Fix debug print slipped in
limbonaut Sep 2, 2025
54a30fa
Update android_lib/src/main/java/io/sentry/godotplugin/SentryAndroidG…
limbonaut Sep 2, 2025
ad1c10a
breadcrumb-class: Update tests with UTF-8
limbonaut Sep 2, 2025
3b7e04b
Fix UTF-8 issues
limbonaut Sep 2, 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
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@

## Unreleased

### Breaking changes

We've redesigned the breadcrumb API for a cleaner, more intuitive interface. Previously, `add_breadcrumb()` method accepted 5 parameters (3 of which were strings), making it confusing to use. The new approach uses a dedicated `SentryBreadcrumb` class:

```gdscript
var crumb := SentryBreadcrumb.create("Something happened")
crumb.type = "info"
crumb.set_data({"some": "data"})
SentrySDK.add_breadcrumb(crumb)
```

For simple breadcrumbs, you can use a one-liner:
```gdscript
SentrySDK.add_breadcrumb(SentryBreadcrumb.create("Something happened"))
```

This change provides better type safety, improved readability, and enables future support for the `before_breadcrumb` callback.

### Features

- Add support for script context and variables on Apple platforms ([#306](https://github.com/getsentry/sentry-godot/pull/306))
Expand All @@ -10,6 +28,7 @@
### Improvements

- Improve initialization flow ([#322](https://github.com/getsentry/sentry-godot/pull/322))
- Introduce `SentryBreadcrumb` class ([#332](https://github.com/getsentry/sentry-godot/pull/332))

### Fixes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ class SentryAndroidGodotPlugin(godot: Godot) : GodotPlugin(godot) {
}
}

private val breadcrumbsByHandle = object : ThreadLocal<MutableMap<Int, Breadcrumb>>() {
override fun initialValue(): MutableMap<Int, Breadcrumb> {
return mutableMapOf()
}
}

private fun getEvent(eventHandle: Int): SentryEvent? {
val event: SentryEvent? = eventsByHandle.get()?.get(eventHandle)
if (event == null) {
Expand All @@ -59,6 +65,14 @@ class SentryAndroidGodotPlugin(godot: Godot) : GodotPlugin(godot) {
return exception
}

private fun getBreadcrumb(breadcrumbHandle: Int): Breadcrumb? {
var crumb: Breadcrumb? = breadcrumbsByHandle.get()?.get(breadcrumbHandle)
if (crumb == null) {
Log.e(TAG, "Internal Error -- Breadcrumb not found: $breadcrumbHandle")
}
return crumb
}

private fun registerEvent(event: SentryEvent): Int {
val eventsMap = eventsByHandle.get() ?: run {
Log.e(TAG, "Internal Error -- eventsByHandle is null")
Expand Down Expand Up @@ -89,6 +103,21 @@ class SentryAndroidGodotPlugin(godot: Godot) : GodotPlugin(godot) {
return handle
}

private fun registerBreadcrumb(crumb: Breadcrumb): Int {
val breadcrumbsMap = breadcrumbsByHandle.get() ?: run {
Log.e(TAG, "Internal Error -- breadcrumbsByHandle is null")
return 0
}

var handle = Random.nextInt()
while (breadcrumbsMap.containsKey(handle)) {
handle = Random.nextInt()
}

breadcrumbsMap[handle] = crumb
return handle
}

override fun getPluginName(): String {
return "SentryAndroidGodotPlugin"
}
Expand Down Expand Up @@ -193,23 +222,8 @@ class SentryAndroidGodotPlugin(godot: Godot) : GodotPlugin(godot) {
}

@UsedByGodot
fun addBreadcrumb(
message: String,
category: String,
level: Int,
type: String,
data: Dictionary
) {
val crumb = Breadcrumb()
crumb.message = message
crumb.category = category
crumb.level = level.toSentryLevel()
crumb.type = type

for ((k, v) in data) {
crumb.data[k] = v
}

fun addBreadcrumb(handle: Int) {
val crumb = getBreadcrumb(handle) ?: return
Sentry.addBreadcrumb(crumb)
}

Expand Down Expand Up @@ -442,4 +456,79 @@ class SentryAndroidGodotPlugin(godot: Godot) : GodotPlugin(godot) {
}
event.exceptions?.add(exception)
}

@UsedByGodot
fun createBreadcrumb(): Int {
val crumb = Breadcrumb()
val handle = registerBreadcrumb(crumb)
return handle
}

@UsedByGodot
fun releaseBreadcrumb(handle: Int) {
val breadcrumbsMap = breadcrumbsByHandle.get() ?: run {
Log.e(TAG, "Internal Error -- breadcrumbsByHandle is null")
return
}

breadcrumbsMap.remove(handle)
}

@UsedByGodot
fun breadcrumbSetMessage(handle: Int, message: String) {
getBreadcrumb(handle)?.message = message
}

@UsedByGodot
fun breadcrumbGetMessage(handle: Int): String {
return getBreadcrumb(handle)?.message ?: ""
}

@UsedByGodot
fun breadcrumbSetCategory(handle: Int, category: String) {
getBreadcrumb(handle)?.category = category
}

@UsedByGodot
fun breadcrumbGetCategory(handle: Int): String {
return getBreadcrumb(handle)?.category ?: ""
}

@UsedByGodot
fun breadcrumbSetLevel(handle: Int, level: Int) {
getBreadcrumb(handle)?.level = level.toSentryLevel()
}

@UsedByGodot
fun breadcrumbGetLevel(handle: Int): Int {
return getBreadcrumb(handle)?.level?.toInt() ?: SentryLevel.INFO.toInt()
}

@UsedByGodot
fun breadcrumbSetType(handle: Int, type: String) {
getBreadcrumb(handle)?.type = type
}

@UsedByGodot
fun breadcrumbGetType(handle: Int): String {
return getBreadcrumb(handle)?.type ?: ""
}

@UsedByGodot
fun breadcrumbSetData(handle: Int, data: Dictionary) {
val crumb = getBreadcrumb(handle) ?: return

crumb.data.clear()

for ((k, v) in data) {
crumb.data[k] = v
}
}

@UsedByGodot
fun breadcrumbGetTimestamp(handle: Int): Long {
val crumb = getBreadcrumb(handle) ?: return 0
return crumb.timestamp.toMicros()
}

}
54 changes: 54 additions & 0 deletions doc_classes/SentryBreadcrumb.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="SentryBreadcrumb" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/godotengine/godot/master/doc/class.xsd">
<brief_description>
Sentry breadcrumb for error reporting context.
</brief_description>
<description>
A breadcrumb is a small piece of diagnostic data that records an event or action leading up to an error or crash. These breadcrumbs create a trail that shows what the user and your game was doing right before something went wrong.
To create a breadcrumb, use the static [method SentryBreadcrumb.create] method with an optional message parameter. Once created, you can configure additional properties like category, level, type, and data before adding it to Sentry using [method SentrySDK.add_breadcrumb].
[codeblock]
var crumb := SentryBreadcrumb.create("Level finished!")
crumb.type = "info"
crumb.category = "level.finished"
SentrySDK.add_breadcrumb(crumb)
[/codeblock]
For more information, check out the [url=https://docs.sentry.io/platforms/godot/enriching-events/breadcrumbs/]Breadcrumbs documentation[/url].
</description>
<tutorials>
</tutorials>
<methods>
<method name="create" qualifiers="static">
<return type="SentryBreadcrumb" />
<param index="0" name="message" type="String" default="&quot;&quot;" />
<description>
</description>
</method>
<method name="get_timestamp">
<return type="SentryTimestamp" />
<description>
Returns the timestamp of the breadcrumb.
</description>
</method>
<method name="set_data">
<return type="void" />
<param index="0" name="data" type="Dictionary" />
<description>
Sets additional data associated with the breadcrumb. This can be useful for providing more context about the event or action that triggered the breadcrumb.
</description>
</method>
</methods>
<members>
<member name="category" type="String" setter="set_category" getter="get_category">
A category label that shows what type of action this breadcrumb represents. Use dot notation like "ui.click" for a button click or "network.request" for an API call.
</member>
<member name="level" type="int" setter="set_level" getter="get_level" enum="SentrySDK.Level">
The severity level of the breadcrumb. Defaults to [code]LEVEL_INFO[/code]. See [enum SentrySDK.Level].
</member>
<member name="message" type="String" setter="set_message" getter="get_message">
A short description of what happened.
</member>
<member name="type" type="String" setter="set_type" getter="get_type">
The type of breadcrumb that determines how it appears in the Sentry interface. Defaults to [code]default[/code] which renders as a Debug entry. Common types include [code]http[/code] for network requests, [code]navigation[/code] for page/screen changes, [code]user[/code] for user interactions, and [code]error[/code] for error events. See [url=https://develop.sentry.dev/sdk/data-model/event-payloads/breadcrumbs/#breadcrumb-types]Breadcrumb Types[/url] for the complete list.
</member>
</members>
</class>
10 changes: 3 additions & 7 deletions doc_classes/SentrySDK.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,10 @@
</method>
<method name="add_breadcrumb">
<return type="void" />
<param index="0" name="message" type="String" />
<param index="1" name="category" type="String" />
<param index="2" name="level" type="int" enum="SentrySDK.Level" default="1" />
<param index="3" name="type" type="String" default="&quot;default&quot;" />
<param index="4" name="data" type="Dictionary" default="{}" />
<param index="0" name="breadcrumb" type="SentryBreadcrumb" />
<description>
Adds a breadcrumb to the next event. Sentry uses breadcrumbs to create a trail of events that happened prior to an issue.
To learn more, visit [url=https://docs.sentry.io/platforms/godot/enriching-events/breadcrumbs/]Breadcrumbs documentation[/url].
Adds a breadcrumb to the future events. You can create a [SentryBreadcrumb] object using the static [method SentryBreadcrumb.create] method with an optional message parameter.
Sentry uses breadcrumbs to create a trail of events that happened prior to an issue. For more information, see [SentryBreadcrumb] class.
</description>
</method>
<method name="capture_event">
Expand Down
66 changes: 66 additions & 0 deletions project/test/suites/test_breadcrumb.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
extends GdUnitTestSuite
## Test SentryBreadcrumb class properties and methods.


## Test string properties accessors and UTF-8 encoding preservation.
@warning_ignore("unused_parameter")
func test_string_properties_and_utf8(property: String, test_parameters := [
["message"],
["category"],
["type"],
]) -> void:
var crumb := SentryBreadcrumb.create()
crumb.set(property, "Hello, World!")
assert_str(crumb.get(property)).is_equal("Hello, World!")
crumb.set(property, "Hello 世界! 👋")
assert_str(crumb.get(property)).is_equal("Hello 世界! 👋")


func test_breadcrumb_create_with_message() -> void:
var crumb := SentryBreadcrumb.create("test-message")
assert_str(crumb.message).is_equal("test-message")


func test_breadcrumb_level() -> void:
var crumb := SentryBreadcrumb.create()
crumb.level = SentrySDK.LEVEL_DEBUG
assert_int(crumb.level).is_equal(SentrySDK.LEVEL_DEBUG)
crumb.level = SentrySDK.LEVEL_INFO
assert_int(crumb.level).is_equal(SentrySDK.LEVEL_INFO)
crumb.level = SentrySDK.LEVEL_WARNING
assert_int(crumb.level).is_equal(SentrySDK.LEVEL_WARNING)
crumb.level = SentrySDK.LEVEL_ERROR
assert_int(crumb.level).is_equal(SentrySDK.LEVEL_ERROR)
crumb.level = SentrySDK.LEVEL_FATAL
assert_int(crumb.level).is_equal(SentrySDK.LEVEL_FATAL)


func test_breadcrumb_can_set_data() -> void:
var crumb := SentryBreadcrumb.create()
crumb.set_data({"biome": "forest", "time_of_day": 0.42})


func test_breadcrumb_default_values() -> void:
var crumb := SentryBreadcrumb.create()
assert_str(crumb.message).is_empty()
assert_str(crumb.type).is_empty()
assert_int(crumb.level).is_equal(SentrySDK.LEVEL_INFO)
assert_bool(crumb.category in ["", "default"]).is_true()


func test_breadcrumb_timestamp_is_set_automatically() -> void:
var time_before: float = Time.get_unix_time_from_system()
await get_tree().process_frame # small delay to ensure timestamp differs

var crumb := SentryBreadcrumb.create()

await get_tree().process_frame
var time_after: float = Time.get_unix_time_from_system()

# Timestamp should be between time_before and time_after
var timestamp: SentryTimestamp = crumb.get_timestamp()
assert_object(timestamp).is_not_null()
var micros: int = timestamp.microseconds_since_unix_epoch
var timestamp_unix: float = float(micros) / 1_000_000.0
assert_float(timestamp_unix).is_greater_equal(time_before)
assert_float(timestamp_unix).is_less_equal(time_after)
1 change: 1 addition & 0 deletions project/test/suites/test_breadcrumb.gd.uid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://dexm1kq44m4cv
4 changes: 3 additions & 1 deletion project/views/enrich_events.gd
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ func _ready() -> void:


func _on_add_breadcrumb_button_pressed() -> void:
SentrySDK.add_breadcrumb(breadcrumb_message.text, breadcrumb_category.text, SentrySDK.LEVEL_ERROR, "default")
var crumb := SentryBreadcrumb.create(breadcrumb_message.text)
crumb.category = breadcrumb_category.text
SentrySDK.add_breadcrumb(crumb)
DemoOutput.print_info("Breadcrumb added.")


Expand Down
8 changes: 8 additions & 0 deletions src/register_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "sentry/processing/view_hierarchy_processor.h"
#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"
Expand All @@ -18,15 +19,18 @@
#include <godot_cpp/classes/window.hpp>

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

#ifdef SDK_ANDROID
#include "sentry/android/android_breadcrumb.h"
#include "sentry/android/android_event.h"
#include "sentry/android/android_sdk.h"
#endif // SDK_ANDROID

#ifdef SDK_COCOA
#include "sentry/cocoa/cocoa_breadcrumb.h"
#include "sentry/cocoa/cocoa_event.h"
#endif // SDK_COCOA

Expand All @@ -49,6 +53,7 @@ void register_runtime_classes() {
GDREGISTER_CLASS(SentrySDK);
GDREGISTER_ABSTRACT_CLASS(SentryAttachment);
GDREGISTER_ABSTRACT_CLASS(SentryEvent);
GDREGISTER_ABSTRACT_CLASS(SentryBreadcrumb);
GDREGISTER_INTERNAL_CLASS(DisabledEvent);
GDREGISTER_INTERNAL_CLASS(SentryEventProcessor);
GDREGISTER_INTERNAL_CLASS(ScreenshotProcessor);
Expand All @@ -57,15 +62,18 @@ void register_runtime_classes() {

#ifdef SDK_NATIVE
GDREGISTER_INTERNAL_CLASS(native::NativeEvent);
GDREGISTER_INTERNAL_CLASS(native::NativeBreadcrumb);
#endif

#ifdef SDK_ANDROID
GDREGISTER_INTERNAL_CLASS(android::AndroidEvent);
GDREGISTER_INTERNAL_CLASS(android::AndroidBreadcrumb);
GDREGISTER_INTERNAL_CLASS(android::SentryAndroidBeforeSendHandler);
#endif

#ifdef SDK_COCOA
GDREGISTER_INTERNAL_CLASS(cocoa::CocoaEvent);
GDREGISTER_INTERNAL_CLASS(cocoa::CocoaBreadcrumb);
#endif
}

Expand Down
Loading
Loading