Skip to content

Commit 7756e28

Browse files
authored
feat: Add user attachments support (#205)
1 parent 50457cb commit 7756e28

21 files changed

+324
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
- Initial Android support ([#169](https://github.com/getsentry/sentry-godot/pull/169))
1212
- Refine demo for mobile screens ([#196](https://github.com/getsentry/sentry-godot/pull/196))
13+
- Add user attachments support ([#205](https://github.com/getsentry/sentry-godot/pull/205))
1314

1415
## Fixes
1516

android_lib/src/main/java/io/sentry/godotplugin/SentryAndroidGodotPlugin.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,18 @@ class SentryAndroidGodotPlugin(godot: Godot) : GodotPlugin(godot) {
135135
Sentry.getGlobalScope().addAttachment(attachment)
136136
}
137137

138+
@UsedByGodot
139+
fun addBytesAttachment(bytes: ByteArray, filename: String, contentType: String, attachmentType: String) {
140+
val attachment = Attachment(
141+
bytes,
142+
filename,
143+
contentType.ifEmpty { null },
144+
attachmentType.ifEmpty { null },
145+
false
146+
)
147+
Sentry.getGlobalScope().addAttachment(attachment)
148+
}
149+
138150
@UsedByGodot
139151
fun setContext(key: String, value: Dictionary) {
140152
Sentry.getGlobalScope().setContexts(key, value)

doc_classes/SentryAttachment.xml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<class name="SentryAttachment" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/godotengine/godot/master/doc/class.xsd">
3+
<brief_description>
4+
Represents a file attachment that can be sent with Sentry events.
5+
</brief_description>
6+
<description>
7+
SentryAttachment represents a file that can be attached to Sentry events to provide additional context. Attachments are files that are uploaded alongside error reports and can include log files, screenshots, configuration files, or any other relevant data.
8+
Attachments can be created using [method SentryAttachment.create_with_path] for existing files on disk. Once created, they can be added to future events using [method SentrySDK.add_attachment]:
9+
[codeblock]
10+
var attachment := SentryAttachment.create_with_path("user://logs/godot.log")
11+
attachment.content_type = "text/plain"
12+
SentrySDK.add_attachment(attachment)
13+
[/codeblock]
14+
Attachments can also be created using [method SentryAttachment.create_with_bytes] for data already in memory:
15+
[codeblock]
16+
var bytes: PackedByteArray = "Hello, world!".to_ascii_buffer()
17+
var attachment := SentryAttachment.create_with_bytes(bytes, "hello.txt")
18+
attachment.content_type = "text/plain"
19+
SentrySDK.add_attachment(attachment)
20+
[/codeblock]
21+
To learn more about attachments, visit [url=https://docs.sentry.io/platforms/godot/enriching-events/attachments/]Attachments documentation[/url].
22+
</description>
23+
<tutorials>
24+
</tutorials>
25+
<methods>
26+
<method name="create_with_bytes" qualifiers="static">
27+
<return type="SentryAttachment" />
28+
<param index="0" name="bytes" type="PackedByteArray" />
29+
<param index="1" name="filename" type="String" />
30+
<description>
31+
Creates a new [SentryAttachment] with the specified [param bytes] data and [param filename]. The [param bytes] parameter contains the raw file data to be attached. The [param filename] parameter specifies the display name for the attachment in Sentry.
32+
This method is useful when you have file data already loaded in memory or when creating attachments from generated content rather than existing files on disk.
33+
</description>
34+
</method>
35+
<method name="create_with_path" qualifiers="static">
36+
<return type="SentryAttachment" />
37+
<param index="0" name="path" type="String" />
38+
<description>
39+
Creates a new [SentryAttachment] with the specified file [param path] and optional [param filename] and [param content_type]. The [param path] should point to an existing file and supports Godot's virtual file system paths like "user://".
40+
[b]Note:[/b] Modifying attachment properties after the attachment has been added with [method SentrySDK.add_attachment] will have no effect. To apply property changes, you need to re-add the attachment.
41+
[b]Important:[/b] Attachments are read lazily at the time an event is sent to Sentry.
42+
</description>
43+
</method>
44+
</methods>
45+
<members>
46+
<member name="bytes" type="PackedByteArray" setter="set_bytes" getter="get_bytes">
47+
Contains the raw byte data of the attachment.
48+
</member>
49+
<member name="content_type" type="String" setter="set_content_type" getter="get_content_type">
50+
The MIME content type of the attachment file. This helps Sentry understand how to handle and display the attachment.
51+
Sentry understands and renders the following MIME types: [code]text/plain[/code], [code]text/css[/code], [code]text/csv[/code], [code]text/html[/code], [code]text/javascript[/code], [code]text/json[/code] or [code]text/x-json[/code] or [code]application/json[/code] or [code]application/ld+json[/code], [code]image/jpeg[/code], [code]image/png[/code], [code]image/gif[/code].
52+
</member>
53+
<member name="filename" type="String" setter="set_filename" getter="get_filename">
54+
The filename of the attachment. This is the name that will be displayed in Sentry. If not provided, the filename will be extracted from the [member path].
55+
</member>
56+
<member name="path" type="String" setter="set_path" getter="get_path">
57+
The file path of the attachment. This can be an absolute path or use Godot's virtual file system paths such as "user://".
58+
</member>
59+
</members>
60+
</class>

doc_classes/SentrySDK.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@
1313
<tutorials>
1414
</tutorials>
1515
<methods>
16+
<method name="add_attachment">
17+
<return type="void" />
18+
<param index="0" name="attachment" type="SentryAttachment" />
19+
<description>
20+
Attaches a file to future Sentry events. The [param attachment] should be a [SentryAttachment] object created with [method SentryAttachment.create_with_path] or [method SentryAttachment.create_with_bytes]. Supports Godot's virtual file system paths like "user://".
21+
To learn more, visit [url=https://docs.sentry.io/platforms/godot/enriching-events/attachments/]Attachments documentation[/url].
22+
</description>
23+
</method>
1624
<method name="add_breadcrumb">
1725
<return type="void" />
1826
<param index="0" name="message" type="String" />
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
extends GdUnitTestSuite
2+
## Basic tests for the SentryAttachment class.
3+
4+
5+
func test_create_with_path() -> void:
6+
var attachment := SentryAttachment.create_with_path("user://logs/godot.log")
7+
attachment.filename = "logfile.txt"
8+
attachment.content_type = "text/plain"
9+
10+
assert_array(attachment.bytes).is_empty()
11+
assert_str(attachment.path).is_equal("user://logs/godot.log")
12+
assert_str(attachment.filename).is_equal("logfile.txt")
13+
assert_str(attachment.content_type).is_equal("text/plain")
14+
15+
16+
func test_create_with_bytes() -> void:
17+
var contents := """
18+
Hello, world!
19+
"""
20+
var bytes: PackedByteArray = contents.to_utf8_buffer()
21+
22+
var attachment := SentryAttachment.create_with_bytes(bytes, "hello.txt")
23+
attachment.content_type = "text/plain"
24+
25+
assert_array(attachment.bytes).is_not_empty()
26+
assert_str(attachment.path).is_empty()
27+
assert_str(attachment.filename).is_equal("hello.txt")
28+
assert_str(attachment.content_type).is_equal("text/plain")
29+
30+
assert_int(attachment.bytes.size()).is_equal(bytes.size())
31+
for i in attachment.bytes.size():
32+
assert_int(attachment.bytes[i]).is_equal(bytes[i])
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://b10m6784fkgyb

project/views/enrich_events.gd

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,12 @@ func _on_set_context_pressed() -> void:
4646
DemoOutput.print_err("Failed set context: Dictionary is expected, but found: " + type_string(typeof(result)))
4747
else:
4848
DemoOutput.print_err("Failed to parse expression: " + expr.get_error_text())
49+
50+
51+
func _on_attach_button_pressed() -> void:
52+
var content: String = %AttachmentContent.text
53+
var bytes: PackedByteArray = content.to_utf8_buffer()
54+
var attachment := SentryAttachment.create_with_bytes(bytes, "hello.txt")
55+
attachment.content_type = "text/plain"
56+
SentrySDK.add_attachment(attachment)
57+
DemoOutput.print_info("Attachment added.")

project/views/enrich_events.tscn

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,24 @@ unique_name_in_owner = true
6363
layout_mode = 2
6464
text = "Add Tag"
6565

66+
[node name="Attachment" type="HBoxContainer" parent="."]
67+
layout_mode = 2
68+
69+
[node name="Label" type="Label" parent="Attachment"]
70+
layout_mode = 2
71+
text = "Attach:"
72+
73+
[node name="AttachmentContent" type="LineEdit" parent="Attachment"]
74+
unique_name_in_owner = true
75+
layout_mode = 2
76+
size_flags_horizontal = 3
77+
text = "Hello, World!"
78+
placeholder_text = "Content"
79+
80+
[node name="AttachButton" type="Button" parent="Attachment"]
81+
layout_mode = 2
82+
text = "Attach hello.txt"
83+
6684
[node name="Context" type="VBoxContainer" parent="."]
6785
layout_mode = 2
6886

@@ -97,4 +115,5 @@ text = "Add context"
97115

98116
[connection signal="pressed" from="Breadcrumb/AddBreadcrumbButton" to="." method="_on_add_breadcrumb_button_pressed"]
99117
[connection signal="pressed" from="Tags/AddTagButton" to="." method="_on_add_tag_button_pressed"]
118+
[connection signal="pressed" from="Attachment/AttachButton" to="." method="_on_attach_button_pressed"]
100119
[connection signal="pressed" from="Context/SetContext" to="." method="_on_set_context_pressed"]

src/register_types.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "sentry/processing/screenshot_processor.h"
66
#include "sentry/processing/view_hierarchy_processor.h"
77
#include "sentry/util/print.h"
8+
#include "sentry_attachment.h"
89
#include "sentry_configuration.h"
910
#include "sentry_event.h"
1011
#include "sentry_event_processor.h"
@@ -68,6 +69,7 @@ void initialize_module(ModuleInitializationLevel p_level) {
6869
GDREGISTER_CLASS(SentryConfiguration);
6970
GDREGISTER_CLASS(SentryUser);
7071
GDREGISTER_CLASS(SentrySDK);
72+
GDREGISTER_ABSTRACT_CLASS(SentryAttachment);
7173
GDREGISTER_ABSTRACT_CLASS(SentryEvent);
7274
GDREGISTER_INTERNAL_CLASS(DisabledEvent);
7375
GDREGISTER_INTERNAL_CLASS(SentryEventProcessor);

src/sentry/android/android_sdk.cpp

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22

33
#include "android_event.h"
44
#include "android_string_names.h"
5+
#include "sentry/common_defs.h"
56
#include "sentry/processing/process_event.h"
67
#include "sentry/util/print.h"
8+
#include "sentry_attachment.h"
79

810
#include <godot_cpp/classes/engine.hpp>
11+
#include <godot_cpp/classes/project_settings.hpp>
912
#include <godot_cpp/variant/callable.hpp>
1013

1114
using namespace godot;
@@ -101,19 +104,39 @@ String AndroidSDK::capture_event(const Ref<SentryEvent> &p_event) {
101104
return android_event->get_id();
102105
}
103106

107+
void AndroidSDK::add_attachment(const Ref<SentryAttachment> &p_attachment) {
108+
ERR_FAIL_COND(p_attachment.is_null());
109+
110+
if (p_attachment->get_path().is_empty()) {
111+
sentry::util::print_debug("attaching bytes with filename: ", p_attachment->get_filename());
112+
android_plugin->call(ANDROID_SN(addBytesAttachment),
113+
p_attachment->get_bytes(),
114+
p_attachment->get_filename(),
115+
p_attachment->get_content_type(),
116+
String());
117+
} else {
118+
String absolute_path = ProjectSettings::get_singleton()->globalize_path(p_attachment->get_path());
119+
sentry::util::print_debug("attaching file: ", absolute_path);
120+
android_plugin->call(ANDROID_SN(addFileAttachment),
121+
absolute_path,
122+
p_attachment->get_filename(),
123+
p_attachment->get_content_type(),
124+
String());
125+
}
126+
}
127+
104128
void AndroidSDK::initialize(const PackedStringArray &p_global_attachments) {
105129
ERR_FAIL_NULL(android_plugin);
106130

107131
sentry::util::print_debug("Initializing Sentry Android SDK");
108132

109133
for (const String &path : p_global_attachments) {
110-
String file = path.get_file();
111-
bool is_view_hierarchy = file == "view-hierarchy.json";
134+
bool is_view_hierarchy = path.ends_with(SENTRY_VIEW_HIERARCHY_FN);
112135
android_plugin->call(ANDROID_SN(addFileAttachment),
113136
path,
114-
file,
115-
is_view_hierarchy ? "application/json" : "",
116-
is_view_hierarchy ? "event.view_hierarchy" : "");
137+
String(), // filename
138+
is_view_hierarchy ? "application/json" : String(),
139+
is_view_hierarchy ? "event.view_hierarchy" : String());
117140
}
118141

119142
android_plugin->call("initialize",

0 commit comments

Comments
 (0)