Skip to content
Open
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 `on_crashed_last_run` callback for inspecting crash envelopes from previous runs. ([#1985](https://github.com/getsentry/sentry-native/pull/1985))

**Fixes**:

- Native: store daemon logs, minidumps, crash envelopes, and scratch files in `.run` directories so they are cleaned up with the run instead of accumulating in the database root. Minidumps can still be retained with `cache_keep`, which stores `.dmp` sidecars alongside cached envelopes. ([#1976](https://github.com/getsentry/sentry-native/pull/1976))
Expand Down
15 changes: 15 additions & 0 deletions examples/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,16 @@ on_crash_callback(
return event;
}

static void
on_crashed_last_run_callback(const sentry_envelope_t *envelope, void *user_data)
{
(void)user_data;
const char *event_id = sentry_value_as_string(
sentry_envelope_get_header(envelope, "event_id"));
printf("CRASHED_LAST_RUN:%s\n", event_id ? event_id : "");
fflush(stdout);
}

static sentry_value_t
restart_on_crash(
const sentry_ucontext_t *uctx, sentry_value_t event, void *user_data)
Expand Down Expand Up @@ -771,6 +781,11 @@ main(int argc, char **argv)
sentry_options_set_on_crash(options, on_crash_callback, NULL);
}

if (has_arg(argc, argv, "on-crashed-last-run")) {
sentry_options_set_on_crashed_last_run(
options, on_crashed_last_run_callback, NULL);
}

if (has_arg(argc, argv, "discarding-on-crash")) {
sentry_options_set_on_crash(
options, discarding_on_crash_callback, NULL);
Expand Down
27 changes: 27 additions & 0 deletions include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -1257,6 +1257,33 @@ typedef sentry_value_t (*sentry_crash_function_t)(
SENTRY_API void sentry_options_set_on_crash(
sentry_options_t *opts, sentry_crash_function_t func, void *data);

/**
* Type of the `on_crashed_last_run` callback.
*
* The callback is invoked synchronously during `sentry_init` for every
* available envelope associated with a crash in a previous run. This can
* include multiple crashes in multi-process and early-startup crash scenarios.
*
* The callback does not take ownership of `envelope`. The envelope is only
* valid for the duration of the callback and must not be freed. Since
* `sentry_init` has not completed yet, the callback must not call SDK functions
* that require an initialized SDK.
*
* Unlike `on_crash`, this callback runs in a healthy process and does not need
* to be signal-safe.
*/
typedef void (*sentry_crashed_last_run_function_t)(
const sentry_envelope_t *envelope, void *user_data);

/**
* Sets the `on_crashed_last_run` callback.
*
* The native backend requires this option to be configured in the crashed run
* so its out-of-process daemon retains the crash envelope for the next launch.
*/
SENTRY_API void sentry_options_set_on_crashed_last_run(sentry_options_t *opts,
sentry_crashed_last_run_function_t func, void *user_data);

/**
* Sets the DSN.
*/
Expand Down
1 change: 1 addition & 0 deletions src/backends/native/sentry_crash_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ typedef struct {
// ms
int cache_keep; // sentry_cache_keep_t
bool require_user_consent;
bool has_on_crashed_last_run;
bool enable_large_attachments;
bool http_retry;
uint64_t shutdown_timeout;
Expand Down
21 changes: 12 additions & 9 deletions src/backends/native/sentry_crash_daemon.c
Original file line number Diff line number Diff line change
Expand Up @@ -4275,17 +4275,18 @@ sentry__process_crash(const sentry_options_t *options, sentry_crash_ipc_t *ipc)
crash_captured = true;
}

// Clean up temporary envelope file (keep minidump for
// inspection/debugging)
// Keep the original crash envelope for the callback on the next launch.
if (!ctx->has_on_crashed_last_run) {
#if defined(SENTRY_PLATFORM_UNIX)
unlink(envelope_path);
unlink(envelope_path);
#elif defined(SENTRY_PLATFORM_WINDOWS)
wchar_t *wenvelope_unlink = sentry__string_to_wstr(envelope_path);
if (wenvelope_unlink) {
_wunlink(wenvelope_unlink);
sentry_free(wenvelope_unlink);
}
wchar_t *wenvelope_unlink = sentry__string_to_wstr(envelope_path);
if (wenvelope_unlink) {
_wunlink(wenvelope_unlink);
sentry_free(wenvelope_unlink);
}
#endif
}

cleanup:
// Send the staged session-replay envelope same-session, enriched from the
Expand Down Expand Up @@ -4334,7 +4335,9 @@ remove_pending_run_envelopes(const sentry_path_t *run_path)
const sentry_path_t *file;
while (it && (file = sentry__pathiter_next(it)) != NULL) {
if (sentry__path_is_file(file) && !sentry__path_is_symlink(file)
&& sentry__path_ends_with(file, ".envelope")) {
&& sentry__path_ends_with(file, ".envelope")
&& !sentry__path_filename_matches(
file, "__sentry-crash.envelope")) {
sentry__path_remove(file);
}
}
Expand Down
8 changes: 8 additions & 0 deletions src/backends/sentry_backend_breakpad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ breakpad_backend_callback(const google_breakpad::MinidumpDescriptor &descriptor,

sentry_value_t transaction
= sentry__trace_finish(SENTRY_SPAN_STATUS_ABORTED);
sentry_uuid_t event_id = sentry_uuid_nil();

bool should_handle = true;

Expand Down Expand Up @@ -184,6 +185,9 @@ breakpad_backend_callback(const google_breakpad::MinidumpDescriptor &descriptor,

sentry_envelope_t *envelope = sentry__prepare_event(
options, event, nullptr, !options->on_crash_func, nullptr);
if (envelope) {
event_id = sentry__envelope_get_event_id(envelope);
}
sentry_session_t *session = sentry__end_current_session_with_status(
SENTRY_SESSION_STATUS_CRASHED);
sentry__envelope_add_session(envelope, session);
Expand Down Expand Up @@ -274,6 +278,10 @@ breakpad_backend_callback(const google_breakpad::MinidumpDescriptor &descriptor,
// after capturing the crash event, try to dump all the in-flight
// data of the previous transports
sentry__transport_dump_queue(options->transport, options->run);
if (!sentry_uuid_is_nil(&event_id)
&& !sentry__run_write_crash_marker(options->run, &event_id)) {
SENTRY_SIGNAL_SAFE_LOG("WARN writing run crash marker failed");
}
// and restore the old transport
}
SENTRY_SIGNAL_SAFE_LOG("INFO crash has been captured");
Expand Down
76 changes: 72 additions & 4 deletions src/backends/sentry_backend_crashpad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -603,8 +603,8 @@ report_attachments_dir(const crashpad::CrashReportDatabase::Report &report,
return attachments_dir;
}

// Converts a completed crashpad report into a sentry envelope by reading the
// event, breadcrumbs, and attachments from the report's attachments directory.
// Converts a crashpad report into a sentry envelope by reading the event,
// breadcrumbs, and attachments from the report's attachments directory.
static sentry_envelope_t *
report_to_envelope(const crashpad::CrashReportDatabase::Report &report,
const sentry_options_t *options)
Expand Down Expand Up @@ -687,8 +687,75 @@ report_to_envelope(const crashpad::CrashReportDatabase::Report &report,
return envelope;
}

static bool
crashpad_backend_process_old_run(sentry_backend_t *backend,
const sentry_options_t *options, const sentry_path_t *run_path)
{
if (!options->on_crashed_last_run_func) {
return true;
}

auto *state = static_cast<crashpad_state_t *>(backend->data);
if (!state || !state->db) {
return false;
}
Comment thread
sentry[bot] marked this conversation as resolved.

sentry_path_t *event_path
= sentry__path_join_str(run_path, "__sentry-event");
if (!event_path) {
return false;
}
sentry_value_t event = read_msgpack_file(event_path);
sentry_uuid_t event_id
= sentry__value_as_uuid(sentry_value_get_by_key(event, "event_id"));
sentry_value_decref(event);
if (sentry_uuid_is_nil(&event_id)) {
sentry__path_free(event_path);
return true;
}

char event_id_str[37];
sentry_uuid_as_string(&event_id, event_id_str);
crashpad::UUID report_id;
if (!report_id.InitializeFromString(event_id_str)) {
sentry__path_free(event_path);
return true;
}

crashpad::CrashReportDatabase::Report report;
crashpad::CrashReportDatabase::OperationStatus status
= state->db->LookUpCrashReport(report_id, &report);
if (status == crashpad::CrashReportDatabase::kReportNotFound) {
sentry__path_free(event_path);
return true;
}
if (status != crashpad::CrashReportDatabase::kNoError) {
sentry__path_free(event_path);
return false;
Comment thread
cursor[bot] marked this conversation as resolved.
}

sentry_envelope_t *envelope = report_to_envelope(report, options);
if (!envelope || !sentry__envelope_materialize(envelope)) {
sentry_envelope_free(envelope);
sentry__path_free(event_path);
return true;
Comment thread
cursor[bot] marked this conversation as resolved.
}
Comment thread
jpnurmi marked this conversation as resolved.

// remove before invoking to prevent repeated callbacks
bool removed = sentry__path_remove(event_path) == 0;
sentry__path_free(event_path);
if (!removed) {
sentry_envelope_free(envelope);
return false;
}
options->on_crashed_last_run_func(
envelope, options->on_crashed_last_run_data);
sentry_envelope_free(envelope);
return true;
}

// Caches completed crashpad reports as sentry envelopes and removes them from
// the crashpad database. Called during startup before the handler is started.
// the crashpad database.
static void
process_completed_reports(
crashpad_state_t *state, const sentry_options_t *options)
Expand Down Expand Up @@ -876,7 +943,6 @@ crashpad_backend_startup(
// Initialize database first, flushing the consent later on as part of
// `sentry_init` will persist the upload flag.
data->db = crashpad::CrashReportDatabase::Initialize(database).release();
process_completed_reports(data, options);
data->client = new (std::nothrow) crashpad::CrashpadClient;
char *minidump_url
= sentry__dsn_get_minidump_url(options->dsn, options->user_agent);
Expand Down Expand Up @@ -1119,6 +1185,7 @@ crashpad_backend_prune_database(sentry_backend_t *backend)
// When offline caching is enabled, the user has full control over these
// parameters via the cache_max_* options.
SENTRY_WITH_OPTIONS (options) {
process_completed_reports(data, options);
Comment thread
jpnurmi marked this conversation as resolved.
if (options->cache_keep) {
max_age = options->cache_max_age;
max_size = options->cache_max_size;
Expand Down Expand Up @@ -1223,6 +1290,7 @@ sentry__backend_new(void)
backend->add_breadcrumb_func = crashpad_backend_add_breadcrumb;
backend->user_consent_changed_func = crashpad_backend_user_consent_changed;
backend->get_last_crash_func = crashpad_backend_last_crash;
backend->process_old_run_func = crashpad_backend_process_old_run;
backend->prune_database_func = crashpad_backend_prune_database;
#if defined(SENTRY_PLATFORM_WINDOWS) || defined(SENTRY_PLATFORM_LINUX) \
|| defined(SENTRY_PLATFORM_MACOS)
Expand Down
8 changes: 8 additions & 0 deletions src/backends/sentry_backend_inproc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,7 @@ process_ucontext_deferred(const sentry_ucontext_t *uctx,

sentry_value_t transaction
= sentry__trace_finish(SENTRY_SPAN_STATUS_ABORTED);
sentry_uuid_t event_id = sentry_uuid_nil();

if (options->on_crash_func && !skip_hooks) {
SENTRY_DEBUG("invoking `on_crash` hook");
Expand Down Expand Up @@ -1102,6 +1103,9 @@ process_ucontext_deferred(const sentry_ucontext_t *uctx,

sentry_envelope_t *envelope = sentry__prepare_event(options, event,
NULL, !options->on_crash_func && !skip_hooks, NULL);
if (envelope) {
event_id = sentry__envelope_get_event_id(envelope);
}
sentry_session_t *session = sentry__end_current_session_with_status(
SENTRY_SESSION_STATUS_CRASHED);
sentry__envelope_add_session(envelope, session);
Expand Down Expand Up @@ -1156,6 +1160,10 @@ process_ucontext_deferred(const sentry_ucontext_t *uctx,

// after capturing the crash event, dump all the envelopes to disk
sentry__transport_dump_queue(options->transport, options->run);
if (!sentry_uuid_is_nil(&event_id)
&& !sentry__run_write_crash_marker(options->run, &event_id)) {
SENTRY_SIGNAL_SAFE_LOG("WARN writing run crash marker failed");
}

// Use signal-safe logging here since this may run in signal handler
// context (fallback path) where stdio functions are not safe.
Expand Down
37 changes: 37 additions & 0 deletions src/backends/sentry_backend_native.c
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,41 @@ typedef struct {
volatile long crashed;
} native_backend_state_t;

static bool
native_backend_process_old_run(sentry_backend_t *backend,
const sentry_options_t *options, const sentry_path_t *run_path)
{
(void)backend;

sentry_pathiter_t *it = sentry__path_iter_directory(run_path);
const sentry_path_t *file;
while (it && (file = sentry__pathiter_next(it)) != NULL) {
if (!sentry__path_is_file(file) || sentry__path_is_symlink(file)
|| !sentry__path_filename_matches(
file, "__sentry-crash.envelope")) {
continue;
}

sentry_envelope_t *envelope = options->on_crashed_last_run_func
? sentry__envelope_from_path(file)
: NULL;
bool materialized = envelope && sentry__envelope_materialize(envelope);
// remove before invoking to prevent repeated callbacks
if (sentry__path_remove(file) != 0) {
sentry_envelope_free(envelope);
Comment thread
sentry[bot] marked this conversation as resolved.
sentry__pathiter_free(it);
return false;
}
if (materialized) {
options->on_crashed_last_run_func(
envelope, options->on_crashed_last_run_data);
}
sentry_envelope_free(envelope);
}
sentry__pathiter_free(it);
return true;
}

static int
native_backend_startup(
sentry_backend_t *backend, const sentry_options_t *options)
Expand Down Expand Up @@ -309,6 +344,7 @@ native_backend_startup(
ctx->session_replay_duration = options->session_replay_duration;
ctx->cache_keep = (int)options->cache_keep;
ctx->require_user_consent = options->require_user_consent;
ctx->has_on_crashed_last_run = options->on_crashed_last_run_func != NULL;
ctx->enable_large_attachments = options->enable_large_attachments;
ctx->http_retry = options->http_retry;
ctx->shutdown_timeout = options->shutdown_timeout;
Expand Down Expand Up @@ -1131,6 +1167,7 @@ sentry__backend_new(void)
backend->add_breadcrumb_func = native_backend_add_breadcrumb;
backend->add_attachment_func = native_backend_add_attachment;
backend->user_consent_changed_func = native_backend_user_consent_changed;
backend->process_old_run_func = native_backend_process_old_run;
backend->can_capture_after_shutdown = false;

return backend;
Expand Down
4 changes: 4 additions & 0 deletions src/sentry_backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "sentry_boot.h"

#include "sentry_path.h"
#include "sentry_scope.h"

/**
Expand All @@ -24,6 +25,9 @@ struct sentry_backend_s {
const sentry_options_t *options);
void (*user_consent_changed_func)(sentry_backend_t *);
uint64_t (*get_last_crash_func)(sentry_backend_t *);
// called with the run file lock held; false retains the run
bool (*process_old_run_func)(sentry_backend_t *,
const sentry_options_t *options, const sentry_path_t *run_path);
void (*prune_database_func)(sentry_backend_t *);
void (*add_attachment_func)(sentry_backend_t *, sentry_attachment_t *);
void (*remove_attachment_func)(sentry_backend_t *, sentry_attachment_t *);
Expand Down
Loading
Loading