Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

**Features**:

- Add attachment support to user feedback ([#1414](https://github.com/getsentry/sentry-native/pull/1414))

**Fixes**:

- 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))
Expand Down
29 changes: 29 additions & 0 deletions examples/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,35 @@ main(int argc, char **argv)

sentry_capture_feedback(user_feedback);
}
if (has_arg(argc, argv, "capture-user-feedback-with-attachment")) {
sentry_value_t user_feedback = sentry_value_new_feedback(
"some-message", "some-email", "some-name", NULL);

// Create a hint and attach both file and byte data
sentry_hint_t *hint = sentry_hint_new();

// Create a temporary file for the attachment
const char *attachment_path = ".sentry-test-feedback-attachment";
FILE *f = fopen(attachment_path, "w");
if (f) {
fprintf(f, "This is feedback attachment content");
fclose(f);
}

// Attach a file
sentry_hint_attach_file(hint, attachment_path);

// Attach bytes data (e.g., binary data from memory)
const char *binary_data = "binary attachment data";
sentry_hint_attach_bytes(
hint, binary_data, strlen(binary_data), "additional-info.txt");

// Capture feedback with attachments
sentry_capture_feedback_with_hint(user_feedback, hint);

// Clean up the temporary file
remove(attachment_path);
}
if (has_arg(argc, argv, "capture-user-report")) {
sentry_value_t event = sentry_value_new_message_event(
SENTRY_LEVEL_INFO, "my-logger", "Hello user feedback!");
Expand Down
65 changes: 65 additions & 0 deletions include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -2822,6 +2822,71 @@ SENTRY_API sentry_value_t sentry_value_new_feedback_n(const char *message,
*/
SENTRY_API void sentry_capture_feedback(sentry_value_t user_feedback);

/**
* A hint that can be passed to capture functions to provide additional context,
* such as attachments.
*/
struct sentry_hint_s;
typedef struct sentry_hint_s sentry_hint_t;

/**
* Creates a new hint to be passed into
* - `sentry_capture_feedback_with_hint`
*/
SENTRY_API sentry_hint_t *sentry_hint_new(void);

/**
* Attaches a file to a hint.
*
* The file will be read and sent when the event is captured.
* Returns a pointer to the attachment, or NULL on error.
*/
SENTRY_API sentry_attachment_t *sentry_hint_attach_file(
sentry_hint_t *hint, const char *path);
SENTRY_API sentry_attachment_t *sentry_hint_attach_file_n(
sentry_hint_t *hint, const char *path, size_t path_len);

/**
* Attaches bytes to a hint.
*
* The data is copied internally and will be sent when the event is captured.
* Returns a pointer to the attachment, or NULL on error.
*/
SENTRY_API sentry_attachment_t *sentry_hint_attach_bytes(
sentry_hint_t *hint, const char *buf, size_t buf_len, const char *filename);
SENTRY_API sentry_attachment_t *sentry_hint_attach_bytes_n(sentry_hint_t *hint,
const char *buf, size_t buf_len, const char *filename, size_t filename_len);

#ifdef SENTRY_PLATFORM_WINDOWS
/**
* Wide char version of `sentry_hint_attach_file`.
*/
SENTRY_API sentry_attachment_t *sentry_hint_attach_filew(
sentry_hint_t *hint, const wchar_t *path);
SENTRY_API sentry_attachment_t *sentry_hint_attach_filew_n(
sentry_hint_t *hint, const wchar_t *path, size_t path_len);

/**
* Wide char version of `sentry_hint_attach_bytes`.
*/
SENTRY_API sentry_attachment_t *sentry_hint_attach_bytesw(sentry_hint_t *hint,
const char *buf, size_t buf_len, const wchar_t *filename);
SENTRY_API sentry_attachment_t *sentry_hint_attach_bytesw_n(sentry_hint_t *hint,
const char *buf, size_t buf_len, const wchar_t *filename,
size_t filename_len);
#endif

/**
* Captures a manually created feedback with a hint and sends it to Sentry.
*
* This function takes ownership of both the feedback value and the hint,
* which will be freed automatically.
*
* The hint parameter can be NULL if no additional context is needed.
*/
SENTRY_API void sentry_capture_feedback_with_hint(
sentry_value_t user_feedback, sentry_hint_t *hint);

/**
* The status of a Span or Transaction.
*
Expand Down
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ sentry_target_sources_cwd(sentry
sentry_database.h
sentry_envelope.c
sentry_envelope.h
sentry_hint.c
sentry_hint.h
sentry_info.c
sentry_json.c
sentry_json.h
Expand Down
23 changes: 19 additions & 4 deletions src/sentry_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "sentry_core.h"
#include "sentry_database.h"
#include "sentry_envelope.h"
#include "sentry_hint.h"
#include "sentry_logs.h"
#include "sentry_options.h"
#include "sentry_path.h"
Expand Down Expand Up @@ -779,7 +780,7 @@ prepare_user_report(sentry_value_t user_report)
}

static sentry_envelope_t *
prepare_user_feedback(sentry_value_t user_feedback)
prepare_user_feedback(sentry_value_t user_feedback, sentry_hint_t *hint)
{
sentry_envelope_t *envelope = NULL;

Expand All @@ -789,6 +790,10 @@ prepare_user_feedback(sentry_value_t user_feedback)
goto fail;
}

if (hint && hint->attachments) {
sentry__envelope_add_attachments(envelope, hint->attachments);
}

return envelope;

fail:
Expand Down Expand Up @@ -1529,17 +1534,27 @@ sentry_capture_user_feedback(sentry_value_t user_report)

void
sentry_capture_feedback(sentry_value_t user_feedback)
{
// Reuse the implementation with NULL hint
sentry_capture_feedback_with_hint(user_feedback, NULL);
}

void
sentry_capture_feedback_with_hint(
sentry_value_t user_feedback, sentry_hint_t *hint)
{
sentry_envelope_t *envelope = NULL;

SENTRY_WITH_OPTIONS (options) {
envelope = prepare_user_feedback(user_feedback);
envelope = prepare_user_feedback(user_feedback, hint);
if (envelope) {
sentry__capture_envelope(options->transport, envelope);
} else {
sentry_value_decref(user_feedback);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this decref intentionally removed? In the happy path above, the envelope takes ownership of user_feedback, but if it failed to prepare an envelope, would user_feedback be leaked?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If prepare_user_feedback fails to create an envelope the user_feedback should be freed automatically:

fail:
SENTRY_WARN("dropping user feedback");
sentry_envelope_free(envelope);
sentry_value_decref(user_feedback);
return NULL;

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, so there's a bug in the existing implementation. good catch, and thanks for fixing it! 👍

}
}

if (hint) {
sentry__hint_free(hint);
}
}

bool
Expand Down
109 changes: 109 additions & 0 deletions src/sentry_hint.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#include "sentry_hint.h"

#include "sentry_alloc.h"
#include "sentry_attachment.h"
#include "sentry_path.h"
#include "sentry_string.h"

#include <string.h>

sentry_hint_t *
sentry_hint_new(void)
{
sentry_hint_t *hint = SENTRY_MAKE(sentry_hint_t);
if (!hint) {
return NULL;
}
memset(hint, 0, sizeof(sentry_hint_t));
return hint;
}

void
sentry__hint_free(sentry_hint_t *hint)
{
if (!hint) {
return;
}
sentry__attachments_free(hint->attachments);
sentry_free(hint);
}

sentry_attachment_t *
sentry_hint_attach_file(sentry_hint_t *hint, const char *path)
{
return sentry_hint_attach_file_n(hint, path, sentry__guarded_strlen(path));
}

sentry_attachment_t *
sentry_hint_attach_file_n(
sentry_hint_t *hint, const char *path, size_t path_len)
{
if (!hint) {
return NULL;
}
return sentry__attachments_add_path(&hint->attachments,
sentry__path_from_str_n(path, path_len), ATTACHMENT, NULL);
}

sentry_attachment_t *
sentry_hint_attach_bytes(
sentry_hint_t *hint, const char *buf, size_t buf_len, const char *filename)
{
return sentry_hint_attach_bytes_n(
hint, buf, buf_len, filename, sentry__guarded_strlen(filename));
}

sentry_attachment_t *
sentry_hint_attach_bytes_n(sentry_hint_t *hint, const char *buf, size_t buf_len,
const char *filename, size_t filename_len)
{
if (!hint) {
return NULL;
}
return sentry__attachments_add(&hint->attachments,
sentry__attachment_from_buffer(
buf, buf_len, sentry__path_from_str_n(filename, filename_len)),
ATTACHMENT, NULL);
}

#ifdef SENTRY_PLATFORM_WINDOWS
sentry_attachment_t *
sentry_hint_attach_filew(sentry_hint_t *hint, const wchar_t *path)
{
size_t path_len = path ? wcslen(path) : 0;
return sentry_hint_attach_filew_n(hint, path, path_len);
}

sentry_attachment_t *
sentry_hint_attach_filew_n(
sentry_hint_t *hint, const wchar_t *path, size_t path_len)
{
if (!hint) {
return NULL;
}
return sentry__attachments_add_path(&hint->attachments,
sentry__path_from_wstr_n(path, path_len), ATTACHMENT, NULL);
}

sentry_attachment_t *
sentry_hint_attach_bytesw(sentry_hint_t *hint, const char *buf, size_t buf_len,
const wchar_t *filename)
{
size_t filename_len = filename ? wcslen(filename) : 0;
return sentry_hint_attach_bytesw_n(
hint, buf, buf_len, filename, filename_len);
}

sentry_attachment_t *
sentry_hint_attach_bytesw_n(sentry_hint_t *hint, const char *buf,
size_t buf_len, const wchar_t *filename, size_t filename_len)
{
if (!hint) {
return NULL;
}
return sentry__attachments_add(&hint->attachments,
sentry__attachment_from_buffer(
buf, buf_len, sentry__path_from_wstr_n(filename, filename_len)),
ATTACHMENT, NULL);
}
#endif
19 changes: 19 additions & 0 deletions src/sentry_hint.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#ifndef SENTRY_HINT_H_INCLUDED
#define SENTRY_HINT_H_INCLUDED

#include "sentry_boot.h"

/**
* A sentry Hint used to pass additional data along with an event
* or feedback when it's being captured.
*/
struct sentry_hint_s {
sentry_attachment_t *attachments;
};

/**
* Frees a hint (internal use only).
*/
void sentry__hint_free(sentry_hint_t *hint);

#endif
33 changes: 33 additions & 0 deletions tests/test_integration_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,39 @@ def test_user_feedback_http(cmake, httpserver):
assert_user_feedback(envelope)


def test_user_feedback_with_attachments_http(cmake, httpserver):
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"})

httpserver.expect_request(
"/api/123456/envelope/",
headers={"x-sentry-auth": auth_header},
).respond_with_data("OK")
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))

run(
tmp_path,
"sentry_example",
["log", "capture-user-feedback-with-attachment"],
env=env,
)

assert len(httpserver.log) == 1
output = httpserver.log[0][0].get_data()
envelope = Envelope.deserialize(output)

# Verify the feedback is present
assert_user_feedback(envelope)

# Verify attachments are present
attachment_count = 0
for item in envelope:
if item.headers.get("type") == "attachment":
attachment_count += 1

# Should have 2 attachments (one file, one bytes)
assert attachment_count == 2


def test_user_report_http(cmake, httpserver):
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"})

Expand Down
1 change: 1 addition & 0 deletions tests/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ add_executable(sentry_test_unit
test_embedded_info.c
test_envelopes.c
test_failures.c
test_feedback.c
test_fuzzfailures.c
test_info.c
test_logger.c
Expand Down
Loading
Loading