Skip to content

Commit 5778177

Browse files
authored
feat(scopes): Support scopes on WebAssembly (#836)
* feat(scopes): Add scopes and SentrySDK.with_scope() on WebAssembly * Remove patching client on scopes (no longer needed) * Update test.js * Update javascript_sdk.cpp * Remove input validation in JavaScriptScope * Produce DisabledScope on creation failure * Update CHANGELOG.md * Update class docs * Lighter breadcrumb cast * Skip reason * Add additional bridge tests * Comment * Implement Castable
1 parent a73c6bf commit 5778177

20 files changed

Lines changed: 406 additions & 52 deletions

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
### Features
66

7-
- Add current scope support to the GDScript API to enrich the telemetry captured within a specific part of the code ([#834](https://github.com/getsentry/sentry-godot/pull/834), [#835](https://github.com/getsentry/sentry-godot/pull/835))
7+
- Add current scope support to the GDScript API to enrich the telemetry captured within a specific part of the code ([#834](https://github.com/getsentry/sentry-godot/pull/834), [#835](https://github.com/getsentry/sentry-godot/pull/835), [#836](https://github.com/getsentry/sentry-godot/pull/836))
88
- `SentrySDK.with_scope()` runs a callable with a forked scope, `SentrySDK.get_current_scope()` returns the scope active on the calling thread, and the new `SentryScope` class carries tags, contexts, user, level, fingerprint, breadcrumbs, and attributes on top of the data set globally
9-
- Only supported on Windows, Linux, and Android for now, with the remaining platforms still capturing telemetry but discarding the scope data and printing a warning
9+
- Not supported on macOS and iOS yet, where telemetry is still captured but the scope data is discarded with a warning
1010

1111
### Dependencies
1212

doc_classes/SentrySDK.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
<description>
7878
Returns the current scope on the calling thread. Inside a [method SentrySDK.with_scope] callable, this is the scope forked for that callable. The fork is discarded when the callable returns, and the parent scope it was forked from becomes current again. Outside of any [method SentrySDK.with_scope], the returned scope stays current for the telemetry captured later on that thread.
7979
The returned scope belongs to the calling thread. Data written to it enriches only the telemetry captured on that thread, and modifying it from another thread is not supported. See [SentryScope] for details. To enrich telemetry captured on every thread, use the [SentrySDK] methods such as [method SentrySDK.set_tag] instead.
80-
[b]Note:[/b] The SDK supports scopes on Windows, Linux, and Android only for now. On the other platforms it still captures telemetry, but discards the data written to the returned scope.
80+
[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 returned scope.
8181
</description>
8282
</method>
8383
<method name="get_last_event_id" qualifiers="const">
@@ -209,7 +209,7 @@
209209
Writes made through [SentrySDK] methods such as [method SentrySDK.set_tag] still apply globally, even when called inside the callable.
210210
For more information, see [SentryScope] class.
211211
[b]Note:[/b] The fork covers the synchronous part of [param callable] only. If the callable awaits, the fork is discarded at the first [code]await[/code] and a warning is printed.
212-
[b]Note:[/b] The SDK supports scopes on Windows, Linux, and Android only for now. On the other platforms it still captures telemetry, but discards the data written to the forked scope and prints a warning.
212+
[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.
213213
</description>
214214
</method>
215215
</methods>

doc_classes/SentryScope.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
)
1515
[/codeblock]
1616
[b]Note:[/b] A scope is thread-local. Modify a scope only from the thread that created it. If a scope is modified from another thread, the SDK rejects the call with an error and discards the data. To enrich telemetry captured on another thread, get that thread's current scope with [method SentrySDK.get_current_scope], or use [SentrySDK] methods such as [method SentrySDK.set_tag] to enrich telemetry captured on all threads.
17-
[b]Note:[/b] The SDK supports scopes on Windows, Linux, and Android only for now. On the other platforms it still captures telemetry, but discards the data written to the scope.
17+
[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 scope.
1818
</description>
1919
<tutorials>
2020
</tutorials>

project/test/isolated/test_metrics.gd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ func test_before_send_metric_discard() -> void:
170170

171171

172172
# TODO: remove skip when implemented on other platforms
173+
# Skipped: JS merges scope attributes at serialization time, after the callback has already run.
173174
func test_metric_with_scope_attributes(_do_skip = OS.get_name() not in ["Windows", "Linux", "Android"]) -> void:
174175
SentrySDK.set_attribute("from_global", "global")
175176
SentrySDK.set_attribute("scope_over_global", "global")
@@ -205,6 +206,7 @@ func test_metric_with_scope_attributes(_do_skip = OS.get_name() not in ["Windows
205206

206207

207208
# TODO: remove skip when implemented on other platforms
209+
# Skipped: JS merges scope attributes at serialization time, after the callback has already run.
208210
func test_metric_with_scope_attribute_types(_do_skip = OS.get_name() not in ["Windows", "Linux", "Android"]) -> void:
209211
SentrySDK.with_scope(func(scope: SentryScope):
210212
scope.set_attribute("level", "forest")

project/test/isolated/test_structured_logs.gd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ func test_structured_logs_with_global_attributes(_do_skip = OS.get_name() == "We
199199

200200

201201
# TODO: remove skip when implemented on other platforms
202+
# Skipped: JS merges scope attributes at serialization time, after the callback has already run.
202203
func test_structured_logs_with_scope_attributes(_do_skip = OS.get_name() not in ["Windows", "Linux", "Android"]) -> void:
203204
SentrySDK.set_attribute("from_global", "global")
204205
SentrySDK.set_attribute("scope_over_global", "global")
@@ -234,6 +235,7 @@ func test_structured_logs_with_scope_attributes(_do_skip = OS.get_name() not in
234235

235236

236237
# TODO: remove skip when implemented on other platforms
238+
# Skipped: JS merges scope attributes at serialization time, after the callback has already run.
237239
func test_structured_logs_with_scope_attribute_types(_do_skip = OS.get_name() not in ["Windows", "Linux", "Android"]) -> void:
238240
SentrySDK.with_scope(func(scope: SentryScope):
239241
scope.set_attribute("level", "forest")

project/test/suites/test_scope.gd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ extends SentryTestSuite
33

44

55
# TODO: widen the platform list as scopes are implemented on other backends.
6-
func before(_do_skip = OS.get_name() not in ["Windows", "Linux", "Android"],
6+
func before(_do_skip = OS.get_name() not in ["Windows", "Linux", "Android", "Web"],
77
_skip_reason = "Scopes are not implemented on this platform yet.") -> void:
88
super()
99

project/test/suites/test_trace.gd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ extends SentryTestSuite
33

44

55
# TODO: widen the platform list as scopes are implemented on other backends.
6-
func before(_do_skip = OS.get_name() not in ["Windows", "Linux", "Android"],
6+
func before(_do_skip = OS.get_name() not in ["Windows", "Linux", "Android", "Web"],
77
_skip_reason = "Scopes are not implemented on this platform yet.") -> void:
88
super()
99

src/sentry/android/android_scope.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
namespace sentry::android {
66

77
class AndroidScope : public SentryScopeImpl {
8+
SENTRY_CASTABLE(AndroidScope, SentryScopeImpl);
9+
810
private:
911
Object *android_plugin = nullptr;
1012
int32_t handle = 0;

src/sentry/castable.h

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#pragma once
2+
3+
#include <type_traits>
4+
5+
namespace sentry {
6+
7+
// Casts between related classes without the cost of dynamic_cast.
8+
// Declare with SENTRY_CASTABLE in every participating class, naming Castable
9+
// as the base in the root class.
10+
class Castable {
11+
public:
12+
struct TypeInfo {
13+
const TypeInfo *parent;
14+
};
15+
16+
static constexpr TypeInfo type_info{ nullptr };
17+
18+
// Returns true if p_from is a T, or derives from T. Null is safe and never matches.
19+
template <typename T>
20+
static bool is_class(const Castable *p_from) {
21+
static_assert(std::is_same_v<typename T::castable_self, T>,
22+
"T must declare SENTRY_CASTABLE");
23+
if (!p_from) {
24+
return false;
25+
}
26+
const TypeInfo *t = p_from->get_type_info();
27+
while (t) {
28+
if (t == &T::type_info) {
29+
return true;
30+
}
31+
t = t->parent;
32+
}
33+
return false;
34+
}
35+
36+
// Returns p_from as T, or null if it isn't one. Null is safe.
37+
template <typename T>
38+
static T *cast_to(Castable *p_from) {
39+
return is_class<T>(p_from) ? static_cast<T *>(p_from) : nullptr;
40+
}
41+
42+
// Returns p_from as T, or null if it isn't one. Null is safe.
43+
template <typename T>
44+
static const T *cast_to(const Castable *p_from) {
45+
return is_class<T>(p_from) ? static_cast<const T *>(p_from) : nullptr;
46+
}
47+
48+
virtual ~Castable() = default;
49+
50+
protected:
51+
virtual const TypeInfo *get_type_info() const = 0;
52+
};
53+
54+
} //namespace sentry
55+
56+
// Adds type info to a castable class. Add at the top of the class body.
57+
#define SENTRY_CASTABLE(m_class, m_base) \
58+
public: \
59+
using castable_self = m_class; \
60+
static constexpr ::sentry::Castable::TypeInfo type_info{ &m_base::type_info }; \
61+
\
62+
protected: \
63+
virtual const ::sentry::Castable::TypeInfo *get_type_info() const override { \
64+
static_assert(std::is_base_of_v<m_base, m_class>, \
65+
#m_base " must be a base of " #m_class "."); \
66+
return &type_info; \
67+
} \
68+
\
69+
private:

src/sentry/disabled/disabled_scope.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
namespace sentry {
66

77
class DisabledScope : public SentryScopeImpl {
8+
SENTRY_CASTABLE(DisabledScope, SentryScopeImpl);
9+
810
public:
911
virtual void set_context(const String &p_key, const Dictionary &p_value) override {}
1012
virtual void set_tag(const String &p_key, const String &p_value) override {}

0 commit comments

Comments
 (0)