diff --git a/src/sentry_attachment.c b/src/sentry_attachment.c index 57d11bb4b..c337a02f8 100644 --- a/src/sentry_attachment.c +++ b/src/sentry_attachment.c @@ -262,12 +262,12 @@ sentry__attachments_add_path(sentry_attachment_t **attachments_ptr, return sentry__attachments_add(attachments_ptr, attachment); } -void +bool sentry__attachments_remove( sentry_attachment_t **attachments_ptr, sentry_attachment_t *attachment) { if (!attachment) { - return; + return false; } sentry_attachment_t **next_ptr = attachments_ptr; @@ -275,12 +275,14 @@ sentry__attachments_remove( for (sentry_attachment_t *it = *attachments_ptr; it; it = it->next) { if (it == attachment) { *next_ptr = it->next; - sentry__attachment_free(it); - return; + it->next = NULL; + return true; } next_ptr = &it->next; } + + return false; } static sentry_attachment_t * diff --git a/src/sentry_attachment.h b/src/sentry_attachment.h index 0004164e7..0ddf96f35 100644 --- a/src/sentry_attachment.h +++ b/src/sentry_attachment.h @@ -90,8 +90,9 @@ sentry_attachment_t *sentry__attachments_add_path( /** * Removes an attachment from the attachments list at `attachments_ptr`. + * Returns true if the attachment was found and removed. */ -void sentry__attachments_remove( +bool sentry__attachments_remove( sentry_attachment_t **attachments_ptr, sentry_attachment_t *attachment); /** diff --git a/src/sentry_core.c b/src/sentry_core.c index e169f7308..6d0f2a1ed 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -899,6 +899,7 @@ sentry_set_release_n(const char *release, size_t release_len) scope->release = sentry__string_clone_n(release, release_len); sentry_value_set_by_key(scope->dynamic_sampling_context, "release", sentry_value_new_string(scope->release)); + SENTRY_SCOPE_NOTIFY(scope, set_release, release, release_len); } } @@ -917,6 +918,8 @@ sentry_set_environment_n(const char *environment, size_t environment_len) = sentry__string_clone_n(environment, environment_len); sentry_value_set_by_key(scope->dynamic_sampling_context, "environment", sentry_value_new_string(scope->environment)); + SENTRY_SCOPE_NOTIFY( + scope, set_environment, environment, environment_len); } } @@ -981,9 +984,7 @@ sentry_set_tag_n( void sentry_remove_tag(const char *key) { - SENTRY_WITH_SCOPE_MUT (scope) { - sentry_value_remove_by_key(scope->tags, key); - } + sentry_remove_tag_n(key, sentry__guarded_strlen(key)); } void @@ -991,6 +992,7 @@ sentry_remove_tag_n(const char *key, size_t key_len) { SENTRY_WITH_SCOPE_MUT (scope) { sentry_value_remove_by_key_n(scope->tags, key, key_len); + SENTRY_SCOPE_NOTIFY(scope, remove_tag, key, key_len); } } @@ -1015,6 +1017,8 @@ sentry_remove_extra(const char *key) { SENTRY_WITH_SCOPE_MUT (scope) { sentry_value_remove_by_key(scope->extra, key); + SENTRY_SCOPE_NOTIFY( + scope, remove_extra, key, sentry__guarded_strlen(key)); } } @@ -1023,6 +1027,7 @@ sentry_remove_extra_n(const char *key, size_t key_len) { SENTRY_WITH_SCOPE_MUT (scope) { sentry_value_remove_by_key_n(scope->extra, key, key_len); + SENTRY_SCOPE_NOTIFY(scope, remove_extra, key, key_len); } } @@ -1096,6 +1101,8 @@ sentry__set_propagation_context(const char *key, sentry_value_t value) { SENTRY_WITH_SCOPE_MUT (scope) { sentry_value_set_by_key(scope->propagation_context, key, value); + SENTRY_SCOPE_NOTIFY( + scope, set_context, key, sentry__guarded_strlen(key), value); } } @@ -1130,6 +1137,8 @@ sentry_remove_context(const char *key) { SENTRY_WITH_SCOPE_MUT (scope) { sentry_value_remove_by_key(scope->contexts, key); + SENTRY_SCOPE_NOTIFY( + scope, remove_context, key, sentry__guarded_strlen(key)); } } @@ -1138,6 +1147,7 @@ sentry_remove_context_n(const char *key, size_t key_len) { SENTRY_WITH_SCOPE_MUT (scope) { sentry_value_remove_by_key_n(scope->contexts, key, key_len); + SENTRY_SCOPE_NOTIFY(scope, remove_context, key, key_len); } } @@ -1174,6 +1184,7 @@ sentry_remove_fingerprint(void) SENTRY_WITH_SCOPE_MUT (scope) { sentry_value_decref(scope->fingerprint); scope->fingerprint = sentry_value_new_null(); + SENTRY_SCOPE_NOTIFY(scope, set_fingerprint, scope->fingerprint); } } @@ -1228,6 +1239,9 @@ sentry_regenerate_trace(void) generate_propagation_context(scope->propagation_context); scope->trace_managed = false; sentry__scope_update_dsc(scope, options); + SENTRY_SCOPE_NOTIFY(scope, set_context, "trace", + sentry__guarded_strlen("trace"), + sentry_value_get_by_key(scope->propagation_context, "trace")); } } } @@ -1242,6 +1256,8 @@ sentry_set_transaction(const char *transaction) if (scope->transaction_object) { sentry_transaction_set_name(scope->transaction_object, transaction); } + SENTRY_SCOPE_NOTIFY(scope, set_transaction, transaction, + sentry__guarded_strlen(transaction)); } } @@ -1257,6 +1273,8 @@ sentry_set_transaction_n(const char *transaction, size_t transaction_len) sentry_transaction_set_name_n( scope->transaction_object, transaction, transaction_len); } + SENTRY_SCOPE_NOTIFY( + scope, set_transaction, transaction, transaction_len); } } @@ -1329,6 +1347,10 @@ sentry_transaction_start_ts(sentry_transaction_context_t *opaque_tx_ctx, sentry_value_remove_by_key(tx, "parent_span_id"); sentry_value_remove_by_key(tx, "sampled"); sentry__scope_update_dsc(scope, options); + SENTRY_SCOPE_NOTIFY(scope, set_context, "trace", + sentry__guarded_strlen("trace"), + sentry_value_get_by_key( + scope->propagation_context, "trace")); } } } @@ -1399,6 +1421,9 @@ sentry__transaction_finish_value( if (sentry__string_eq(tx_id, scope_tx_id)) { sentry__transaction_decref(scope->transaction_object); scope->transaction_object = NULL; + if (!scope->trace_managed) { + sentry__scope_notify_trace_context(scope); + } } } // if the SDK manages the trace (rather than the user or a downstream @@ -1411,6 +1436,7 @@ sentry__transaction_finish_value( sentry_value_set_by_key( sentry_value_get_by_key(scope->propagation_context, "trace"), "trace_id", txn_trace_id); + sentry__scope_notify_trace_context(scope); } } // The sampling decision should already be made for transactions @@ -1475,6 +1501,7 @@ sentry_set_transaction_object(sentry_transaction_t *tx) sentry__transaction_decref(scope->transaction_object); sentry__transaction_incref(tx); scope->transaction_object = tx; + sentry__scope_notify_trace_context(scope); } } @@ -1487,6 +1514,7 @@ sentry_set_span(sentry_span_t *span) sentry__span_decref(scope->span); sentry__span_incref(span); scope->span = span; + sentry__scope_notify_trace_context(scope); } } @@ -1648,6 +1676,7 @@ sentry_span_finish_ts(sentry_span_t *opaque_span, uint64_t timestamp) if (sentry__string_eq(span_id, scope_span_id)) { sentry__span_decref(scope->span); scope->span = NULL; + sentry__scope_notify_trace_context(scope); } } } @@ -1903,13 +1932,17 @@ sentry_capture_minidump_n(const char *path, size_t path_len) static sentry_attachment_t * add_attachment(sentry_attachment_t *attachment) { + if (!attachment) { + return NULL; + } + SENTRY_WITH_OPTIONS (options) { if (options->backend && options->backend->add_attachment_func) { options->backend->add_attachment_func(options->backend, attachment); } } SENTRY_WITH_SCOPE_MUT (scope) { - attachment = sentry__attachments_add(&scope->attachments, attachment); + attachment = sentry__scope_add_attachment(scope, attachment); } return attachment; } @@ -1947,15 +1980,17 @@ sentry_clear_attachments(void) { SENTRY_WITH_OPTIONS (options) { SENTRY_WITH_SCOPE_MUT (scope) { - if (options->backend && options->backend->remove_attachment_func) { - for (sentry_attachment_t *it = scope->attachments; it; - it = it->next) { + sentry_attachment_t *attachments = scope->attachments; + scope->attachments = NULL; + for (sentry_attachment_t *it = attachments; it; it = it->next) { + if (options->backend + && options->backend->remove_attachment_func) { options->backend->remove_attachment_func( options->backend, it); } + SENTRY_SCOPE_NOTIFY(scope, remove_attachment, it); } - sentry__attachments_free(scope->attachments); - scope->attachments = NULL; + sentry__attachments_free(attachments); } } } @@ -1963,15 +1998,23 @@ sentry_clear_attachments(void) void sentry_remove_attachment(sentry_attachment_t *attachment) { + if (!attachment) { + return; + } + SENTRY_WITH_OPTIONS (options) { - if (options->backend && options->backend->remove_attachment_func) { - options->backend->remove_attachment_func( - options->backend, attachment); + SENTRY_WITH_SCOPE_MUT (scope) { + if (sentry__attachments_remove(&scope->attachments, attachment)) { + if (options->backend + && options->backend->remove_attachment_func) { + options->backend->remove_attachment_func( + options->backend, attachment); + } + SENTRY_SCOPE_NOTIFY(scope, remove_attachment, attachment); + sentry__attachment_free(attachment); + } } } - SENTRY_WITH_SCOPE_MUT (scope) { - sentry__attachments_remove(&scope->attachments, attachment); - } } #ifdef SENTRY_PLATFORM_WINDOWS diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 1a6f54a55..b197455ba 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -88,6 +88,9 @@ init_scope(sentry_scope_t *scope) scope->transaction_object = NULL; scope->span = NULL; scope->trace_managed = true; + scope->observers = NULL; + scope->num_observers = 0; + scope->is_notifying = 0; } static sentry_scope_t * @@ -127,6 +130,12 @@ cleanup_scope(sentry_scope_t *scope) sentry__attachments_free(scope->attachments); sentry__transaction_decref(scope->transaction_object); sentry__span_decref(scope->span); + for (size_t i = 0; i < scope->num_observers; i++) { + sentry_free(scope->observers[i]); + } + sentry_free(scope->observers); + scope->observers = NULL; + scope->num_observers = 0; } void @@ -159,7 +168,11 @@ sentry__scope_unlock(void) void sentry__scope_flush_unlock(void) { + bool was_notifying = g_scope.is_notifying > 0; sentry__scope_unlock(); + if (was_notifying) { + return; + } SENTRY_WITH_OPTIONS (options) { // we try to unlock the scope as soon as possible. The // backend will do its own `WITH_SCOPE` internally. @@ -169,6 +182,101 @@ sentry__scope_flush_unlock(void) } } +sentry_scope_observer_t * +sentry__scope_observer_new(void) +{ + return SENTRY_MAKE(sentry_scope_observer_t); +} + +bool +sentry__scope_add_observer( + sentry_scope_t *scope, sentry_scope_observer_t *observer) +{ + if (!observer) { + return false; + } + + size_t new_count = scope->num_observers + 1; + sentry_scope_observer_t **new_array + = sentry__calloc(new_count, sizeof(sentry_scope_observer_t *)); + if (!new_array) { + sentry_free(observer); + return false; + } + if (scope->observers) { + memcpy(new_array, scope->observers, + scope->num_observers * sizeof(sentry_scope_observer_t *)); + sentry_free(scope->observers); + } + new_array[scope->num_observers] = observer; + scope->observers = new_array; + scope->num_observers = new_count; + return true; +} + +void +sentry__scope_remove_observer( + sentry_scope_t *scope, sentry_scope_observer_t *observer) +{ + if (!observer || !scope->observers) { + return; + } + + for (size_t i = 0; i < scope->num_observers; i++) { + if (scope->observers[i] != observer) { + continue; + } + + sentry_free(observer); + if (scope->is_notifying) { + // avoid shifting the array while SENTRY_SCOPE_NOTIFY is iterating + scope->observers[i] = NULL; + return; + } + for (size_t j = i + 1; j < scope->num_observers; j++) { + scope->observers[j - 1] = scope->observers[j]; + } + scope->num_observers--; + if (scope->num_observers == 0) { + sentry_free(scope->observers); + scope->observers = NULL; + } + return; + } +} + +size_t +sentry__scope_begin_notify(sentry_scope_t *scope) +{ + scope->is_notifying++; + return scope->num_observers; +} + +void +sentry__scope_end_notify(sentry_scope_t *scope) +{ + if (--scope->is_notifying > 0) { + return; + } + if (!scope->observers) { + return; + } + + // removals during notification leave tombstones to avoid shifting the array + // while it is being iterated + size_t j = 0; + for (size_t i = 0; i < scope->num_observers; i++) { + if (scope->observers[i]) { + scope->observers[j++] = scope->observers[i]; + } + } + scope->num_observers = j; + if (scope->num_observers == 0) { + sentry_free(scope->observers); + scope->observers = NULL; + } +} + sentry_scope_t * sentry_local_scope_new(void) { @@ -350,6 +458,39 @@ get_span_or_transaction(const sentry_scope_t *scope) } } +static sentry_value_t +get_scope_trace_context(const sentry_scope_t *scope) +{ + sentry_value_t scoped_txn_or_span = get_span_or_transaction(scope); + sentry_value_t trace_context + = sentry__value_get_trace_context(scoped_txn_or_span); + if (sentry_value_is_null(trace_context)) { + return trace_context; + } + + sentry_value_t data = sentry_value_get_by_key(scoped_txn_or_span, "data"); + if (!sentry_value_is_null(data)) { + sentry_value_incref(data); + sentry_value_set_by_key(trace_context, "data", data); + } + return trace_context; +} + +void +sentry__scope_notify_trace_context(sentry_scope_t *scope) +{ + sentry_value_t trace_context = get_scope_trace_context(scope); + if (sentry_value_is_null(trace_context)) { + // Fall back to the propagation trace so observers see the same trace + // context as an event with scope applied. + trace_context + = sentry_value_get_by_key(scope->propagation_context, "trace"); + sentry_value_incref(trace_context); + } + SENTRY_SCOPE_NOTIFY(scope, set_trace_context, trace_context); + sentry_value_decref(trace_context); +} + #ifdef SENTRY_UNITTEST sentry_value_t sentry__scope_get_span_or_transaction(void) @@ -446,23 +587,14 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, // prep contexts sourced from scope; data about transaction on scope needs // to be extracted and inserted - sentry_value_t scoped_txn_or_span = sentry_value_new_null(); sentry_value_t scope_trace = sentry_value_new_null(); if (!is_transaction) { - scoped_txn_or_span = get_span_or_transaction(scope); - scope_trace = sentry__value_get_trace_context(scoped_txn_or_span); + scope_trace = get_scope_trace_context(scope); } if (!sentry_value_is_null(scope_trace)) { if (sentry_value_is_null(contexts)) { contexts = sentry_value_new_object(); } - sentry_value_t scoped_txn_or_span_data - = sentry_value_get_by_key(scoped_txn_or_span, "data"); - if (!sentry_value_is_null(scoped_txn_or_span_data)) { - sentry_value_incref(scoped_txn_or_span_data); - sentry_value_set_by_key( - scope_trace, "data", scoped_txn_or_span_data); - } sentry_value_set_by_key(contexts, "trace", scope_trace); } @@ -515,7 +647,9 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, void sentry_scope_add_breadcrumb(sentry_scope_t *scope, sentry_value_t breadcrumb) { - sentry__ringbuffer_append(scope->breadcrumbs, breadcrumb); + if (sentry__ringbuffer_append(scope->breadcrumbs, breadcrumb) == 0) { + SENTRY_SCOPE_NOTIFY(scope, add_breadcrumb, breadcrumb); + } } void @@ -523,12 +657,14 @@ sentry_scope_set_user(sentry_scope_t *scope, sentry_value_t user) { sentry_value_decref(scope->user); scope->user = user; + SENTRY_SCOPE_NOTIFY(scope, set_user, user); } void sentry_scope_set_tag(sentry_scope_t *scope, const char *key, const char *value) { - sentry_value_set_by_key(scope->tags, key, sentry_value_new_string(value)); + sentry_scope_set_tag_n(scope, key, sentry__guarded_strlen(key), value, + sentry__guarded_strlen(value)); } void @@ -537,13 +673,14 @@ sentry_scope_set_tag_n(sentry_scope_t *scope, const char *key, size_t key_len, { sentry_value_set_by_key_n( scope->tags, key, key_len, sentry_value_new_string_n(value, value_len)); + SENTRY_SCOPE_NOTIFY(scope, set_tag, key, key_len, value, value_len); } void sentry_scope_set_extra( sentry_scope_t *scope, const char *key, sentry_value_t value) { - sentry_value_set_by_key(scope->extra, key, value); + sentry_scope_set_extra_n(scope, key, sentry__guarded_strlen(key), value); } void @@ -551,6 +688,7 @@ sentry_scope_set_extra_n(sentry_scope_t *scope, const char *key, size_t key_len, sentry_value_t value) { sentry_value_set_by_key_n(scope->extra, key, key_len, value); + SENTRY_SCOPE_NOTIFY(scope, set_extra, key, key_len, value); } void @@ -590,6 +728,8 @@ sentry_scope_set_context( sentry_scope_t *scope, const char *key, sentry_value_t value) { sentry_value_set_by_key(scope->contexts, key, value); + SENTRY_SCOPE_NOTIFY( + scope, set_context, key, sentry__guarded_strlen(key), value); } void @@ -597,6 +737,7 @@ sentry_scope_set_context_n(sentry_scope_t *scope, const char *key, size_t key_len, sentry_value_t value) { sentry_value_set_by_key_n(scope->contexts, key, key_len, value); + SENTRY_SCOPE_NOTIFY(scope, set_context, key, key_len, value); } void @@ -619,6 +760,7 @@ sentry_scope_update_context_n(sentry_scope_t *scope, const char *key, sentry__value_merge_objects(value, context); sentry_value_set_by_key_n(scope->contexts, key, key_len, value); } + SENTRY_SCOPE_NOTIFY(scope, set_context, key, key_len, value); } void @@ -633,6 +775,7 @@ sentry__scope_set_fingerprint_va( sentry_value_decref(scope->fingerprint); scope->fingerprint = fingerprint_value; + SENTRY_SCOPE_NOTIFY(scope, set_fingerprint, fingerprint_value); } void @@ -687,12 +830,30 @@ sentry_scope_set_fingerprints( sentry_value_decref(scope->fingerprint); scope->fingerprint = fingerprints; + SENTRY_SCOPE_NOTIFY(scope, set_fingerprint, fingerprints); } void sentry_scope_set_level(sentry_scope_t *scope, sentry_level_t level) { scope->level = level; + SENTRY_SCOPE_NOTIFY(scope, set_level, level); +} + +sentry_attachment_t * +sentry__scope_add_attachment( + sentry_scope_t *scope, sentry_attachment_t *attachment) +{ + if (!attachment) { + return NULL; + } + + sentry_attachment_t *added + = sentry__attachments_add(&scope->attachments, attachment); + if (added == attachment) { + SENTRY_SCOPE_NOTIFY(scope, add_attachment, attachment); + } + return added; } sentry_attachment_t * @@ -706,8 +867,8 @@ sentry_attachment_t * sentry_scope_attach_file_n( sentry_scope_t *scope, const char *path, size_t path_len) { - return sentry__attachments_add_path(&scope->attachments, - sentry__path_from_str_n(path, path_len), NULL, NULL); + return sentry__scope_add_attachment(scope, + sentry__attachment_from_path(sentry__path_from_str_n(path, path_len))); } sentry_attachment_t * @@ -722,7 +883,7 @@ sentry_attachment_t * sentry_scope_attach_bytes_n(sentry_scope_t *scope, const char *buf, size_t buf_len, const char *filename, size_t filename_len) { - return sentry__attachments_add(&scope->attachments, + return sentry__scope_add_attachment(scope, sentry__attachment_from_buffer( buf, buf_len, sentry__path_from_str_n(filename, filename_len))); } @@ -739,8 +900,8 @@ sentry_attachment_t * sentry_scope_attach_filew_n( sentry_scope_t *scope, const wchar_t *path, size_t path_len) { - return sentry__attachments_add_path(&scope->attachments, - sentry__path_from_wstr_n(path, path_len), NULL, NULL); + return sentry__scope_add_attachment(scope, + sentry__attachment_from_path(sentry__path_from_wstr_n(path, path_len))); } sentry_attachment_t * @@ -756,7 +917,7 @@ sentry_attachment_t * sentry_scope_attach_bytesw_n(sentry_scope_t *scope, const char *buf, size_t buf_len, const wchar_t *filename, size_t filename_len) { - return sentry__attachments_add(&scope->attachments, + return sentry__scope_add_attachment(scope, sentry__attachment_from_buffer( buf, buf_len, sentry__path_from_wstr_n(filename, filename_len))); } diff --git a/src/sentry_scope.h b/src/sentry_scope.h index 6047fa803..9d1f6b3d8 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -8,6 +8,46 @@ #include "sentry_session.h" #include "sentry_value.h" +/** + * Scope observer — one callback per scope property. + * + * Implementors set the function pointers they care about. NULL pointers are + * skipped. Callbacks are invoked while the scope lock is held. + * + * Ownership: the scope takes ownership of the observer pointer on + * registration; the caller must not free it after that point. + */ +typedef struct sentry_scope_observer_s { + void *data; + + void (*set_release)(void *data, const char *release, size_t release_len); + void (*set_environment)( + void *data, const char *environment, size_t environment_len); + void (*set_transaction)( + void *data, const char *transaction, size_t transaction_len); + void (*set_fingerprint)(void *data, sentry_value_t fingerprint); + void (*set_level)(void *data, sentry_level_t level); + void (*set_user)(void *data, sentry_value_t user); + + void (*add_breadcrumb)(void *data, sentry_value_t breadcrumb); + + void (*set_tag)(void *data, const char *key, size_t key_len, + const char *value, size_t value_len); + void (*remove_tag)(void *data, const char *key, size_t key_len); + + void (*set_extra)( + void *data, const char *key, size_t key_len, sentry_value_t value); + void (*remove_extra)(void *data, const char *key, size_t key_len); + + void (*set_context)( + void *data, const char *key, size_t key_len, sentry_value_t value); + void (*remove_context)(void *data, const char *key, size_t key_len); + void (*set_trace_context)(void *data, sentry_value_t value); + + void (*add_attachment)(void *data, sentry_attachment_t *attachment); + void (*remove_attachment)(void *data, sentry_attachment_t *attachment); +} sentry_scope_observer_t; + /** * This represents the current scope. */ @@ -39,6 +79,10 @@ struct sentry_scope_s { sentry_transaction_t *transaction_object; sentry_span_t *span; bool trace_managed; + + sentry_scope_observer_t **observers; + size_t num_observers; + size_t is_notifying; }; /** @@ -114,6 +158,9 @@ void sentry__scope_remove_attribute(sentry_scope_t *scope, const char *key); void sentry__scope_remove_attribute_n( sentry_scope_t *scope, const char *key, size_t key_len); +sentry_attachment_t *sentry__scope_add_attachment( + sentry_scope_t *scope, sentry_attachment_t *attachment); + /** * These are convenience macros to automatically lock/unlock the global scope * inside a code block. @@ -128,6 +175,50 @@ void sentry__scope_remove_attribute_n( for (sentry_scope_t *Scope = sentry__scope_lock(); Scope; \ sentry__scope_unlock(), Scope = NULL) +/** + * Allocate and zero-initialize a scope observer. + * + * Returns NULL on allocation failure. The caller sets whichever callback + * function pointers and the `data` pointer they need, then registers the + * observer with `sentry__scope_add_observer`, which takes ownership. + */ +sentry_scope_observer_t *sentry__scope_observer_new(void); + +/** + * Register a scope observer. + * + * Takes ownership of `observer`; the caller must not free it after this call. + * Must be called while holding the scope lock. Registration order is respected + * — observers are notified in registration order. + */ +bool sentry__scope_add_observer( + sentry_scope_t *scope, sentry_scope_observer_t *observer); + +/** + * Remove a scope observer. + * + * Frees `observer` if it is registered. Must be called while holding the scope + * lock. Does nothing if `observer` is NULL or not registered. + */ +void sentry__scope_remove_observer( + sentry_scope_t *scope, sentry_scope_observer_t *observer); + +size_t sentry__scope_begin_notify(sentry_scope_t *scope); +void sentry__scope_end_notify(sentry_scope_t *scope); + +/** Notify observers registered before this notification started. */ +#define SENTRY_SCOPE_NOTIFY(scope, callback, ...) \ + do { \ + size_t _end = sentry__scope_begin_notify(scope); \ + for (size_t _i = 0; _i < _end && _i < (scope)->num_observers; _i++) { \ + sentry_scope_observer_t *_observer = (scope)->observers[_i]; \ + if (_observer && _observer->callback) { \ + _observer->callback(_observer->data, __VA_ARGS__); \ + } \ + } \ + sentry__scope_end_notify(scope); \ + } while (0) + /** * Rebuilds the scope's dynamic sampling context (DSC) from the SDK options * and the current propagation context. The previous DSC is discarded. @@ -135,6 +226,12 @@ void sentry__scope_remove_attribute_n( void sentry__scope_update_dsc( sentry_scope_t *scope, const sentry_options_t *options); +/** + * Notifies observers with the trace context derived from the active scoped span + * or transaction, falling back to the propagation context when neither is set. + */ +void sentry__scope_notify_trace_context(sentry_scope_t *scope); + /** * Replaces the scope's dynamic sampling context (DSC) with a verbatim copy * of the incoming object. Used when continuing an upstream trace: per the diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index 0ac3951ff..4d793fd03 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -1132,3 +1132,772 @@ SENTRY_TEST(scope_local_attributes) sentry_close(); } + +typedef struct { + sentry_value_t release; + sentry_value_t environment; + sentry_value_t transaction; + sentry_value_t fingerprint; + sentry_level_t level; + sentry_value_t user; + sentry_value_t breadcrumbs; + sentry_value_t tags; + sentry_value_t extras; + sentry_value_t contexts; + sentry_value_t trace_contexts; + sentry_value_t attachments; + bool was_called; +} test_observer_data_t; + +typedef struct { + test_observer_data_t *self_data; + test_observer_data_t *nested_data; + sentry_scope_observer_t *self; + sentry_scope_observer_t *added; +} reentrant_observer_data_t; + +static void +observe_set_release(void *data, const char *release, size_t release_len) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + d->release = sentry_value_new_string_n(release, release_len); + d->was_called = true; +} + +static void +observe_set_environment( + void *data, const char *environment, size_t environment_len) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + d->environment = sentry_value_new_string_n(environment, environment_len); + d->was_called = true; +} + +static void +observe_set_transaction( + void *data, const char *transaction, size_t transaction_len) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + d->transaction = sentry_value_new_string_n(transaction, transaction_len); + d->was_called = true; +} + +static void +observe_set_fingerprint(void *data, sentry_value_t fingerprint) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + if (!sentry_value_is_null(d->fingerprint)) { + sentry_value_decref(d->fingerprint); + } + sentry_value_incref(fingerprint); + d->fingerprint = fingerprint; + d->was_called = true; +} + +static void +observe_set_level(void *data, sentry_level_t level) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + d->level = level; + d->was_called = true; +} + +static void +observe_add_attachment(void *data, sentry_attachment_t *attachment) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + if (sentry_value_is_null(d->attachments)) { + d->attachments = sentry_value_new_list(); + } + sentry_value_t obj = sentry_value_new_object(); + const char *filename = sentry__attachment_get_filename(attachment); + if (filename) { + sentry_value_set_by_key( + obj, "filename", sentry_value_new_string(filename)); + } + if (attachment->buf) { + sentry_value_set_by_key(obj, "buf", + sentry_value_new_string_n(attachment->buf, attachment->buf_len)); + } + sentry_value_append(d->attachments, obj); + d->was_called = true; +} + +static void +observe_remove_attachment(void *data, sentry_attachment_t *attachment) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + if (sentry_value_is_null(d->attachments)) { + d->attachments = sentry_value_new_list(); + } + sentry_value_t obj = sentry_value_new_object(); + const char *filename = sentry__attachment_get_filename(attachment); + if (filename) { + sentry_value_set_by_key( + obj, "filename", sentry_value_new_string(filename)); + } + sentry_value_set_by_key(obj, "removed", sentry_value_new_bool(true)); + sentry_value_append(d->attachments, obj); + d->was_called = true; +} + +static void +observe_set_user(void *data, sentry_value_t user) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + d->user = user; + d->was_called = true; +} + +static void +observe_add_breadcrumb(void *data, sentry_value_t breadcrumb) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + if (sentry_value_is_null(d->breadcrumbs)) { + d->breadcrumbs = sentry_value_new_list(); + } + sentry_value_incref(breadcrumb); + sentry_value_append(d->breadcrumbs, breadcrumb); + d->was_called = true; +} + +static void +observe_set_tag(void *data, const char *key, size_t key_len, const char *value, + size_t value_len) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + if (sentry_value_is_null(d->tags)) { + d->tags = sentry_value_new_object(); + } + sentry_value_set_by_key_n( + d->tags, key, key_len, sentry_value_new_string_n(value, value_len)); + d->was_called = true; +} + +static void +observe_set_tag_remove_self_and_add(void *data, const char *key, size_t key_len, + const char *value, size_t value_len) +{ + reentrant_observer_data_t *d = (reentrant_observer_data_t *)data; + observe_set_tag(d->self_data, key, key_len, value, value_len); + SENTRY_WITH_SCOPE_MUT_NO_FLUSH (scope) { + sentry__scope_remove_observer(scope, d->self); + TEST_CHECK(sentry__scope_add_observer(scope, d->added)); + } + d->nested_data->was_called = false; + sentry_set_extra("nested", sentry_value_new_string("notify")); + TEST_CHECK(d->nested_data->was_called); +} + +static void +observe_remove_tag(void *data, const char *key, size_t key_len) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + if (sentry_value_is_null(d->tags)) { + d->tags = sentry_value_new_object(); + } + sentry_value_set_by_key_n( + d->tags, key, key_len, sentry_value_new_string("(removed)")); + d->was_called = true; +} + +static void +observe_set_extra( + void *data, const char *key, size_t key_len, sentry_value_t value) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + if (sentry_value_is_null(d->extras)) { + d->extras = sentry_value_new_object(); + } + sentry_value_incref(value); + sentry_value_set_by_key_n(d->extras, key, key_len, value); + d->was_called = true; +} + +static void +observe_remove_extra(void *data, const char *key, size_t key_len) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + if (sentry_value_is_null(d->extras)) { + d->extras = sentry_value_new_object(); + } + sentry_value_set_by_key_n( + d->extras, key, key_len, sentry_value_new_string("(removed)")); + d->was_called = true; +} + +static void +observe_set_context( + void *data, const char *key, size_t key_len, sentry_value_t value) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + if (sentry_value_is_null(d->contexts)) { + d->contexts = sentry_value_new_object(); + } + sentry_value_incref(value); + sentry_value_set_by_key_n(d->contexts, key, key_len, value); + d->was_called = true; +} + +static void +observe_remove_context(void *data, const char *key, size_t key_len) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + if (sentry_value_is_null(d->contexts)) { + d->contexts = sentry_value_new_object(); + } + sentry_value_set_by_key_n( + d->contexts, key, key_len, sentry_value_new_string("(removed)")); + d->was_called = true; +} + +static void +observe_set_trace_context(void *data, sentry_value_t trace_context) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + if (sentry_value_is_null(d->trace_contexts)) { + d->trace_contexts = sentry_value_new_list(); + } + sentry_value_incref(trace_context); + sentry_value_append(d->trace_contexts, trace_context); + d->was_called = true; +} + +SENTRY_TEST(scope_observer_null) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d = { .tags = sentry_value_new_null() }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->set_tag = observe_set_tag; + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_add_observer(scope, observer); + } + + sentry_set_tag("my-tag", "my-value"); + TEST_CHECK(d.was_called); + + d.was_called = false; + sentry_remove_tag("my-tag"); + TEST_CHECK(!d.was_called); + + sentry_value_decref(d.tags); + + sentry_close(); +} + +SENTRY_TEST(scope_observer_multiple) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d1 = { .tags = sentry_value_new_null() }; + test_observer_data_t d2 = { .tags = sentry_value_new_null() }; + sentry_scope_observer_t *observer1 = sentry__scope_observer_new(); + observer1->data = &d1; + observer1->set_tag = observe_set_tag; + + sentry_scope_observer_t *observer2 = sentry__scope_observer_new(); + observer2->data = &d2; + observer2->set_tag = observe_set_tag; + + SENTRY_WITH_SCOPE_MUT (scope) { + TEST_CHECK(sentry__scope_add_observer(scope, observer1)); + TEST_CHECK(sentry__scope_add_observer(scope, observer2)); + } + + sentry_set_tag("multi", "test"); + TEST_CHECK(d1.was_called); + TEST_CHECK(d2.was_called); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(d1.tags, "multi")), + "test"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(d2.tags, "multi")), + "test"); + + d1.was_called = false; + d2.was_called = false; + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_remove_observer(scope, observer1); + } + + sentry_set_tag("multi", "again"); + TEST_CHECK(!d1.was_called); + TEST_CHECK(d2.was_called); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(d2.tags, "multi")), + "again"); + + sentry_value_decref(d1.tags); + sentry_value_decref(d2.tags); + sentry_close(); +} + +SENTRY_TEST(scope_observer_mutate) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d1 = { .tags = sentry_value_new_null() }; + test_observer_data_t d2 = { .tags = sentry_value_new_null() }; + test_observer_data_t d3 = { .tags = sentry_value_new_null() }; + reentrant_observer_data_t reentrant + = { .self_data = &d1, .nested_data = &d2 }; + + sentry_scope_observer_t *observer1 = sentry__scope_observer_new(); + reentrant.self = observer1; + observer1->data = &reentrant; + observer1->set_tag = observe_set_tag_remove_self_and_add; + + sentry_scope_observer_t *observer2 = sentry__scope_observer_new(); + observer2->data = &d2; + observer2->set_tag = observe_set_tag; + observer2->set_extra = observe_set_extra; + + sentry_scope_observer_t *observer3 = sentry__scope_observer_new(); + reentrant.added = observer3; + observer3->data = &d3; + observer3->set_tag = observe_set_tag; + + SENTRY_WITH_SCOPE_MUT (scope) { + TEST_CHECK(sentry__scope_add_observer(scope, observer1)); + TEST_CHECK(sentry__scope_add_observer(scope, observer2)); + } + + sentry_set_tag("reentrant", "first"); + TEST_CHECK(d1.was_called); + TEST_CHECK(d2.was_called); + TEST_CHECK(!d3.was_called); + + d1.was_called = false; + d2.was_called = false; + sentry_set_tag("reentrant", "second"); + TEST_CHECK(!d1.was_called); + TEST_CHECK(d2.was_called); + TEST_CHECK(d3.was_called); + + sentry_value_decref(d1.tags); + sentry_value_decref(d2.tags); + sentry_value_decref(d3.tags); + sentry_close(); +} + +SENTRY_TEST(scope_observer_release) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d = { 0 }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->set_release = observe_set_release; + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_add_observer(scope, observer); + } + + sentry_set_release("my-release"); + TEST_CHECK(d.was_called); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(d.release), "my-release"); + + sentry_value_decref(d.release); + sentry_close(); +} + +SENTRY_TEST(scope_observer_environment) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d = { 0 }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->set_environment = observe_set_environment; + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_add_observer(scope, observer); + } + + sentry_set_environment("my-env"); + TEST_CHECK(d.was_called); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(d.environment), "my-env"); + + sentry_value_decref(d.environment); + sentry_close(); +} + +SENTRY_TEST(scope_observer_transaction) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d = { 0 }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->set_transaction = observe_set_transaction; + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_add_observer(scope, observer); + } + + sentry_set_transaction("my-transaction"); + TEST_CHECK(d.was_called); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(d.transaction), "my-transaction"); + + sentry_value_decref(d.transaction); + sentry_close(); +} + +SENTRY_TEST(scope_observer_fingerprint) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d = { 0 }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->set_fingerprint = observe_set_fingerprint; + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_add_observer(scope, observer); + } + + sentry_set_fingerprint("my-fingerprint", NULL); + TEST_CHECK(d.was_called); + TEST_CHECK(!sentry_value_is_null(d.fingerprint)); + TEST_CHECK_JSON_VALUE(d.fingerprint, "[\"my-fingerprint\"]"); + + d.was_called = false; + sentry_remove_fingerprint(); + TEST_CHECK(d.was_called); + TEST_CHECK(sentry_value_is_null(d.fingerprint)); + + sentry_close(); +} + +SENTRY_TEST(scope_observer_level) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d = { 0 }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->set_level = observe_set_level; + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_add_observer(scope, observer); + } + + sentry_set_level(SENTRY_LEVEL_WARNING); + TEST_CHECK(d.was_called); + TEST_CHECK(d.level == SENTRY_LEVEL_WARNING); + + sentry_close(); +} + +SENTRY_TEST(scope_observer_user) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d = { 0 }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->set_user = observe_set_user; + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_add_observer(scope, observer); + } + + sentry_value_t user = sentry_value_new_object(); + sentry_value_set_by_key(user, "id", sentry_value_new_string("user123")); + sentry_set_user(user); + TEST_CHECK(d.was_called); + TEST_CHECK(!sentry_value_is_null(d.user)); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(d.user, "id")), + "user123"); + + d.was_called = false; + sentry_remove_user(); + TEST_CHECK(d.was_called); + TEST_CHECK(sentry_value_is_null(d.user)); + + sentry_close(); +} + +SENTRY_TEST(scope_observer_breadcrumbs) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d = { .breadcrumbs = sentry_value_new_null() }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->add_breadcrumb = observe_add_breadcrumb; + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_add_observer(scope, observer); + } + + sentry_add_breadcrumb( + sentry_value_new_breadcrumb(NULL, "first breadcrumb")); + TEST_CHECK(d.was_called); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.breadcrumbs), 1); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key( + sentry_value_get_by_index(d.breadcrumbs, 0), "message")), + "first breadcrumb"); + + sentry_add_breadcrumb( + sentry_value_new_breadcrumb("warning", "second breadcrumb")); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.breadcrumbs), 2); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key( + sentry_value_get_by_index(d.breadcrumbs, 1), "message")), + "second breadcrumb"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key( + sentry_value_get_by_index(d.breadcrumbs, 1), "type")), + "warning"); + + sentry_value_decref(d.breadcrumbs); + sentry_close(); +} + +SENTRY_TEST(scope_observer_tags) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d = { .tags = sentry_value_new_null() }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->set_tag = observe_set_tag; + observer->remove_tag = observe_remove_tag; + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_add_observer(scope, observer); + } + + sentry_set_tag("my-tag", "my-value"); + TEST_CHECK(d.was_called); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(d.tags, "my-tag")), + "my-value"); + TEST_CHECK_INT_EQUAL( + sentry_value_get_length(sentry_value_get_by_key(d.tags, "my-tag")), 8); + + d.was_called = false; + sentry_remove_tag("my-tag"); + TEST_CHECK(d.was_called); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(d.tags, "my-tag")), + "(removed)"); + + sentry_value_decref(d.tags); + sentry_close(); +} + +SENTRY_TEST(scope_observer_extras) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d = { .extras = sentry_value_new_null() }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->set_extra = observe_set_extra; + observer->remove_extra = observe_remove_extra; + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_add_observer(scope, observer); + } + + sentry_value_t val = sentry_value_new_string("extra-value"); + sentry_set_extra("my-extra", val); + TEST_CHECK(d.was_called); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(d.extras, "my-extra")), + "extra-value"); + + d.was_called = false; + sentry_remove_extra("my-extra"); + TEST_CHECK(d.was_called); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(d.extras, "my-extra")), + "(removed)"); + + sentry_value_decref(d.extras); + sentry_close(); +} + +SENTRY_TEST(scope_observer_contexts) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d = { .contexts = sentry_value_new_null() }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->set_context = observe_set_context; + observer->remove_context = observe_remove_context; + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_add_observer(scope, observer); + } + + sentry_value_t ctx = sentry_value_new_object(); + sentry_value_set_by_key(ctx, "type", sentry_value_new_string("device")); + sentry_set_context("my-context", ctx); + TEST_CHECK(d.was_called); + sentry_value_t received = sentry_value_get_by_key(d.contexts, "my-context"); + TEST_CHECK(!sentry_value_is_null(received)); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(received, "type")), + "device"); + + sentry_value_t update = sentry_value_new_object(); + sentry_value_set_by_key(update, "version", sentry_value_new_string("1.0")); + d.was_called = false; + sentry_update_context("my-context", update); + TEST_CHECK(d.was_called); + received = sentry_value_get_by_key(d.contexts, "my-context"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(received, "type")), + "device"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(received, "version")), + "1.0"); + + d.was_called = false; + sentry_remove_context("my-context"); + TEST_CHECK(d.was_called); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( + d.contexts, "my-context")), + "(removed)"); + + sentry_value_decref(d.contexts); + sentry_close(); +} + +SENTRY_TEST(scope_observer_trace_context) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_options_set_traces_sample_rate(options, 1.0); + sentry_init(options); + + test_observer_data_t d = { .trace_contexts = sentry_value_new_null() }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->set_trace_context = observe_set_trace_context; + + SENTRY_WITH_SCOPE_MUT (scope) { + TEST_CHECK(sentry__scope_add_observer(scope, observer)); + } + + sentry_transaction_context_t *tx_ctx + = sentry_transaction_context_new("root", "op"); + sentry_transaction_t *tx + = sentry_transaction_start(tx_ctx, sentry_value_new_null()); + sentry_transaction_set_data(tx, "tx-data", sentry_value_new_string("root")); + sentry_set_transaction_object(tx); + + TEST_CHECK(d.was_called); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.trace_contexts), 1); + sentry_value_t trace = sentry_value_get_by_index(d.trace_contexts, 0); + TEST_CHECK( + !sentry_value_is_null(sentry_value_get_by_key(trace, "trace_id"))); + TEST_CHECK( + !sentry_value_is_null(sentry_value_get_by_key(trace, "span_id"))); + sentry_value_t data = sentry_value_get_by_key(trace, "data"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(data, "tx-data")), + "root"); + + sentry_span_t *child = sentry_transaction_start_child(tx, "child", "desc"); + sentry_span_set_data(child, "span-data", sentry_value_new_string("child")); + sentry_set_span(child); + + TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.trace_contexts), 2); + trace = sentry_value_get_by_index(d.trace_contexts, 1); + data = sentry_value_get_by_key(trace, "data"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(data, "span-data")), + "child"); + + sentry_span_finish(child); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.trace_contexts), 3); + + sentry_transaction_finish(tx); + sentry_value_decref(d.trace_contexts); + sentry_close(); +} + +SENTRY_TEST(scope_observer_attachments) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d = { .attachments = sentry_value_new_null() }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->add_attachment = observe_add_attachment; + observer->remove_attachment = observe_remove_attachment; + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_add_observer(scope, observer); + } + + sentry_attachment_t *attachment = sentry_attach_bytes("buf", 3, "test.txt"); + TEST_CHECK(d.was_called); + TEST_CHECK(attachment != NULL); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.attachments), 1); + sentry_value_t added = sentry_value_get_by_index(d.attachments, 0); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(added, "buf")), "buf"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(added, "filename")), + "test.txt"); + + d.was_called = false; + sentry_remove_attachment(attachment); + TEST_CHECK(d.was_called); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.attachments), 2); + sentry_value_t removed = sentry_value_get_by_index(d.attachments, 1); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(removed, "filename")), + "test.txt"); + TEST_CHECK( + sentry_value_is_true(sentry_value_get_by_key(removed, "removed"))); + + attachment = sentry_attach_file("test.txt"); + TEST_CHECK(d.was_called); + TEST_CHECK(attachment != NULL); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.attachments), 3); + + d.was_called = false; + sentry_attachment_t *duplicate = sentry_attach_file("test.txt"); + TEST_CHECK(duplicate == attachment); + TEST_CHECK(!d.was_called); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.attachments), 3); + + d.was_called = false; + sentry_remove_attachment(attachment); + TEST_CHECK(d.was_called); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.attachments), 4); + + d.was_called = false; + sentry_remove_attachment(duplicate); + TEST_CHECK(!d.was_called); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.attachments), 4); + + sentry_value_decref(d.attachments); + sentry_close(); +} diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 86addc6da..f16b745ae 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -285,14 +285,29 @@ XX(sampling_decision) XX(sampling_transaction) XX(scope_breadcrumbs) XX(scope_contexts) -XX(scope_update_context) XX(scope_extra) XX(scope_fingerprint) XX(scope_fingerprint_n) XX(scope_global_attributes) XX(scope_level) XX(scope_local_attributes) +XX(scope_observer_attachments) +XX(scope_observer_breadcrumbs) +XX(scope_observer_contexts) +XX(scope_observer_environment) +XX(scope_observer_extras) +XX(scope_observer_fingerprint) +XX(scope_observer_level) +XX(scope_observer_multiple) +XX(scope_observer_mutate) +XX(scope_observer_null) +XX(scope_observer_release) +XX(scope_observer_tags) +XX(scope_observer_trace_context) +XX(scope_observer_transaction) +XX(scope_observer_user) XX(scope_tags) +XX(scope_update_context) XX(scope_user) XX(scope_user_id) XX(scoped_txn)