Skip to content

Commit

Permalink
feat: invoke on_crash when handling a crash
Browse files Browse the repository at this point in the history
- don't invoke before_send if on_crash is set
- if on_crash returns false crash report will be discarded
  • Loading branch information
espkk committed Jul 1, 2022
1 parent f32330b commit 7bbce0d
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 71 deletions.
27 changes: 27 additions & 0 deletions include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ extern "C" {

#include <inttypes.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>

/* context type dependencies */
Expand Down Expand Up @@ -794,6 +795,32 @@ typedef sentry_value_t (*sentry_event_function_t)(
SENTRY_API void sentry_options_set_before_send(
sentry_options_t *opts, sentry_event_function_t func, void *data);

/**
* Type of the `on_crash` callback.
*
* Does not work with crashpad on macOS.
* The callback passes a pointer to sentry_ucontext_s structure when exception
* handler is invoked. For breakpad on Linux this pointer is NULL.
*
* If the callback returns false outgoing crash report will be discarded.
*
* This function may be invoked inside of a signal handler and must be safe for
* that purpose, see https://man7.org/linux/man-pages/man7/signal-safety.7.html.
* On Windows, it may be called from inside of a `UnhandledExceptionFilter`, see
* the documentation on SEH (structured exception handling) for more information
* https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling
*/
typedef bool (*sentry_crash_function_t)(
const sentry_ucontext_t *uctx, void *closure);

/**
* Sets the `on_crash` callback.
*
* See the `sentry_crash_function_t` typedef above for more information.
*/
SENTRY_API void sentry_options_set_on_crash(
sentry_options_t *opts, sentry_crash_function_t func, void *data);

/**
* Sets the DSN.
*/
Expand Down
88 changes: 55 additions & 33 deletions src/backends/sentry_backend_breakpad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ extern "C" {
static bool
sentry__breakpad_backend_callback(const wchar_t *breakpad_dump_path,
const wchar_t *minidump_id, void *UNUSED(context),
EXCEPTION_POINTERS *UNUSED(exinfo), MDRawAssertionInfo *UNUSED(assertion),
EXCEPTION_POINTERS *exinfo, MDRawAssertionInfo *UNUSED(assertion),
bool succeeded)
#elif defined(SENTRY_PLATFORM_DARWIN)
static bool
Expand Down Expand Up @@ -95,42 +95,64 @@ sentry__breakpad_backend_callback(
SENTRY_WITH_OPTIONS (options) {
sentry__write_crash_marker(options);

sentry_value_t event = sentry_value_new_event();
sentry_envelope_t *envelope
= sentry__prepare_event(options, event, NULL);
// the event we just prepared is empty, so no error is recorded for it
sentry__record_errors_on_current_session(1);
sentry_session_t *session = sentry__end_current_session_with_status(
SENTRY_SESSION_STATUS_CRASHED);
sentry__envelope_add_session(envelope, session);

// the minidump is added as an attachment, with type `event.minidump`
sentry_envelope_item_t *item
= sentry__envelope_add_from_path(envelope, dump_path, "attachment");
if (item) {
sentry__envelope_item_set_header(item, "attachment_type",
sentry_value_new_string("event.minidump"));

sentry__envelope_item_set_header(item, "filename",
bool should_handle = true;

if (options->on_crash_func) {
sentry_ucontext_t *uctx = nullptr;

#ifdef SENTRY_PLATFORM_WINDOWS
sentry__value_new_string_from_wstr(
#else
sentry_value_new_string(
sentry_ucontext_t uctx_data;
uctx_data.exception_ptrs = *exinfo;
uctx = &uctx_data;
#endif
sentry__path_filename(dump_path)));

SENTRY_TRACE("invoking `on_crash` hook");
should_handle
= options->on_crash_func(uctx, options->on_crash_data);
}

// 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
sentry__path_remove(dump_path);
sentry__path_free(dump_path);
if (should_handle) {
sentry_value_t event = sentry_value_new_event();
sentry_envelope_t *envelope
= sentry__prepare_event(options, event, NULL);
// the event we just prepared is empty,
// so no error is recorded for it
sentry__record_errors_on_current_session(1);
sentry_session_t *session = sentry__end_current_session_with_status(
SENTRY_SESSION_STATUS_CRASHED);
sentry__envelope_add_session(envelope, session);

// the minidump is added as an attachment,
// with type `event.minidump`
sentry_envelope_item_t *item = sentry__envelope_add_from_path(
envelope, dump_path, "attachment");
if (item) {
sentry__envelope_item_set_header(item, "attachment_type",
sentry_value_new_string("event.minidump"));

sentry__envelope_item_set_header(item, "filename",
#ifdef SENTRY_PLATFORM_WINDOWS
sentry__value_new_string_from_wstr(
#else
sentry_value_new_string(
#endif
sentry__path_filename(dump_path)));
}

// 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
sentry__path_remove(dump_path);
sentry__path_free(dump_path);
} else {
SENTRY_TRACE("event was discarded by the `on_crash` hook");
}

// after capturing the crash event, try to dump all the in-flight
// data of the previous transports
Expand Down
64 changes: 41 additions & 23 deletions src/backends/sentry_backend_crashpad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,42 +122,60 @@ sentry__crashpad_backend_flush_scope(
#if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_WINDOWS)
# ifdef SENTRY_PLATFORM_WINDOWS
static bool
sentry__crashpad_handler(EXCEPTION_POINTERS *UNUSED(ExceptionInfo))
sentry__crashpad_handler(EXCEPTION_POINTERS *ExceptionInfo)
{
# else
static bool
sentry__crashpad_handler(int UNUSED(signum), siginfo_t *UNUSED(info),
ucontext_t *UNUSED(user_context))
sentry__crashpad_handler(int signum, siginfo_t *info, ucontext_t *user_context)
{
sentry__page_allocator_enable();
sentry__enter_signal_handler();
# endif
SENTRY_DEBUG("flushing session and queue before crashpad handler");

bool should_handle = true;

SENTRY_WITH_OPTIONS (options) {
sentry__write_crash_marker(options);

sentry_value_t event = sentry_value_new_event();
if (options->before_send_func) {
if (options->on_crash_func) {
sentry_ucontext_t uctx;
# ifdef SENTRY_PLATFORM_WINDOWS
uctx.exception_ptrs = *ExceptionInfo;
# else
uctx.signum = signum;
uctx.siginfo = info;
uctx.user_context = user_context;
# endif

SENTRY_TRACE("invoking `on_crash` hook");
should_handle
= options->on_crash_func(&uctx, options->on_crash_data);
} else if (options->before_send_func) {
sentry_value_t event = sentry_value_new_event();
SENTRY_TRACE("invoking `before_send` hook");
event = options->before_send_func(
event, NULL, options->before_send_data);
event, nullptr, options->before_send_data);
sentry_value_decref(event);
}
sentry_value_decref(event);

sentry__record_errors_on_current_session(1);
sentry_session_t *session = sentry__end_current_session_with_status(
SENTRY_SESSION_STATUS_CRASHED);
if (session) {
sentry_envelope_t *envelope = sentry__envelope_new();
sentry__envelope_add_session(envelope, session);

// 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 (should_handle) {
sentry__record_errors_on_current_session(1);
sentry_session_t *session = sentry__end_current_session_with_status(
SENTRY_SESSION_STATUS_CRASHED);
if (session) {
sentry_envelope_t *envelope = sentry__envelope_new();
sentry__envelope_add_session(envelope, session);

// 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_TRACE("event was discarded by the `on_crash` hook");
}

sentry__transport_dump_queue(options->transport, options->run);
Expand All @@ -167,8 +185,8 @@ sentry__crashpad_handler(int UNUSED(signum), siginfo_t *UNUSED(info),
# ifndef SENTRY_PLATFORM_WINDOWS
sentry__leave_signal_handler();
# endif
// we did not "handle" the signal, so crashpad should do that.
return false;
// further handling can be skipped via on_crash hook
return !should_handle;
}
#endif

Expand Down
42 changes: 27 additions & 15 deletions src/backends/sentry_backend_inproc.c
Original file line number Diff line number Diff line change
Expand Up @@ -526,21 +526,33 @@ handle_ucontext(const sentry_ucontext_t *uctx)
SENTRY_WITH_OPTIONS (options) {
sentry__write_crash_marker(options);

sentry_envelope_t *envelope
= sentry__prepare_event(options, event, NULL);
// TODO(tracing): Revisit when investigating transaction flushing during
// hard crashes.

sentry_session_t *session = sentry__end_current_session_with_status(
SENTRY_SESSION_STATUS_CRASHED);
sentry__envelope_add_session(envelope, session);

// 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);
bool should_handle = true;

if (options->on_crash_func) {
SENTRY_TRACE("invoking `on_crash` hook");
should_handle
= options->on_crash_func(uctx, options->on_crash_data);
}

if (should_handle) {
sentry_envelope_t *envelope
= sentry__prepare_event(options, event, NULL);
// TODO(tracing): Revisit when investigating transaction flushing
// during hard crashes.

sentry_session_t *session = sentry__end_current_session_with_status(
SENTRY_SESSION_STATUS_CRASHED);
sentry__envelope_add_session(envelope, session);

// 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_TRACE("event was discarded by the `on_crash` hook");
}

// after capturing the crash event, dump all the envelopes to disk
sentry__transport_dump_queue(options->transport, options->run);
Expand Down
8 changes: 8 additions & 0 deletions src/sentry_options.c
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,14 @@ sentry_options_set_before_send(
opts->before_send_data = data;
}

void
sentry_options_set_on_crash(
sentry_options_t *opts, sentry_crash_function_t func, void *data)
{
opts->on_crash_func = func;
opts->on_crash_data = data;
}

void
sentry_options_set_dsn(sentry_options_t *opts, const char *raw_dsn)
{
Expand Down
2 changes: 2 additions & 0 deletions src/sentry_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ typedef struct sentry_options_s {
sentry_transport_t *transport;
sentry_event_function_t before_send_func;
void *before_send_data;
sentry_crash_function_t on_crash_func;
void *on_crash_data;

/* Experimentally exposed */
double traces_sample_rate;
Expand Down

0 comments on commit 7bbce0d

Please sign in to comment.