Skip to content

Commit b7b5a7f

Browse files
committed
WIP: feat: feedback handler (squashed)
1 parent ed67fcb commit b7b5a7f

21 files changed

+485
-22
lines changed

CMakeLists.txt

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

693693
if(SENTRY_BUILD_TESTS)
694694
add_subdirectory(tests/unit)
695+
add_subdirectory(tests/fixtures/feedback)
695696
add_subdirectory(tests/fixtures/screenshot)
696697
if(SENTRY_BUILD_BENCHMARKS)
697698
set(BENCHMARK_ENABLE_TESTING OFF)

examples/example.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,15 @@ main(int argc, char **argv)
381381
sentry_options_add_view_hierarchy(options, "./view-hierarchy.json");
382382
}
383383

384+
if (has_arg(argc, argv, "install-feedback-handler")) {
385+
#ifdef SENTRY_PLATFORM_WINDOWS
386+
sentry_options_set_feedback_handler_pathw(
387+
options, L"sentry_feedback.exe");
388+
#else
389+
sentry_options_set_feedback_handler_path(options, "./sentry_feedback");
390+
#endif
391+
}
392+
384393
sentry_init(options);
385394

386395
if (has_arg(argc, argv, "attachment")) {

include/sentry.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1316,6 +1316,23 @@ SENTRY_API void sentry_options_set_handler_path(
13161316
SENTRY_API void sentry_options_set_handler_path_n(
13171317
sentry_options_t *opts, const char *path, size_t path_len);
13181318

1319+
/**
1320+
* Sets the path to the feedback handler executable.
1321+
*
1322+
* The feedback handler is a separate process that gets spawned when a crash
1323+
* occurs to collect additional user feedback or perform custom actions.
1324+
* The handler receives the path to the crash event envelope as its first
1325+
* argument.
1326+
*
1327+
* `path` is assumed to be in platform-specific filesystem path encoding.
1328+
* API Users on windows are encouraged to use
1329+
* `sentry_options_set_feedback_handler_pathw` instead.
1330+
*/
1331+
SENTRY_API void sentry_options_set_feedback_handler_path(
1332+
sentry_options_t *opts, const char *path);
1333+
SENTRY_API void sentry_options_set_feedback_handler_path_n(
1334+
sentry_options_t *opts, const char *path, size_t path_len);
1335+
13191336
/**
13201337
* Sets the path to the Sentry Database Directory.
13211338
*
@@ -1375,6 +1392,14 @@ SENTRY_API void sentry_options_set_handler_pathw(
13751392
SENTRY_API void sentry_options_set_handler_pathw_n(
13761393
sentry_options_t *opts, const wchar_t *path, size_t path_len);
13771394

1395+
/**
1396+
* Wide char version of `sentry_options_set_feedback_handler_path`.
1397+
*/
1398+
SENTRY_API void sentry_options_set_feedback_handler_pathw(
1399+
sentry_options_t *opts, const wchar_t *path);
1400+
SENTRY_API void sentry_options_set_feedback_handler_pathw_n(
1401+
sentry_options_t *opts, const wchar_t *path, size_t path_len);
1402+
13781403
/**
13791404
* Wide char version of `sentry_options_set_database_path`.
13801405
*/

src/backends/sentry_backend_breakpad.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ breakpad_backend_callback(const google_breakpad::MinidumpDescriptor &descriptor,
143143
}
144144

145145
if (should_handle) {
146+
sentry_value_incref(event);
146147
sentry_envelope_t *envelope = sentry__prepare_event(
147148
options, event, nullptr, !options->on_crash_func, NULL);
148149
sentry_session_t *session = sentry__end_current_session_with_status(
@@ -189,13 +190,14 @@ breakpad_backend_callback(const google_breakpad::MinidumpDescriptor &descriptor,
189190
sentry__path_free(dump_path);
190191
} else {
191192
SENTRY_DEBUG("event was discarded by the `on_crash` hook");
192-
sentry_value_decref(event);
193193
}
194194

195195
// after capturing the crash event, try to dump all the in-flight
196196
// data of the previous transports
197197
sentry__transport_dump_queue(options->transport, options->run);
198-
// and restore the old transport
198+
// and launch the feedback handler
199+
sentry__launch_feedback_handler(event);
200+
sentry_value_decref(event);
199201
}
200202
SENTRY_INFO("crash has been captured");
201203

src/backends/sentry_backend_crashpad.cpp

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ extern "C" {
1919
# include "sentry_unix_pageallocator.h"
2020
#endif
2121
#include "sentry_utils.h"
22+
#include "sentry_uuid.h"
2223
#include "transports/sentry_disk_transport.h"
2324
}
2425

@@ -114,9 +115,11 @@ typedef struct {
114115
sentry_path_t *event_path;
115116
sentry_path_t *breadcrumb1_path;
116117
sentry_path_t *breadcrumb2_path;
118+
sentry_path_t *feedback_path;
117119
size_t num_breadcrumbs;
118120
std::atomic<bool> crashed;
119121
std::atomic<bool> scope_flush;
122+
sentry_uuid_t crash_event_id;
120123
} crashpad_state_t;
121124

122125
/**
@@ -185,7 +188,7 @@ crashpad_register_wer_module(
185188
#endif
186189

187190
static void
188-
crashpad_backend_flush_scope_to_event(const sentry_path_t *event_path,
191+
flush_scope_to_event(const sentry_path_t *event_path,
189192
const sentry_options_t *options, sentry_value_t crash_event)
190193
{
191194
SENTRY_WITH_SCOPE (scope) {
@@ -196,7 +199,6 @@ crashpad_backend_flush_scope_to_event(const sentry_path_t *event_path,
196199

197200
size_t mpack_size;
198201
char *mpack = sentry_value_to_msgpack(crash_event, &mpack_size);
199-
sentry_value_decref(crash_event);
200202
if (!mpack) {
201203
return;
202204
}
@@ -209,6 +211,26 @@ crashpad_backend_flush_scope_to_event(const sentry_path_t *event_path,
209211
}
210212
}
211213

214+
static void
215+
flush_scope_to_feedback(const sentry_path_t *event_path,
216+
const sentry_options_t *options, sentry_value_t crash_event)
217+
{
218+
sentry_envelope_t *envelope = sentry__envelope_new();
219+
if (!envelope) {
220+
return;
221+
}
222+
sentry__envelope_add_event(envelope, crash_event);
223+
if (options->session) {
224+
sentry__envelope_add_session(envelope, options->session);
225+
}
226+
227+
if (sentry__path_create_dir_all(options->run->feedback_path) != 0
228+
|| sentry_envelope_write_to_path(envelope, event_path) != 0) {
229+
SENTRY_WARN("flushing scope to feedback failed");
230+
}
231+
sentry_envelope_free(envelope);
232+
}
233+
212234
// This function is necessary for macOS since it has no `FirstChanceHandler`.
213235
// but it is also necessary on Windows if the WER handler is enabled.
214236
// This means we have to continuously flush the scope on
@@ -234,12 +256,19 @@ crashpad_backend_flush_scope(
234256
}
235257

236258
sentry_value_t event = sentry_value_new_object();
259+
sentry_value_set_by_key(
260+
event, "event_id", sentry__value_new_uuid(&data->crash_event_id));
237261
// Since this will only be uploaded in case of a crash we must make this
238262
// event fatal.
239263
sentry_value_set_by_key(
240264
event, "level", sentry__value_new_level(SENTRY_LEVEL_FATAL));
241265

242-
crashpad_backend_flush_scope_to_event(data->event_path, options, event);
266+
flush_scope_to_event(data->event_path, options, event);
267+
if (data->feedback_path) {
268+
flush_scope_to_feedback(data->feedback_path, options, event);
269+
} else {
270+
sentry_value_decref(event);
271+
}
243272
data->scope_flush.store(false, std::memory_order_release);
244273
#endif
245274
}
@@ -262,8 +291,12 @@ flush_scope_from_handler(
262291
}
263292

264293
// now we are the sole flusher and can flush into the crash event
265-
crashpad_backend_flush_scope_to_event(
266-
state->event_path, options, crash_event);
294+
flush_scope_to_event(state->event_path, options, crash_event);
295+
if (state->feedback_path) {
296+
flush_scope_to_feedback(state->feedback_path, options, crash_event);
297+
} else {
298+
sentry_value_decref(crash_event);
299+
}
267300
}
268301

269302
# ifdef SENTRY_PLATFORM_WINDOWS
@@ -281,7 +314,9 @@ sentry__crashpad_handler(int signum, siginfo_t *info, ucontext_t *user_context)
281314
bool should_dump = true;
282315

283316
SENTRY_WITH_OPTIONS (options) {
284-
sentry_value_t crash_event = sentry_value_new_event();
317+
auto state = static_cast<crashpad_state_t *>(options->backend->data);
318+
sentry_value_t crash_event
319+
= sentry__value_new_event_with_uuid(&state->crash_event_id);
285320
sentry_value_set_by_key(
286321
crash_event, "level", sentry__value_new_level(SENTRY_LEVEL_FATAL));
287322

@@ -418,6 +453,9 @@ crashpad_backend_startup(
418453
sentry_path_t *current_run_folder = options->run->run_path;
419454
auto *data = static_cast<crashpad_state_t *>(backend->data);
420455

456+
// prepare a predictable event ID for a potential future crash
457+
data->crash_event_id = sentry__new_event_id();
458+
421459
base::FilePath database(options->database_path->path);
422460
base::FilePath handler(absolute_handler_path->path);
423461

@@ -455,6 +493,19 @@ crashpad_backend_startup(
455493
sentry__path_free(screenshot_path);
456494
}
457495

496+
base::FilePath feedback_handler;
497+
base::FilePath feedback_path;
498+
if (options->feedback_handler_path) {
499+
char *filename
500+
= sentry__uuid_as_filename(&data->crash_event_id, ".envelope");
501+
data->feedback_path
502+
= sentry__path_join_str(options->run->feedback_path, filename);
503+
sentry_free(filename);
504+
505+
feedback_handler = base::FilePath(options->feedback_handler_path->path);
506+
feedback_path = base::FilePath(data->feedback_path->path);
507+
}
508+
458509
std::vector<std::string> arguments { "--no-rate-limit" };
459510

460511
// Initialize database first, flushing the consent later on as part of
@@ -482,7 +533,7 @@ crashpad_backend_startup(
482533
minidump_url ? minidump_url : "", proxy_url, annotations, arguments,
483534
/* restartable */ true,
484535
/* asynchronous_start */ false, attachments, screenshot,
485-
options->crashpad_wait_for_upload);
536+
options->crashpad_wait_for_upload, feedback_handler, feedback_path);
486537
sentry_free(minidump_url);
487538

488539
#ifdef SENTRY_PLATFORM_WINDOWS
@@ -595,6 +646,7 @@ crashpad_backend_free(sentry_backend_t *backend)
595646
sentry__path_free(data->event_path);
596647
sentry__path_free(data->breadcrumb1_path);
597648
sentry__path_free(data->breadcrumb2_path);
649+
sentry__path_free(data->feedback_path);
598650
sentry_free(data);
599651
}
600652

src/backends/sentry_backend_inproc.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,7 @@ handle_ucontext(const sentry_ucontext_t *uctx)
589589
}
590590

591591
if (should_handle) {
592+
sentry_value_incref(event);
592593
sentry_envelope_t *envelope = sentry__prepare_event(
593594
options, event, NULL, !options->on_crash_func, NULL);
594595
// TODO(tracing): Revisit when investigating transaction flushing
@@ -616,11 +617,13 @@ handle_ucontext(const sentry_ucontext_t *uctx)
616617
sentry_transport_free(disk_transport);
617618
} else {
618619
SENTRY_DEBUG("event was discarded by the `on_crash` hook");
619-
sentry_value_decref(event);
620620
}
621621

622622
// after capturing the crash event, dump all the envelopes to disk
623623
sentry__transport_dump_queue(options->transport, options->run);
624+
// and launch the feedback handler
625+
sentry__launch_feedback_handler(event);
626+
sentry_value_decref(event);
624627
}
625628

626629
SENTRY_INFO("crash has been captured");

src/sentry_core.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "sentry_options.h"
1212
#include "sentry_os.h"
1313
#include "sentry_path.h"
14+
#include "sentry_process.h"
1415
#include "sentry_random.h"
1516
#include "sentry_scope.h"
1617
#include "sentry_screenshot.h"
@@ -19,6 +20,7 @@
1920
#include "sentry_sync.h"
2021
#include "sentry_tracing.h"
2122
#include "sentry_transport.h"
23+
#include "sentry_uuid.h"
2224
#include "sentry_value.h"
2325

2426
#ifdef SENTRY_INTEGRATION_QT
@@ -1435,6 +1437,29 @@ sentry_capture_feedback(sentry_value_t user_feedback)
14351437
}
14361438
}
14371439

1440+
void
1441+
sentry__launch_feedback_handler(sentry_value_t event)
1442+
{
1443+
sentry_uuid_t event_id = sentry_uuid_nil();
1444+
sentry__ensure_event_id(event, &event_id);
1445+
1446+
SENTRY_WITH_OPTIONS (options) {
1447+
if (!options->feedback_handler_path) {
1448+
return;
1449+
}
1450+
1451+
sentry_path_t *feedback_path
1452+
= sentry__run_write_feedback(options->run, &event_id);
1453+
if (!feedback_path) {
1454+
return;
1455+
}
1456+
1457+
sentry__process_spawn(
1458+
options->feedback_handler_path, feedback_path->path, NULL);
1459+
sentry__path_free(feedback_path);
1460+
}
1461+
}
1462+
14381463
int
14391464
sentry_get_crashed_last_run(void)
14401465
{

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+
void sentry__launch_feedback_handler(sentry_value_t event);
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)