diff --git a/CHANGELOG.md b/CHANGELOG.md index f842f2021..044b15050 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +**Features**: + +- Add `sentry_capture_minidump()` to capture independently created minidumps ([#1067](https://github.com/getsentry/sentry-native/pull/1067)) **Fixes**: diff --git a/CMakeLists.txt b/CMakeLists.txt index 426bd0682..770ce074d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -582,6 +582,9 @@ if(SENTRY_BUILD_EXAMPLES) set_target_properties(sentry_example PROPERTIES FOLDER ${SENTRY_FOLDER}) endif() + add_custom_command(TARGET sentry_example POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/tests/fixtures/minidump.dmp" "$/minidump.dmp") + add_test(NAME sentry_example COMMAND sentry_example) endif() diff --git a/examples/example.c b/examples/example.c index 5c87b3f32..750dffec1 100644 --- a/examples/example.c +++ b/examples/example.c @@ -428,6 +428,10 @@ main(int argc, char **argv) sentry_transaction_finish(tx); } + if (has_arg(argc, argv, "capture-minidump")) { + sentry_capture_minidump("minidump.dmp"); + } + // make sure everything flushes sentry_close(); diff --git a/include/sentry.h b/include/sentry.h index 0d3b8b83b..85baf2ac1 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1362,6 +1362,16 @@ SENTRY_API sentry_user_consent_t sentry_user_consent_get(void); */ SENTRY_API sentry_uuid_t sentry_capture_event(sentry_value_t event); +/** + * Allows capturing independently created minidumps. + * + * This generates a fatal error event, includes the scope and attachments. + * If the event isn't dropped by a before-send hook, the minidump is attached + * and the event is sent. + */ +SENTRY_API void sentry_capture_minidump(const char *path); +SENTRY_API void sentry_capture_minidump_n(const char *path, size_t path_len); + /** * Captures an exception to be handled by the backend. * diff --git a/src/sentry_core.c b/src/sentry_core.c index 3b75cb82e..ebd8ec7a9 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -1174,3 +1174,60 @@ sentry_clear_crashed_last_run(void) sentry__options_unlock(); return success ? 0 : 1; } + +void +sentry_capture_minidump(const char *path) +{ + sentry_capture_minidump_n(path, sentry__guarded_strlen(path)); +} + +void +sentry_capture_minidump_n(const char *path, size_t path_len) +{ + sentry_path_t *dump_path = sentry__path_from_str_n(path, path_len); + + if (!dump_path) { + SENTRY_WARN( + "sentry_capture_minidump() failed due to null path to minidump"); + return; + } + + SENTRY_DEBUGF( + "Capturing minidump \"%" SENTRY_PATH_PRI "\"", dump_path->path); + + sentry_value_t event = sentry_value_new_event(); + sentry_value_set_by_key( + event, "level", sentry__value_new_level(SENTRY_LEVEL_FATAL)); + + SENTRY_WITH_OPTIONS (options) { + sentry_envelope_t *envelope + = sentry__prepare_event(options, event, NULL, true); + + if (envelope) { + // 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))); + } + + sentry__capture_envelope(options->transport, envelope); + + SENTRY_DEBUGF("Minidump has been captured: \"%" SENTRY_PATH_PRI + "\"", + dump_path->path); + } + } + + sentry__path_free(dump_path); +} diff --git a/tests/fixtures/minidump.dmp b/tests/fixtures/minidump.dmp new file mode 100644 index 000000000..70b2ef7e8 Binary files /dev/null and b/tests/fixtures/minidump.dmp differ diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index 6bb34fbe4..ad75ab9b6 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -1,6 +1,7 @@ import itertools import json import os +import shutil import time import uuid @@ -573,3 +574,35 @@ def test_transaction_only(cmake, httpserver, build_args): assert start_timestamp timestamp = time.strptime(payload["timestamp"], RFC3339_FORMAT) assert timestamp >= start_timestamp + + +def test_capture_minidump(cmake, httpserver): + tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"}) + + # make sure we are isolated from previous runs + shutil.rmtree(tmp_path / ".sentry-native", ignore_errors=True) + + httpserver.expect_oneshot_request( + "/api/123456/envelope/", + headers={"x-sentry-auth": auth_header}, + ).respond_with_data("OK") + + run( + tmp_path, + "sentry_example", + ["log", "attachment", "capture-minidump"], + check=True, + env=dict(os.environ, SENTRY_DSN=make_dsn(httpserver)), + ) + + assert len(httpserver.log) == 1 + + req = httpserver.log[0][0] + body = req.get_data() + + envelope = Envelope.deserialize(body) + + assert_breadcrumb(envelope) + assert_attachment(envelope) + + assert_minidump(envelope)