Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
15d0b34
Initial API draft
limbonaut Aug 10, 2026
7dfa1df
Define implementation interface and forwarding
limbonaut Aug 10, 2026
0056ebe
Add stubs for native implemention
limbonaut Aug 10, 2026
568134a
Stubs for future top-level API
limbonaut Aug 10, 2026
cc79521
Stub SentrySpan::start_child()
limbonaut Aug 10, 2026
f247be4
Expose span status enum constants to scripting
limbonaut Aug 10, 2026
1fd2c28
Align enum to JS SDK and OpenTelemetry
limbonaut Aug 10, 2026
870579b
Refine span creation APIs
limbonaut Aug 10, 2026
df98cd6
Scope-span bookkeeping
limbonaut Aug 11, 2026
b35e3fd
Warnings on currently unsupported platforms
limbonaut Aug 12, 2026
33afb86
Drop getters
limbonaut Aug 12, 2026
eff13b9
Native implementation
limbonaut Aug 12, 2026
2305e22
Use noop()
limbonaut Aug 12, 2026
0ae570a
Drop set_name()
limbonaut Aug 12, 2026
1b30ea5
Bind active span to scope
limbonaut Aug 12, 2026
4d70bf1
noop() => create_noop()
limbonaut Aug 12, 2026
daefd87
SentryScope::clear() should unref active span
limbonaut Aug 13, 2026
c15d709
Fix crash due to unassigned sentinel outliving ObjectDB
limbonaut Aug 13, 2026
92120d0
Add tests
limbonaut Aug 13, 2026
5c74f6a
Better descriptions
limbonaut Aug 13, 2026
d523a4c
Implement with_span()
limbonaut Aug 13, 2026
e19037d
Add with_span tests
limbonaut Aug 13, 2026
3f8c851
Thread guard
limbonaut Aug 13, 2026
e0377a4
Put attributes before parent_span in start_span()
limbonaut Aug 13, 2026
cd2ea1e
Fork the scope when starting an active span
limbonaut Aug 13, 2026
809dde8
Add span scope tests
limbonaut Aug 13, 2026
be9d6c2
Corrections
limbonaut Aug 14, 2026
ad4b026
Add class docs
limbonaut Aug 14, 2026
bddbb2f
Restrict span attributes to supported value types
limbonaut Aug 14, 2026
be54852
Doc fixes
limbonaut Aug 14, 2026
00382b6
Main thread note
limbonaut Aug 14, 2026
9d16be1
Update CHANGELOG.md
limbonaut Aug 14, 2026
201c2d2
Expose traces_sample_rate option
limbonaut Aug 14, 2026
7e84af6
Enable tracing in the demo project
limbonaut Aug 14, 2026
f55db3e
Pass traces_sample_rate across the C# interop boundary
limbonaut Aug 14, 2026
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
- Not supported on macOS and iOS yet, where telemetry is still captured but the scope data is discarded with a warning
- Add `SentryScope.add_attachment()` to send a file or a block of bytes with the events captured within a scope instead of with every event ([#856](https://github.com/getsentry/sentry-godot/pull/856))

- Add Spans support to the GDScript API for measuring operations and grouping telemetry captured while they run ([#863](https://github.com/getsentry/sentry-godot/pull/863))
- `SentrySDK.start_span()` starts a span and makes it active, `SentrySDK.with_span()` runs a callable with an active span and ends it on return, and `SentrySDK.get_active_span()` returns the active span for the calling thread
- The new `SentrySpan` class carries attributes and status; call `SentrySpan.end()` to finish the operation
- Events captured during an active span are associated with that operation
- `SentryOptions.traces_sample_rate` controls the share of traces sent to Sentry. It defaults to `0.0`; set it above `0.0` to enable performance tracing and send spans
- Not supported on macOS, iOS, Android, or Web yet, where the SDK returns a no-op span with a warning

### Fixes

- Web: Fixed tags, breadcrumbs and other globally set data carrying over into the next session when the SDK is closed and initialized again ([#857](https://github.com/getsentry/sentry-godot/pull/857))
Expand Down
4 changes: 4 additions & 0 deletions doc_classes/SentryOptions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@
<member name="shutdown_timeout_ms" type="int" setter="set_shutdown_timeout_ms" getter="get_shutdown_timeout_ms" default="2000">
The maximum time in milliseconds the SDK will wait for pending events to be sent when [method SentrySDK.close] is called. If the timeout expires, the SDK will perform a forced shutdown and any unsent events may be lost.
</member>
<member name="traces_sample_rate" type="float" setter="set_traces_sample_rate" getter="get_traces_sample_rate" default="0.0">
Configures the sample rate for performance traces, in the range of 0.0 to 1.0. The default is 0.0, which disables tracing and prevents spans from being sent. If set to 1.0, all traces are sent. If set to 0.1, approximately 10% of traces are sent.
The sampling decision is made randomly per trace, so either all spans in a trace are sent or none of them are. See [method SentrySDK.start_span] for information on recording spans.
</member>
</members>
<constants>
<constant name="MASK_NONE" value="0" enum="GodotLoggerEventMask" is_bitfield="true">
Expand Down
52 changes: 52 additions & 0 deletions doc_classes/SentrySDK.xml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@
Creates a new [SentryEvent] object. You can capture the event with [method SentrySDK.capture_event].
</description>
</method>
<method name="get_active_span" qualifiers="const">
<return type="SentrySpan" />
<description>
Returns the span currently bound to the calling thread's current scope, or [code]null[/code] if no span is active. Events captured while a span is active are associated with that span's trace context.
Active spans are created by [method SentrySDK.start_span] and [method SentrySDK.with_span]. When an active span ends, the SDK restores the previously active span, if any.
[b]Note:[/b] The returned span belongs to the calling thread. Span methods must be called from the thread that created the span.
</description>
</method>
<method name="get_current_scope" qualifiers="const">
<return type="SentryScope" />
<description>
Expand Down Expand Up @@ -185,6 +193,30 @@
Assigns user data. See [SentryUser].
</description>
</method>
<method name="start_span">
<return type="SentrySpan" />
<param index="0" name="name" type="String" />
<param index="1" name="attributes" type="Dictionary" default="{}" />
<param index="2" name="parent_span" type="SentrySpan" default="Object(SentrySpan,&quot;script&quot;:null) " />
<param index="3" name="active" type="bool" default="true" />
<description>
Starts a span named [param name] and returns it. Use the returned [SentrySpan] to attach attributes, set a status, and call [method SentrySpan.end] when the measured operation finishes.
If [param active] is [code]true[/code], the SDK forks the current scope and makes the new span active on the fork, so telemetry captured while the span is active is associated with it. Writes to the current scope during the span are discarded when the span ends, and the scope that was current when the span started becomes current again. Pass [code]false[/code] to record a span without making it current. An inactive span is not bound to a scope, so it does not affect [method SentrySDK.get_active_span] and does not stamp telemetry captured alongside it.
[codeblock]
var span := SentrySDK.start_span("load_level", {
"sentry.op": "asset.load",
"level": level_name
})
load_level(level_name)
span.set_status(SentrySpan.SPAN_STATUS_OK)
span.end()
[/codeblock]
When [param parent_span] is omitted, the new span becomes a child of [method SentrySDK.get_active_span] if one exists; otherwise it starts a new root span. Pass [code]null[/code] explicitly to force a new root span. Pass a [SentrySpan] to create the new span under that parent, even if another span is currently active. When such a span is active, it forks the explicit parent's scope instead of the current scope, so it inherits the parent's scope data.
The [param attributes] dictionary is added to the span when it starts. The special [code]sentry.op[/code] attribute sets the Sentry operation name used for the span. Empty span names are rejected. Empty attribute keys are skipped and reported as errors.
[b]Note:[/b] Spans are only sent to Sentry if [member SentryOptions.traces_sample_rate] is raised above its default of 0.0. The rest of the API works either way, so telemetry captured during a span is associated with it even when the span itself is not sent.
[b]Note:[/b] The SDK does not support spans on macOS, iOS, Android and Web yet. On those platforms it returns a no-op span and prints a warning.
</description>
</method>
<method name="with_scope">
<return type="Variant" />
<param index="0" name="callable" type="Callable" />
Expand Down Expand Up @@ -212,6 +244,26 @@
[b]Note:[/b] The SDK does not support scopes on macOS and iOS yet. On those platforms it still captures telemetry, but discards the data written to the forked scope and prints a warning.
</description>
</method>
<method name="with_span">
<return type="Variant" />
<param index="0" name="name" type="String" />
<param index="1" name="callable" type="Callable" />
<description>
Starts an active span named [param name], calls [param callable] with that [SentrySpan], ends the span when the callable returns, and returns the callable's return value.
Use this helper when the operation fits in one synchronous callable. Use [method SentrySDK.start_span] instead when you need to set initial attributes, choose a parent span, keep a span open across multiple functions, or create an inactive span.
[codeblock]
var result: Variant = SentrySDK.with_span("generate_chunk", func(span: SentrySpan) -&gt; Variant:
span.set_attribute("chunk_x", chunk_x)
span.set_attribute("chunk_y", chunk_y)
return generate_chunk(chunk_x, chunk_y)
)
[/codeblock]
Nested calls create nested spans. When the callable returns, the enclosing active span and scope are restored.
[b]Note:[/b] [method SentrySDK.with_span] covers the synchronous part of [param callable] only. If the callable awaits, the span is ended at the first [code]await[/code] and a warning is printed.
[b]Note:[/b] Spans are only sent to Sentry if [member SentryOptions.traces_sample_rate] is raised above its default of 0.0.
[b]Note:[/b] The SDK does not support spans on macOS, iOS, Android and Web yet. On those platforms it returns a no-op span and prints a warning.
</description>
</method>
</methods>
<members>
<member name="bad_code" type="SentryBadCode" setter="" getter="get_bad_code">
Expand Down
67 changes: 67 additions & 0 deletions doc_classes/SentrySpan.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="SentrySpan" 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>
Represents a timed operation in a Sentry trace.
</brief_description>
<description>
A span measures one operation and records metadata about that operation. Spans can be nested, letting Sentry show how work is structured inside a trace.
Create spans with [method SentrySDK.start_span] or [method SentrySDK.with_span]. While a span is active, events captured on the same thread are associated with that span's trace context. You can add details with [method set_attribute] and [method set_attributes], set the outcome with [method set_status], and finish the span with [method end].
[codeblock]
SentrySDK.with_span("load_level", func(span: SentrySpan) -&gt; void:
span.set_attribute("level", level_name)
load_level(level_name)
span.set_status(SentrySpan.SPAN_STATUS_OK)
)
[/codeblock]
Always end spans created with [method SentrySDK.start_span]. A span that never ends is never sent, and an active one keeps its scope fork alive, so spans left unended accumulate on the thread's scope stack until the SDK warns that it has grown unusually deep. [method SentrySDK.with_span] ends the span for you.
[b]Note:[/b] Spans are thread-local. Call span methods only from the thread that created the span.
[b]Note:[/b] On platforms where spans are not implemented yet, the SDK returns a no-op span and prints a one-time warning. Calling methods on that span is safe, but no span is recorded.
</description>
<tutorials>
</tutorials>
<methods>
<method name="end">
<return type="void" />
<description>
Finishes the span and records its duration. If this span is active, ending it removes the scope fork created for the span and restores the previous active span, if there was one.
Calling [method end] more than once is safe. Calls after the first one do nothing.
</description>
</method>
<method name="set_attribute">
<return type="void" />
<param index="0" name="key" type="String" />
<param index="1" name="value" type="Variant" />
<description>
Sets one attribute on the span. Attributes are searchable metadata that help describe the operation, such as the level being loaded, the asset path, or a gameplay flag.
The [param key] must not be empty. Supported value types are [bool], [int], [float], and [String]. Other types will be stringified.
</description>
</method>
<method name="set_attributes">
<return type="void" />
<param index="0" name="attributes" type="Dictionary" />
<description>
Sets multiple attributes on the span from [param attributes]. Each dictionary entry becomes one span attribute.
Empty keys are skipped and reported as errors. Values use the same conversion rules as [method set_attribute].
</description>
</method>
<method name="set_status">
<return type="void" />
<param index="0" name="status" type="int" enum="SentrySpan.SpanStatus" />
<description>
Sets the outcome of the span. Use [constant SentrySpan.SPAN_STATUS_OK] for successful operations and [constant SentrySpan.SPAN_STATUS_ERROR] for failed operations.
Sentry reports a span that was never given a status as successful, so set [constant SentrySpan.SPAN_STATUS_ERROR] on the paths that fail. Clearing a status by passing [constant SentrySpan.SPAN_STATUS_UNSET] after a span has started is not supported on all platforms and may be ignored with a warning.
</description>
</method>
</methods>
<constants>
<constant name="SPAN_STATUS_UNSET" value="0" enum="SpanStatus">
No explicit status has been set for the span. Sentry reports such a span as successful.
</constant>
<constant name="SPAN_STATUS_OK" value="1" enum="SpanStatus">
The operation completed successfully.
</constant>
<constant name="SPAN_STATUS_ERROR" value="2" enum="SpanStatus">
The operation failed.
</constant>
</constants>
</class>
1 change: 1 addition & 0 deletions project/project.godot
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ textures/vram_compression/import_etc2_astc=true
options/auto_init=false
options/dsn="https://3f1e095cf2e14598a0bd5b4ff324f712@o447951.ingest.us.sentry.io/6680910"
schema_version=4
options/traces_sample_rate=1.0
options/attach_scene_tree=true
godot_logger/include_variables=true
godot_logger/logs=143
Expand Down
7 changes: 7 additions & 0 deletions project/test/suites/test_options.gd
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ func test_sample_rate() -> void:
assert_float(options.sample_rate).is_equal_approx(0.5, 0.01)


## SentryOptions.traces_sample_rate should default to 0.0 and be set to the specified value.
func test_traces_sample_rate() -> void:
assert_float(options.traces_sample_rate).is_equal_approx(0.0, 0.01)
options.traces_sample_rate = 0.5
assert_float(options.traces_sample_rate).is_equal_approx(0.5, 0.01)


## SentryOptions.max_breadcrumbs should be set to the specified value.
func test_max_breadcrumbs() -> void:
options.max_breadcrumbs = 42
Expand Down
Loading
Loading