Skip to content

Commit b58c786

Browse files
authored
feat: external crash reporter (#1303)
1 parent 5d60bff commit b58c786

24 files changed

+527
-41
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
**Features**:
66

77
- Add support for outgoing W3C traceparent header propagation with the `propagate_traceparent` option. ([#1394](https://github.com/getsentry/sentry-native/pull/1394))
8+
- Add `sentry_options_set_external_crash_reporter_path` to allow specifying an external crash reporter. ([#1303](https://github.com/getsentry/sentry-native/pull/1303))
89

910
**Fixes**:
1011

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,7 @@ endif()
751751

752752
if(SENTRY_BUILD_TESTS)
753753
add_subdirectory(tests/unit)
754+
add_subdirectory(tests/fixtures/crash_reporter)
754755
add_subdirectory(tests/fixtures/screenshot)
755756
if(SENTRY_BUILD_BENCHMARKS)
756757
set(BENCHMARK_ENABLE_TESTING OFF)

examples/example.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,16 @@ main(int argc, char **argv)
494494
sentry_options_set_enable_logs(options, true);
495495
}
496496

497+
if (has_arg(argc, argv, "crash-reporter")) {
498+
#ifdef SENTRY_PLATFORM_WINDOWS
499+
sentry_options_set_external_crash_reporter_pathw(
500+
options, L"sentry_crash_reporter.exe");
501+
#else
502+
sentry_options_set_external_crash_reporter_path(
503+
options, "./sentry_crash_reporter");
504+
#endif
505+
}
506+
497507
if (0 != sentry_init(options)) {
498508
return EXIT_FAILURE;
499509
}

include/sentry.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,6 +1396,31 @@ SENTRY_API void sentry_options_set_handler_path(
13961396
SENTRY_API void sentry_options_set_handler_path_n(
13971397
sentry_options_t *opts, const char *path, size_t path_len);
13981398

1399+
/**
1400+
* Sets the path to an external crash reporter executable, which can be used to
1401+
* display crash information to the user, collect user feedback, or perform
1402+
* other actions when a crash occurs.
1403+
*
1404+
* The external crash reporter is a user-defined executable, distinct from the
1405+
* system crash reporter, that is launched by the Native SDK upon a crash. It
1406+
* receives the path to the crash report as its only command-line argument and
1407+
* is responsible for submitting the crash report to Sentry. If using the Native
1408+
* SDK, this can be done using the `sentry_capture_envelope` function.
1409+
*
1410+
* A well-behaving external crash reporter should delete the crash report
1411+
* after handling it. As a safeguard, the Native SDK automatically removes
1412+
* crash reports older than one hour on startup to prevent filling up the disk
1413+
* with stale crash reports.
1414+
*
1415+
* The `path` parameter should use platform-specific filesystem encoding.
1416+
* On Windows, API users are encouraged to use
1417+
* `sentry_options_set_external_crash_reporter_pathw` instead.
1418+
*/
1419+
SENTRY_API void sentry_options_set_external_crash_reporter_path(
1420+
sentry_options_t *opts, const char *path);
1421+
SENTRY_API void sentry_options_set_external_crash_reporter_path_n(
1422+
sentry_options_t *opts, const char *path, size_t path_len);
1423+
13991424
/**
14001425
* Sets the path to the Sentry Database Directory.
14011426
*
@@ -1455,6 +1480,14 @@ SENTRY_API void sentry_options_set_handler_pathw(
14551480
SENTRY_API void sentry_options_set_handler_pathw_n(
14561481
sentry_options_t *opts, const wchar_t *path, size_t path_len);
14571482

1483+
/**
1484+
* Wide char version of `sentry_options_set_external_crash_reporter_path`.
1485+
*/
1486+
SENTRY_API void sentry_options_set_external_crash_reporter_pathw(
1487+
sentry_options_t *opts, const wchar_t *path);
1488+
SENTRY_API void sentry_options_set_external_crash_reporter_pathw_n(
1489+
sentry_options_t *opts, const wchar_t *path, size_t path_len);
1490+
14581491
/**
14591492
* Wide char version of `sentry_options_set_database_path`.
14601493
*/

src/backends/sentry_backend_breakpad.cpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -184,12 +184,14 @@ breakpad_backend_callback(const google_breakpad::MinidumpDescriptor &descriptor,
184184
sentry__attachment_free(screenshot);
185185
}
186186

187-
// capture the envelope with the disk transport
188-
sentry_transport_t *disk_transport
189-
= sentry_new_disk_transport(options->run);
190-
sentry__capture_envelope(disk_transport, envelope);
191-
sentry__transport_dump_queue(disk_transport, options->run);
192-
sentry_transport_free(disk_transport);
187+
if (!sentry__launch_external_crash_reporter(envelope)) {
188+
// capture the envelope with the disk transport
189+
sentry_transport_t *disk_transport
190+
= sentry_new_disk_transport(options->run);
191+
sentry__capture_envelope(disk_transport, envelope);
192+
sentry__transport_dump_queue(disk_transport, options->run);
193+
sentry_transport_free(disk_transport);
194+
}
193195

194196
// now that the envelope was written, we can remove the temporary
195197
// minidump file

src/backends/sentry_backend_crashpad.cpp

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ typedef struct {
116116
sentry_path_t *event_path;
117117
sentry_path_t *breadcrumb1_path;
118118
sentry_path_t *breadcrumb2_path;
119+
sentry_path_t *external_report_path;
119120
size_t num_breadcrumbs;
120121
std::atomic<bool> crashed;
121122
std::atomic<bool> scope_flush;
@@ -188,7 +189,7 @@ crashpad_register_wer_module(
188189
#endif
189190

190191
static void
191-
crashpad_backend_flush_scope_to_event(const sentry_path_t *event_path,
192+
flush_scope_to_event(const sentry_path_t *event_path,
192193
const sentry_options_t *options, sentry_value_t crash_event)
193194
{
194195
SENTRY_WITH_SCOPE (scope) {
@@ -212,6 +213,25 @@ crashpad_backend_flush_scope_to_event(const sentry_path_t *event_path,
212213
}
213214
}
214215

216+
// Prepares an envelope with DSN, event ID, and session if available, for an
217+
// external crash reporter.
218+
static void
219+
flush_external_crash_report(
220+
const sentry_options_t *options, const sentry_uuid_t *crash_event_id)
221+
{
222+
sentry_envelope_t *envelope = sentry__envelope_new();
223+
if (!envelope) {
224+
return;
225+
}
226+
sentry__envelope_set_event_id(envelope, crash_event_id);
227+
if (options->session) {
228+
sentry__envelope_add_session(envelope, options->session);
229+
}
230+
231+
sentry__run_write_external(options->run, envelope);
232+
sentry_envelope_free(envelope);
233+
}
234+
215235
// This function is necessary for macOS since it has no `FirstChanceHandler`.
216236
// but it is also necessary on Windows if the WER handler is enabled.
217237
// This means we have to continuously flush the scope on
@@ -244,7 +264,10 @@ crashpad_backend_flush_scope(
244264
sentry_value_set_by_key(
245265
event, "level", sentry__value_new_level(SENTRY_LEVEL_FATAL));
246266

247-
crashpad_backend_flush_scope_to_event(data->event_path, options, event);
267+
flush_scope_to_event(data->event_path, options, event);
268+
if (data->external_report_path) {
269+
flush_external_crash_report(options, &data->crash_event_id);
270+
}
248271
data->scope_flush.store(false, std::memory_order_release);
249272
#endif
250273
}
@@ -267,8 +290,10 @@ flush_scope_from_handler(
267290
}
268291

269292
// now we are the sole flusher and can flush into the crash event
270-
crashpad_backend_flush_scope_to_event(
271-
state->event_path, options, crash_event);
293+
flush_scope_to_event(state->event_path, options, crash_event);
294+
if (state->external_report_path) {
295+
flush_external_crash_report(options, &state->crash_event_id);
296+
}
272297
}
273298

274299
# ifdef SENTRY_PLATFORM_WINDOWS
@@ -474,6 +499,22 @@ crashpad_backend_startup(
474499
sentry__path_free(screenshot_path);
475500
}
476501

502+
base::FilePath crash_reporter;
503+
base::FilePath crash_envelope;
504+
if (options->external_crash_reporter) {
505+
char *filename
506+
= sentry__uuid_as_filename(&data->crash_event_id, ".envelope");
507+
data->external_report_path
508+
= sentry__path_join_str(options->run->external_path, filename);
509+
sentry_free(filename);
510+
511+
if (data->external_report_path) {
512+
crash_reporter
513+
= base::FilePath(options->external_crash_reporter->path);
514+
crash_envelope = base::FilePath(data->external_report_path->path);
515+
}
516+
}
517+
477518
std::vector<std::string> arguments { "--no-rate-limit" };
478519

479520
// Initialize database first, flushing the consent later on as part of
@@ -501,7 +542,7 @@ crashpad_backend_startup(
501542
minidump_url ? minidump_url : "", proxy_url, annotations, arguments,
502543
/* restartable */ true,
503544
/* asynchronous_start */ false, attachments, screenshot,
504-
options->crashpad_wait_for_upload);
545+
options->crashpad_wait_for_upload, crash_reporter, crash_envelope);
505546
sentry_free(minidump_url);
506547

507548
#ifdef SENTRY_PLATFORM_WINDOWS
@@ -614,6 +655,7 @@ crashpad_backend_free(sentry_backend_t *backend)
614655
sentry__path_free(data->event_path);
615656
sentry__path_free(data->breadcrumb1_path);
616657
sentry__path_free(data->breadcrumb2_path);
658+
sentry__path_free(data->external_report_path);
617659
sentry_free(data);
618660
}
619661

src/backends/sentry_backend_inproc.c

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -616,12 +616,14 @@ handle_ucontext(const sentry_ucontext_t *uctx)
616616
sentry__attachment_free(screenshot);
617617
}
618618

619-
// capture the envelope with the disk transport
620-
sentry_transport_t *disk_transport
621-
= sentry_new_disk_transport(options->run);
622-
sentry__capture_envelope(disk_transport, envelope);
623-
sentry__transport_dump_queue(disk_transport, options->run);
624-
sentry_transport_free(disk_transport);
619+
if (!sentry__launch_external_crash_reporter(envelope)) {
620+
// capture the envelope with the disk transport
621+
sentry_transport_t *disk_transport
622+
= sentry_new_disk_transport(options->run);
623+
sentry__capture_envelope(disk_transport, envelope);
624+
sentry__transport_dump_queue(disk_transport, options->run);
625+
sentry_transport_free(disk_transport);
626+
}
625627
} else {
626628
SENTRY_DEBUG("event was discarded by the `on_crash` hook");
627629
sentry_value_decref(event);

src/sentry_core.c

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "sentry_logs.h"
1212
#include "sentry_options.h"
1313
#include "sentry_path.h"
14+
#include "sentry_process.h"
1415
#include "sentry_random.h"
1516
#include "sentry_scope.h"
1617
#include "sentry_session.h"
@@ -19,7 +20,9 @@
1920
#include "sentry_tracing.h"
2021
#include "sentry_transport.h"
2122
#include "sentry_tsan.h"
23+
#include "sentry_uuid.h"
2224
#include "sentry_value.h"
25+
#include "transports/sentry_disk_transport.h"
2326

2427
#ifdef SENTRY_PLATFORM_WINDOWS
2528
# include "sentry_os.h"
@@ -1493,6 +1496,48 @@ sentry_capture_feedback(sentry_value_t user_feedback)
14931496
}
14941497
}
14951498

1499+
bool
1500+
sentry__launch_external_crash_reporter(sentry_envelope_t *envelope)
1501+
{
1502+
SENTRY_WITH_OPTIONS (options) {
1503+
if (!options->external_crash_reporter) {
1504+
return false;
1505+
}
1506+
1507+
sentry_uuid_t event_id = sentry__envelope_get_event_id(envelope);
1508+
char *envelope_filename
1509+
= sentry__uuid_as_filename(&event_id, ".envelope");
1510+
if (!envelope_filename) {
1511+
return false;
1512+
}
1513+
1514+
sentry_path_t *report_path = sentry__path_join_str(
1515+
options->run->external_path, envelope_filename);
1516+
if (!report_path) {
1517+
sentry_free(envelope_filename);
1518+
return false;
1519+
}
1520+
1521+
// capture the envelope with the disk transport
1522+
sentry_transport_t *disk_transport
1523+
= sentry_new_external_disk_transport(options->run);
1524+
if (!disk_transport) {
1525+
sentry__path_free(report_path);
1526+
sentry_free(envelope_filename);
1527+
return false;
1528+
}
1529+
sentry__capture_envelope(disk_transport, envelope);
1530+
sentry__transport_dump_queue(disk_transport, options->run);
1531+
sentry_transport_free(disk_transport);
1532+
1533+
sentry__process_spawn(
1534+
options->external_crash_reporter, report_path->path, NULL);
1535+
sentry__path_free(report_path);
1536+
sentry_free(envelope_filename);
1537+
}
1538+
return true;
1539+
}
1540+
14961541
int
14971542
sentry_get_crashed_last_run(void)
14981543
{

src/sentry_core.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ void sentry__options_unlock(void);
122122

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

125+
bool sentry__launch_external_crash_reporter(sentry_envelope_t *envelope);
126+
125127
#define SENTRY_WITH_OPTIONS(Options) \
126128
for (const sentry_options_t *Options = sentry__options_getref(); Options; \
127129
sentry_options_free((sentry_options_t *)Options), Options = NULL)

0 commit comments

Comments
 (0)