Skip to content

Commit 3b96558

Browse files
jpnurmicodex
andcommitted
feat: allow fetching event_id from last crash
Expose `sentry_get_last_crash` so callers can inspect the previous crash timestamp and event id after initialization. Persist the crash event id next to the existing crash marker timestamp while keeping the old timestamp-only marker format readable. Co-Authored-By: OpenAI Codex <noreply@openai.com>
1 parent e4821a6 commit 3b96558

16 files changed

Lines changed: 277 additions & 48 deletions

include/sentry.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3562,6 +3562,39 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_iter_headers(
35623562
*/
35633563
SENTRY_EXPERIMENTAL_API int sentry_get_crashed_last_run(void);
35643564

3565+
/**
3566+
* Information about the crash from the last run.
3567+
*/
3568+
typedef struct sentry_last_crash_s {
3569+
/**
3570+
* The crash timestamp in microseconds since the Unix epoch, or 0 if it is
3571+
* unavailable.
3572+
*/
3573+
uint64_t timestamp;
3574+
/**
3575+
* The crash event ID, or a nil UUID if it is unavailable.
3576+
*/
3577+
sentry_uuid_t event_id;
3578+
} sentry_last_crash_t;
3579+
3580+
/**
3581+
* Returns whether the application crashed on the last run, and populates
3582+
* `crash` with available information.
3583+
*
3584+
* Pass `sizeof(sentry_last_crash_t)` as `crash_size` so future SDK versions can
3585+
* add fields without breaking callers compiled against older headers. Call
3586+
* `sentry_clear_crashed_last_run()` to reset for the next app run.
3587+
*
3588+
* Note: This does not work with crashpad on macOS, similar to `on_crash`.
3589+
*
3590+
* Possible return values:
3591+
* 1 = the last run was a crash
3592+
* 0 = no crash recognized
3593+
* -1 = sentry_init() hasn't been called yet
3594+
*/
3595+
SENTRY_API int sentry_get_last_crash(
3596+
sentry_last_crash_t *crash, size_t crash_size);
3597+
35653598
/**
35663599
* Clear the status of the "crashed-last-run". You should explicitly call
35673600
* this after sentry_init() if you're using sentry_get_crashed_last_run().

src/backends/sentry_backend_breakpad.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,12 +132,13 @@ breakpad_backend_callback(const google_breakpad::MinidumpDescriptor &descriptor,
132132
#else
133133
dump_path = sentry__path_new(descriptor.path());
134134
#endif
135-
sentry_value_t event = sentry_value_new_event();
135+
sentry_uuid_t event_id = sentry__new_event_id();
136+
sentry_value_t event = sentry__value_new_event_with_id(&event_id);
136137
sentry_value_set_by_key(
137138
event, "level", sentry__value_new_level(SENTRY_LEVEL_FATAL));
138139

139140
SENTRY_WITH_OPTIONS (options) {
140-
sentry__write_crash_marker(options);
141+
sentry__write_crash_marker(options, &event_id);
141142

142143
bool should_handle = true;
143144

src/backends/sentry_backend_crashpad.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ crashpad_handler(int signum, siginfo_t *info, ucontext_t *user_context)
386386

387387
if (should_dump) {
388388
flush_scope_from_handler(options, crash_event);
389-
sentry__write_crash_marker(options);
389+
sentry__write_crash_marker(options, &state->crash_event_id);
390390

391391
sentry__record_errors_on_current_session(1);
392392
sentry_session_t *session = sentry__end_current_session_with_status(

src/backends/sentry_backend_inproc.c

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -999,9 +999,10 @@ get_instruction_pointer(const sentry_ucontext_t *uctx)
999999

10001000
static sentry_value_t
10011001
make_signal_event(const struct signal_slot *sig_slot,
1002-
const sentry_ucontext_t *uctx, sentry_handler_strategy_t strategy)
1002+
const sentry_ucontext_t *uctx, sentry_handler_strategy_t strategy,
1003+
const sentry_uuid_t *event_id)
10031004
{
1004-
sentry_value_t event = sentry_value_new_event();
1005+
sentry_value_t event = sentry__value_new_event_with_id(event_id);
10051006
sentry_value_set_by_key(
10061007
event, "level", sentry__value_new_level(SENTRY_LEVEL_FATAL));
10071008

@@ -1111,9 +1112,11 @@ process_ucontext_deferred(const sentry_ucontext_t *uctx,
11111112
options ? sentry_options_get_handler_strategy(options) :
11121113
#endif
11131114
SENTRY_HANDLER_STRATEGY_DEFAULT;
1114-
sentry_value_t event = make_signal_event(sig_slot, uctx, strategy);
1115+
sentry_uuid_t event_id = sentry__new_event_id();
1116+
sentry_value_t event
1117+
= make_signal_event(sig_slot, uctx, strategy, &event_id);
11151118
bool should_handle = true;
1116-
sentry__write_crash_marker(options);
1119+
sentry__write_crash_marker(options, &event_id);
11171120

11181121
if (options->on_crash_func && !skip_hooks) {
11191122
SENTRY_DEBUG("invoking `on_crash` hook");

src/backends/sentry_backend_native.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -986,10 +986,11 @@ native_backend_except(sentry_backend_t *backend, const sentry_ucontext_t *uctx)
986986
}
987987

988988
// Write crash marker
989-
sentry__write_crash_marker(options);
989+
sentry_uuid_t event_id = sentry__new_event_id();
990+
sentry__write_crash_marker(options, &event_id);
990991

991992
// Create crash event
992-
sentry_value_t event = sentry_value_new_event();
993+
sentry_value_t event = sentry__value_new_event_with_id(&event_id);
993994
sentry_value_set_by_key(
994995
event, "level", sentry__value_new_level(SENTRY_LEVEL_FATAL));
995996

src/sentry_core.c

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "sentry_boot.h"
22

33
#include <stdarg.h>
4+
#include <stddef.h>
45
#include <string.h>
56

67
#include "sentry_attachment.h"
@@ -44,6 +45,13 @@ static sentry_mutex_t g_options_lock = SENTRY__MUTEX_INIT;
4445
#endif
4546
/// see sentry_get_crashed_last_run() for the possible values
4647
static int g_last_crash = -1;
48+
static sentry_last_crash_t g_last_crash_info;
49+
50+
static inline bool
51+
last_crash_has_field(size_t struct_size, size_t offset, size_t size)
52+
{
53+
return offset <= struct_size && size <= struct_size - offset;
54+
}
4755

4856
const sentry_options_t *
4957
sentry__options_getref(void)
@@ -194,7 +202,7 @@ sentry_init(sentry_options_t *options)
194202
last_crash = backend->get_last_crash_func(backend);
195203
}
196204

197-
g_last_crash = sentry__has_crash_marker(options);
205+
g_last_crash = sentry__read_crash_marker(options, &g_last_crash_info);
198206
g_options = options;
199207

200208
// *after* setting the global options, trigger a scope and consent flush,
@@ -1745,6 +1753,28 @@ sentry_get_crashed_last_run(void)
17451753
return g_last_crash;
17461754
}
17471755

1756+
int
1757+
sentry_get_last_crash(sentry_last_crash_t *crash, size_t crash_size)
1758+
{
1759+
if (crash && crash_size > 0) {
1760+
memset(crash, 0, crash_size);
1761+
1762+
if (g_last_crash == 1
1763+
&& last_crash_has_field(crash_size,
1764+
offsetof(sentry_last_crash_t, timestamp),
1765+
sizeof(crash->timestamp))) {
1766+
crash->timestamp = g_last_crash_info.timestamp;
1767+
}
1768+
if (g_last_crash == 1
1769+
&& last_crash_has_field(crash_size,
1770+
offsetof(sentry_last_crash_t, event_id),
1771+
sizeof(crash->event_id))) {
1772+
crash->event_id = g_last_crash_info.event_id;
1773+
}
1774+
}
1775+
return g_last_crash;
1776+
}
1777+
17481778
int
17491779
sentry_clear_crashed_last_run(void)
17501780
{

src/sentry_database.c

Lines changed: 111 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,58 @@
1616
#include <stdlib.h>
1717
#include <string.h>
1818

19+
static char *
20+
read_metadata(
21+
const sentry_path_t *path, sentry_slice_t *first, sentry_slice_t *second)
22+
{
23+
size_t size = 0;
24+
char *contents_buf = sentry__path_read_to_buffer(path, &size);
25+
if (!contents_buf) {
26+
return NULL;
27+
}
28+
29+
sentry_slice_t contents
30+
= sentry__slice_trim((sentry_slice_t) { contents_buf, size });
31+
if (contents.len == 0) {
32+
return contents_buf;
33+
}
34+
35+
size_t first_len = 0;
36+
while (first_len < contents.len
37+
&& !isspace((unsigned char)contents.ptr[first_len])) {
38+
first_len++;
39+
}
40+
*first = (sentry_slice_t) { contents.ptr, first_len };
41+
*second = sentry__slice_trim((sentry_slice_t) {
42+
contents.ptr + first_len, contents.len - first_len });
43+
44+
return contents_buf;
45+
}
46+
47+
static int
48+
write_metadata(const sentry_path_t *path, const char *first, size_t first_len,
49+
const char *second, size_t second_len)
50+
{
51+
bool has_second = second != NULL;
52+
size_t buf_len = first_len + (has_second ? 1 + second_len + 1 : 0);
53+
char *buf = sentry_malloc(buf_len);
54+
if (!buf) {
55+
return 1;
56+
}
57+
58+
memcpy(buf, first, first_len);
59+
if (has_second) {
60+
buf[first_len] = '\n';
61+
memcpy(buf + first_len + 1, second, second_len);
62+
buf[first_len + 1 + second_len] = '\n';
63+
}
64+
65+
int rv = sentry__path_write_buffer(path, buf, buf_len);
66+
sentry_free(buf);
67+
68+
return rv;
69+
}
70+
1971
sentry_run_t *
2072
sentry__run_new(const sentry_path_t *database_path)
2173
{
@@ -149,35 +201,24 @@ sentry__run_load_installation_id(sentry_run_t *run,
149201

150202
const size_t uuid_len = 36;
151203
char uuid_str[37] = { 0 };
152-
size_t size = 0;
153-
char *contents = sentry__path_read_to_buffer(id_path, &size);
154-
if (contents && size >= uuid_len) {
155-
// expect: "<uuid>(\s+<public_key>)?"
156-
sentry_slice_t tail = { contents + uuid_len, size - uuid_len };
157-
sentry_slice_t key = sentry__slice_trim(tail);
158-
if ((tail.len == 0 || isspace((unsigned char)tail.ptr[0]))
159-
&& sentry__slice_eqs(key, public_key)) {
160-
memcpy(uuid_str, contents, uuid_len);
161-
uuid_str[uuid_len] = '\0';
162-
}
204+
sentry_slice_t first = { 0 };
205+
sentry_slice_t second = { 0 };
206+
char *contents = read_metadata(id_path, &first, &second);
207+
// expect: "<uuid>(\s+<public_key>)?"
208+
if (first.len == uuid_len && sentry__slice_eqs(second, public_key)) {
209+
memcpy(uuid_str, first.ptr, uuid_len);
210+
uuid_str[uuid_len] = '\0';
163211
}
164212
sentry_free(contents);
165213

166214
if (uuid_str[0] == '\0') {
167215
sentry_uuid_t uuid = sentry_uuid_new_v4();
168216
sentry_uuid_as_string(&uuid, uuid_str);
169217

170-
const size_t buf_len = uuid_len + 1 + key_len + 1;
171-
char *buf = sentry_malloc(buf_len);
172-
if (buf) {
173-
memcpy(buf, uuid_str, uuid_len);
174-
buf[uuid_len] = '\n';
175-
memcpy(buf + uuid_len + 1, public_key, key_len);
176-
buf[uuid_len + 1 + key_len] = '\n';
177-
if (sentry__path_write_buffer(id_path, buf, buf_len) != 0) {
178-
SENTRY_WARN("failed to persist installation ID");
179-
}
180-
sentry_free(buf);
218+
int rv
219+
= write_metadata(id_path, uuid_str, uuid_len, public_key, key_len);
220+
if (rv) {
221+
SENTRY_WARN("failed to persist installation ID");
181222
}
182223
}
183224

@@ -974,7 +1015,8 @@ sentry__cleanup_cache(const sentry_options_t *options)
9741015
static const char *g_last_crash_filename = "last_crash";
9751016

9761017
bool
977-
sentry__write_crash_marker(const sentry_options_t *options)
1018+
sentry__write_crash_marker(
1019+
const sentry_options_t *options, const sentry_uuid_t *event_id)
9781020
{
9791021
char *iso_time = sentry__usec_time_to_iso8601(sentry__usec_time());
9801022
if (!iso_time) {
@@ -989,7 +1031,17 @@ sentry__write_crash_marker(const sentry_options_t *options)
9891031
}
9901032

9911033
size_t iso_time_len = strlen(iso_time);
992-
int rv = sentry__path_write_buffer(marker_path, iso_time, iso_time_len);
1034+
char event_id_str[37];
1035+
const char *event_id_value = NULL;
1036+
size_t event_id_len = 0;
1037+
if (event_id && !sentry_uuid_is_nil(event_id)) {
1038+
sentry_uuid_as_string(event_id, event_id_str);
1039+
event_id_value = event_id_str;
1040+
event_id_len = strlen(event_id_str);
1041+
}
1042+
1043+
int rv = write_metadata(
1044+
marker_path, iso_time, iso_time_len, event_id_value, event_id_len);
9931045
sentry_free(iso_time);
9941046
sentry__path_free(marker_path);
9951047

@@ -999,6 +1051,41 @@ sentry__write_crash_marker(const sentry_options_t *options)
9991051
return !rv;
10001052
}
10011053

1054+
int
1055+
sentry__read_crash_marker(
1056+
const sentry_options_t *options, sentry_last_crash_t *crash)
1057+
{
1058+
if (crash) {
1059+
memset(crash, 0, sizeof(*crash));
1060+
}
1061+
1062+
sentry_path_t *marker_path
1063+
= sentry__path_join_str(options->database_path, g_last_crash_filename);
1064+
if (!marker_path) {
1065+
return 0;
1066+
}
1067+
1068+
sentry_slice_t first = { 0 };
1069+
sentry_slice_t second = { 0 };
1070+
char *contents = read_metadata(marker_path, &first, &second);
1071+
int crashed = contents ? 1 : 0;
1072+
if (crash && first.len > 0) {
1073+
crash->timestamp = sentry__iso8601_to_usec(first.ptr, first.len);
1074+
}
1075+
1076+
if (crash && (second.len == 32 || second.len == 36)) {
1077+
sentry_uuid_t parsed
1078+
= sentry_uuid_from_string_n(second.ptr, second.len);
1079+
if (!sentry_uuid_is_nil(&parsed)) {
1080+
crash->event_id = parsed;
1081+
}
1082+
}
1083+
1084+
sentry_free(contents);
1085+
sentry__path_free(marker_path);
1086+
return crashed;
1087+
}
1088+
10021089
bool
10031090
sentry__has_crash_marker(const sentry_options_t *options)
10041091
{

src/sentry_database.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,14 @@ void sentry__cleanup_cache(const sentry_options_t *options);
192192
* This will write the current ISO8601 formatted timestamp into the
193193
* `<database>/last_crash` file.
194194
*/
195-
bool sentry__write_crash_marker(const sentry_options_t *options);
195+
bool sentry__write_crash_marker(
196+
const sentry_options_t *options, const sentry_uuid_t *event_id);
197+
198+
/**
199+
* This will read the crash information from the `<database>/last_crash` file.
200+
*/
201+
int sentry__read_crash_marker(
202+
const sentry_options_t *options, sentry_last_crash_t *crash);
196203

197204
/**
198205
* This will check whether the `<database>/last_crash` file exists.

src/sentry_session.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ sentry__session_from_json(const char *buf, size_t buflen)
182182
rv->errors = (uint64_t)sentry_value_as_int32(
183183
sentry_value_get_by_key(value, "errors"));
184184
rv->started_us = sentry__iso8601_to_usec(
185-
sentry_value_as_string(sentry_value_get_by_key(value, "started")));
185+
sentry_value_as_string(sentry_value_get_by_key(value, "started")), -1);
186186

187187
double duration
188188
= sentry_value_as_double(sentry_value_get_by_key(value, "duration"));

src/sentry_utils.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -479,9 +479,9 @@ sentry__usec_time_to_iso8601(uint64_t time)
479479

480480
#ifndef SENTRY_PLATFORM_PS
481481
uint64_t
482-
sentry__iso8601_to_usec(const char *iso)
482+
sentry__iso8601_to_usec(const char *iso, size_t iso_len)
483483
{
484-
size_t len = sentry__guarded_strlen(iso);
484+
size_t len = iso_len == (size_t)-1 ? sentry__guarded_strlen(iso) : iso_len;
485485
if (len != 20 && len != 27) {
486486
return 0;
487487
}

0 commit comments

Comments
 (0)