diff --git a/CHANGELOG.md b/CHANGELOG.md index 8def114fe..46cddeffb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unrelaased + +**Features**: + +- Add ability to capture independently created minidumps ([#1052](https://github.com/getsentry/sentry-native/pull/1052)) + ## 0.7.11 **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 42e76231e..f9a45c227 100644 --- a/examples/example.c +++ b/examples/example.c @@ -428,6 +428,13 @@ main(int argc, char **argv) sentry_transaction_finish(tx); } + if (has_arg(argc, argv, "capture-minidump")) { + sentry_value_t event = sentry_value_new_message_event( + SENTRY_LEVEL_INFO, "my-logger", "Hello Minidump!"); + sentry_capture_minidump("minidump.dmp", event, 0); + sentry_capture_event(event); + } + // make sure everything flushes sentry_close(); diff --git a/include/sentry.h b/include/sentry.h index 0d3b8b83b..52d7c45a3 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1362,6 +1362,14 @@ 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. + */ +SENTRY_API void sentry_capture_minidump( + const char *dump_path, sentry_value_t event, int remove_dump_on_send); +SENTRY_API void sentry_capture_minidump_n(const char *dump_path, + size_t dump_path_len, sentry_value_t event, int remove_dump_on_send); + /** * Captures an exception to be handled by the backend. * diff --git a/src/sentry_core.c b/src/sentry_core.c index 4a74f8978..6bce69237 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -501,7 +501,7 @@ sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, SENTRY_TRACE("adding attachments to envelope"); for (sentry_attachment_t *attachment = options->attachments; attachment; - attachment = attachment->next) { + attachment = attachment->next) { sentry_envelope_item_t *item = sentry__envelope_add_from_path( envelope, attachment->path, "attachment"); if (!item) { @@ -1174,3 +1174,76 @@ sentry_clear_crashed_last_run(void) sentry__options_unlock(); return success ? 0 : 1; } + +void +sentry_capture_minidump( + const char *dump_path, sentry_value_t event, int remove_dump_on_send) +{ + sentry_capture_minidump_n(dump_path, sentry__guarded_strlen(dump_path), + event, remove_dump_on_send); +} + +void +sentry_capture_minidump_n(const char *dump_path, size_t dump_path_len, + sentry_value_t event, int remove_dump_on_send) +{ + sentry_path_t *sentry_dump_path + = sentry__path_from_str_n(dump_path, dump_path_len); + + if (sentry_dump_path == NULL) { + SENTRY_WARN( + "sentry_capture_minidump() failed due to null path to minidump"); + return; + } + + SENTRY_WITH_OPTIONS (options) { + sentry__ensure_event_id(event, NULL); + sentry_envelope_t *envelope = sentry__envelope_new(); + if (!envelope || !sentry__envelope_add_event(envelope, event)) { + sentry_envelope_free(envelope); + sentry_value_decref(event); + sentry__path_free(sentry_dump_path); + return; + } + + SENTRY_TRACE("adding attachments to envelope"); + for (sentry_attachment_t *attachment = options->attachments; attachment; + attachment = attachment->next) { + sentry_envelope_item_t *item = sentry__envelope_add_from_path( + envelope, attachment->path, "attachment"); + if (!item) { + continue; + } + 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(attachment->path))); + } + + sentry_envelope_item_t *item = sentry__envelope_add_from_path( + envelope, sentry_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(sentry_dump_path))); + } + + sentry__capture_envelope(options->transport, envelope); + + if (remove_dump_on_send) { + sentry__path_remove(sentry_dump_path); + } + + sentry__path_free(sentry_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_capture_minidump.py b/tests/test_capture_minidump.py new file mode 100644 index 000000000..8b13648c7 --- /dev/null +++ b/tests/test_capture_minidump.py @@ -0,0 +1,64 @@ +import itertools +import json +import os +import time +import uuid + +import pytest + +from . import make_dsn, run, Envelope +from .assertions import ( + assert_attachment, + assert_meta, + assert_breadcrumb, + assert_stacktrace, + assert_event, + assert_exception, + assert_inproc_crash, + assert_session, + assert_user_feedback, + assert_minidump, + assert_breakpad_crash, + assert_gzip_content_encoding, + assert_gzip_file_header, +) +from .conditions import has_http, has_breakpad, has_files + +pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") + +# fmt: off +auth_header = ( + "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.7.10" +) +# fmt: on + +def test_capture_minidump(cmake, httpserver, build_args): + build_args.update({"SENTRY_BACKEND": "none"}) + tmp_path = cmake(["sentry_example"], build_args) + + httpserver.expect_oneshot_request( + "/api/123456/envelope/", + headers={"x-sentry-auth": auth_header}, + ).respond_with_data("OK") + env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver), SENTRY_RELEASE="🤮🚀") + + run( + tmp_path, + "sentry_example", + ["log", "release-env", "capture-minidump"], + check=True, + env=env, + ) + + assert len(httpserver.log) == 1 + req = httpserver.log[0][0] + body = req.get_data() + + envelope = Envelope.deserialize(body) + + assert_meta(envelope, "🤮🚀") + assert_breadcrumb(envelope) + assert_stacktrace(envelope) + + assert_event(envelope) +