Skip to content

Commit ab723c3

Browse files
feat: Add attachment support to user feedback (getsentry#1414)
* add attachment support for user feedback via hints * Add tests * Add new files to cmake * Clean up * Add more tests * Add missing empty line at the end of file * Update changelog * Fix double decref * Clean up comment * Fix tests * Rename feedback-specific hint type to generic one * Fix formatting * Fix changelog * Rename * Formatting * Lint * Update include/sentry.h Co-authored-by: JoshuaMoelans <60878493+JoshuaMoelans@users.noreply.github.com> --------- Co-authored-by: JoshuaMoelans <60878493+JoshuaMoelans@users.noreply.github.com>
1 parent a61944c commit ab723c3

11 files changed

Lines changed: 501 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## 0.12.4
44

5+
**Features**:
6+
7+
- Add attachment support to user feedback ([#1414](https://github.com/getsentry/sentry-native/pull/1414))
8+
59
**Fixes**:
610

711
- Crashpad: namespace mpack to avoid ODR violation. ([#1476](https://github.com/getsentry/sentry-native/pull/1476), [crashpad#143](https://github.com/getsentry/crashpad/pull/143))

examples/example.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,35 @@ main(int argc, char **argv)
781781

782782
sentry_capture_feedback(user_feedback);
783783
}
784+
if (has_arg(argc, argv, "capture-user-feedback-with-attachment")) {
785+
sentry_value_t user_feedback = sentry_value_new_feedback(
786+
"some-message", "some-email", "some-name", NULL);
787+
788+
// Create a hint and attach both file and byte data
789+
sentry_hint_t *hint = sentry_hint_new();
790+
791+
// Create a temporary file for the attachment
792+
const char *attachment_path = ".sentry-test-feedback-attachment";
793+
FILE *f = fopen(attachment_path, "w");
794+
if (f) {
795+
fprintf(f, "This is feedback attachment content");
796+
fclose(f);
797+
}
798+
799+
// Attach a file
800+
sentry_hint_attach_file(hint, attachment_path);
801+
802+
// Attach bytes data (e.g., binary data from memory)
803+
const char *binary_data = "binary attachment data";
804+
sentry_hint_attach_bytes(
805+
hint, binary_data, strlen(binary_data), "additional-info.txt");
806+
807+
// Capture feedback with attachments
808+
sentry_capture_feedback_with_hint(user_feedback, hint);
809+
810+
// Clean up the temporary file
811+
remove(attachment_path);
812+
}
784813
if (has_arg(argc, argv, "capture-user-report")) {
785814
sentry_value_t event = sentry_value_new_message_event(
786815
SENTRY_LEVEL_INFO, "my-logger", "Hello user feedback!");

include/sentry.h

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2822,6 +2822,71 @@ SENTRY_API sentry_value_t sentry_value_new_feedback_n(const char *message,
28222822
*/
28232823
SENTRY_API void sentry_capture_feedback(sentry_value_t user_feedback);
28242824

2825+
/**
2826+
* A hint that can be passed to capture functions to provide additional context,
2827+
* such as attachments.
2828+
*/
2829+
struct sentry_hint_s;
2830+
typedef struct sentry_hint_s sentry_hint_t;
2831+
2832+
/**
2833+
* Creates a new hint to be passed into
2834+
* - `sentry_capture_feedback_with_hint`
2835+
*/
2836+
SENTRY_API sentry_hint_t *sentry_hint_new(void);
2837+
2838+
/**
2839+
* Attaches a file to a hint.
2840+
*
2841+
* The file will be read and sent when the event is captured.
2842+
* Returns a pointer to the attachment, or NULL on error.
2843+
*/
2844+
SENTRY_API sentry_attachment_t *sentry_hint_attach_file(
2845+
sentry_hint_t *hint, const char *path);
2846+
SENTRY_API sentry_attachment_t *sentry_hint_attach_file_n(
2847+
sentry_hint_t *hint, const char *path, size_t path_len);
2848+
2849+
/**
2850+
* Attaches bytes to a hint.
2851+
*
2852+
* The data is copied internally and will be sent when the event is captured.
2853+
* Returns a pointer to the attachment, or NULL on error.
2854+
*/
2855+
SENTRY_API sentry_attachment_t *sentry_hint_attach_bytes(
2856+
sentry_hint_t *hint, const char *buf, size_t buf_len, const char *filename);
2857+
SENTRY_API sentry_attachment_t *sentry_hint_attach_bytes_n(sentry_hint_t *hint,
2858+
const char *buf, size_t buf_len, const char *filename, size_t filename_len);
2859+
2860+
#ifdef SENTRY_PLATFORM_WINDOWS
2861+
/**
2862+
* Wide char version of `sentry_hint_attach_file`.
2863+
*/
2864+
SENTRY_API sentry_attachment_t *sentry_hint_attach_filew(
2865+
sentry_hint_t *hint, const wchar_t *path);
2866+
SENTRY_API sentry_attachment_t *sentry_hint_attach_filew_n(
2867+
sentry_hint_t *hint, const wchar_t *path, size_t path_len);
2868+
2869+
/**
2870+
* Wide char version of `sentry_hint_attach_bytes`.
2871+
*/
2872+
SENTRY_API sentry_attachment_t *sentry_hint_attach_bytesw(sentry_hint_t *hint,
2873+
const char *buf, size_t buf_len, const wchar_t *filename);
2874+
SENTRY_API sentry_attachment_t *sentry_hint_attach_bytesw_n(sentry_hint_t *hint,
2875+
const char *buf, size_t buf_len, const wchar_t *filename,
2876+
size_t filename_len);
2877+
#endif
2878+
2879+
/**
2880+
* Captures a manually created feedback with a hint and sends it to Sentry.
2881+
*
2882+
* This function takes ownership of both the feedback value and the hint,
2883+
* which will be freed automatically.
2884+
*
2885+
* The hint parameter can be NULL if no additional context is needed.
2886+
*/
2887+
SENTRY_API void sentry_capture_feedback_with_hint(
2888+
sentry_value_t user_feedback, sentry_hint_t *hint);
2889+
28252890
/**
28262891
* The status of a Span or Transaction.
28272892
*

src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ sentry_target_sources_cwd(sentry
1515
sentry_database.h
1616
sentry_envelope.c
1717
sentry_envelope.h
18+
sentry_hint.c
19+
sentry_hint.h
1820
sentry_info.c
1921
sentry_json.c
2022
sentry_json.h

src/sentry_core.c

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "sentry_core.h"
99
#include "sentry_database.h"
1010
#include "sentry_envelope.h"
11+
#include "sentry_hint.h"
1112
#include "sentry_logs.h"
1213
#include "sentry_options.h"
1314
#include "sentry_path.h"
@@ -779,7 +780,7 @@ prepare_user_report(sentry_value_t user_report)
779780
}
780781

781782
static sentry_envelope_t *
782-
prepare_user_feedback(sentry_value_t user_feedback)
783+
prepare_user_feedback(sentry_value_t user_feedback, sentry_hint_t *hint)
783784
{
784785
sentry_envelope_t *envelope = NULL;
785786

@@ -789,6 +790,10 @@ prepare_user_feedback(sentry_value_t user_feedback)
789790
goto fail;
790791
}
791792

793+
if (hint && hint->attachments) {
794+
sentry__envelope_add_attachments(envelope, hint->attachments);
795+
}
796+
792797
return envelope;
793798

794799
fail:
@@ -1555,17 +1560,27 @@ sentry_capture_user_feedback(sentry_value_t user_report)
15551560

15561561
void
15571562
sentry_capture_feedback(sentry_value_t user_feedback)
1563+
{
1564+
// Reuse the implementation with NULL hint
1565+
sentry_capture_feedback_with_hint(user_feedback, NULL);
1566+
}
1567+
1568+
void
1569+
sentry_capture_feedback_with_hint(
1570+
sentry_value_t user_feedback, sentry_hint_t *hint)
15581571
{
15591572
sentry_envelope_t *envelope = NULL;
15601573

15611574
SENTRY_WITH_OPTIONS (options) {
1562-
envelope = prepare_user_feedback(user_feedback);
1575+
envelope = prepare_user_feedback(user_feedback, hint);
15631576
if (envelope) {
15641577
sentry__capture_envelope(options->transport, envelope);
1565-
} else {
1566-
sentry_value_decref(user_feedback);
15671578
}
15681579
}
1580+
1581+
if (hint) {
1582+
sentry__hint_free(hint);
1583+
}
15691584
}
15701585

15711586
bool

src/sentry_hint.c

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#include "sentry_hint.h"
2+
3+
#include "sentry_alloc.h"
4+
#include "sentry_attachment.h"
5+
#include "sentry_path.h"
6+
#include "sentry_string.h"
7+
8+
#include <string.h>
9+
10+
sentry_hint_t *
11+
sentry_hint_new(void)
12+
{
13+
sentry_hint_t *hint = SENTRY_MAKE(sentry_hint_t);
14+
if (!hint) {
15+
return NULL;
16+
}
17+
memset(hint, 0, sizeof(sentry_hint_t));
18+
return hint;
19+
}
20+
21+
void
22+
sentry__hint_free(sentry_hint_t *hint)
23+
{
24+
if (!hint) {
25+
return;
26+
}
27+
sentry__attachments_free(hint->attachments);
28+
sentry_free(hint);
29+
}
30+
31+
sentry_attachment_t *
32+
sentry_hint_attach_file(sentry_hint_t *hint, const char *path)
33+
{
34+
return sentry_hint_attach_file_n(hint, path, sentry__guarded_strlen(path));
35+
}
36+
37+
sentry_attachment_t *
38+
sentry_hint_attach_file_n(
39+
sentry_hint_t *hint, const char *path, size_t path_len)
40+
{
41+
if (!hint) {
42+
return NULL;
43+
}
44+
return sentry__attachments_add_path(&hint->attachments,
45+
sentry__path_from_str_n(path, path_len), ATTACHMENT, NULL);
46+
}
47+
48+
sentry_attachment_t *
49+
sentry_hint_attach_bytes(
50+
sentry_hint_t *hint, const char *buf, size_t buf_len, const char *filename)
51+
{
52+
return sentry_hint_attach_bytes_n(
53+
hint, buf, buf_len, filename, sentry__guarded_strlen(filename));
54+
}
55+
56+
sentry_attachment_t *
57+
sentry_hint_attach_bytes_n(sentry_hint_t *hint, const char *buf, size_t buf_len,
58+
const char *filename, size_t filename_len)
59+
{
60+
if (!hint) {
61+
return NULL;
62+
}
63+
return sentry__attachments_add(&hint->attachments,
64+
sentry__attachment_from_buffer(
65+
buf, buf_len, sentry__path_from_str_n(filename, filename_len)),
66+
ATTACHMENT, NULL);
67+
}
68+
69+
#ifdef SENTRY_PLATFORM_WINDOWS
70+
sentry_attachment_t *
71+
sentry_hint_attach_filew(sentry_hint_t *hint, const wchar_t *path)
72+
{
73+
size_t path_len = path ? wcslen(path) : 0;
74+
return sentry_hint_attach_filew_n(hint, path, path_len);
75+
}
76+
77+
sentry_attachment_t *
78+
sentry_hint_attach_filew_n(
79+
sentry_hint_t *hint, const wchar_t *path, size_t path_len)
80+
{
81+
if (!hint) {
82+
return NULL;
83+
}
84+
return sentry__attachments_add_path(&hint->attachments,
85+
sentry__path_from_wstr_n(path, path_len), ATTACHMENT, NULL);
86+
}
87+
88+
sentry_attachment_t *
89+
sentry_hint_attach_bytesw(sentry_hint_t *hint, const char *buf, size_t buf_len,
90+
const wchar_t *filename)
91+
{
92+
size_t filename_len = filename ? wcslen(filename) : 0;
93+
return sentry_hint_attach_bytesw_n(
94+
hint, buf, buf_len, filename, filename_len);
95+
}
96+
97+
sentry_attachment_t *
98+
sentry_hint_attach_bytesw_n(sentry_hint_t *hint, const char *buf,
99+
size_t buf_len, const wchar_t *filename, size_t filename_len)
100+
{
101+
if (!hint) {
102+
return NULL;
103+
}
104+
return sentry__attachments_add(&hint->attachments,
105+
sentry__attachment_from_buffer(
106+
buf, buf_len, sentry__path_from_wstr_n(filename, filename_len)),
107+
ATTACHMENT, NULL);
108+
}
109+
#endif

src/sentry_hint.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#ifndef SENTRY_HINT_H_INCLUDED
2+
#define SENTRY_HINT_H_INCLUDED
3+
4+
#include "sentry_boot.h"
5+
6+
/**
7+
* A sentry Hint used to pass additional data along with an event
8+
* or feedback when it's being captured.
9+
*/
10+
struct sentry_hint_s {
11+
sentry_attachment_t *attachments;
12+
};
13+
14+
/**
15+
* Frees a hint (internal use only).
16+
*/
17+
void sentry__hint_free(sentry_hint_t *hint);
18+
19+
#endif

tests/test_integration_http.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,39 @@ def test_user_feedback_http(cmake, httpserver):
177177
assert_user_feedback(envelope)
178178

179179

180+
def test_user_feedback_with_attachments_http(cmake, httpserver):
181+
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"})
182+
183+
httpserver.expect_request(
184+
"/api/123456/envelope/",
185+
headers={"x-sentry-auth": auth_header},
186+
).respond_with_data("OK")
187+
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))
188+
189+
run(
190+
tmp_path,
191+
"sentry_example",
192+
["log", "capture-user-feedback-with-attachment"],
193+
env=env,
194+
)
195+
196+
assert len(httpserver.log) == 1
197+
output = httpserver.log[0][0].get_data()
198+
envelope = Envelope.deserialize(output)
199+
200+
# Verify the feedback is present
201+
assert_user_feedback(envelope)
202+
203+
# Verify attachments are present
204+
attachment_count = 0
205+
for item in envelope:
206+
if item.headers.get("type") == "attachment":
207+
attachment_count += 1
208+
209+
# Should have 2 attachments (one file, one bytes)
210+
assert attachment_count == 2
211+
212+
180213
def test_user_report_http(cmake, httpserver):
181214
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"})
182215

tests/unit/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ add_executable(sentry_test_unit
2828
test_embedded_info.c
2929
test_envelopes.c
3030
test_failures.c
31+
test_feedback.c
3132
test_fuzzfailures.c
3233
test_info.c
3334
test_logger.c

0 commit comments

Comments
 (0)