diff --git a/CHANGELOG.md b/CHANGELOG.md index 547da0656..530d3b729 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ **Features**: - Provide `before_send_transaction` callback. ([#1236](https://github.com/getsentry/sentry-native/pull/1236)) +- Add support for capturing events with local scopes. ([#1248](https://github.com/getsentry/sentry-native/pull/1248)) **Fixes**: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c44ad96c4..805fbc233 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -184,6 +184,7 @@ The example currently supports the following commands: - `attach-view-hierarchy`: Adds a `view-hierarchy.json` attachment file, giving it the proper `attachment_type` and `content_type`. This file can be found in `./tests/fixtures/view-hierachy.json`. - `set-trace`: Sets the scope `propagation_context`'s trace data to the given `trace_id="aaaabbbbccccddddeeeeffff00001111"` and `parent_span_id=""f0f0f0f0f0f0f0f0"`. +- `capture-with-scope`: Captures an event with a local scope. Only on Linux using crashpad: - `crashpad-wait-for-upload`: Couples application shutdown to complete the upload in the `crashpad_handler`. diff --git a/examples/example.c b/examples/example.c index 8f27165b9..b6897cfaf 100644 --- a/examples/example.c +++ b/examples/example.c @@ -220,6 +220,30 @@ trigger_stack_overflow() trigger_stack_overflow(); } +static sentry_value_t +create_debug_crumb(const char *message) +{ + sentry_value_t debug_crumb = sentry_value_new_breadcrumb("http", message); + sentry_value_set_by_key( + debug_crumb, "category", sentry_value_new_string("example!")); + sentry_value_set_by_key( + debug_crumb, "level", sentry_value_new_string("debug")); + + // extend the `http` crumb with (optional) data properties as documented + // here: + // https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/#breadcrumb-types + sentry_value_t http_data = sentry_value_new_object(); + sentry_value_set_by_key(http_data, "url", + sentry_value_new_string("https://example.com/api/1.0/users")); + sentry_value_set_by_key( + http_data, "method", sentry_value_new_string("GET")); + sentry_value_set_by_key( + http_data, "status_code", sentry_value_new_int32(200)); + sentry_value_set_by_key(http_data, "reason", sentry_value_new_string("OK")); + sentry_value_set_by_key(debug_crumb, "data", http_data); + return debug_crumb; +} + int main(int argc, char **argv) { @@ -359,27 +383,7 @@ main(int argc, char **argv) = sentry_value_new_breadcrumb(NULL, "default level is info"); sentry_add_breadcrumb(default_crumb); - sentry_value_t debug_crumb - = sentry_value_new_breadcrumb("http", "debug crumb"); - sentry_value_set_by_key( - debug_crumb, "category", sentry_value_new_string("example!")); - sentry_value_set_by_key( - debug_crumb, "level", sentry_value_new_string("debug")); - - // extend the `http` crumb with (optional) data properties as documented - // here: - // https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/#breadcrumb-types - sentry_value_t http_data = sentry_value_new_object(); - sentry_value_set_by_key(http_data, "url", - sentry_value_new_string("https://example.com/api/1.0/users")); - sentry_value_set_by_key( - http_data, "method", sentry_value_new_string("GET")); - sentry_value_set_by_key( - http_data, "status_code", sentry_value_new_int32(200)); - sentry_value_set_by_key( - http_data, "reason", sentry_value_new_string("OK")); - sentry_value_set_by_key(debug_crumb, "data", http_data); - + sentry_value_t debug_crumb = create_debug_crumb("debug crumb"); sentry_add_breadcrumb(debug_crumb); sentry_value_t nl_crumb @@ -407,6 +411,22 @@ main(int argc, char **argv) } } + if (has_arg(argc, argv, "capture-with-scope")) { + sentry_scope_t *scope = sentry_local_scope_new(); + + sentry_value_t event = sentry_value_new_message_event( + SENTRY_LEVEL_INFO, NULL, "Hello Scope!"); + + sentry_value_t default_crumb + = sentry_value_new_breadcrumb(NULL, "default level is info"); + sentry_scope_add_breadcrumb(scope, default_crumb); + + sentry_value_t debug_crumb = create_debug_crumb("scoped crumb"); + sentry_scope_add_breadcrumb(scope, debug_crumb); + + sentry_capture_event_with_scope(event, scope); + } + if (has_arg(argc, argv, "capture-multiple")) { for (size_t i = 0; i < 10; i++) { char buffer[10]; diff --git a/include/sentry.h b/include/sentry.h index bf7a25fb2..b9d37fa2a 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1402,7 +1402,7 @@ SENTRY_API uint64_t sentry_options_get_shutdown_timeout(sentry_options_t *opts); SENTRY_API void sentry_options_set_backend( sentry_options_t *opts, sentry_backend_t *backend); -/* -- Global APIs -- */ +/* -- Global/Scope APIs -- */ /** * Initializes the Sentry SDK with the specified options. @@ -1502,6 +1502,19 @@ SENTRY_API void sentry_user_consent_reset(void); */ SENTRY_API sentry_user_consent_t sentry_user_consent_get(void); +/** + * A sentry Scope. + * + * See https://develop.sentry.dev/sdk/telemetry/scopes/ + */ +struct sentry_scope_s; +typedef struct sentry_scope_s sentry_scope_t; + +/** + * Creates a local scope. + */ +SENTRY_API sentry_scope_t *sentry_local_scope_new(void); + /** * Sends a sentry event. * @@ -1511,6 +1524,14 @@ SENTRY_API sentry_user_consent_t sentry_user_consent_get(void); */ SENTRY_API sentry_uuid_t sentry_capture_event(sentry_value_t event); +/** + * Sends a sentry event with a local scope. + * + * Takes ownership of `scope`. + */ +SENTRY_API sentry_uuid_t sentry_capture_event_with_scope( + sentry_value_t event, sentry_scope_t *scope); + /** * Allows capturing independently created minidumps. * @@ -1555,11 +1576,15 @@ SENTRY_EXPERIMENTAL_API void sentry_handle_exception( * Adds the breadcrumb to be sent in case of an event. */ SENTRY_API void sentry_add_breadcrumb(sentry_value_t breadcrumb); +SENTRY_API void sentry_scope_add_breadcrumb( + sentry_scope_t *scope, sentry_value_t breadcrumb); /** * Sets the specified user. */ SENTRY_API void sentry_set_user(sentry_value_t user); +SENTRY_API void sentry_scope_set_user( + sentry_scope_t *scope, sentry_value_t user); /** * Removes a user. @@ -1572,6 +1597,10 @@ SENTRY_API void sentry_remove_user(void); SENTRY_API void sentry_set_tag(const char *key, const char *value); SENTRY_API void sentry_set_tag_n( const char *key, size_t key_len, const char *value, size_t value_len); +SENTRY_API void sentry_scope_set_tag( + sentry_scope_t *scope, const char *key, const char *value); +SENTRY_API void sentry_scope_set_tag_n(sentry_scope_t *scope, const char *key, + size_t key_len, const char *value, size_t value_len); /** * Removes the tag with the specified key. @@ -1585,6 +1614,10 @@ SENTRY_API void sentry_remove_tag_n(const char *key, size_t key_len); SENTRY_API void sentry_set_extra(const char *key, sentry_value_t value); SENTRY_API void sentry_set_extra_n( const char *key, size_t key_len, sentry_value_t value); +SENTRY_API void sentry_scope_set_extra( + sentry_scope_t *scope, const char *key, sentry_value_t value); +SENTRY_API void sentry_scope_set_extra_n(sentry_scope_t *scope, const char *key, + size_t key_len, sentry_value_t value); /** * Removes the extra with the specified key. @@ -1598,6 +1631,10 @@ SENTRY_API void sentry_remove_extra_n(const char *key, size_t key_len); SENTRY_API void sentry_set_context(const char *key, sentry_value_t value); SENTRY_API void sentry_set_context_n( const char *key, size_t key_len, sentry_value_t value); +SENTRY_API void sentry_scope_set_context( + sentry_scope_t *scope, const char *key, sentry_value_t value); +SENTRY_API void sentry_scope_set_context_n(sentry_scope_t *scope, + const char *key, size_t key_len, sentry_value_t value); /** * Removes the context object with the specified key. @@ -1614,6 +1651,18 @@ SENTRY_API void sentry_remove_context_n(const char *key, size_t key_len); SENTRY_API void sentry_set_fingerprint(const char *fingerprint, ...); SENTRY_API void sentry_set_fingerprint_n( const char *fingerprint, size_t fingerprint_len, ...); +SENTRY_API void sentry_scope_set_fingerprint( + sentry_scope_t *scope, const char *fingerprint, ...); +SENTRY_API void sentry_scope_set_fingerprint_n(sentry_scope_t *scope, + const char *fingerprint, size_t fingerprint_len, ...); + +/** + * Sets the event fingerprints. + * + * This accepts a list of fingerprints created with `sentry_value_new_list`. + */ +SENTRY_API void sentry_scope_set_fingerprints( + sentry_scope_t *scope, sentry_value_t fingerprints); /** * Removes the fingerprint. @@ -1640,6 +1689,8 @@ SENTRY_API void sentry_set_transaction_n( * Sets the event level. */ SENTRY_API void sentry_set_level(sentry_level_t level); +SENTRY_API void sentry_scope_set_level( + sentry_scope_t *scope, sentry_level_t level); /** * Sets the maximum number of spans that can be attached to a diff --git a/src/backends/sentry_backend_breakpad.cpp b/src/backends/sentry_backend_breakpad.cpp index 2e29eeb89..07e0f6361 100644 --- a/src/backends/sentry_backend_breakpad.cpp +++ b/src/backends/sentry_backend_breakpad.cpp @@ -143,7 +143,7 @@ breakpad_backend_callback(const google_breakpad::MinidumpDescriptor &descriptor, if (should_handle) { sentry_envelope_t *envelope = sentry__prepare_event( - options, event, nullptr, !options->on_crash_func); + options, event, nullptr, !options->on_crash_func, NULL); sentry_session_t *session = sentry__end_current_session_with_status( SENTRY_SESSION_STATUS_CRASHED); sentry__envelope_add_session(envelope, session); diff --git a/src/backends/sentry_backend_inproc.c b/src/backends/sentry_backend_inproc.c index b95d20b9a..b03982df1 100644 --- a/src/backends/sentry_backend_inproc.c +++ b/src/backends/sentry_backend_inproc.c @@ -589,7 +589,7 @@ handle_ucontext(const sentry_ucontext_t *uctx) if (should_handle) { sentry_envelope_t *envelope = sentry__prepare_event( - options, event, NULL, !options->on_crash_func); + options, event, NULL, !options->on_crash_func, NULL); // TODO(tracing): Revisit when investigating transaction flushing // during hard crashes. diff --git a/src/sentry_core.c b/src/sentry_core.c index d00305782..24d7a94bf 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -432,7 +432,17 @@ sentry_capture_event(sentry_value_t event) if (sentry__event_is_transaction(event)) { return sentry_uuid_nil(); } else { - return sentry__capture_event(event); + return sentry__capture_event(event, NULL); + } +} + +sentry_uuid_t +sentry_capture_event_with_scope(sentry_value_t event, sentry_scope_t *scope) +{ + if (sentry__event_is_transaction(event)) { + return sentry_uuid_nil(); + } else { + return sentry__capture_event(event, scope); } } @@ -448,7 +458,7 @@ static } sentry_uuid_t -sentry__capture_event(sentry_value_t event) +sentry__capture_event(sentry_value_t event, sentry_scope_t *local_scope) { // `event_id` is only used as an argument to pure output parameters. // Initialization only happens to prevent compiler warnings. @@ -463,7 +473,8 @@ sentry__capture_event(sentry_value_t event) if (sentry__event_is_transaction(event)) { envelope = sentry__prepare_transaction(options, event, &event_id); } else { - envelope = sentry__prepare_event(options, event, &event_id, true); + envelope = sentry__prepare_event( + options, event, &event_id, true, local_scope); } if (envelope) { if (options->session) { @@ -552,7 +563,8 @@ str_from_attachment_type(sentry_attachment_type_t attachment_type) 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_uuid_t *event_id, bool invoke_before_send, + sentry_scope_t *local_scope) { sentry_envelope_t *envelope = NULL; @@ -560,8 +572,15 @@ sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, sentry__record_errors_on_current_session(1); } + if (local_scope) { + SENTRY_DEBUG("merging local scope into event"); + sentry_scope_mode_t mode = SENTRY_SCOPE_BREADCRUMBS; + sentry__scope_apply_to_event(local_scope, options, event, mode); + sentry__scope_free(local_scope); + } + SENTRY_WITH_SCOPE (scope) { - SENTRY_DEBUG("merging scope into event"); + SENTRY_DEBUG("merging global scope into event"); sentry_scope_mode_t mode = SENTRY_SCOPE_ALL; if (!options->symbolize_stacktraces) { mode &= ~SENTRY_SCOPE_STACKTRACES; @@ -731,8 +750,7 @@ sentry_set_user(sentry_value_t user) } SENTRY_WITH_SCOPE_MUT (scope) { - sentry_value_decref(scope->user); - scope->user = user; + sentry_scope_set_user(scope, user); } } @@ -745,21 +763,18 @@ sentry_remove_user(void) void sentry_add_breadcrumb(sentry_value_t breadcrumb) { - size_t max_breadcrumbs = SENTRY_BREADCRUMBS_MAX; SENTRY_WITH_OPTIONS (options) { if (options->backend && options->backend->add_breadcrumb_func) { // the hook will *not* take ownership options->backend->add_breadcrumb_func( options->backend, breadcrumb, options); } - max_breadcrumbs = options->max_breadcrumbs; } // the `no_flush` will avoid triggering *both* scope-change and // breadcrumb-add events. SENTRY_WITH_SCOPE_MUT_NO_FLUSH (scope) { - sentry__value_append_ringbuffer( - scope->breadcrumbs, breadcrumb, max_breadcrumbs); + sentry_scope_add_breadcrumb(scope, breadcrumb); } } @@ -767,8 +782,7 @@ void sentry_set_tag(const char *key, const char *value) { SENTRY_WITH_SCOPE_MUT (scope) { - sentry_value_set_by_key( - scope->tags, key, sentry_value_new_string(value)); + sentry_scope_set_tag(scope, key, value); } } @@ -777,8 +791,7 @@ sentry_set_tag_n( const char *key, size_t key_len, const char *value, size_t value_len) { SENTRY_WITH_SCOPE_MUT (scope) { - sentry_value_set_by_key_n(scope->tags, key, key_len, - sentry_value_new_string_n(value, value_len)); + sentry_scope_set_tag_n(scope, key, key_len, value, value_len); } } @@ -802,7 +815,7 @@ void sentry_set_extra(const char *key, sentry_value_t value) { SENTRY_WITH_SCOPE_MUT (scope) { - sentry_value_set_by_key(scope->extra, key, value); + sentry_scope_set_extra(scope, key, value); } } @@ -810,7 +823,7 @@ void sentry_set_extra_n(const char *key, size_t key_len, sentry_value_t value) { SENTRY_WITH_SCOPE_MUT (scope) { - sentry_value_set_by_key_n(scope->extra, key, key_len, value); + sentry_scope_set_extra_n(scope, key, key_len, value); } } @@ -834,7 +847,7 @@ void sentry_set_context(const char *key, sentry_value_t value) { SENTRY_WITH_SCOPE_MUT (scope) { - sentry_value_set_by_key(scope->contexts, key, value); + sentry_scope_set_context(scope, key, value); } } @@ -842,7 +855,7 @@ void sentry_set_context_n(const char *key, size_t key_len, sentry_value_t value) { SENTRY_WITH_SCOPE_MUT (scope) { - sentry_value_set_by_key_n(scope->contexts, key, key_len, value); + sentry_scope_set_context_n(scope, key, key_len, value); } } @@ -873,39 +886,28 @@ sentry_remove_context_n(const char *key, size_t key_len) void sentry_set_fingerprint_n(const char *fingerprint, size_t fingerprint_len, ...) { - sentry_value_t fingerprint_value = sentry_value_new_list(); - va_list va; va_start(va, fingerprint_len); - for (; fingerprint; fingerprint = va_arg(va, const char *)) { - sentry_value_append(fingerprint_value, - sentry_value_new_string_n(fingerprint, fingerprint_len)); - } - va_end(va); SENTRY_WITH_SCOPE_MUT (scope) { - sentry_value_decref(scope->fingerprint); - scope->fingerprint = fingerprint_value; + sentry__scope_set_fingerprint_nva( + scope, fingerprint, fingerprint_len, va); } + + va_end(va); } void sentry_set_fingerprint(const char *fingerprint, ...) { - sentry_value_t fingerprint_value = sentry_value_new_list(); - va_list va; va_start(va, fingerprint); - for (; fingerprint; fingerprint = va_arg(va, const char *)) { - sentry_value_append( - fingerprint_value, sentry_value_new_string(fingerprint)); - } - va_end(va); SENTRY_WITH_SCOPE_MUT (scope) { - sentry_value_decref(scope->fingerprint); - scope->fingerprint = fingerprint_value; + sentry__scope_set_fingerprint_va(scope, fingerprint, va); } + + va_end(va); } void @@ -979,7 +981,7 @@ void sentry_set_level(sentry_level_t level) { SENTRY_WITH_SCOPE_MUT (scope) { - scope->level = level; + sentry_scope_set_level(scope, level); } } @@ -1110,7 +1112,7 @@ sentry_transaction_finish_ts( // This takes ownership of the transaction, generates an event ID, merges // scope - return sentry__capture_event(tx); + return sentry__capture_event(tx, NULL); fail: sentry__transaction_decref(opaque_tx); return sentry_uuid_nil(); @@ -1404,7 +1406,7 @@ sentry_capture_minidump_n(const char *path, size_t path_len) 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); + = sentry__prepare_event(options, event, &event_id, true, NULL); if (!envelope || sentry_uuid_is_nil(&event_id)) { sentry_value_decref(event); diff --git a/src/sentry_core.h b/src/sentry_core.h index 2be13d313..ba34debdd 100644 --- a/src/sentry_core.h +++ b/src/sentry_core.h @@ -58,12 +58,14 @@ bool sentry__event_is_transaction(sentry_value_t event); * `event_id` out-parameter. */ 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_value_t event, sentry_uuid_t *event_id, bool invoke_before_send, + sentry_scope_t *local_scope); /** * Sends a sentry event, regardless of its type. */ -sentry_uuid_t sentry__capture_event(sentry_value_t event); +sentry_uuid_t sentry__capture_event( + sentry_value_t event, sentry_scope_t *local_scope); /** * Convert the given transaction into an envelope. This assumes that the diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 40246e6b2..7109cd984 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -1,4 +1,5 @@ #include "sentry_scope.h" +#include "sentry_alloc.h" #include "sentry_backend.h" #include "sentry_core.h" #include "sentry_database.h" @@ -62,6 +63,24 @@ get_client_sdk(void) return client_sdk; } +static void +init_scope(sentry_scope_t *scope) +{ + memset(scope, 0, sizeof(sentry_scope_t)); + scope->transaction = NULL; + scope->fingerprint = sentry_value_new_null(); + scope->user = sentry_value_new_null(); + scope->tags = sentry_value_new_object(); + scope->extra = sentry_value_new_object(); + scope->contexts = sentry_value_new_object(); + scope->propagation_context = sentry_value_new_object(); + scope->breadcrumbs = sentry_value_new_list(); + scope->level = SENTRY_LEVEL_ERROR; + scope->client_sdk = sentry_value_new_null(); + scope->transaction_object = NULL; + scope->span = NULL; +} + static sentry_scope_t * get_scope(void) { @@ -69,26 +88,31 @@ get_scope(void) return &g_scope; } - memset(&g_scope, 0, sizeof(sentry_scope_t)); - g_scope.transaction = NULL; - g_scope.fingerprint = sentry_value_new_null(); - g_scope.user = sentry_value_new_null(); - g_scope.tags = sentry_value_new_object(); - g_scope.extra = sentry_value_new_object(); - g_scope.contexts = sentry_value_new_object(); + init_scope(&g_scope); sentry_value_set_by_key(g_scope.contexts, "os", sentry__get_os_context()); - g_scope.propagation_context = sentry_value_new_object(); - g_scope.breadcrumbs = sentry_value_new_list(); - g_scope.level = SENTRY_LEVEL_ERROR; g_scope.client_sdk = get_client_sdk(); - g_scope.transaction_object = NULL; - g_scope.span = NULL; g_scope_initialized = true; return &g_scope; } +static void +cleanup_scope(sentry_scope_t *scope) +{ + sentry_free(scope->transaction); + sentry_value_decref(scope->fingerprint); + sentry_value_decref(scope->user); + sentry_value_decref(scope->tags); + sentry_value_decref(scope->extra); + sentry_value_decref(scope->contexts); + sentry_value_decref(scope->propagation_context); + sentry_value_decref(scope->breadcrumbs); + sentry_value_decref(scope->client_sdk); + sentry__transaction_decref(scope->transaction_object); + sentry__span_decref(scope->span); +} + void sentry__scope_cleanup(void) { @@ -96,17 +120,7 @@ sentry__scope_cleanup(void) sentry__mutex_lock(&g_lock); if (g_scope_initialized) { g_scope_initialized = false; - sentry_free(g_scope.transaction); - sentry_value_decref(g_scope.fingerprint); - sentry_value_decref(g_scope.user); - sentry_value_decref(g_scope.tags); - sentry_value_decref(g_scope.extra); - sentry_value_decref(g_scope.contexts); - sentry_value_decref(g_scope.propagation_context); - sentry_value_decref(g_scope.breadcrumbs); - sentry_value_decref(g_scope.client_sdk); - sentry__transaction_decref(g_scope.transaction_object); - sentry__span_decref(g_scope.span); + cleanup_scope(&g_scope); } sentry__mutex_unlock(&g_lock); } @@ -139,6 +153,29 @@ sentry__scope_flush_unlock(void) } } +sentry_scope_t * +sentry_local_scope_new(void) +{ + sentry_scope_t *scope = SENTRY_MAKE(sentry_scope_t); + if (!scope) { + return NULL; + } + + init_scope(scope); + return scope; +} + +void +sentry__scope_free(sentry_scope_t *scope) +{ + if (!scope) { + return; + } + + cleanup_scope(scope); + sentry_free(scope); +} + #if !defined(SENTRY_PLATFORM_NX) && !defined(SENTRY_PLATFORM_PS) static void sentry__foreach_stacktrace( @@ -265,6 +302,116 @@ sentry__scope_get_span_or_transaction(void) } #endif +static int +cmp_breadcrumb(sentry_value_t a, sentry_value_t b, bool *error) +{ + sentry_value_t timestamp_a = sentry_value_get_by_key(a, "timestamp"); + sentry_value_t timestamp_b = sentry_value_get_by_key(b, "timestamp"); + if (sentry_value_is_null(timestamp_a)) { + *error = true; + return -1; + } + if (sentry_value_is_null(timestamp_b)) { + *error = true; + return 1; + } + + return strcmp(sentry_value_as_string(timestamp_a), + sentry_value_as_string(timestamp_b)); +} + +static bool +append_breadcrumb(sentry_value_t target, sentry_value_t source, size_t index) +{ + int rv = sentry_value_append( + target, sentry_value_get_by_index_owned(source, index)); + if (rv != 0) { + SENTRY_ERROR("Failed to merge breadcrumbs"); + sentry_value_decref(target); + return false; + } + return true; +} + +static sentry_value_t +merge_breadcrumbs(sentry_value_t list_a, sentry_value_t list_b, size_t max) +{ + size_t len_a = sentry_value_get_type(list_a) == SENTRY_VALUE_TYPE_LIST + ? sentry_value_get_length(list_a) + : 0; + size_t len_b = sentry_value_get_type(list_b) == SENTRY_VALUE_TYPE_LIST + ? sentry_value_get_length(list_b) + : 0; + + if (len_a == 0 && len_b == 0) { + return sentry_value_new_null(); + } else if (len_a == 0) { + sentry_value_incref(list_b); + return list_b; + } else if (len_b == 0) { + sentry_value_incref(list_a); + return list_a; + } + + bool error = false; + size_t idx_a = 0; + size_t idx_b = 0; + size_t total = len_a + len_b; + size_t skip = total > max ? total - max : 0; + sentry_value_t result = sentry__value_new_list_with_size(total - skip); + + // skip oldest breadcrumbs to fit max + while (idx_a < len_a && idx_b < len_b && idx_a + idx_b < skip) { + sentry_value_t item_a = sentry_value_get_by_index(list_a, idx_a); + sentry_value_t item_b = sentry_value_get_by_index(list_b, idx_b); + + if (cmp_breadcrumb(item_a, item_b, &error) <= 0) { + idx_a++; + } else { + idx_b++; + } + } + while (idx_a < len_a && idx_a + idx_b < skip) { + idx_a++; + } + while (idx_b < len_b && idx_a + idx_b < skip) { + idx_b++; + } + + // merge the remaining breadcrumbs in timestamp order + while (idx_a < len_a && idx_b < len_b) { + sentry_value_t item_a = sentry_value_get_by_index(list_a, idx_a); + sentry_value_t item_b = sentry_value_get_by_index(list_b, idx_b); + + if (cmp_breadcrumb(item_a, item_b, &error) <= 0) { + if (!append_breadcrumb(result, list_a, idx_a++)) { + return sentry_value_new_null(); + } + } else { + if (!append_breadcrumb(result, list_b, idx_b++)) { + return sentry_value_new_null(); + } + } + } + while (idx_a < len_a) { + if (!append_breadcrumb(result, list_a, idx_a++)) { + return sentry_value_new_null(); + } + } + while (idx_b < len_b) { + if (!append_breadcrumb(result, list_b, idx_b++)) { + return sentry_value_new_null(); + } + } + + if (error) { + SENTRY_WARN("Detected missing timestamps while merging breadcrumbs. " + "This may lead to unexpected results."); + } + + return result; +} + void sentry__scope_apply_to_event(const sentry_scope_t *scope, const sentry_options_t *options, sentry_value_t event, @@ -360,10 +507,14 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, sentry_value_decref(contexts); if (mode & SENTRY_SCOPE_BREADCRUMBS) { - sentry_value_t l + sentry_value_t event_breadcrumbs + = sentry_value_get_by_key(event, "breadcrumbs"); + sentry_value_t scope_breadcrumbs = sentry__value_ring_buffer_to_list(scope->breadcrumbs); - PLACE_VALUE("breadcrumbs", l); - sentry_value_decref(l); + sentry_value_set_by_key(event, "breadcrumbs", + merge_breadcrumbs(event_breadcrumbs, scope_breadcrumbs, + options->max_breadcrumbs)); + sentry_value_decref(scope_breadcrumbs); } #if !defined(SENTRY_PLATFORM_NX) && !defined(SENTRY_PLATFORM_PS) @@ -387,3 +538,134 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, #undef SET #undef IS_NULL } + +void +sentry_scope_add_breadcrumb(sentry_scope_t *scope, sentry_value_t breadcrumb) +{ + size_t max_breadcrumbs = SENTRY_BREADCRUMBS_MAX; + SENTRY_WITH_OPTIONS (options) { + max_breadcrumbs = options->max_breadcrumbs; + } + + sentry__value_append_ringbuffer( + scope->breadcrumbs, breadcrumb, max_breadcrumbs); +} + +void +sentry_scope_set_user(sentry_scope_t *scope, sentry_value_t user) +{ + sentry_value_decref(scope->user); + scope->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)); +} + +void +sentry_scope_set_tag_n(sentry_scope_t *scope, const char *key, size_t key_len, + const char *value, size_t value_len) +{ + sentry_value_set_by_key_n( + scope->tags, key, key_len, sentry_value_new_string_n(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); +} + +void +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); +} + +void +sentry_scope_set_context( + sentry_scope_t *scope, const char *key, sentry_value_t value) +{ + sentry_value_set_by_key(scope->contexts, key, value); +} + +void +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); +} + +void +sentry__scope_set_fingerprint_va( + sentry_scope_t *scope, const char *fingerprint, va_list va) +{ + sentry_value_t fingerprint_value = sentry_value_new_list(); + for (; fingerprint; fingerprint = va_arg(va, const char *)) { + sentry_value_append( + fingerprint_value, sentry_value_new_string(fingerprint)); + } + + sentry_value_decref(scope->fingerprint); + scope->fingerprint = fingerprint_value; +} + +void +sentry__scope_set_fingerprint_nva(sentry_scope_t *scope, + const char *fingerprint, size_t fingerprint_len, va_list va) +{ + sentry_value_t fingerprint_value = sentry_value_new_list(); + for (; fingerprint; fingerprint = va_arg(va, const char *)) { + sentry_value_append(fingerprint_value, + sentry_value_new_string_n(fingerprint, fingerprint_len)); + } + + sentry_scope_set_fingerprints(scope, fingerprint_value); +} + +void +sentry_scope_set_fingerprint( + sentry_scope_t *scope, const char *fingerprint, ...) +{ + va_list va; + va_start(va, fingerprint); + + sentry__scope_set_fingerprint_va(scope, fingerprint, va); + + va_end(va); +} + +void +sentry_scope_set_fingerprint_n( + sentry_scope_t *scope, const char *fingerprint, size_t fingerprint_len, ...) +{ + va_list va; + va_start(va, fingerprint_len); + + sentry__scope_set_fingerprint_nva(scope, fingerprint, fingerprint_len, va); + + va_end(va); +} + +void +sentry_scope_set_fingerprints( + sentry_scope_t *scope, sentry_value_t fingerprints) +{ + if (sentry_value_get_type(fingerprints) != SENTRY_VALUE_TYPE_LIST) { + SENTRY_WARN("invalid fingerprints type, expected list"); + return; + } + + sentry_value_decref(scope->fingerprint); + scope->fingerprint = fingerprints; +} + +void +sentry_scope_set_level(sentry_scope_t *scope, sentry_level_t level) +{ + scope->level = level; +} diff --git a/src/sentry_scope.h b/src/sentry_scope.h index bf28c009b..eaf7eb37d 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -9,7 +9,7 @@ /** * This represents the current scope. */ -typedef struct sentry_scope_s { +struct sentry_scope_s { char *transaction; sentry_value_t fingerprint; sentry_value_t user; @@ -31,7 +31,7 @@ typedef struct sentry_scope_s { // `name` property nested in transaction_object or span. sentry_transaction_t *transaction_object; sentry_span_t *span; -} sentry_scope_t; +}; /** * When applying a scope to an event object, this specifies all the additional @@ -71,6 +71,11 @@ void sentry__scope_cleanup(void); */ void sentry__scope_flush_unlock(void); +/** + * Deallocates a (local) scope. + */ +void sentry__scope_free(sentry_scope_t *scope); + /** * This will merge the requested data which is in the given `scope` to the given * `event`. @@ -81,9 +86,14 @@ void sentry__scope_apply_to_event(const sentry_scope_t *scope, const sentry_options_t *options, sentry_value_t event, sentry_scope_mode_t mode); +void sentry__scope_set_fingerprint_va( + sentry_scope_t *scope, const char *fingerprint, va_list va); +void sentry__scope_set_fingerprint_nva(sentry_scope_t *scope, + const char *fingerprint, size_t fingerprint_len, va_list va); + /** - * These are convenience macros to automatically lock/unlock a scope inside a - * code block. + * These are convenience macros to automatically lock/unlock the global scope + * inside a code block. */ #define SENTRY_WITH_SCOPE(Scope) \ for (const sentry_scope_t *Scope = sentry__scope_lock(); Scope; \ diff --git a/tests/assertions.py b/tests/assertions.py index ef4c18808..1751a4008 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -169,10 +169,10 @@ def assert_stacktrace(envelope, inside_exception=False, check_size=True): ) -def assert_breadcrumb_inner(breadcrumbs): +def assert_breadcrumb_inner(breadcrumbs, message="debug crumb"): expected = { "type": "http", - "message": "debug crumb", + "message": message, "category": "example!", "level": "debug", "data": { @@ -185,9 +185,9 @@ def assert_breadcrumb_inner(breadcrumbs): assert any(matches(b, expected) for b in breadcrumbs) -def assert_breadcrumb(envelope): +def assert_breadcrumb(envelope, message="debug crumb"): event = envelope.get_event() - assert_breadcrumb_inner(event["breadcrumbs"]) + assert_breadcrumb_inner(event["breadcrumbs"], message) def assert_attachment(envelope): diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index 49a3613b5..14bceacb3 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -1157,3 +1157,33 @@ def test_capture_proxy(cmake, httpserver, run_args, proxy_running): expected_logsize = 0 finally: proxy_test_finally(expected_logsize, httpserver, proxy_process) + + +def test_capture_with_scope(cmake, httpserver): + tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"}) + + # make sure we are isolated from previous runs + shutil.rmtree(tmp_path / ".sentry-native", ignore_errors=True) + + httpserver.expect_oneshot_request( + "/api/123456/envelope/", + headers={"x-sentry-auth": auth_header}, + ).respond_with_data("OK") + + run( + tmp_path, + "sentry_example", + ["log", "attachment", "capture-with-scope"], + check=True, + env=dict(os.environ, SENTRY_DSN=make_dsn(httpserver)), + ) + + assert len(httpserver.log) == 1 + + req = httpserver.log[0][0] + body = req.get_data() + + envelope = Envelope.deserialize(body) + + assert_breadcrumb(envelope, "scoped crumb") + assert_attachment(envelope) diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index 58e426d91..aba4c808f 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -1,6 +1,7 @@ #include "sentry.h" #include "sentry_scope.h" #include "sentry_testsupport.h" +#include "sentry_utils.h" SENTRY_TEST(scope_contexts) { @@ -15,31 +16,83 @@ SENTRY_TEST(scope_contexts) value); \ } while (0) - // scope: {"both":"scope","scope":"scope"} - sentry_set_context("both", sentry_value_new_string("scope")); - sentry_set_context("scope", sentry_value_new_string("scope")); + // global: + // {"all":"global","scope":"global","global":"global"} + sentry_set_context("all", sentry_value_new_string("global")); + sentry_set_context("global", sentry_value_new_string("global")); + sentry_set_context("scope", sentry_value_new_string("global")); - SENTRY_WITH_SCOPE (scope) { - // event: {"both":"event","event":"event"} + SENTRY_WITH_SCOPE (global_scope) { + // event: + // {"all":"event","event":"event"} sentry_value_t event = sentry_value_new_object(); { sentry_value_t contexts = sentry_value_new_object(); sentry_value_set_by_key( - contexts, "both", sentry_value_new_string("event")); + contexts, "all", sentry_value_new_string("event")); sentry_value_set_by_key( contexts, "event", sentry_value_new_string("event")); sentry_value_set_by_key(event, "contexts", contexts); } - // event <- scope: {"both":"event","event":"event","scope":"scope"} - sentry__scope_apply_to_event(scope, options, event, SENTRY_SCOPE_NONE); - TEST_CHECK_CONTEXT_EQUAL(event, "both", "event"); + // event <- global: + // {"all":"event","event":"event","global":"global","scope":"global"} + sentry__scope_apply_to_event( + global_scope, options, event, SENTRY_SCOPE_NONE); + TEST_CHECK_CONTEXT_EQUAL(event, "all", "event"); TEST_CHECK_CONTEXT_EQUAL(event, "event", "event"); - TEST_CHECK_CONTEXT_EQUAL(event, "scope", "scope"); + TEST_CHECK_CONTEXT_EQUAL(event, "global", "global"); + TEST_CHECK_CONTEXT_EQUAL(event, "scope", "global"); sentry_value_decref(event); } + SENTRY_WITH_SCOPE (global_scope) { + // local: + // {"all":"scope","scope":"scope","local":"local"} + sentry_scope_t *local_scope = sentry_local_scope_new(); + sentry_scope_set_context( + local_scope, "all", sentry_value_new_string("local")); + sentry_scope_set_context( + local_scope, "local", sentry_value_new_string("local")); + sentry_scope_set_context( + local_scope, "scope", sentry_value_new_string("local")); + + // event: + // {"all":"event","event":"event"} + sentry_value_t event = sentry_value_new_object(); + { + sentry_value_t contexts = sentry_value_new_object(); + sentry_value_set_by_key( + contexts, "all", sentry_value_new_string("event")); + sentry_value_set_by_key( + contexts, "event", sentry_value_new_string("event")); + sentry_value_set_by_key(event, "contexts", contexts); + } + + // event <- local: + // {"all":"event","event":"event","local":"local","scope":"local"} + sentry__scope_apply_to_event( + local_scope, options, event, SENTRY_SCOPE_NONE); + TEST_CHECK_CONTEXT_EQUAL(event, "all", "event"); + TEST_CHECK_CONTEXT_EQUAL(event, "event", "event"); + TEST_CHECK_CONTEXT_EQUAL(event, "local", "local"); + TEST_CHECK_CONTEXT_EQUAL(event, "scope", "local"); + + // event <- global: + // {"all":"event","event":"event","global":"global","local":"local","scope":"local"} + sentry__scope_apply_to_event( + global_scope, options, event, SENTRY_SCOPE_NONE); + TEST_CHECK_CONTEXT_EQUAL(event, "all", "event"); + TEST_CHECK_CONTEXT_EQUAL(event, "event", "event"); + TEST_CHECK_CONTEXT_EQUAL(event, "global", "global"); + TEST_CHECK_CONTEXT_EQUAL(event, "local", "local"); + TEST_CHECK_CONTEXT_EQUAL(event, "scope", "local"); + + sentry__scope_free(local_scope); + sentry_value_decref(event); + } + #undef TEST_CHECK_CONTEXT_EQUAL sentry_close(); @@ -58,28 +111,80 @@ SENTRY_TEST(scope_extra) value); \ } while (0) - // scope: {"both":"scope","scope":"scope"} - sentry_set_extra("both", sentry_value_new_string("scope")); - sentry_set_extra("scope", sentry_value_new_string("scope")); + // global: + // {"all":"global","scope":"global","global":"global"} + sentry_set_extra("all", sentry_value_new_string("global")); + sentry_set_extra("global", sentry_value_new_string("global")); + sentry_set_extra("scope", sentry_value_new_string("global")); + + SENTRY_WITH_SCOPE (global_scope) { + // event: + // {"all":"event","event":"event"} + sentry_value_t event = sentry_value_new_object(); + { + sentry_value_t extra = sentry_value_new_object(); + sentry_value_set_by_key( + extra, "all", sentry_value_new_string("event")); + sentry_value_set_by_key( + extra, "event", sentry_value_new_string("event")); + sentry_value_set_by_key(event, "extra", extra); + } + + // event <- global: + // {"all":"event","event":"event","global":"global","scope":"global"} + sentry__scope_apply_to_event( + global_scope, options, event, SENTRY_SCOPE_NONE); + TEST_CHECK_EXTRA_EQUAL(event, "all", "event"); + TEST_CHECK_EXTRA_EQUAL(event, "event", "event"); + TEST_CHECK_EXTRA_EQUAL(event, "global", "global"); + TEST_CHECK_EXTRA_EQUAL(event, "scope", "global"); - SENTRY_WITH_SCOPE (scope) { - // event: {"both":"event","event":"event"} + sentry_value_decref(event); + } + + SENTRY_WITH_SCOPE (global_scope) { + // local: + // {"all":"scope","scope":"scope","local":"local"} + sentry_scope_t *local_scope = sentry_local_scope_new(); + sentry_scope_set_extra( + local_scope, "all", sentry_value_new_string("local")); + sentry_scope_set_extra( + local_scope, "local", sentry_value_new_string("local")); + sentry_scope_set_extra( + local_scope, "scope", sentry_value_new_string("local")); + + // event: + // {"all":"event","event":"event"} sentry_value_t event = sentry_value_new_object(); { sentry_value_t extra = sentry_value_new_object(); sentry_value_set_by_key( - extra, "both", sentry_value_new_string("event")); + extra, "all", sentry_value_new_string("event")); sentry_value_set_by_key( extra, "event", sentry_value_new_string("event")); sentry_value_set_by_key(event, "extra", extra); } - // event <- scope: {"both":"event","event":"event","scope":"scope"} - sentry__scope_apply_to_event(scope, options, event, SENTRY_SCOPE_NONE); - TEST_CHECK_EXTRA_EQUAL(event, "both", "event"); + // event <- local: + // {"all":"event","event":"event","local":"local","scope":"local"} + sentry__scope_apply_to_event( + local_scope, options, event, SENTRY_SCOPE_NONE); + TEST_CHECK_EXTRA_EQUAL(event, "all", "event"); + TEST_CHECK_EXTRA_EQUAL(event, "event", "event"); + TEST_CHECK_EXTRA_EQUAL(event, "local", "local"); + TEST_CHECK_EXTRA_EQUAL(event, "scope", "local"); + + // event <- global: + // {"all":"event","event":"event","global":"global","local":"local","scope":"local"} + sentry__scope_apply_to_event( + global_scope, options, event, SENTRY_SCOPE_NONE); + TEST_CHECK_EXTRA_EQUAL(event, "all", "event"); TEST_CHECK_EXTRA_EQUAL(event, "event", "event"); - TEST_CHECK_EXTRA_EQUAL(event, "scope", "scope"); + TEST_CHECK_EXTRA_EQUAL(event, "global", "global"); + TEST_CHECK_EXTRA_EQUAL(event, "local", "local"); + TEST_CHECK_EXTRA_EQUAL(event, "scope", "local"); + sentry__scope_free(local_scope); sentry_value_decref(event); } @@ -88,6 +193,88 @@ SENTRY_TEST(scope_extra) sentry_close(); } +SENTRY_TEST(scope_fingerprint) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + // global: + // ["global1", "global2"] + sentry_set_fingerprint("global1", "global2", NULL); + + SENTRY_WITH_SCOPE (global_scope) { + // event: + // null + sentry_value_t event = sentry_value_new_object(); + + // event <- global: + // ["global1", "global2"] + sentry__scope_apply_to_event( + global_scope, options, event, SENTRY_SCOPE_NONE); + TEST_CHECK_JSON_VALUE(sentry_value_get_by_key(event, "fingerprint"), + "[\"global1\",\"global2\"]"); + + sentry_value_decref(event); + } + + SENTRY_WITH_SCOPE (global_scope) { + // event: + // ["event1", "event2"] + sentry_value_t event = sentry_value_new_object(); + { + sentry_value_t fingerprint = sentry_value_new_list(); + sentry_value_append(fingerprint, sentry_value_new_string("event1")); + sentry_value_append(fingerprint, sentry_value_new_string("event2")); + sentry_value_set_by_key(event, "fingerprint", fingerprint); + } + + // event <- global: + // ["event1", "event2"] + sentry__scope_apply_to_event( + global_scope, options, event, SENTRY_SCOPE_NONE); + TEST_CHECK_JSON_VALUE(sentry_value_get_by_key(event, "fingerprint"), + "[\"event1\",\"event2\"]"); + + sentry_value_decref(event); + } + + SENTRY_WITH_SCOPE (global_scope) { + // local: + // ["local1", "local2"] + sentry_scope_t *local_scope = sentry_local_scope_new(); + sentry_scope_set_fingerprint(local_scope, "local1", "local2", NULL); + + // event: + // ["event1", "event2"] + sentry_value_t event = sentry_value_new_object(); + { + sentry_value_t fingerprint = sentry_value_new_list(); + sentry_value_append(fingerprint, sentry_value_new_string("event1")); + sentry_value_append(fingerprint, sentry_value_new_string("event2")); + sentry_value_set_by_key(event, "fingerprint", fingerprint); + } + + // event <- local: + // ["event1", "event2"] + sentry__scope_apply_to_event( + local_scope, options, event, SENTRY_SCOPE_NONE); + TEST_CHECK_JSON_VALUE(sentry_value_get_by_key(event, "fingerprint"), + "[\"event1\",\"event2\"]"); + + // event <- global: + // ["event1", "event2"] + sentry__scope_apply_to_event( + global_scope, options, event, SENTRY_SCOPE_NONE); + TEST_CHECK_JSON_VALUE(sentry_value_get_by_key(event, "fingerprint"), + "[\"event1\",\"event2\"]"); + + sentry__scope_free(local_scope); + sentry_value_decref(event); + } + + sentry_close(); +} + SENTRY_TEST(scope_tags) { SENTRY_TEST_OPTIONS_NEW(options); @@ -101,32 +288,317 @@ SENTRY_TEST(scope_tags) value); \ } while (0) - // scope: {"both":"scope","scope":"scope"} - sentry_set_tag("both", "scope"); - sentry_set_tag("scope", "scope"); + // global: + // {"all":"global","scope":"global","global":"global"} + sentry_set_tag("all", "global"); + sentry_set_tag("global", "global"); + sentry_set_tag("scope", "global"); - SENTRY_WITH_SCOPE (scope) { - // event: {"both":"event","event":"event"} + SENTRY_WITH_SCOPE (global_scope) { + // event: + // {"all":"event","event":"event"} sentry_value_t event = sentry_value_new_object(); { - sentry_value_t tags = sentry_value_new_object(); + sentry_value_t event_tags = sentry_value_new_object(); sentry_value_set_by_key( - tags, "both", sentry_value_new_string("event")); + event_tags, "all", sentry_value_new_string("event")); sentry_value_set_by_key( - tags, "event", sentry_value_new_string("event")); - sentry_value_set_by_key(event, "tags", tags); + event_tags, "event", sentry_value_new_string("event")); + sentry_value_set_by_key(event, "tags", event_tags); } - // event <- scope: {"both":"event","event":"event","scope":"scope"} - sentry__scope_apply_to_event(scope, options, event, SENTRY_SCOPE_NONE); - TEST_CHECK_TAG_EQUAL(event, "both", "event"); + // event <- global: + // {"all":"event","event":"event","global":"global","scope":"global"} + sentry__scope_apply_to_event( + global_scope, options, event, SENTRY_SCOPE_NONE); + TEST_CHECK_TAG_EQUAL(event, "all", "event"); TEST_CHECK_TAG_EQUAL(event, "event", "event"); - TEST_CHECK_TAG_EQUAL(event, "scope", "scope"); + TEST_CHECK_TAG_EQUAL(event, "global", "global"); + TEST_CHECK_TAG_EQUAL(event, "scope", "global"); sentry_value_decref(event); } + SENTRY_WITH_SCOPE (global_scope) { + // local: + // {"all":"scope","scope":"scope","local":"local"} + sentry_scope_t *local_scope = sentry_local_scope_new(); + sentry_scope_set_tag(local_scope, "all", "local"); + sentry_scope_set_tag(local_scope, "local", "local"); + sentry_scope_set_tag(local_scope, "scope", "local"); + + // event: + // {"all":"event","event":"event"} + sentry_value_t event = sentry_value_new_object(); + { + sentry_value_t event_tags = sentry_value_new_object(); + sentry_value_set_by_key( + event_tags, "all", sentry_value_new_string("event")); + sentry_value_set_by_key( + event_tags, "event", sentry_value_new_string("event")); + sentry_value_set_by_key(event, "tags", event_tags); + } + + // event <- local: + // {"all":"event","event":"event","local":"local","scope":"local"} + sentry__scope_apply_to_event( + local_scope, options, event, SENTRY_SCOPE_NONE); + TEST_CHECK_TAG_EQUAL(event, "all", "event"); + TEST_CHECK_TAG_EQUAL(event, "event", "event"); + TEST_CHECK_TAG_EQUAL(event, "local", "local"); + TEST_CHECK_TAG_EQUAL(event, "scope", "local"); + + // event <- global: + // {"all":"event","event":"event","global":"global","local":"local","scope":"local"} + sentry__scope_apply_to_event( + global_scope, options, event, SENTRY_SCOPE_NONE); + TEST_CHECK_TAG_EQUAL(event, "all", "event"); + TEST_CHECK_TAG_EQUAL(event, "event", "event"); + TEST_CHECK_TAG_EQUAL(event, "global", "global"); + TEST_CHECK_TAG_EQUAL(event, "local", "local"); + TEST_CHECK_TAG_EQUAL(event, "scope", "local"); + + sentry__scope_free(local_scope); + sentry_value_decref(event); + } + #undef TEST_CHECK_TAG_EQUAL sentry_close(); } + +SENTRY_TEST(scope_user) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + // global: {"id":"1","username":"global","email":"@global"} + sentry_set_user(sentry_value_new_user("1", "global", "@global", NULL)); + + SENTRY_WITH_SCOPE (global_scope) { + // event: {"id":"2","username":"event"} + sentry_value_t event = sentry_value_new_object(); + sentry_value_set_by_key( + event, "user", sentry_value_new_user("2", "event", NULL, NULL)); + + // event <- global: {"id":"2","username":"event"} + sentry__scope_apply_to_event( + global_scope, options, event, SENTRY_SCOPE_NONE); + TEST_CHECK_JSON_VALUE(sentry_value_get_by_key(event, "user"), + "{\"id\":\"2\",\"username\":\"event\"}"); + + sentry_value_decref(event); + } + + SENTRY_WITH_SCOPE (global_scope) { + // local: {"id":"2","username":"local","email":"@local"} + sentry_scope_t *local_scope = sentry_local_scope_new(); + sentry_scope_set_user( + local_scope, sentry_value_new_user("2", "local", "@local", NULL)); + + // event: {"id":"3","username":"event"} + sentry_value_t event = sentry_value_new_object(); + sentry_value_set_by_key( + event, "user", sentry_value_new_user("3", "event", NULL, NULL)); + + // event <- local: {"id":"3","username":"event"} + sentry__scope_apply_to_event( + local_scope, options, event, SENTRY_SCOPE_NONE); + TEST_CHECK_JSON_VALUE(sentry_value_get_by_key(event, "user"), + "{\"id\":\"3\",\"username\":\"event\"}"); + + // event <- local: {"id":"3","username":"event"} + sentry__scope_apply_to_event( + global_scope, options, event, SENTRY_SCOPE_NONE); + TEST_CHECK_JSON_VALUE(sentry_value_get_by_key(event, "user"), + "{\"id\":\"3\",\"username\":\"event\"}"); + + sentry__scope_free(local_scope); + sentry_value_decref(event); + } + + sentry_close(); +} + +SENTRY_TEST(scope_level) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + +#define TEST_CHECK_LEVEL_EQUAL(event, value) \ + do { \ + sentry_value_t level = sentry_value_get_by_key(event, "level"); \ + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(level), value); \ + } while (0) + + // global: warning + sentry_set_level(SENTRY_LEVEL_WARNING); + + SENTRY_WITH_SCOPE (global_scope) { + // event: null + sentry_value_t event = sentry_value_new_object(); + + // event <- global: warning + sentry__scope_apply_to_event( + global_scope, options, event, SENTRY_SCOPE_NONE); + TEST_CHECK_LEVEL_EQUAL(event, "warning"); + + sentry_value_decref(event); + } + + SENTRY_WITH_SCOPE (global_scope) { + // event: info + sentry_value_t event = sentry_value_new_object(); + sentry_value_set_by_key( + event, "level", sentry_value_new_string("info")); + + // event <- global: info + sentry__scope_apply_to_event( + global_scope, options, event, SENTRY_SCOPE_NONE); + TEST_CHECK_LEVEL_EQUAL(event, "info"); + + sentry_value_decref(event); + } + + SENTRY_WITH_SCOPE (global_scope) { + // local: fatal + sentry_scope_t *local_scope = sentry_local_scope_new(); + sentry_scope_set_level(local_scope, SENTRY_LEVEL_FATAL); + + // event: debug + sentry_value_t event = sentry_value_new_object(); + sentry_value_set_by_key( + event, "level", sentry_value_new_string("debug")); + + // event <- local: debug + sentry__scope_apply_to_event( + local_scope, options, event, SENTRY_SCOPE_NONE); + TEST_CHECK_LEVEL_EQUAL(event, "debug"); + + // event <- global: debug + sentry__scope_apply_to_event( + local_scope, options, event, SENTRY_SCOPE_NONE); + TEST_CHECK_LEVEL_EQUAL(event, "debug"); + + sentry__scope_free(local_scope); + sentry_value_decref(event); + } + +#undef TEST_CHECK_LEVEL_EQUAL + + sentry_close(); +} + +static sentry_value_t +breadcrumb_ts(const char *message, uint64_t ts) +{ + sentry_value_t breadcrumb = sentry_value_new_breadcrumb(NULL, message); + sentry_value_set_by_key(breadcrumb, "timestamp", + sentry__value_new_string_owned(sentry__usec_time_to_iso8601(ts))); + return breadcrumb; +} + +SENTRY_TEST(scope_breadcrumbs) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_options_set_max_breadcrumbs(options, 5); + sentry_init(options); + + // global: ["global1", "global4"] + sentry_add_breadcrumb(breadcrumb_ts("global1", 1)); + sentry_add_breadcrumb(breadcrumb_ts("global4", 4)); + +#define TEST_CHECK_MESSAGE_EQUAL(breadcrumbs, index, message) \ + TEST_CHECK_STRING_EQUAL( \ + sentry_value_as_string(sentry_value_get_by_key( \ + sentry_value_get_by_index(breadcrumbs, index), "message")), \ + message) + + SENTRY_WITH_SCOPE (global_scope) { + // event: null + sentry_value_t event = sentry_value_new_object(); + + // event <- global: ["global1", "global4"] + sentry__scope_apply_to_event( + global_scope, options, event, SENTRY_SCOPE_BREADCRUMBS); + + sentry_value_t result = sentry_value_get_by_key(event, "breadcrumbs"); + TEST_CHECK(sentry_value_get_type(result) == SENTRY_VALUE_TYPE_LIST); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(result), 2); + TEST_CHECK_MESSAGE_EQUAL(result, 0, "global1"); + TEST_CHECK_MESSAGE_EQUAL(result, 1, "global4"); + + sentry_value_decref(event); + } + + SENTRY_WITH_SCOPE (global_scope) { + // event: ["event3", "event5"] + sentry_value_t event = sentry_value_new_object(); + { + sentry_value_t breadcrumbs = sentry_value_new_list(); + sentry_value_append(breadcrumbs, breadcrumb_ts("event3", 3)); + sentry_value_append(breadcrumbs, breadcrumb_ts("event5", 5)); + sentry_value_set_by_key(event, "breadcrumbs", breadcrumbs); + } + + // event <- global: ["global1", "event3", "global4", "event5"] + sentry__scope_apply_to_event( + global_scope, options, event, SENTRY_SCOPE_BREADCRUMBS); + + sentry_value_t result = sentry_value_get_by_key(event, "breadcrumbs"); + TEST_CHECK(sentry_value_get_type(result) == SENTRY_VALUE_TYPE_LIST); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(result), 4); + TEST_CHECK_MESSAGE_EQUAL(result, 0, "global1"); + TEST_CHECK_MESSAGE_EQUAL(result, 1, "event3"); + TEST_CHECK_MESSAGE_EQUAL(result, 2, "global4"); + TEST_CHECK_MESSAGE_EQUAL(result, 3, "event5"); + + sentry_value_decref(event); + } + + SENTRY_WITH_SCOPE (global_scope) { + // local: ["local2", "local6"] + sentry_scope_t *local_scope = sentry_local_scope_new(); + sentry_scope_add_breadcrumb(local_scope, breadcrumb_ts("local2", 2)); + sentry_scope_add_breadcrumb(local_scope, breadcrumb_ts("local6", 6)); + + // event: ["event3", "event5"] + sentry_value_t event = sentry_value_new_object(); + { + sentry_value_t breadcrumbs = sentry_value_new_list(); + sentry_value_append(breadcrumbs, breadcrumb_ts("event3", 3)); + sentry_value_append(breadcrumbs, breadcrumb_ts("event5", 5)); + sentry_value_set_by_key(event, "breadcrumbs", breadcrumbs); + } + + // event <- local: ["local2", "event3", "event5", "local6"] + sentry__scope_apply_to_event( + local_scope, options, event, SENTRY_SCOPE_BREADCRUMBS); + + sentry_value_t result = sentry_value_get_by_key(event, "breadcrumbs"); + TEST_CHECK(sentry_value_get_type(result) == SENTRY_VALUE_TYPE_LIST); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(result), 4); + TEST_CHECK_MESSAGE_EQUAL(result, 0, "local2"); + TEST_CHECK_MESSAGE_EQUAL(result, 1, "event3"); + TEST_CHECK_MESSAGE_EQUAL(result, 2, "event5"); + TEST_CHECK_MESSAGE_EQUAL(result, 3, "local6"); + + // event <- global: ["local2", "event3", "global4", "event5", "local6"] + sentry__scope_apply_to_event( + global_scope, options, event, SENTRY_SCOPE_BREADCRUMBS); + + result = sentry_value_get_by_key(event, "breadcrumbs"); + TEST_CHECK(sentry_value_get_type(result) == SENTRY_VALUE_TYPE_LIST); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(result), 5); + TEST_CHECK_MESSAGE_EQUAL(result, 0, "local2"); + TEST_CHECK_MESSAGE_EQUAL(result, 1, "event3"); + TEST_CHECK_MESSAGE_EQUAL(result, 2, "global4"); + TEST_CHECK_MESSAGE_EQUAL(result, 3, "event5"); + TEST_CHECK_MESSAGE_EQUAL(result, 4, "local6"); + + sentry__scope_free(local_scope); + sentry_value_decref(event); + } + + sentry_close(); +} diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index d1d887d53..048f554c5 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -88,9 +88,13 @@ XX(recursive_paths) XX(sampling_before_send) XX(sampling_decision) XX(sampling_transaction) +XX(scope_breadcrumbs) XX(scope_contexts) XX(scope_extra) +XX(scope_fingerprint) +XX(scope_level) XX(scope_tags) +XX(scope_user) XX(scoped_txn) XX(sentry__value_span_new_requires_unfinished_parent) XX(serialize_envelope)