Skip to content
Open
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
b23ef21
WIP: feat: feedback handler (squashed)
jpnurmi Jun 25, 2025
a7992df
WIP: transfer feedback envelope ownership
jpnurmi Jul 21, 2025
07e6553
crashpad
jpnurmi Jul 21, 2025
dfa6be9
update feedback.c
jpnurmi Jul 22, 2025
e962323
Drop sentry__envelope_get_event_id changes
jpnurmi Jul 22, 2025
f874017
clean/fix up
jpnurmi Jul 22, 2025
fbbad55
fix sentry__launch_feedback_handler
jpnurmi Jul 23, 2025
d35d178
Rename to Crash Reporter
jpnurmi Jul 23, 2025
9867efb
Bump crashpad
jpnurmi Jul 25, 2025
e52436b
bump crashpad
jpnurmi Jul 28, 2025
863ca69
bump crashpad
jpnurmi Jul 29, 2025
b7938d7
Update header docs
jpnurmi Jul 30, 2025
b6faa46
Update CHANGELOG.md
jpnurmi Jul 30, 2025
1753c54
Bump crashpad
jpnurmi Aug 5, 2025
792b447
Bump crashpad & keep __sentry-event
jpnurmi Aug 12, 2025
a1c3b05
Bump crashpad
jpnurmi Aug 12, 2025
891b17e
test_crashpad_crash_reporter: assert session and breadcrumbs
jpnurmi Aug 12, 2025
6c26861
Bump crashpad
jpnurmi Aug 12, 2025
97da455
Bump crashpad
jpnurmi Aug 12, 2025
ac23ae2
Bump crashpad again
jpnurmi Aug 12, 2025
1624daf
Drop the broken session test that only works on Linux
jpnurmi Aug 12, 2025
5681c61
Fix missing event ID header in Crashpad reports
jpnurmi Aug 13, 2025
1cfaab1
WIP: Clean up and rename to "external" crash reporter for clarity
jpnurmi Aug 13, 2025
f2ecaa7
Update docs
jpnurmi Aug 13, 2025
1562b32
Fix windows build
jpnurmi Aug 13, 2025
17ee224
Remove unused argument
jpnurmi Aug 13, 2025
a41d48a
Bump crashpad
jpnurmi Aug 13, 2025
d046c36
Merge remote-tracking branch 'upstream/master' into feat/feedback-han…
jpnurmi Aug 29, 2025
5b37d01
Bump crashpad
jpnurmi Aug 29, 2025
2e2b11e
Update CHANGELOG.md
jpnurmi Aug 29, 2025
eee2949
Fix memory leak / remove unused return value
jpnurmi Aug 29, 2025
906c884
Fix return value
jpnurmi Aug 29, 2025
69e969d
Add missing null-check
jpnurmi Aug 29, 2025
8776986
Return bool from sentry__launch_external_crash_reporter
jpnurmi Aug 29, 2025
c28f757
Check sentry_new_external_disk_transport return value
jpnurmi Aug 29, 2025
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

**Features**:

- Add `sentry_options_set_external_crash_reporter_path` to allow specifying an external crash reporter. ([#1303](https://github.com/getsentry/sentry-native/pull/1303))

## 0.10.0

**Breaking changes**:
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,7 @@ endif()

if(SENTRY_BUILD_TESTS)
add_subdirectory(tests/unit)
add_subdirectory(tests/fixtures/crash_reporter)
add_subdirectory(tests/fixtures/screenshot)
if(SENTRY_BUILD_BENCHMARKS)
set(BENCHMARK_ENABLE_TESTING OFF)
Expand Down
10 changes: 10 additions & 0 deletions examples/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,16 @@ main(int argc, char **argv)
sentry_options_add_view_hierarchy(options, "./view-hierarchy.json");
}

if (has_arg(argc, argv, "crash-reporter")) {
#ifdef SENTRY_PLATFORM_WINDOWS
sentry_options_set_external_crash_reporter_pathw(
options, L"sentry_crash_reporter.exe");
#else
sentry_options_set_external_crash_reporter_path(
options, "./sentry_crash_reporter");
#endif
}

sentry_init(options);

if (has_arg(argc, argv, "attachment")) {
Expand Down
33 changes: 33 additions & 0 deletions include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -1386,6 +1386,31 @@ SENTRY_API void sentry_options_set_handler_path(
SENTRY_API void sentry_options_set_handler_path_n(
sentry_options_t *opts, const char *path, size_t path_len);

/**
* Sets the path to an external crash reporter executable, which can be used to
* display crash information to the user, collect user feedback, or perform
* other actions when a crash occurs.
*
* The external crash reporter is a user-defined executable, distinct from the
* system crash reporter, that is launched by the Native SDK upon a crash. It
* receives the path to the crash report as its only command-line argument and
* is responsible for submitting the crash report to Sentry. If using the Native
* SDK, this can be done using the `sentry_capture_envelope` function.
*
* A well-behaving external crash reporter should delete the crash report
* after handling it. As a safeguard, the Native SDK automatically removes
* crash reports older than one hour on startup to prevent filling up the disk
* with stale crash reports.
*
* The `path` parameter should use platform-specific filesystem encoding.
* On Windows, API users are encouraged to use
* `sentry_options_set_external_crash_reporter_pathw` instead.
*/
SENTRY_API void sentry_options_set_external_crash_reporter_path(
sentry_options_t *opts, const char *path);
SENTRY_API void sentry_options_set_external_crash_reporter_path_n(
sentry_options_t *opts, const char *path, size_t path_len);

/**
* Sets the path to the Sentry Database Directory.
*
Expand Down Expand Up @@ -1445,6 +1470,14 @@ SENTRY_API void sentry_options_set_handler_pathw(
SENTRY_API void sentry_options_set_handler_pathw_n(
sentry_options_t *opts, const wchar_t *path, size_t path_len);

/**
* Wide char version of `sentry_options_set_external_crash_reporter_path`.
*/
SENTRY_API void sentry_options_set_external_crash_reporter_pathw(
sentry_options_t *opts, const wchar_t *path);
SENTRY_API void sentry_options_set_external_crash_reporter_pathw_n(
sentry_options_t *opts, const wchar_t *path, size_t path_len);

/**
* Wide char version of `sentry_options_set_database_path`.
*/
Expand Down
14 changes: 8 additions & 6 deletions src/backends/sentry_backend_breakpad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,14 @@ breakpad_backend_callback(const google_breakpad::MinidumpDescriptor &descriptor,
sentry__attachment_free(screenshot);
}

// capture the envelope with the disk transport
sentry_transport_t *disk_transport
= sentry_new_disk_transport(options->run);
sentry__capture_envelope(disk_transport, envelope);
sentry__transport_dump_queue(disk_transport, options->run);
sentry_transport_free(disk_transport);
if (!sentry__launch_external_crash_reporter(envelope)) {
// capture the envelope with the disk transport
sentry_transport_t *disk_transport
= sentry_new_disk_transport(options->run);
sentry__capture_envelope(disk_transport, envelope);
sentry__transport_dump_queue(disk_transport, options->run);
sentry_transport_free(disk_transport);
}

// now that the envelope was written, we can remove the temporary
// minidump file
Expand Down
52 changes: 47 additions & 5 deletions src/backends/sentry_backend_crashpad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ typedef struct {
sentry_path_t *event_path;
sentry_path_t *breadcrumb1_path;
sentry_path_t *breadcrumb2_path;
sentry_path_t *external_report_path;
size_t num_breadcrumbs;
std::atomic<bool> crashed;
std::atomic<bool> scope_flush;
Expand Down Expand Up @@ -187,7 +188,7 @@ crashpad_register_wer_module(
#endif

static void
crashpad_backend_flush_scope_to_event(const sentry_path_t *event_path,
flush_scope_to_event(const sentry_path_t *event_path,
const sentry_options_t *options, sentry_value_t crash_event)
{
SENTRY_WITH_SCOPE (scope) {
Expand All @@ -211,6 +212,25 @@ crashpad_backend_flush_scope_to_event(const sentry_path_t *event_path,
}
}

// Prepares an envelope with DSN, event ID, and session if available, for an
// external crash reporter.
static void
Copy link
Collaborator

Choose a reason for hiding this comment

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

It makes sense to have a comment here that clarifies the difference between a report and an event (which is mainly about serialization format if one only considers the differences inside the two flush functions), since this nomenclature difference appears primarily in the crashpad backend.

Copy link
Collaborator

@supervacuus supervacuus Aug 11, 2025

Choose a reason for hiding this comment

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

Just to be clear: I am primarily asking this because you once raised the topic of having the "reporter" executable configured (which might be called as early as possible) vs exposing a callback (from which a user explicitly spawns the reporter executable) vs having a callback that runs during the next start.

Now, with this change, if you have a backend that can't upload during the crash (i.e., which doesn't have an upload process like the crashpad_handler), enabling a "crash reporter" could be used to bypass this limitation, since that executable could be configured to run, load, and send the crash envelope even without user-feedback interaction, right?

flush_external_crash_report(
const sentry_options_t *options, const sentry_uuid_t *crash_event_id)
{
sentry_envelope_t *envelope = sentry__envelope_new();
if (!envelope) {
return;
}
sentry__envelope_set_event_id(envelope, crash_event_id);
if (options->session) {
sentry__envelope_add_session(envelope, options->session);
}

sentry__run_write_external(options->run, envelope);
sentry_envelope_free(envelope);
}

// This function is necessary for macOS since it has no `FirstChanceHandler`.
// but it is also necessary on Windows if the WER handler is enabled.
// This means we have to continuously flush the scope on
Expand Down Expand Up @@ -243,7 +263,10 @@ crashpad_backend_flush_scope(
sentry_value_set_by_key(
event, "level", sentry__value_new_level(SENTRY_LEVEL_FATAL));

crashpad_backend_flush_scope_to_event(data->event_path, options, event);
flush_scope_to_event(data->event_path, options, event);
if (data->external_report_path) {
flush_external_crash_report(options, &data->crash_event_id);
}
data->scope_flush.store(false, std::memory_order_release);
#endif
}
Expand All @@ -266,8 +289,10 @@ flush_scope_from_handler(
}

// now we are the sole flusher and can flush into the crash event
crashpad_backend_flush_scope_to_event(
state->event_path, options, crash_event);
flush_scope_to_event(state->event_path, options, crash_event);
if (state->external_report_path) {
flush_external_crash_report(options, &state->crash_event_id);
}
}

# ifdef SENTRY_PLATFORM_WINDOWS
Expand Down Expand Up @@ -465,6 +490,22 @@ crashpad_backend_startup(
sentry__path_free(screenshot_path);
}

base::FilePath crash_reporter;
base::FilePath crash_envelope;
if (options->external_crash_reporter) {
char *filename
= sentry__uuid_as_filename(&data->crash_event_id, ".envelope");
data->external_report_path
= sentry__path_join_str(options->run->external_path, filename);
sentry_free(filename);

if (data->external_report_path) {
crash_reporter
= base::FilePath(options->external_crash_reporter->path);
crash_envelope = base::FilePath(data->external_report_path->path);
}
}

std::vector<std::string> arguments { "--no-rate-limit" };

// Initialize database first, flushing the consent later on as part of
Expand Down Expand Up @@ -492,7 +533,7 @@ crashpad_backend_startup(
minidump_url ? minidump_url : "", proxy_url, annotations, arguments,
/* restartable */ true,
/* asynchronous_start */ false, attachments, screenshot,
options->crashpad_wait_for_upload);
options->crashpad_wait_for_upload, crash_reporter, crash_envelope);
sentry_free(minidump_url);

#ifdef SENTRY_PLATFORM_WINDOWS
Expand Down Expand Up @@ -605,6 +646,7 @@ crashpad_backend_free(sentry_backend_t *backend)
sentry__path_free(data->event_path);
sentry__path_free(data->breadcrumb1_path);
sentry__path_free(data->breadcrumb2_path);
sentry__path_free(data->external_report_path);
sentry_free(data);
}

Expand Down
14 changes: 8 additions & 6 deletions src/backends/sentry_backend_inproc.c
Original file line number Diff line number Diff line change
Expand Up @@ -608,12 +608,14 @@ handle_ucontext(const sentry_ucontext_t *uctx)
sentry__attachment_free(screenshot);
}

// capture the envelope with the disk transport
sentry_transport_t *disk_transport
= sentry_new_disk_transport(options->run);
sentry__capture_envelope(disk_transport, envelope);
sentry__transport_dump_queue(disk_transport, options->run);
sentry_transport_free(disk_transport);
if (!sentry__launch_external_crash_reporter(envelope)) {
// capture the envelope with the disk transport
sentry_transport_t *disk_transport
= sentry_new_disk_transport(options->run);
sentry__capture_envelope(disk_transport, envelope);
sentry__transport_dump_queue(disk_transport, options->run);
sentry_transport_free(disk_transport);
}
} else {
SENTRY_DEBUG("event was discarded by the `on_crash` hook");
sentry_value_decref(event);
Expand Down
45 changes: 45 additions & 0 deletions src/sentry_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@
#include "sentry_envelope.h"
#include "sentry_options.h"
#include "sentry_path.h"
#include "sentry_process.h"
#include "sentry_random.h"
#include "sentry_scope.h"
#include "sentry_session.h"
#include "sentry_string.h"
#include "sentry_sync.h"
#include "sentry_tracing.h"
#include "sentry_transport.h"
#include "sentry_uuid.h"
#include "sentry_value.h"
#include "transports/sentry_disk_transport.h"

#ifdef SENTRY_PLATFORM_WINDOWS
# include "sentry_os.h"
Expand Down Expand Up @@ -1462,6 +1465,48 @@ sentry_capture_feedback(sentry_value_t user_feedback)
}
}

bool
sentry__launch_external_crash_reporter(sentry_envelope_t *envelope)
{
SENTRY_WITH_OPTIONS (options) {
if (!options->external_crash_reporter) {
return false;
}

sentry_uuid_t event_id = sentry__envelope_get_event_id(envelope);
char *envelope_filename
= sentry__uuid_as_filename(&event_id, ".envelope");
if (!envelope_filename) {
return false;
}

sentry_path_t *report_path = sentry__path_join_str(
options->run->external_path, envelope_filename);
if (!report_path) {
sentry_free(envelope_filename);
return false;
}

// capture the envelope with the disk transport
sentry_transport_t *disk_transport
= sentry_new_external_disk_transport(options->run);
if (!disk_transport) {
sentry__path_free(report_path);
sentry_free(envelope_filename);
return false;
}
sentry__capture_envelope(disk_transport, envelope);
sentry__transport_dump_queue(disk_transport, options->run);
sentry_transport_free(disk_transport);

sentry__process_spawn(
options->external_crash_reporter, report_path->path, NULL);
sentry__path_free(report_path);
sentry_free(envelope_filename);
}
return true;
}

int
sentry_get_crashed_last_run(void)
{
Expand Down
2 changes: 2 additions & 0 deletions src/sentry_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ void sentry__options_unlock(void);

void sentry__set_propagation_context(const char *key, sentry_value_t value);

bool sentry__launch_external_crash_reporter(sentry_envelope_t *envelope);

#define SENTRY_WITH_OPTIONS(Options) \
for (const sentry_options_t *Options = sentry__options_getref(); Options; \
sentry_options_free((sentry_options_t *)Options), Options = NULL)
Expand Down
Loading
Loading