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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

**Features**:

- Add `sentry_options_set_tags` for configuring tags before the crash backend is started, including out-of-process crash handlers. ([#2087](https://github.com/getsentry/sentry-native/pull/2087))
- Add `sentry_attachment_from_file/bytes` (and their wide-string variants) for creating attachment values that can be fully configured before they are added. ([#2079](https://github.com/getsentry/sentry-native/pull/2079))
- Add `sentry_add_attachment`, `sentry_scope_add_attachment`, and `sentry_hint_add_attachment` for adding configured attachments to the global scope, a specific scope, or a hint. ([#2079](https://github.com/getsentry/sentry-native/pull/2079))

Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ The example currently supports the following commands:
- `clear-attachments`: Clears all attachments from the global scope.
- `capture-user-feedback`: Captures a user feedback event.
- `test-logger`: Sets up a test logger for integration tests that outputs in a format the integration tests can parse.
- `initial-tags`: Configures a test tag on the initial scope before SDK initialization.
- `disable-logger-when-crashed`: Disables logging during crash handling.
- `enable-logger-when-crashed`: Explicitly enables logging during crash handling (default behavior).
- `test-logger-before-crash`: Outputs marker directly using printf for test parsing before crash.
Expand Down
14 changes: 14 additions & 0 deletions examples/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,13 @@ on_crashed_last_run_callback(const sentry_envelope_t *envelope, 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 : "");
sentry_value_t event = sentry_envelope_get_event(envelope);
sentry_value_t tags = sentry_value_get_by_key(event, "tags");
const char *initial_tag = sentry_value_as_string(
sentry_value_get_by_key(tags, "test.initial-tag"));
if (initial_tag) {
printf("CRASHED_LAST_RUN_INITIAL_TAG:%s\n", initial_tag);
}
fflush(stdout);
}

Expand Down Expand Up @@ -838,6 +845,13 @@ main(int argc, char **argv)
sentry_options_set_crashpad_wait_for_upload(options, true);
}

if (has_arg(argc, argv, "initial-tags")) {
sentry_value_t tags = sentry_value_new_object();
sentry_value_set_by_key(
tags, "test.initial-tag", sentry_value_new_string("initial-value"));
sentry_options_set_tags(options, tags);
}

if (has_arg(argc, argv, "test-logger")) {
// Set up the test logger for integration tests
sentry_options_set_logger(options, test_logger_callback, NULL);
Expand Down
12 changes: 12 additions & 0 deletions include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -1426,6 +1426,18 @@ SENTRY_API sentry_options_t *sentry_options_new(void);
*/
SENTRY_API void sentry_options_free(sentry_options_t *opts);

/**
* Configures tags to add to the initial scope before the crash backend is
* started.
*
* `tags` must be an object. Values that are not strings are ignored. Calling
* this function again replaces the previously configured tags.
*
* The function takes ownership of `tags`.
*/
SENTRY_API void sentry_options_set_tags(
sentry_options_t *opts, sentry_value_t tags);

/**
* Sets a transport.
*/
Expand Down
50 changes: 36 additions & 14 deletions src/backends/sentry_backend_crashpad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,9 +229,24 @@ static int
write_attachment(crashpad_state_t *state, const base::FilePath &path,
const char *data, size_t size)
{
if (path.empty() || !state || !state->client) {
if (path.empty() || !state) {
return 1;
}
if (!state->client) {
#ifdef SENTRY_PLATFORM_WINDOWS
sentry_path_t *sentry_path
= sentry__path_from_wstr(path.value().c_str());
#else
sentry_path_t *sentry_path
= sentry__path_from_str(path.value().c_str());
#endif
if (!sentry_path) {
return 1;
}
int rv = sentry__path_write_buffer(sentry_path, data, size);
sentry__path_free(sentry_path);
return rv;
}
return state->client->WriteAttachment(
path, base::as_bytes(base::make_span(data, size)))
? 0
Expand Down Expand Up @@ -346,6 +361,22 @@ to_sentry_level(logging::LogSeverity severity)
return SENTRY_LEVEL_DEBUG;
}

static void
flush_scope_attachments(crashpad_state_t *data, const sentry_options_t *options)
{
sentry_value_t event = sentry_value_new_object();
sentry_value_set_by_key(
event, "event_id", sentry__value_new_uuid(&data->crash_event_id));
sentry_value_set_by_key(
event, "level", sentry__value_new_level(SENTRY_LEVEL_FATAL));

flush_scope_to_event(data, data->event_path, options, event);
if (!data->external_report_path.empty()) {
flush_external_crash_report(
data, data->external_report_path, options, &data->crash_event_id);
}
}

// This function is necessary for macOS since it has no `FirstChanceHandler`.
// but it is also necessary on Windows if the WER handler is enabled.
// This means we have to continuously flush the scope on
Expand All @@ -371,19 +402,7 @@ crashpad_backend_flush_scope(
return;
}

sentry_value_t event = sentry_value_new_object();
sentry_value_set_by_key(
event, "event_id", sentry__value_new_uuid(&data->crash_event_id));
// Since this will only be uploaded in case of a crash we must make this
// event fatal.
sentry_value_set_by_key(
event, "level", sentry__value_new_level(SENTRY_LEVEL_FATAL));

flush_scope_to_event(data, data->event_path, options, event);
if (!data->external_report_path.empty()) {
flush_external_crash_report(
data, data->external_report_path, options, &data->crash_event_id);
}
flush_scope_attachments(data, options);
data->scope_flush.store(false, std::memory_order_release);
#endif
}
Expand Down Expand Up @@ -913,6 +932,9 @@ crashpad_backend_startup(
}
}

// Persist the preloaded scope before Crashpad starts handling crashes.
flush_scope_attachments(data, options);

std::vector<std::string> arguments { "--no-rate-limit" };
sentry_path_t *log_path
= sentry__path_join_str(current_run_folder, "crashpad-handler.log");
Expand Down
6 changes: 6 additions & 0 deletions src/backends/sentry_backend_native.c
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,9 @@ typedef struct {
volatile long crashed;
} native_backend_state_t;

static void native_backend_flush_scope(
sentry_backend_t *backend, const sentry_options_t *options);

static bool
native_backend_process_old_run(sentry_backend_t *backend,
const sentry_options_t *options, const sentry_path_t *run_path)
Expand Down Expand Up @@ -810,6 +813,9 @@ native_backend_startup(
}
#endif

// Persist the preloaded scope before any crash handler becomes active.
native_backend_flush_scope(backend, options);

// Install crash handlers (signal handlers on Linux/macOS, Mach exception
// handler on iOS)
#if defined(SENTRY_PLATFORM_IOS)
Expand Down
10 changes: 10 additions & 0 deletions src/integrations/sentry_integration_wer.c
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,15 @@ wer_attachment_path(const sentry_attachment_t *attachment)
return absolute_path;
}

static int
wer_sync_tag(const char *key, sentry_value_t value, void *data)
{
if (sentry_value_get_type(value) == SENTRY_VALUE_TYPE_STRING) {
wer_set_tag(data, key, sentry_value_as_string(value));
}
return 0;
}

static void
wer_add_attachment(void *UNUSED(data), sentry_attachment_t *attachment)
{
Expand Down Expand Up @@ -238,6 +247,7 @@ register_wer(
if (sentry__scope_add_observer(scope, observer)) {
wer_data->scope = scope;
wer_data->observer = observer;
sentry_value_foreach_key_value(scope->tags, wer_sync_tag, wer_data);
for (sentry_attachment_t *attachment = scope->attachments; attachment;
attachment = attachment->next) {
wer_add_attachment(wer_data, attachment);
Expand Down
13 changes: 13 additions & 0 deletions src/sentry_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ sentry_init(sentry_options_t *options)
{
// pre-init here, so we can consistently use bailing out to :fail
sentry_transport_t *transport = NULL;
bool initial_scope_tags_applied = false;

SENTRY__MUTEX_INIT_DYN_ONCE(g_options_lock);
// Stop the app hang watchdog before locking options. The watchdog thread
Expand Down Expand Up @@ -248,6 +249,15 @@ sentry_init(sentry_options_t *options)
sentry__init_cached_kernel32_functions();
#endif

if (!sentry_value_is_null(options->initial_scope_tags)) {
sentry_value_t tags = options->initial_scope_tags;
options->initial_scope_tags = sentry_value_new_null();
SENTRY_WITH_SCOPE_MUT_NO_FLUSH (scope) {
sentry_scope_set_tags(scope, tags);
}
initial_scope_tags_applied = true;
}

// and then we will start the backend, since it requires a valid run
sentry_backend_t *backend = options->backend;
if (backend && backend->startup_func) {
Expand Down Expand Up @@ -342,6 +352,9 @@ sentry_init(sentry_options_t *options)
sentry__transport_shutdown(transport, 0);
}
sentry_options_free(options);
if (initial_scope_tags_applied) {
sentry__scope_cleanup();
}
sentry__mutex_unlock(&g_options_lock);
return 1;
}
Expand Down
15 changes: 15 additions & 0 deletions src/sentry_options.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ sentry_options_new(void)
return NULL;
}
opts->database_path = sentry__path_from_str(".sentry-native");
opts->initial_scope_tags = sentry_value_new_null();
// we assume the DSN to be ASCII only
sentry_options_set_dsn(opts, getenv("SENTRY_DSN"));
const char *debug = getenv("SENTRY_DEBUG");
Expand Down Expand Up @@ -186,6 +187,7 @@ sentry_options_free(sentry_options_t *opts)
sentry__path_free(opts->database_path);
sentry__path_free(opts->handler_path);
sentry__path_free(opts->external_crash_reporter);
sentry_value_decref(opts->initial_scope_tags);
sentry_transport_free(opts->transport);
sentry__backend_free(opts->backend);
sentry__attachments_free(opts->attachments);
Expand All @@ -198,6 +200,19 @@ sentry_options_free(sentry_options_t *opts)
sentry_free(opts);
}

void
sentry_options_set_tags(sentry_options_t *opts, sentry_value_t tags)
{
sentry_value_decref(opts->initial_scope_tags);
if (sentry_value_get_type(tags) == SENTRY_VALUE_TYPE_OBJECT) {
opts->initial_scope_tags = tags;
} else {
SENTRY_WARN("initial tags must be an object");
sentry_value_decref(tags);
opts->initial_scope_tags = sentry_value_new_null();
}
}

void
sentry_options_set_transport(
sentry_options_t *opts, sentry_transport_t *transport)
Expand Down
1 change: 1 addition & 0 deletions src/sentry_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ struct sentry_options_s {
sentry_path_t *database_path;
sentry_path_t *handler_path;
sentry_path_t *external_crash_reporter;
sentry_value_t initial_scope_tags;
sentry_logger_t logger;
size_t max_breadcrumbs;
bool debug;
Expand Down
28 changes: 27 additions & 1 deletion tests/test_integration_crashpad.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def test_crashpad_on_crashed_last_run(cmake):
run(
tmp_path,
"sentry_example",
["log", "crash"],
[*args, "initial-tags", "crash"],
expect_failure=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
Expand Down Expand Up @@ -125,6 +125,7 @@ def test_crashpad_on_crashed_last_run(cmake):
]
assert len(callbacks) == 1
assert len(callbacks[0].partition(b":")[2]) == 36
assert b"CRASHED_LAST_RUN_INITIAL_TAG:initial-value" in restarted.stdout

restarted_again = run(
tmp_path,
Expand Down Expand Up @@ -169,6 +170,31 @@ def test_crashpad_codeview(cmake, httpserver):
assert any(identifier)


@pytest.mark.skipif(
sys.platform != "win32" or bool(os.environ.get("TEST_MINGW")),
reason="fast-fail is only available in MSVC Windows builds",
)
@pytest.mark.with_wer
def test_crashpad_initial_tags_fastfail(cmake, httpserver):
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"})

httpserver.expect_oneshot_request("/api/123456/minidump/").respond_with_data("OK")

with httpserver.wait(timeout=10) as waiting:
run(
tmp_path,
"sentry_example",
["initial-tags", "crashpad-wait-for-upload", "fastfail"],
expect_failure=True,
env=dict(os.environ, SENTRY_DSN=make_dsn(httpserver)),
)

assert waiting.result
assert len(httpserver.log) == 1
attachments = assert_crashpad_upload(httpserver.log[0][0])
assert attachments.event["tags"]["test.initial-tag"] == "initial-value"


def _setup_crashpad_proxy_test(cmake, httpserver, proxy):
if proxy:
proxy_process, port = start_proxy(proxy)
Expand Down
3 changes: 2 additions & 1 deletion tests/test_integration_native.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def test_native_on_crashed_last_run(cmake, httpserver):
run_crash(
tmp_path,
"sentry_example",
[*args, "crash"],
[*args, "initial-tags", "crash"],
env=env,
wait_for_daemon=True,
stdout=subprocess.PIPE,
Expand Down Expand Up @@ -126,6 +126,7 @@ def test_native_on_crashed_last_run(cmake, httpserver):
if line.startswith(b"CRASHED_LAST_RUN:")
]
assert callbacks == [f"CRASHED_LAST_RUN:{event_id}".encode()]
assert b"CRASHED_LAST_RUN_INITIAL_TAG:initial-value" in restarted.stdout
assert len(httpserver.log) == 1
assert not list(db_dir.glob("*.run"))
assert not list(db_dir.glob("*.run*.lock"))
Expand Down
5 changes: 5 additions & 0 deletions tests/test_integration_wer.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,14 @@ def assert_sentry_event(httpserver, backend, crash_arg):
assert httpserver.log[0][0].path == "/api/123456/minidump/"
attachments = assert_crashpad_upload(httpserver.log[0][0])
assert attachments.event["event_id"]
assert attachments.event["tags"]["test.initial-tag"] == "initial-value"
return attachments.event

envelope = Envelope.deserialize(httpserver.log[0][0].get_data())
event = envelope.get_event()
assert event is not None
assert event["event_id"]
assert event["tags"]["test.initial-tag"] == "initial-value"
assert_event_meta(event, integrations=[backend, "wer"])

if backend == "inproc":
Expand Down Expand Up @@ -262,6 +264,7 @@ def run_wer_crash(cmake, backend, crash_arg, httpserver=None, appx=False):
if appx:
run_args.append("appx")
run_args.append(crash_arg)
run_args.append("initial-tags")
if backend == "crashpad":
run_args.append("crashpad-wait-for-upload")

Expand Down Expand Up @@ -363,6 +366,8 @@ def run_wer_crash(cmake, backend, crash_arg, httpserver=None, appx=False):
def test_wer_custom_metadata(cmake, backend):
report = run_wer_crash(cmake, backend, "crash")

assert "test.initial-tag" in report
assert "initial-value" in report
assert "expected-tag" in report
assert "some value" in report
assert "not-expected-tag" not in report
Expand Down
Loading
Loading