From b0804e19f9db25fa89889bf61859d4496e180d70 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 15 Sep 2026 15:45:43 +0200 Subject: [PATCH 1/4] feat!: Add hint support to event capture Accept hints in `sentry_scope_capture_event` and pass them to `before_send`. Keep `sentry_capture_event` unchanged. Merge hint and scope attachments before `before_send` and `before_send_feedback`, then use the resulting hint attachments in the envelope. Add `sentry_hint_remove_attachment` and `sentry_hint_clear_attachments` to filter attachments without modifying scopes. BREAKING CHANGE: `before_send` callbacks now take `sentry_hint_t *` instead of `void *` for the hint parameter. `sentry_scope_capture_event` now requires a hint argument; pass `NULL` when no hint is needed. Scope attachment changes inside either callback no longer affect the current event; modify the hint instead. Close: #2098 --- CHANGELOG.md | 4 + examples/example.c | 6 +- include/sentry.h | 44 ++++-- src/backends/sentry_backend_breakpad.cpp | 4 +- src/backends/sentry_backend_inproc.c | 2 +- src/sentry_app_hang_monitor.c | 2 +- src/sentry_core.c | 129 +++++++-------- src/sentry_core.h | 4 +- src/sentry_hint.c | 18 +++ tests/unit/test_app_hang.c | 4 +- tests/unit/test_attachments.c | 9 +- tests/unit/test_basic.c | 192 ++++++++++++++++++++++- tests/unit/test_scope.c | 33 ++-- tests/unit/test_tracing.c | 2 +- tests/unit/tests.inc | 3 + 15 files changed, 350 insertions(+), 106 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 001371ca3c..39fc0f4b6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ - Attachment APIs now use `sentry_value_t` and `sentry_uuid_t` instead of `sentry_attachment_t *` handles. Most attachment APIs, function names and arguments, are otherwise unchanged. ([#1974](https://github.com/getsentry/sentry-native/pull/1974)) - `sentry_init()` now consumes `/last_crash` after caching its value, aligning crashed-last-run behavior with other Sentry SDKs. ([#2023](https://github.com/getsentry/sentry-native/pull/2023)) +- Change the `hint` parameter of `before_send` callbacks (`sentry_event_function_t`) from `void *` to `sentry_hint_t *`. Update callbacks registered with `sentry_options_set_before_send` to use the new parameter type. ([#2099](https://github.com/getsentry/sentry-native/pull/2099)) +- Add a `sentry_hint_t *hint` argument to `sentry_scope_capture_event`. Pass `NULL` if no hint is needed. ([#2099](https://github.com/getsentry/sentry-native/pull/2099)) - Remove `sentry_options_get/set_enable_logs` and `sentry_options_get/set_enable_metrics`. ([#1980](https://github.com/getsentry/sentry-native/pull/1980)) > Structured logs and metrics have been enabled by default since `0.13`, and the options were deprecated and made no-ops in `0.16`. > @@ -16,10 +18,12 @@ - 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. These functions consume and freeze the attachment value. ([#2079](https://github.com/getsentry/sentry-native/pull/2079), [#1974](https://github.com/getsentry/sentry-native/pull/1974)) - Add `sentry_start_new_trace()` as a clearer name for starting a new trace. ([#2095](https://github.com/getsentry/sentry-native/pull/2095)) +- Add hint support to `sentry_scope_capture_event` to pass event-specific attachments. The hint is also passed to `before_send`, which can modify attachments before the event is sent. ([#2099](https://github.com/getsentry/sentry-native/pull/2099)) **Fixes**: - Reduce lock contention for multi-threaded log and metric capture by allowing concurrent reads of scope data, and reusing a single options reference throughout each capture. ([#2091](https://github.com/getsentry/sentry-native/pull/2091), [#2094](https://github.com/getsentry/sentry-native/pull/2094)) +- Add `sentry_hint_remove_attachment` and `sentry_hint_clear_attachments` to filter attachments without modifying scopes. ([#2099](https://github.com/getsentry/sentry-native/pull/2099)) **Other changes**: diff --git a/examples/example.c b/examples/example.c index 5a16a1cfaa..420d82786a 100644 --- a/examples/example.c +++ b/examples/example.c @@ -127,7 +127,7 @@ traces_sampler_callback(const sentry_transaction_context_t *transaction_ctx, } static sentry_value_t -before_send_callback(sentry_value_t event, void *hint, void *user_data) +before_send_callback(sentry_value_t event, sentry_hint_t *hint, void *user_data) { (void)hint; (void)user_data; @@ -142,7 +142,7 @@ before_send_callback(sentry_value_t event, void *hint, void *user_data) static sentry_value_t discarding_before_send_callback( - sentry_value_t event, void *hint, void *user_data) + sentry_value_t event, sentry_hint_t *hint, void *user_data) { (void)hint; (void)user_data; @@ -1247,7 +1247,7 @@ main(int argc, char **argv) sentry_scope_add_attachment(scope, bytes); } - sentry_scope_capture_event(scope, event); + sentry_scope_capture_event(scope, event, NULL); } if (has_arg(argc, argv, "capture-multiple")) { diff --git a/include/sentry.h b/include/sentry.h index 2516a83228..8725f4e85b 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1478,6 +1478,13 @@ SENTRY_API void sentry_options_set_send_default_pii( sentry_options_t *opts, int value); #endif +/** + * A hint that can be passed to capture functions to provide additional context, + * such as attachments. + */ +struct sentry_hint_s; +typedef struct sentry_hint_s sentry_hint_t; + /** * Type of the `before_send` callback. * @@ -1486,6 +1493,9 @@ SENTRY_API void sentry_options_set_send_default_pii( * call `sentry_value_decref` on the provided event and return a * `sentry_value_new_null()` instead. * + * The hint is always provided and can be used to modify attachments on the + * event. + * * If you have set an `on_crash` callback (independent of whether it discards or * retains the event), `before_send` will no longer be invoked for crash-events, * which allows you to better distinguish between crashes and all other events @@ -1510,7 +1520,7 @@ SENTRY_API void sentry_options_set_send_default_pii( * though a crash report will be sent. */ typedef sentry_value_t (*sentry_event_function_t)( - sentry_value_t event, void *hint, void *user_data); + sentry_value_t event, sentry_hint_t *hint, void *user_data); /** * Sets the `before_send` callback. @@ -2592,7 +2602,10 @@ SENTRY_API sentry_uuid_t sentry_scope_get_last_event_id( SENTRY_API sentry_uuid_t sentry_capture_event(sentry_value_t event); /** - * Sends a sentry event with a scope. + * Sends a sentry event with a scope and a hint. + * + * This function takes ownership of the event and hint, which will be freed + * automatically. The hint may be NULL. * * If `scope` is a local scope (`sentry_local_scope_new`), this takes ownership * of it and frees it. If `scope` is user-owned (`sentry_scope_new` or @@ -2600,7 +2613,7 @@ SENTRY_API sentry_uuid_t sentry_capture_event(sentry_value_t event); * it yourself with `sentry_scope_free`. */ SENTRY_API sentry_uuid_t sentry_scope_capture_event( - sentry_scope_t *scope, sentry_value_t event); + sentry_scope_t *scope, sentry_value_t event, sentry_hint_t *hint); /** * Deprecated alias for `sentry_scope_capture_event`. Note the reversed argument @@ -4139,16 +4152,7 @@ SENTRY_API sentry_value_t sentry_value_new_feedback_n(const char *message, SENTRY_API void sentry_capture_feedback(sentry_value_t user_feedback); /** - * A hint that can be passed to capture functions to provide additional context, - * such as attachments. - */ -struct sentry_hint_s; -typedef struct sentry_hint_s sentry_hint_t; - -/** - * Creates a new hint to be passed into - * - `sentry_capture_feedback_with_hint` - * - `sentry_scope_capture_feedback` + * Creates a new hint to be passed into capture functions. */ SENTRY_API sentry_hint_t *sentry_hint_new(void); @@ -4202,6 +4206,17 @@ SENTRY_API sentry_uuid_t sentry_hint_attach_bytesw_n(sentry_hint_t *hint, size_t filename_len); #endif +/** + * Removes an attachment from the hint by its ID. Does not modify scopes. + */ +SENTRY_API void sentry_hint_remove_attachment( + sentry_hint_t *hint, sentry_uuid_t attachment_id); + +/** + * Removes all attachments from the hint. Does not modify scopes. + */ +SENTRY_API void sentry_hint_clear_attachments(sentry_hint_t *hint); + /** * Captures a manually created feedback with a hint and sends it to Sentry. * @@ -4237,7 +4252,8 @@ SENTRY_API sentry_uuid_t sentry_scope_capture_feedback( * callback needs to call `sentry_value_decref` on the provided event and * return a `sentry_value_new_null()` instead. * - * The hint is always provided and can be used to add attachments to the event. + * The hint is always provided and can be used to modify attachments on the + * event. * * Feedback events do not go through the `before_send` callback. */ diff --git a/src/backends/sentry_backend_breakpad.cpp b/src/backends/sentry_backend_breakpad.cpp index f8a913f9b9..58b68b54b3 100644 --- a/src/backends/sentry_backend_breakpad.cpp +++ b/src/backends/sentry_backend_breakpad.cpp @@ -183,8 +183,8 @@ breakpad_backend_callback(const google_breakpad::MinidumpDescriptor &descriptor, } #endif - sentry_envelope_t *envelope = sentry__prepare_event( - options, event, nullptr, !options->on_crash_func, nullptr); + sentry_envelope_t *envelope = sentry__prepare_event(options, event, + nullptr, !options->on_crash_func, nullptr, nullptr); if (envelope) { event_id = sentry__envelope_get_event_id(envelope); } diff --git a/src/backends/sentry_backend_inproc.c b/src/backends/sentry_backend_inproc.c index 150c29201b..a6e2747601 100644 --- a/src/backends/sentry_backend_inproc.c +++ b/src/backends/sentry_backend_inproc.c @@ -1102,7 +1102,7 @@ process_ucontext_deferred(const sentry_ucontext_t *uctx, #endif sentry_envelope_t *envelope = sentry__prepare_event(options, event, - NULL, !options->on_crash_func && !skip_hooks, NULL); + NULL, !options->on_crash_func && !skip_hooks, NULL, NULL); if (envelope) { event_id = sentry__envelope_get_event_id(envelope); } diff --git a/src/sentry_app_hang_monitor.c b/src/sentry_app_hang_monitor.c index dc2d2b672d..da31c83f70 100644 --- a/src/sentry_app_hang_monitor.c +++ b/src/sentry_app_hang_monitor.c @@ -81,7 +81,7 @@ app_hang_capture(uint64_t hang_time_ms, uint64_t tid) return false; } sentry_value_t event = sentry__app_hang_make_event(ips, n, hang_time_ms); - sentry__capture_event(event, NULL); + sentry__capture_event(event, NULL, NULL); return true; } diff --git a/src/sentry_core.c b/src/sentry_core.c index af925b6de1..b637fadc69 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -565,24 +565,26 @@ sentry_capture_event(sentry_value_t event) if (sentry__event_is_transaction(event)) { return sentry_uuid_nil(); } else { - return sentry__capture_event(event, NULL); + return sentry__capture_event(event, NULL, NULL); } } sentry_uuid_t -sentry_scope_capture_event(sentry_scope_t *scope, sentry_value_t event) +sentry_scope_capture_event( + sentry_scope_t *scope, sentry_value_t event, sentry_hint_t *hint) { if (sentry__event_is_transaction(event)) { + sentry__hint_free(hint); return sentry_uuid_nil(); } else { - return sentry__capture_event(event, scope); + return sentry__capture_event(event, scope, hint); } } sentry_uuid_t sentry_capture_event_with_scope(sentry_value_t event, sentry_scope_t *scope) { - return sentry_scope_capture_event(scope, event); + return sentry_scope_capture_event(scope, event, NULL); } #ifndef SENTRY_UNITTEST @@ -597,7 +599,8 @@ static } sentry_uuid_t -sentry__capture_event(sentry_value_t event, sentry_scope_t *local_scope) +sentry__capture_event( + sentry_value_t event, sentry_scope_t *local_scope, sentry_hint_t *hint) { // `event_id` is only used as an argument to pure output parameters. // Initialization only happens to prevent compiler warnings. @@ -612,8 +615,15 @@ sentry__capture_event(sentry_value_t event, sentry_scope_t *local_scope) if (sentry__event_is_transaction(event)) { envelope = sentry__prepare_transaction(options, event, &event_id); } else { + // Give the hook something to attach to when the caller passed no + // hint. + if (!hint && options->before_send_func) { + // A failed allocation is tolerated: operating on a NULL hint + // no-ops. + hint = sentry_hint_new(); + } envelope = sentry__prepare_event( - options, event, &event_id, true, local_scope); + options, event, &event_id, true, local_scope, hint); } if (envelope) { // Accept a racy read here, since SENTRY_WITH_OPTIONS only prevents @@ -663,6 +673,7 @@ sentry__capture_event(sentry_value_t event, sentry_scope_t *local_scope) sentry_value_decref(event); } sentry__scope_free_one_shot(local_scope); + sentry__hint_free(hint); return was_sent ? event_id : sentry_uuid_nil(); } @@ -708,10 +719,36 @@ static return send; } +static sentry_value_t +prepare_attachments(sentry_hint_t *hint, sentry_scope_t *local_scope) +{ + sentry_value_t attachments = sentry_value_new_null(); + if (hint) { + sentry__attachments_extend(&attachments, hint->attachments); + } + if (local_scope) { + sentry_value_t local_attachments + = sentry__scope_load_attachments(local_scope); + sentry__attachments_extend(&attachments, local_attachments); + sentry_value_decref(local_attachments); + } + SENTRY_WITH_SCOPE (scope) { + sentry_value_t global_attachments + = sentry__scope_load_attachments(scope); + sentry__attachments_extend(&attachments, global_attachments); + sentry_value_decref(global_attachments); + } + if (hint) { + sentry_value_decref(hint->attachments); + hint->attachments = sentry_value_incref(attachments); + } + return attachments; +} + sentry_envelope_t * sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, sentry_uuid_t *event_id, bool invoke_before_send, - sentry_scope_t *local_scope) + sentry_scope_t *local_scope, sentry_hint_t *hint) { sentry_envelope_t *envelope = NULL; @@ -719,7 +756,6 @@ sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, sentry__record_errors_on_current_session(1); } - sentry_value_t all_attachments = sentry_value_new_null(); if (local_scope) { SENTRY_DEBUG("merging local scope into event"); sentry_scope_mode_t mode = SENTRY_SCOPE_BREADCRUMBS; @@ -735,14 +771,17 @@ sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, sentry__scope_apply_to_event(scope, options, event, mode); } + sentry_value_t all_attachments = prepare_attachments(hint, local_scope); + if (options->before_send_func && invoke_before_send) { SENTRY_DEBUG("invoking `before_send` hook"); event - = options->before_send_func(event, NULL, options->before_send_data); + = options->before_send_func(event, hint, options->before_send_data); if (sentry_value_is_null(event)) { SENTRY_DEBUG("event was discarded by the `before_send` hook"); sentry__client_report_discard(SENTRY_DISCARD_REASON_BEFORE_SEND, SENTRY_DATA_CATEGORY_ERROR, 1); + sentry_value_decref(all_attachments); return NULL; } } @@ -753,29 +792,11 @@ sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, goto fail; } - SENTRY_WITH_SCOPE (scope) { - sentry_value_t global_attachments - = sentry__scope_load_attachments(scope); - sentry_value_t attachments = global_attachments; - if (local_scope) { - sentry_value_t local_attachments - = sentry__scope_load_attachments(local_scope); - if (sentry_value_get_length(local_attachments) > 0) { - // all attachments merged from multiple scopes - sentry__attachments_extend(&all_attachments, local_attachments); - sentry__attachments_extend( - &all_attachments, global_attachments); - attachments = all_attachments; - } - sentry_value_decref(local_attachments); - } - // otherwise only global scope has attachments - sentry__envelope_add_attachments(envelope, attachments, options); - if (options->run) { - sentry__cache_attachment_refs(envelope, attachments, options, - options->run->cache_path, options->run->run_path); - } - sentry_value_decref(global_attachments); + sentry_value_t attachments = hint ? hint->attachments : all_attachments; + sentry__envelope_add_attachments(envelope, attachments, options); + if (options->run) { + sentry__cache_attachment_refs(envelope, attachments, options, + options->run->cache_path, options->run->run_path); } sentry_value_decref(all_attachments); @@ -873,6 +894,8 @@ prepare_user_feedback(const sentry_options_t *options, sentry__scope_apply_to_event(scope, options, event, SENTRY_SCOPE_NONE); } + sentry_value_t all_attachments = prepare_attachments(hint, local_scope); + if (options->before_send_feedback_func) { SENTRY_DEBUG("invoking `before_send_feedback` hook"); event = options->before_send_feedback_func( @@ -882,6 +905,7 @@ prepare_user_feedback(const sentry_options_t *options, "feedback was discarded by the `before_send_feedback` hook"); sentry__client_report_discard(SENTRY_DISCARD_REASON_BEFORE_SEND, SENTRY_DATA_CATEGORY_FEEDBACK, 1); + sentry_value_decref(all_attachments); return NULL; } } @@ -893,39 +917,18 @@ prepare_user_feedback(const sentry_options_t *options, goto fail; } - sentry_value_t all_attachments = sentry_value_new_null(); - if (hint - && sentry_value_get_type(hint->attachments) == SENTRY_VALUE_TYPE_LIST - && sentry_value_get_length(hint->attachments) > 0) { - sentry__attachments_extend(&all_attachments, hint->attachments); - } - if (local_scope) { - sentry_value_t local_attachments - = sentry__scope_load_attachments(local_scope); - sentry__attachments_extend(&all_attachments, local_attachments); - sentry_value_decref(local_attachments); - } - - SENTRY_WITH_SCOPE (scope) { - sentry_value_t global_attachments - = sentry__scope_load_attachments(scope); - sentry_value_t attachments = global_attachments; - if (sentry_value_get_length(all_attachments) > 0) { - sentry__attachments_extend(&all_attachments, global_attachments); - attachments = all_attachments; - } - sentry__envelope_add_attachments(envelope, attachments, options); - if (options->run) { - sentry__cache_attachment_refs(envelope, attachments, options, - options->run->cache_path, options->run->run_path); - } - sentry_value_decref(global_attachments); + sentry_value_t attachments = hint ? hint->attachments : all_attachments; + sentry__envelope_add_attachments(envelope, attachments, options); + if (options->run) { + sentry__cache_attachment_refs(envelope, attachments, options, + options->run->cache_path, options->run->run_path); } sentry_value_decref(all_attachments); return envelope; fail: + sentry_value_decref(all_attachments); SENTRY_WARN("dropping user feedback"); sentry_envelope_free(envelope); sentry_value_decref(event); @@ -1526,7 +1529,7 @@ sentry_transaction_finish_ts( // This takes ownership of the transaction, generates an event ID, merges // scope - return sentry__capture_event(tx, NULL); + return sentry__capture_event(tx, NULL, NULL); } void @@ -2025,8 +2028,10 @@ capture_minidump(sentry_path_t *dump_path) sentry_value_t event = sentry_value_new_event(); sentry_value_set_by_key( event, "level", sentry__value_new_level(SENTRY_LEVEL_FATAL)); - sentry_envelope_t *envelope - = sentry__prepare_event(options, event, &event_id, true, NULL); + sentry_hint_t hint = { sentry_value_new_null() }; + sentry_envelope_t *envelope = sentry__prepare_event( + options, event, &event_id, true, NULL, &hint); + sentry_value_decref(hint.attachments); if (!envelope || sentry_uuid_is_nil(&event_id)) { sentry_envelope_free(envelope); diff --git a/src/sentry_core.h b/src/sentry_core.h index 3b04c7ad43..cc06c2b49a 100644 --- a/src/sentry_core.h +++ b/src/sentry_core.h @@ -63,13 +63,13 @@ bool sentry__event_is_transaction(sentry_value_t event); */ sentry_envelope_t *sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, sentry_uuid_t *event_id, bool invoke_before_send, - sentry_scope_t *local_scope); + sentry_scope_t *local_scope, sentry_hint_t *hint); /** * Sends a sentry event, regardless of its type. */ sentry_uuid_t sentry__capture_event( - sentry_value_t event, sentry_scope_t *local_scope); + sentry_value_t event, sentry_scope_t *local_scope, sentry_hint_t *hint); /** * Convert the given transaction into an envelope. This assumes that the diff --git a/src/sentry_hint.c b/src/sentry_hint.c index b819be7624..b097f6a982 100644 --- a/src/sentry_hint.c +++ b/src/sentry_hint.c @@ -105,3 +105,21 @@ sentry_hint_attach_bytesw_n(sentry_hint_t *hint, const char *buf, sentry_attachment_from_bytesw_n(buf, buf_len, filename, filename_len)); } #endif + +void +sentry_hint_remove_attachment(sentry_hint_t *hint, sentry_uuid_t attachment_id) +{ + if (hint) { + sentry_value_decref( + sentry__attachments_remove(hint->attachments, &attachment_id)); + } +} + +void +sentry_hint_clear_attachments(sentry_hint_t *hint) +{ + if (hint) { + sentry_value_decref(hint->attachments); + hint->attachments = sentry_value_new_null(); + } +} diff --git a/tests/unit/test_app_hang.c b/tests/unit/test_app_hang.c index 176e8eee59..273a4bd891 100644 --- a/tests/unit/test_app_hang.c +++ b/tests/unit/test_app_hang.c @@ -122,7 +122,7 @@ fake_stackwalk(uint64_t tid, void **ips, size_t max) } static sentry_value_t -capture_before_send(sentry_value_t event, void *hint, void *data) +capture_before_send(sentry_value_t event, sentry_hint_t *hint, void *data) { (void)hint; (void)data; @@ -245,7 +245,7 @@ static long g_real_frames; static volatile long g_keep_spinning; static sentry_value_t -real_before_send(sentry_value_t event, void *hint, void *data) +real_before_send(sentry_value_t event, sentry_hint_t *hint, void *data) { (void)hint; (void)data; diff --git a/tests/unit/test_attachments.c b/tests/unit/test_attachments.c index 3ca5f8b84d..d740b2014e 100644 --- a/tests/unit/test_attachments.c +++ b/tests/unit/test_attachments.c @@ -599,15 +599,15 @@ typedef struct { } sentry_before_send_attachment_testdata_t; static sentry_value_t -before_send_attach_bytes(sentry_value_t event, void *UNUSED(hint), void *_data) +before_send_attach_bytes(sentry_value_t event, sentry_hint_t *hint, void *_data) { sentry_before_send_attachment_testdata_t *data = _data; data->called += 1; if (data->called == 1) { - sentry_attach_bytes("global", 6, ".before-send.txt"); + sentry_hint_attach_bytes(hint, "global", 6, ".before-send.txt"); } else { - sentry_attach_bytes("first", 5, ".before-send.txt"); + sentry_hint_attach_bytes(hint, "first", 5, ".before-send.txt"); } return event; @@ -657,7 +657,8 @@ SENTRY_TEST(attachments_before_send) sentry_scope_t *scope = sentry_local_scope_new(); sentry_scope_attach_file(scope, SENTRY_TEST_PATH_PREFIX ".local.txt"); sentry_scope_capture_event(scope, - sentry_value_new_message_event(SENTRY_LEVEL_INFO, "root", "first")); + sentry_value_new_message_event(SENTRY_LEVEL_INFO, "root", "first"), + NULL); serialized = sentry_stringbuilder_take_string(&testdata.serialized_envelope); diff --git a/tests/unit/test_basic.c b/tests/unit/test_basic.c index 676fd0e223..d2d2f5abc6 100644 --- a/tests/unit/test_basic.c +++ b/tests/unit/test_basic.c @@ -2,6 +2,7 @@ #include "sentry_backend.h" #include "sentry_core.h" #include "sentry_database.h" +#include "sentry_hint.h" #include "sentry_options.h" #include "sentry_scope.h" #include "sentry_string.h" @@ -81,7 +82,7 @@ counting_transport_func(sentry_envelope_t *envelope, void *data) } static sentry_value_t -before_send(sentry_value_t event, void *UNUSED(hint), void *data) +before_send(sentry_value_t event, sentry_hint_t *UNUSED(hint), void *data) { uint64_t *called = data; *called += 1; @@ -121,7 +122,8 @@ SENTRY_TEST(sampling_before_send) } static sentry_value_t -discarding_before_send(sentry_value_t event, void *UNUSED(hint), void *data) +discarding_before_send( + sentry_value_t event, sentry_hint_t *UNUSED(hint), void *data) { uint64_t *called = data; *called += 1; @@ -546,3 +548,189 @@ SENTRY_TEST(clear_options) // sentry__options_getref() must no longer expose the options. TEST_CHECK(options_ref == NULL); } + +static void +capture_envelope(sentry_envelope_t *envelope, void *data) +{ + sentry_envelope_t **captured = data; + TEST_CHECK(*captured == NULL); + *captured = envelope; +} + +static sentry_value_t +attach_before_send(sentry_value_t event, sentry_hint_t *hint, void *data) +{ + TEST_CHECK(hint != NULL); + if (data) { + TEST_CHECK(hint == data); + } + sentry_hint_attach_bytes(hint, "callback", 8, "callback.txt"); + return event; +} + +static sentry_value_t +discard_before_send(sentry_value_t event, sentry_hint_t *hint, void *data) +{ + TEST_CHECK(hint == data); + sentry_value_decref(event); + return sentry_value_new_null(); +} + +SENTRY_TEST(capture_event_hints) +{ + for (int mode = 0; mode < 4; mode++) { + sentry_envelope_t *captured = NULL; + SENTRY_TEST_OPTIONS_NEW(options); + sentry_options_set_auto_session_tracking(options, false); + sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); + sentry_transport_t *transport = sentry_transport_new(capture_envelope); + sentry_transport_set_state(transport, &captured); + sentry_options_set_transport(options, transport); + sentry_hint_t *hint = mode ? sentry_hint_new() : NULL; + if (hint) { + sentry_hint_attach_bytes(hint, "hint", 4, "hint.txt"); + } + sentry_options_set_before_send(options, + mode == 3 ? discard_before_send : attach_before_send, hint); + TEST_CHECK_INT_EQUAL(sentry_init(options), 0); + sentry_attach_bytes("global", 6, "global.txt"); + sentry_uuid_t id; + if (mode == 2) { + sentry_scope_t *scope = sentry_scope_new(); + sentry_scope_attach_bytes(scope, "local", 5, "local.txt"); + id = sentry_scope_capture_event( + scope, sentry_value_new_event(), hint); + sentry_scope_free(scope); + } else if (mode == 0) { + id = sentry_capture_event(sentry_value_new_event()); + } else { + id = sentry_scope_capture_event( + NULL, sentry_value_new_event(), hint); + } + sentry_close(); + TEST_CHECK(sentry_uuid_is_nil(&id) == (mode == 3)); + if (mode == 3) { + TEST_CHECK(captured == NULL); + continue; + } + TEST_ASSERT(captured != NULL); + size_t size; + char *serialized = sentry_envelope_serialize(captured, &size); + TEST_ASSERT(serialized != NULL); + TEST_CHECK(strstr(serialized, "global.txt") != NULL); + TEST_CHECK(strstr(serialized, "callback.txt") != NULL); + TEST_CHECK((strstr(serialized, "hint.txt") != NULL) == (mode != 0)); + TEST_CHECK((strstr(serialized, "local.txt") != NULL) == (mode == 2)); + sentry_free(serialized); + sentry_envelope_free(captured); + } +} + +SENTRY_TEST(capture_hint_cleanup) +{ + sentry_hint_t *hint = sentry_hint_new(); + sentry_hint_attach_bytes(hint, "hint", 4, "hint.txt"); + sentry_uuid_t id + = sentry_scope_capture_event(NULL, sentry_value_new_event(), hint); + TEST_CHECK(sentry_uuid_is_nil(&id)); + sentry_value_t event = sentry_value_new_event(); + sentry_value_set_by_key( + event, "type", sentry_value_new_string("transaction")); + sentry_scope_t *scope = sentry_local_scope_new(); + id = sentry_scope_capture_event(scope, event, sentry_hint_new()); + TEST_CHECK(sentry_uuid_is_nil(&id)); + sentry_value_decref(event); + sentry_scope_free(scope); +} + +typedef struct { + sentry_uuid_t global; + sentry_uuid_t local; + bool clear; + bool discard; + int calls; +} attachment_filter_t; + +static sentry_value_t +filter_attachments(sentry_value_t event, sentry_hint_t *hint, void *data) +{ + attachment_filter_t *filter = data; + filter->calls++; + TEST_CHECK_INT_EQUAL(sentry_value_get_length(hint->attachments), 3); + sentry_hint_remove_attachment(hint, filter->global); + sentry_hint_remove_attachment(hint, filter->local); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(hint->attachments), 1); + if (filter->clear) { + sentry_hint_clear_attachments(hint); + } + sentry_hint_attach_bytes(hint, "callback", 8, "callback.txt"); + if (filter->discard) { + sentry_value_decref(event); + return sentry_value_new_null(); + } + return event; +} + +SENTRY_TEST(capture_filter_attachments) +{ + for (int feedback = 0; feedback < 2; feedback++) { + for (int mode = 0; mode < 3; mode++) { + sentry_envelope_t *captured = NULL; + attachment_filter_t filter = { 0 }; + filter.clear = mode == 1; + filter.discard = mode == 2; + SENTRY_TEST_OPTIONS_NEW(options); + sentry_options_set_auto_session_tracking(options, false); + sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); + sentry_transport_t *transport + = sentry_transport_new(capture_envelope); + sentry_transport_set_state(transport, &captured); + sentry_options_set_transport(options, transport); + if (feedback) { + sentry_options_set_before_send_feedback( + options, filter_attachments, &filter); + } else { + sentry_options_set_before_send( + options, filter_attachments, &filter); + } + TEST_CHECK_INT_EQUAL(sentry_init(options), 0); + filter.global = sentry_attach_bytes("global", 6, "global.txt"); + sentry_scope_t *scope = sentry_scope_new(); + filter.local + = sentry_scope_attach_bytes(scope, "local", 5, "local.txt"); + for (int capture = 0; capture < 2; capture++) { + sentry_hint_t *hint = sentry_hint_new(); + sentry_hint_attach_bytes(hint, "hint", 4, "hint.txt"); + sentry_uuid_t id; + if (feedback) { + id = sentry_scope_capture_feedback(scope, + sentry_value_new_feedback("message", NULL, NULL, NULL), + hint); + } else { + id = sentry_scope_capture_event( + scope, sentry_value_new_event(), hint); + } + TEST_CHECK(sentry_uuid_is_nil(&id) == filter.discard); + if (filter.discard) { + TEST_CHECK(captured == NULL); + continue; + } + TEST_ASSERT(captured != NULL); + size_t size; + char *serialized = sentry_envelope_serialize(captured, &size); + TEST_ASSERT(serialized != NULL); + TEST_CHECK(strstr(serialized, "global.txt") == NULL); + TEST_CHECK(strstr(serialized, "local.txt") == NULL); + TEST_CHECK( + (strstr(serialized, "hint.txt") == NULL) == filter.clear); + TEST_CHECK(strstr(serialized, "callback.txt") != NULL); + sentry_free(serialized); + sentry_envelope_free(captured); + captured = NULL; + } + TEST_CHECK_INT_EQUAL(filter.calls, 2); + sentry_scope_free(scope); + sentry_close(); + } + } +} diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index 118212bc60..32e8714af3 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -537,7 +537,8 @@ SENTRY_TEST(scope_fingerprint_n) } static sentry_value_t -before_send_capture_fingerprint(sentry_value_t event, void *hint, void *data) +before_send_capture_fingerprint( + sentry_value_t event, sentry_hint_t *hint, void *data) { (void)hint; char **fingerprint_json = data; @@ -562,7 +563,7 @@ SENTRY_TEST(scope_remove_fingerprint_capture) sentry_scope_set_fingerprint(local_scope, "local1", NULL); sentry_scope_remove_fingerprint(local_scope); sentry_scope_capture_event(local_scope, - sentry_value_new_message_event(SENTRY_LEVEL_INFO, NULL, "test")); + sentry_value_new_message_event(SENTRY_LEVEL_INFO, NULL, "test"), NULL); TEST_ASSERT(!!fingerprint_json); TEST_CHECK_STRING_EQUAL(fingerprint_json, "[\"global1\",\"global2\"]"); @@ -1126,7 +1127,7 @@ SENTRY_TEST(before_breadcrumb_passthrough) static sentry_value_t before_send_modify_scope_values( - sentry_value_t event, void *UNUSED(hint), void *UNUSED(data)) + sentry_value_t event, sentry_hint_t *UNUSED(hint), void *UNUSED(data)) { sentry_value_t contexts = sentry_value_get_by_key(event, "contexts"); sentry_value_t gpu = sentry_value_get_by_key(contexts, "gpu"); @@ -3077,7 +3078,7 @@ SENTRY_TEST(scope_capture_unlocked) static sentry_value_t conditionally_discard_event( - sentry_value_t event, void *UNUSED(hint), void *data) + sentry_value_t event, sentry_hint_t *UNUSED(hint), void *data) { if (*(bool *)data) { sentry_value_decref(event); @@ -3117,7 +3118,8 @@ SENTRY_TEST(scope_last_event_id) TEST_CHECK(sentry_uuid_is_nil(&last_event_id)); sentry_uuid_t scoped_event_id = sentry_scope_capture_event(scope, - sentry_value_new_message_event(SENTRY_LEVEL_ERROR, NULL, "scoped")); + sentry_value_new_message_event(SENTRY_LEVEL_ERROR, NULL, "scoped"), + NULL); TEST_CHECK(!sentry_uuid_is_nil(&scoped_event_id)); last_event_id = sentry_scope_get_last_event_id(scope); TEST_CHECK_UUID_EQUAL(last_event_id, scoped_event_id); @@ -3130,7 +3132,8 @@ SENTRY_TEST(scope_last_event_id) discard = true; sentry_uuid_t discarded_event_id = sentry_scope_capture_event(scope, - sentry_value_new_message_event(SENTRY_LEVEL_ERROR, NULL, "discarded")); + sentry_value_new_message_event(SENTRY_LEVEL_ERROR, NULL, "discarded"), + NULL); TEST_CHECK(sentry_uuid_is_nil(&discarded_event_id)); last_event_id = sentry_scope_get_last_event_id(scope); TEST_CHECK_UUID_EQUAL(last_event_id, scoped_event_id); @@ -3195,7 +3198,8 @@ SENTRY_TEST(scope_capture_user_owned) sentry_scope_set_tag(scope, "run", "first"); sentry_scope_capture_event(scope, - sentry_value_new_message_event(SENTRY_LEVEL_INFO, "logger", "one")); + sentry_value_new_message_event(SENTRY_LEVEL_INFO, "logger", "one"), + NULL); // The scope was applied but not freed, so reading and reusing it is safe // (a use-after-free here would trip the sanitizers). @@ -3206,7 +3210,8 @@ SENTRY_TEST(scope_capture_user_owned) sentry_scope_set_tag(scope, "run", "second"); sentry_scope_capture_event(scope, - sentry_value_new_message_event(SENTRY_LEVEL_INFO, "logger", "two")); + sentry_value_new_message_event(SENTRY_LEVEL_INFO, "logger", "two"), + NULL); sentry_scope_free(scope); @@ -3217,7 +3222,8 @@ SENTRY_TEST(scope_capture_user_owned) // Keeps the trace context the scopes produced and drops the event. static sentry_value_t -keep_trace_context(sentry_value_t event, void *UNUSED(hint), void *data) +keep_trace_context( + sentry_value_t event, sentry_hint_t *UNUSED(hint), void *data) { sentry_value_t *trace = data; sentry_value_decref(*trace); @@ -3244,7 +3250,8 @@ SENTRY_TEST(scope_bind_transaction_object) sentry_scope_set_transaction_object(scope, tx); sentry_scope_capture_event(scope, - sentry_value_new_message_event(SENTRY_LEVEL_ERROR, "logger", "boom")); + sentry_value_new_message_event(SENTRY_LEVEL_ERROR, "logger", "boom"), + NULL); TEST_ASSERT(!sentry_value_is_null(trace)); TEST_CHECK_STRING_EQUAL( @@ -3254,7 +3261,8 @@ SENTRY_TEST(scope_bind_transaction_object) sentry_scope_set_transaction_object(scope, NULL); sentry_scope_capture_event(scope, - sentry_value_new_message_event(SENTRY_LEVEL_ERROR, "logger", "boom")); + sentry_value_new_message_event(SENTRY_LEVEL_ERROR, "logger", "boom"), + NULL); // After unbinding, event falls back to the propagation context. TEST_ASSERT(!sentry_value_is_null(trace)); @@ -3309,7 +3317,8 @@ SENTRY_TEST(scope_bind_span) } sentry_scope_capture_event(scope, - sentry_value_new_message_event(SENTRY_LEVEL_ERROR, "logger", "boom")); + sentry_value_new_message_event(SENTRY_LEVEL_ERROR, "logger", "boom"), + NULL); TEST_ASSERT(!sentry_value_is_null(trace)); TEST_CHECK_STRING_EQUAL( diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c index 7f27131b06..5a3a449703 100644 --- a/tests/unit/test_tracing.c +++ b/tests/unit/test_tracing.c @@ -348,7 +348,7 @@ SENTRY_TEST(transport_sampling_transactions) } static sentry_value_t -before_send(sentry_value_t event, void *UNUSED(hint), void *data) +before_send(sentry_value_t event, sentry_hint_t *UNUSED(hint), void *data) { uint64_t *called = data; *called += 1; diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 98d78647f9..a3d7268eb9 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -95,6 +95,9 @@ XX(cache_symlink_run) XX(cache_write_minidump) XX(cache_write_raw_with_minidump) XX(callback_envelope_is_not_resent_without_backend) +XX(capture_event_hints) +XX(capture_filter_attachments) +XX(capture_hint_cleanup) XX(capture_minidump_basic) XX(capture_minidump_discard) XX(capture_minidump_invalid_path) From ce10a77281bc16b9444b961283a1f4ca1a65789d Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Mon, 21 Sep 2026 17:49:52 +0200 Subject: [PATCH 2/4] sentry__invoke_before_send --- src/backends/sentry_backend_crashpad.cpp | 5 ++--- src/backends/sentry_backend_native.c | 4 +--- src/sentry_core.c | 26 +++++++++++++++++------- src/sentry_core.h | 9 ++++++++ 4 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/backends/sentry_backend_crashpad.cpp b/src/backends/sentry_backend_crashpad.cpp index 33a3d8a295..57c351a8d5 100644 --- a/src/backends/sentry_backend_crashpad.cpp +++ b/src/backends/sentry_backend_crashpad.cpp @@ -564,9 +564,8 @@ crashpad_handler(int signum, siginfo_t *info, ucontext_t *user_context) crash_event = options->on_crash_func( &uctx, crash_event, options->on_crash_data); } else if (options->before_send_func) { - SENTRY_DEBUG("invoking `before_send` hook"); - crash_event = options->before_send_func( - crash_event, nullptr, options->before_send_data); + crash_event + = sentry__invoke_before_send(options, crash_event, nullptr); } sentry__transport_suspend(options->transport); diff --git a/src/backends/sentry_backend_native.c b/src/backends/sentry_backend_native.c index e70f5502da..faf4ad9717 100644 --- a/src/backends/sentry_backend_native.c +++ b/src/backends/sentry_backend_native.c @@ -1368,9 +1368,7 @@ native_backend_except(sentry_backend_t *backend, const sentry_ucontext_t *uctx) if (should_handle) { // Apply before_send hook if on_crash wasn't set if (!options->on_crash_func && options->before_send_func) { - SENTRY_DEBUG("invoking `before_send` hook"); - event = options->before_send_func( - event, NULL, options->before_send_data); + event = sentry__invoke_before_send(options, event, NULL); should_handle = !sentry_value_is_null(event); } diff --git a/src/sentry_core.c b/src/sentry_core.c index b637fadc69..6c9ad23058 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -745,6 +745,23 @@ prepare_attachments(sentry_hint_t *hint, sentry_scope_t *local_scope) return attachments; } +sentry_value_t +sentry__invoke_before_send( + const sentry_options_t *options, sentry_value_t event, sentry_hint_t *hint) +{ + if (!options->before_send_func) { + return event; + } + SENTRY_DEBUG("invoking `before_send` hook"); + event = options->before_send_func(event, hint, options->before_send_data); + if (sentry_value_is_null(event)) { + SENTRY_DEBUG("event was discarded by the `before_send` hook"); + sentry__client_report_discard( + SENTRY_DISCARD_REASON_BEFORE_SEND, SENTRY_DATA_CATEGORY_ERROR, 1); + } + return event; +} + sentry_envelope_t * sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, sentry_uuid_t *event_id, bool invoke_before_send, @@ -773,14 +790,9 @@ sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, sentry_value_t all_attachments = prepare_attachments(hint, local_scope); - if (options->before_send_func && invoke_before_send) { - SENTRY_DEBUG("invoking `before_send` hook"); - event - = options->before_send_func(event, hint, options->before_send_data); + if (invoke_before_send) { + event = sentry__invoke_before_send(options, event, hint); if (sentry_value_is_null(event)) { - SENTRY_DEBUG("event was discarded by the `before_send` hook"); - sentry__client_report_discard(SENTRY_DISCARD_REASON_BEFORE_SEND, - SENTRY_DATA_CATEGORY_ERROR, 1); sentry_value_decref(all_attachments); return NULL; } diff --git a/src/sentry_core.h b/src/sentry_core.h index cc06c2b49a..2043f5b9d6 100644 --- a/src/sentry_core.h +++ b/src/sentry_core.h @@ -47,6 +47,15 @@ bool sentry__should_skip_upload(void); */ bool sentry__event_is_transaction(sentry_value_t event); +/** + * Invokes the configured `before_send` callback, if any. + * + * Returns the callback result, or `event` unchanged when no callback is + * configured. Records a client report when the callback discards the event. + */ +sentry_value_t sentry__invoke_before_send( + const sentry_options_t *options, sentry_value_t event, sentry_hint_t *hint); + /** * Convert the given event into an envelope. This assumes that the event * being passed in is not a transaction. From 030edffbb2193734e4de3febbb45ed1b952529fb Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Mon, 21 Sep 2026 21:18:40 +0200 Subject: [PATCH 3/4] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39fc0f4b6c..90051c98bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,11 +19,11 @@ - 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. These functions consume and freeze the attachment value. ([#2079](https://github.com/getsentry/sentry-native/pull/2079), [#1974](https://github.com/getsentry/sentry-native/pull/1974)) - Add `sentry_start_new_trace()` as a clearer name for starting a new trace. ([#2095](https://github.com/getsentry/sentry-native/pull/2095)) - Add hint support to `sentry_scope_capture_event` to pass event-specific attachments. The hint is also passed to `before_send`, which can modify attachments before the event is sent. ([#2099](https://github.com/getsentry/sentry-native/pull/2099)) +- Add `sentry_hint_remove_attachment` and `sentry_hint_clear_attachments`. ([#2099](https://github.com/getsentry/sentry-native/pull/2099)) **Fixes**: - Reduce lock contention for multi-threaded log and metric capture by allowing concurrent reads of scope data, and reusing a single options reference throughout each capture. ([#2091](https://github.com/getsentry/sentry-native/pull/2091), [#2094](https://github.com/getsentry/sentry-native/pull/2094)) -- Add `sentry_hint_remove_attachment` and `sentry_hint_clear_attachments` to filter attachments without modifying scopes. ([#2099](https://github.com/getsentry/sentry-native/pull/2099)) **Other changes**: From 98b9bfe4ea99c73308a7e1fd876b545b5a69f340 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 22 Sep 2026 14:02:18 +0200 Subject: [PATCH 4/4] add sentry_hint_get_attachments --- CHANGELOG.md | 2 +- include/sentry.h | 7 +++++++ src/sentry_hint.c | 6 ++++++ tests/unit/test_basic.c | 6 ++++-- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90051c98bf..1c08e150a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ - 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. These functions consume and freeze the attachment value. ([#2079](https://github.com/getsentry/sentry-native/pull/2079), [#1974](https://github.com/getsentry/sentry-native/pull/1974)) - Add `sentry_start_new_trace()` as a clearer name for starting a new trace. ([#2095](https://github.com/getsentry/sentry-native/pull/2095)) - Add hint support to `sentry_scope_capture_event` to pass event-specific attachments. The hint is also passed to `before_send`, which can modify attachments before the event is sent. ([#2099](https://github.com/getsentry/sentry-native/pull/2099)) -- Add `sentry_hint_remove_attachment` and `sentry_hint_clear_attachments`. ([#2099](https://github.com/getsentry/sentry-native/pull/2099)) +- Add `sentry_hint_get_attachments`, `sentry_hint_remove_attachment`, and `sentry_hint_clear_attachments`. ([#2099](https://github.com/getsentry/sentry-native/pull/2099)) **Fixes**: diff --git a/include/sentry.h b/include/sentry.h index 8725f4e85b..3d984e04d0 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -4206,6 +4206,13 @@ SENTRY_API sentry_uuid_t sentry_hint_attach_bytesw_n(sentry_hint_t *hint, size_t filename_len); #endif +/** + * Returns a borrowed list of attachments in the hint, or a null value if the + * hint is NULL. + */ +SENTRY_API sentry_value_t sentry_hint_get_attachments( + const sentry_hint_t *hint); + /** * Removes an attachment from the hint by its ID. Does not modify scopes. */ diff --git a/src/sentry_hint.c b/src/sentry_hint.c index b097f6a982..922dd629a8 100644 --- a/src/sentry_hint.c +++ b/src/sentry_hint.c @@ -106,6 +106,12 @@ sentry_hint_attach_bytesw_n(sentry_hint_t *hint, const char *buf, } #endif +sentry_value_t +sentry_hint_get_attachments(const sentry_hint_t *hint) +{ + return hint ? hint->attachments : sentry_value_new_null(); +} + void sentry_hint_remove_attachment(sentry_hint_t *hint, sentry_uuid_t attachment_id) { diff --git a/tests/unit/test_basic.c b/tests/unit/test_basic.c index d2d2f5abc6..a90eda37a6 100644 --- a/tests/unit/test_basic.c +++ b/tests/unit/test_basic.c @@ -656,10 +656,12 @@ filter_attachments(sentry_value_t event, sentry_hint_t *hint, void *data) { attachment_filter_t *filter = data; filter->calls++; - TEST_CHECK_INT_EQUAL(sentry_value_get_length(hint->attachments), 3); + TEST_CHECK_INT_EQUAL( + sentry_value_get_length(sentry_hint_get_attachments(hint)), 3); sentry_hint_remove_attachment(hint, filter->global); sentry_hint_remove_attachment(hint, filter->local); - TEST_CHECK_INT_EQUAL(sentry_value_get_length(hint->attachments), 1); + TEST_CHECK_INT_EQUAL( + sentry_value_get_length(sentry_hint_get_attachments(hint)), 1); if (filter->clear) { sentry_hint_clear_attachments(hint); }