diff --git a/CHANGELOG.md b/CHANGELOG.md index d3f17eaf6..e5adf1572 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ **Features**: - Removed the `SENTRY_PERFORMANCE_MONITORING` compile flag requirement to access performance monitoring in the Sentry SDK. Performance monitoring is now available to everybody who has opted into the experimental API. +- New API to check whether the application has crashed in the previous run: `sentry_get_crashed_last_run()` and `sentry_clear_crashed_last_run()` ([#685](https://github.com/getsentry/sentry-native/pull/685)). - Allow overriding the SDK name at build time - set the `SENTRY_SDK_NAME` CMake cache variable. ## 0.4.15 diff --git a/include/sentry.h b/include/sentry.h index 428633ee9..635eb437e 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1711,6 +1711,34 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_iter_headers( sentry_transaction_t *tx, sentry_iter_headers_function_t callback, void *userdata); +/** + * Returns whether the application has crashed on the last run. + * + * Notes: + * * The underlying value is set by sentry_init() - it must be called first. + * * Call sentry_clear_crashed_last_run() to reset for the next app run. + * + * Possible return values: + * 1 = the last run was a crash + * 0 = no crash recognized + * -1 = sentry_init() hasn't been called yet + */ +SENTRY_EXPERIMENTAL_API int sentry_get_crashed_last_run(); + +/** + * Clear the status of the "crashed-last-run". You should explicitly call + * this after sentry_init() if you're using sentry_get_crashed_last_run(). + * Otherwise, the same information is reported on any subsequent runs. + * + * Notes: + * * This doesn't change the value of sentry_get_crashed_last_run() yet. + * However, if sentry_init() is called again, the value will change. + * * This may only be called after sentry_init() and before sentry_close(). + * + * Returns 0 on success, 1 on error. + */ +SENTRY_EXPERIMENTAL_API int sentry_clear_crashed_last_run(); + #ifdef __cplusplus } #endif diff --git a/src/sentry_core.c b/src/sentry_core.c index fd90d7104..6bcdf8fe1 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -27,6 +27,9 @@ static sentry_options_t *g_options = NULL; static sentry_mutex_t g_options_lock = SENTRY__MUTEX_INIT; +/// see sentry_get_crashed_last_run() for the possible values +static int g_last_crash = -1; + const sentry_options_t * sentry__options_getref(void) { @@ -158,6 +161,7 @@ sentry_init(sentry_options_t *options) last_crash = backend->get_last_crash_func(backend); } + g_last_crash = sentry__has_crash_marker(options); g_options = options; // *after* setting the global options, trigger a scope and consent flush, @@ -999,3 +1003,21 @@ sentry_span_finish(sentry_span_t *opaque_span) sentry__span_free(opaque_span); return; } + +int +sentry_get_crashed_last_run() +{ + return g_last_crash; +} + +int +sentry_clear_crashed_last_run() +{ + bool success = false; + sentry_options_t *options = sentry__options_lock(); + if (options) { + success = sentry__clear_crash_marker(options); + } + sentry__options_unlock(); + return success ? 0 : 1; +} diff --git a/src/sentry_database.c b/src/sentry_database.c index a2c146358..62a619d8a 100644 --- a/src/sentry_database.c +++ b/src/sentry_database.c @@ -235,6 +235,8 @@ sentry__process_old_runs(const sentry_options_t *options, uint64_t last_crash) sentry__capture_envelope(options->transport, session_envelope); } +static const char *g_last_crash_filename = "last_crash"; + bool sentry__write_crash_marker(const sentry_options_t *options) { @@ -244,7 +246,7 @@ sentry__write_crash_marker(const sentry_options_t *options) } sentry_path_t *marker_path - = sentry__path_join_str(options->database_path, "last_crash"); + = sentry__path_join_str(options->database_path, g_last_crash_filename); if (!marker_path) { sentry_free(iso_time); return false; @@ -260,3 +262,34 @@ sentry__write_crash_marker(const sentry_options_t *options) } return !rv; } + +bool +sentry__has_crash_marker(const sentry_options_t *options) +{ + sentry_path_t *marker_path + = sentry__path_join_str(options->database_path, g_last_crash_filename); + if (!marker_path) { + return false; + } + + bool result = sentry__path_is_file(marker_path); + sentry__path_free(marker_path); + return result; +} + +bool +sentry__clear_crash_marker(const sentry_options_t *options) +{ + sentry_path_t *marker_path + = sentry__path_join_str(options->database_path, g_last_crash_filename); + if (!marker_path) { + return false; + } + + int rv = sentry__path_remove(marker_path); + sentry__path_free(marker_path); + if (rv) { + SENTRY_DEBUG("removing the crash timestamp file has failed"); + } + return !rv; +} diff --git a/src/sentry_database.h b/src/sentry_database.h index 086ebab8e..2d99bedb0 100644 --- a/src/sentry_database.h +++ b/src/sentry_database.h @@ -75,4 +75,14 @@ void sentry__process_old_runs( */ bool sentry__write_crash_marker(const sentry_options_t *options); +/** + * This will check whether the `/last_crash` file exists. + */ +bool sentry__has_crash_marker(const sentry_options_t *options); + +/** + * This will remove the `/last_crash` file. + */ +bool sentry__clear_crash_marker(const sentry_options_t *options); + #endif diff --git a/tests/unit/test_basic.c b/tests/unit/test_basic.c index dc85df926..ca257f59d 100644 --- a/tests/unit/test_basic.c +++ b/tests/unit/test_basic.c @@ -1,5 +1,7 @@ #include "sentry_core.h" +#include "sentry_database.h" #include "sentry_testsupport.h" +#include "sentry_utils.h" static void send_envelope_test_basic(const sentry_envelope_t *envelope, void *data) @@ -96,3 +98,70 @@ SENTRY_TEST(sampling_before_send) // well, its random after all TEST_CHECK(called_beforesend > 50 && called_beforesend < 100); } + +SENTRY_TEST(crash_marker) +{ + sentry_options_t *options = sentry_options_new(); + + // clear returns true, regardless if the file exists + TEST_CHECK(sentry__clear_crash_marker(options)); + + // write should normally be true, even when called multiple times + TEST_CHECK(!sentry__has_crash_marker(options)); + TEST_CHECK(sentry__write_crash_marker(options)); + TEST_CHECK(sentry__has_crash_marker(options)); + TEST_CHECK(sentry__write_crash_marker(options)); + TEST_CHECK(sentry__has_crash_marker(options)); + + TEST_CHECK(sentry__clear_crash_marker(options)); + TEST_CHECK(!sentry__has_crash_marker(options)); + TEST_CHECK(sentry__clear_crash_marker(options)); + + sentry_options_free(options); +} + +SENTRY_TEST(crashed_last_run) +{ + // fails before init() is called + TEST_CHECK_INT_EQUAL(sentry_clear_crashed_last_run(), 1); + + // clear any leftover from previous test runs + sentry_options_t *options = sentry_options_new(); + TEST_CHECK(sentry__clear_crash_marker(options)); + sentry_options_free(options); + + // -1 before sentry_init() + TEST_CHECK_INT_EQUAL(sentry_get_crashed_last_run(), -1); + + options = sentry_options_new(); + sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); + TEST_CHECK_INT_EQUAL(sentry_init(options), 0); + sentry_close(); + + TEST_CHECK_INT_EQUAL(sentry_get_crashed_last_run(), 0); + + options = sentry_options_new(); + sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); + + // simulate a crash + TEST_CHECK(sentry__write_crash_marker(options)); + + TEST_CHECK_INT_EQUAL(sentry_init(options), 0); + + TEST_CHECK_INT_EQUAL(sentry_get_crashed_last_run(), 1); + + // clear the status and re-init + TEST_CHECK_INT_EQUAL(sentry_clear_crashed_last_run(), 0); + + sentry_close(); + + // no change yet before sentry_init() is called + TEST_CHECK_INT_EQUAL(sentry_get_crashed_last_run(), 1); + + options = sentry_options_new(); + sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); + TEST_CHECK_INT_EQUAL(sentry_init(options), 0); + sentry_close(); + + TEST_CHECK_INT_EQUAL(sentry_get_crashed_last_run(), 0); +} diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 6414d109e..268010e22 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -15,6 +15,8 @@ XX(child_spans) XX(concurrent_init) XX(concurrent_uninit) XX(count_sampled_events) +XX(crash_marker) +XX(crashed_last_run) XX(custom_logger) XX(distributed_headers) XX(drop_unfinished_spans)