#include "sentry_boot.h" #include #include #include #include "sentry_alloc.h" #include "sentry_backend.h" #include "sentry_core.h" #include "sentry_database.h" #include "sentry_envelope.h" #include "sentry_options.h" #include "sentry_path.h" #include "sentry_random.h" #include "sentry_scope.h" #include "sentry_session.h" #include "sentry_string.h" #include "sentry_sync.h" #include "sentry_tracing.h" #include "sentry_transport.h" #include "sentry_value.h" #ifdef SENTRY_INTEGRATION_QT # include "integrations/sentry_integration_qt.h" #endif static sentry_options_t *g_options = NULL; static sentry_mutex_t g_options_lock = SENTRY__MUTEX_INIT; /// see sentry_get_crashed_last_run() for the possible values static int g_last_crash = -1; const sentry_options_t * sentry__options_getref(void) { sentry_options_t *options; sentry__mutex_lock(&g_options_lock); options = sentry__options_incref(g_options); sentry__mutex_unlock(&g_options_lock); return options; } sentry_options_t * sentry__options_lock(void) { sentry__mutex_lock(&g_options_lock); return g_options; } void sentry__options_unlock(void) { sentry__mutex_unlock(&g_options_lock); } static void load_user_consent(sentry_options_t *opts) { sentry_path_t *consent_path = sentry__path_join_str(opts->database_path, "user-consent"); char *contents = sentry__path_read_to_buffer(consent_path, NULL); sentry__path_free(consent_path); switch (contents ? contents[0] : 0) { case '1': opts->user_consent = SENTRY_USER_CONSENT_GIVEN; break; case '0': opts->user_consent = SENTRY_USER_CONSENT_REVOKED; break; default: opts->user_consent = SENTRY_USER_CONSENT_UNKNOWN; break; } sentry_free(contents); } bool sentry__should_skip_upload(void) { bool skip = true; SENTRY_WITH_OPTIONS (options) { skip = options->require_user_consent && sentry__atomic_fetch((long *)&options->user_consent) != SENTRY_USER_CONSENT_GIVEN; } return skip; } int sentry_init(sentry_options_t *options) { // this function is to be called only once, so we do not allow more than one // caller sentry__mutex_lock(&g_options_lock); // pre-init here, so we can consistently use bailing out to :fail sentry_transport_t *transport = NULL; sentry_close(); sentry_logger_t logger = { NULL, NULL }; if (options->debug) { logger = options->logger; } sentry__logger_set_global(logger); // we need to ensure the dir exists, otherwise `path_absolute` will fail. if (sentry__path_create_dir_all(options->database_path)) { SENTRY_WARN("failed to create database directory or there is no write " "access to this directory"); goto fail; } transport = options->transport; sentry_path_t *database_path = options->database_path; options->database_path = sentry__path_absolute(database_path); if (options->database_path) { sentry__path_free(database_path); } else { SENTRY_DEBUG("falling back to non-absolute database path"); options->database_path = database_path; } SENTRY_DEBUGF("using database path \"%" SENTRY_PATH_PRI "\"", options->database_path->path); // try to create and lock our run folder as early as possibly, since it is // fallible. since it does locking, it will not interfere with run folder // enumeration. options->run = sentry__run_new(options->database_path); if (!options->run) { SENTRY_WARN("failed to initialize run directory"); goto fail; } load_user_consent(options); if (!options->dsn || !options->dsn->is_valid) { const char *raw_dsn = sentry_options_get_dsn(options); SENTRY_WARNF( "the provided DSN \"%s\" is not valid", raw_dsn ? raw_dsn : ""); } if (transport) { if (sentry__transport_startup(transport, options) != 0) { SENTRY_WARN("failed to initialize transport"); goto fail; } } uint64_t last_crash = 0; // and then we will start the backend, since it requires a valid run sentry_backend_t *backend = options->backend; if (backend && backend->startup_func) { SENTRY_TRACE("starting backend"); if (backend->startup_func(backend, options) != 0) { SENTRY_WARN("failed to initialize backend"); goto fail; } } if (backend && backend->get_last_crash_func) { last_crash = backend->get_last_crash_func(backend); } g_last_crash = sentry__has_crash_marker(options); g_options = options; // *after* setting the global options, trigger a scope and consent flush, // since at least crashpad needs that. // the only way to get a reference to the scope is by locking it, the macro // does all that at once, including invoking the backends scope flush hook SENTRY_WITH_SCOPE_MUT (scope) { (void)scope; } if (backend && backend->user_consent_changed_func) { backend->user_consent_changed_func(backend); } #ifdef SENTRY_INTEGRATION_QT SENTRY_TRACE("setting up Qt integration"); sentry_integration_setup_qt(); #endif // after initializing the transport, we will submit all the unsent envelopes // and handle remaining sessions. SENTRY_TRACE("processing and pruning old runs"); sentry__process_old_runs(options, last_crash); if (backend && backend->prune_database_func) { backend->prune_database_func(backend); } if (options->auto_session_tracking) { sentry_start_session(); } sentry__mutex_unlock(&g_options_lock); return 0; fail: SENTRY_WARN("`sentry_init` failed"); if (transport) { sentry__transport_shutdown(transport, 0); } sentry_options_free(options); sentry__mutex_unlock(&g_options_lock); return 1; } int sentry_flush(uint64_t timeout) { int rv = 0; SENTRY_WITH_OPTIONS (options) { rv = sentry__transport_flush(options->transport, timeout); } return rv; } int sentry_close(void) { // this function is to be called only once, so we do not allow more than one // caller sentry__mutex_lock(&g_options_lock); sentry_options_t *options = g_options; size_t dumped_envelopes = 0; if (options) { sentry_end_session(); if (options->backend && options->backend->shutdown_func) { SENTRY_TRACE("shutting down backend"); options->backend->shutdown_func(options->backend); } if (options->transport) { if (sentry__transport_shutdown( options->transport, options->shutdown_timeout) != 0) { SENTRY_WARN("transport did not shut down cleanly"); } dumped_envelopes = sentry__transport_dump_queue( options->transport, options->run); } if (!dumped_envelopes && (!options->backend || !options->backend->can_capture_after_shutdown)) { sentry__run_clean(options->run); } sentry_options_free(options); } else { SENTRY_DEBUG("sentry_close() called, but options was empty"); } g_options = NULL; sentry__mutex_unlock(&g_options_lock); sentry__scope_cleanup(); sentry_clear_modulecache(); return (int)dumped_envelopes; } int sentry_shutdown(void) { return sentry_close(); } int sentry_reinstall_backend(void) { int rv = 0; SENTRY_WITH_OPTIONS (options) { sentry_backend_t *backend = options->backend; if (backend && backend->shutdown_func) { backend->shutdown_func(backend); } if (backend && backend->startup_func) { if (backend->startup_func(backend, options)) { rv = 1; } } } return rv; } static void set_user_consent(sentry_user_consent_t new_val) { SENTRY_WITH_OPTIONS (options) { if (sentry__atomic_store((long *)&options->user_consent, new_val) == new_val) { // nothing was changed break; // SENTRY_WITH_OPTIONS } if (options->backend && options->backend->user_consent_changed_func) { options->backend->user_consent_changed_func(options->backend); } sentry_path_t *consent_path = sentry__path_join_str(options->database_path, "user-consent"); switch (new_val) { case SENTRY_USER_CONSENT_GIVEN: sentry__path_write_buffer(consent_path, "1\n", 2); break; case SENTRY_USER_CONSENT_REVOKED: sentry__path_write_buffer(consent_path, "0\n", 2); break; case SENTRY_USER_CONSENT_UNKNOWN: sentry__path_remove(consent_path); break; } sentry__path_free(consent_path); } } void sentry_user_consent_give(void) { set_user_consent(SENTRY_USER_CONSENT_GIVEN); } void sentry_user_consent_revoke(void) { set_user_consent(SENTRY_USER_CONSENT_REVOKED); } void sentry_user_consent_reset(void) { set_user_consent(SENTRY_USER_CONSENT_UNKNOWN); } sentry_user_consent_t sentry_user_consent_get(void) { sentry_user_consent_t rv = SENTRY_USER_CONSENT_UNKNOWN; SENTRY_WITH_OPTIONS (options) { rv = (sentry_user_consent_t)sentry__atomic_fetch( (long *)&options->user_consent); } return rv; } void sentry__capture_envelope( sentry_transport_t *transport, sentry_envelope_t *envelope) { bool has_consent = !sentry__should_skip_upload(); if (!has_consent) { SENTRY_TRACE("discarding envelope due to missing user consent"); sentry_envelope_free(envelope); return; } sentry__transport_send_envelope(transport, envelope); } static bool event_is_considered_error(sentry_value_t event) { const char *level = sentry_value_as_string(sentry_value_get_by_key(event, "level")); if (sentry__string_eq(level, "fatal") || sentry__string_eq(level, "error")) { return true; } if (!sentry_value_is_null(sentry_value_get_by_key(event, "exception"))) { return true; } return false; } bool sentry__event_is_transaction(sentry_value_t event) { sentry_value_t event_type = sentry_value_get_by_key(event, "type"); return sentry__string_eq("transaction", sentry_value_as_string(event_type)); } sentry_uuid_t sentry_capture_event(sentry_value_t event) { if (sentry__event_is_transaction(event)) { return sentry_uuid_nil(); } else { return sentry__capture_event(event); } } bool sentry__roll_dice(double probability) { uint64_t rnd; return probability >= 1.0 || sentry__getrandom(&rnd, sizeof(rnd)) || ((double)rnd / (double)UINT64_MAX) <= probability; } sentry_uuid_t sentry__capture_event(sentry_value_t event) { sentry_uuid_t event_id; sentry_envelope_t *envelope = NULL; bool was_captured = false; bool was_sent = false; SENTRY_WITH_OPTIONS (options) { was_captured = true; if (sentry__event_is_transaction(event)) { envelope = sentry__prepare_transaction(options, event, &event_id); } else { envelope = sentry__prepare_event(options, event, &event_id, true); } if (envelope) { if (options->session) { sentry_options_t *mut_options = sentry__options_lock(); sentry__envelope_add_session(envelope, mut_options->session); // we're assuming that if a session is added to an envelope // it will be sent onwards. This means we now need to set // the init flag to false because we're no longer the // initial session update. mut_options->session->init = false; sentry__options_unlock(); } bool should_skip = !sentry__roll_dice(options->sample_rate); if (should_skip) { SENTRY_DEBUG("throwing away event due to sample rate"); sentry_envelope_free(envelope); } else { sentry__capture_envelope(options->transport, envelope); was_sent = true; } } } if (!was_captured) { sentry_value_decref(event); } return was_sent ? event_id : sentry_uuid_nil(); } bool sentry__should_send_transaction(sentry_value_t tx_cxt) { sentry_value_t context_setting = sentry_value_get_by_key(tx_cxt, "sampled"); if (!sentry_value_is_null(context_setting)) { return sentry_value_is_true(context_setting); } bool send = false; SENTRY_WITH_OPTIONS (options) { send = sentry__roll_dice(options->traces_sample_rate); // TODO(tracing): Run through traces sampler function if rate is // unavailable. } return send; } 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_envelope_t *envelope = NULL; if (event_is_considered_error(event)) { sentry__record_errors_on_current_session(1); } SENTRY_WITH_SCOPE (scope) { SENTRY_TRACE("merging scope into event"); sentry_scope_mode_t mode = SENTRY_SCOPE_ALL; if (!options->symbolize_stacktraces) { mode &= ~SENTRY_SCOPE_STACKTRACES; } sentry__scope_apply_to_event(scope, options, event, mode); } if (options->before_send_func && invoke_before_send) { SENTRY_TRACE("invoking `before_send` hook"); event = options->before_send_func(event, NULL, options->before_send_data); if (sentry_value_is_null(event)) { SENTRY_TRACE("event was discarded by the `before_send` hook"); return NULL; } } sentry__ensure_event_id(event, event_id); envelope = sentry__envelope_new(); if (!envelope || !sentry__envelope_add_event(envelope, event)) { goto fail; } SENTRY_TRACE("adding attachments to envelope"); for (sentry_attachment_t *attachment = options->attachments; attachment; attachment = attachment->next) { sentry_envelope_item_t *item = sentry__envelope_add_from_path( envelope, attachment->path, "attachment"); if (!item) { continue; } sentry__envelope_item_set_header(item, "filename", #ifdef SENTRY_PLATFORM_WINDOWS sentry__value_new_string_from_wstr( #else sentry_value_new_string( #endif sentry__path_filename(attachment->path))); } return envelope; fail: sentry_envelope_free(envelope); sentry_value_decref(event); return NULL; } sentry_envelope_t * sentry__prepare_transaction(const sentry_options_t *options, sentry_value_t transaction, sentry_uuid_t *event_id) { sentry_envelope_t *envelope = NULL; SENTRY_WITH_SCOPE (scope) { SENTRY_TRACE("merging scope into transaction"); // Don't include debugging info sentry_scope_mode_t mode = SENTRY_SCOPE_ALL & ~SENTRY_SCOPE_MODULES & ~SENTRY_SCOPE_STACKTRACES; sentry__scope_apply_to_event(scope, options, transaction, mode); } sentry__ensure_event_id(transaction, event_id); envelope = sentry__envelope_new(); if (!envelope || !sentry__envelope_add_transaction(envelope, transaction)) { goto fail; } // TODO(tracing): Revisit when adding attachment support for transactions. return envelope; fail: SENTRY_WARN("dropping transaction"); sentry_envelope_free(envelope); sentry_value_decref(transaction); return NULL; } void sentry_handle_exception(const sentry_ucontext_t *uctx) { SENTRY_WITH_OPTIONS (options) { SENTRY_DEBUG("handling exception"); if (options->backend && options->backend->except_func) { options->backend->except_func(options->backend, uctx); } } } sentry_uuid_t sentry__new_event_id(void) { #ifdef SENTRY_UNITTEST return sentry_uuid_from_string("4c035723-8638-4c3a-923f-2ab9d08b4018"); #else return sentry_uuid_new_v4(); #endif } sentry_value_t sentry__ensure_event_id(sentry_value_t event, sentry_uuid_t *uuid_out) { sentry_value_t event_id = sentry_value_get_by_key(event, "event_id"); sentry_uuid_t uuid = sentry__value_as_uuid(event_id); if (sentry_uuid_is_nil(&uuid)) { uuid = sentry__new_event_id(); event_id = sentry__value_new_uuid(&uuid); sentry_value_set_by_key(event, "event_id", event_id); } if (uuid_out) { *uuid_out = uuid; } return event_id; } void sentry_set_user(sentry_value_t user) { if (!sentry_value_is_null(user)) { sentry_options_t *options = sentry__options_lock(); if (options && options->session) { sentry__session_sync_user(options->session, user); sentry__run_write_session(options->run, options->session); } sentry__options_unlock(); } SENTRY_WITH_SCOPE_MUT (scope) { sentry_value_decref(scope->user); scope->user = user; } } void sentry_remove_user(void) { sentry_set_user(sentry_value_new_null()); } 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_bounded( scope->breadcrumbs, breadcrumb, max_breadcrumbs); } } 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)); } } void sentry_remove_tag(const char *key) { SENTRY_WITH_SCOPE_MUT (scope) { sentry_value_remove_by_key(scope->tags, key); } } 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); } } void sentry_remove_extra(const char *key) { SENTRY_WITH_SCOPE_MUT (scope) { sentry_value_remove_by_key(scope->extra, key); } } 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); } } void sentry_remove_context(const char *key) { SENTRY_WITH_SCOPE_MUT (scope) { sentry_value_remove_by_key(scope->contexts, key); } } 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; }; } void sentry_remove_fingerprint(void) { SENTRY_WITH_SCOPE_MUT (scope) { sentry_value_decref(scope->fingerprint); scope->fingerprint = sentry_value_new_null(); }; } void sentry_set_transaction(const char *transaction) { SENTRY_WITH_SCOPE_MUT (scope) { sentry_free(scope->transaction); scope->transaction = sentry__string_clone(transaction); if (scope->transaction_object) { sentry_transaction_set_name(scope->transaction_object, transaction); } } } void sentry_set_level(sentry_level_t level) { SENTRY_WITH_SCOPE_MUT (scope) { scope->level = level; } } sentry_transaction_t * sentry_transaction_start( sentry_transaction_context_t *opaque_tx_cxt, sentry_value_t sampling_ctx) { // Just free this immediately until we implement proper support for // traces_sampler. sentry_value_decref(sampling_ctx); if (!opaque_tx_cxt) { return NULL; } sentry_value_t tx_cxt = opaque_tx_cxt->inner; // If the parent span ID is some empty-ish value, just remove it sentry_value_t parent_span = sentry_value_get_by_key(tx_cxt, "parent_span_id"); if (sentry_value_get_length(parent_span) < 1) { sentry_value_remove_by_key(tx_cxt, "parent_span_id"); } // The ending timestamp is stripped to avoid misleading ourselves later // down the line, as it is the only way to determine whether a transaction // has ended or not. sentry_value_t tx = sentry_value_new_event(); sentry_value_remove_by_key(tx, "timestamp"); sentry__value_merge_objects(tx, tx_cxt); bool should_sample = sentry__should_send_transaction(tx_cxt); sentry_value_set_by_key( tx, "sampled", sentry_value_new_bool(should_sample)); sentry_value_set_by_key(tx, "start_timestamp", sentry__value_new_string_owned( sentry__msec_time_to_iso8601(sentry__msec_time()))); sentry__transaction_context_free(opaque_tx_cxt); return sentry__transaction_new(tx); } sentry_uuid_t sentry_transaction_finish(sentry_transaction_t *opaque_tx) { if (!opaque_tx || sentry_value_is_null(opaque_tx->inner)) { SENTRY_DEBUG("no transaction available to finish"); goto fail; } sentry_value_t tx = sentry__value_clone(opaque_tx->inner); SENTRY_WITH_SCOPE_MUT (scope) { if (scope->transaction_object) { sentry_value_t scope_tx = scope->transaction_object->inner; const char *tx_id = sentry_value_as_string( sentry_value_get_by_key(tx, "span_id")); const char *scope_tx_id = sentry_value_as_string( sentry_value_get_by_key(scope_tx, "span_id")); if (sentry__string_eq(tx_id, scope_tx_id)) { sentry__transaction_decref(scope->transaction_object); scope->transaction_object = NULL; } } } // The sampling decision should already be made for transactions // during their construction. No need to recalculate here. See // `sentry__should_skip_transaction`. sentry_value_t sampled = sentry_value_get_by_key(tx, "sampled"); if (!sentry_value_is_true(sampled)) { SENTRY_DEBUG("throwing away transaction due to sample rate or " "user-provided sampling value in transaction context"); sentry_value_decref(tx); goto fail; } sentry_value_remove_by_key(tx, "sampled"); sentry_value_set_by_key(tx, "type", sentry_value_new_string("transaction")); sentry_value_set_by_key(tx, "timestamp", sentry__value_new_string_owned( sentry__msec_time_to_iso8601(sentry__msec_time()))); // TODO: This might not actually be necessary. Revisit after talking to // the relay team about this. sentry_value_set_by_key(tx, "level", sentry_value_new_string("info")); sentry_value_t name = sentry_value_get_by_key(tx, "transaction"); if (sentry_value_is_null(name) || sentry_value_get_length(name) == 0) { sentry_value_set_by_key(tx, "transaction", sentry_value_new_string("")); } // TODO: add tracestate sentry_value_t trace_context = sentry__value_get_trace_context(opaque_tx->inner); sentry_value_t contexts = sentry_value_new_object(); sentry_value_set_by_key(contexts, "trace", trace_context); sentry_value_set_by_key(tx, "contexts", contexts); // clean up trace context fields sentry_value_remove_by_key(tx, "trace_id"); sentry_value_remove_by_key(tx, "span_id"); sentry_value_remove_by_key(tx, "parent_span_id"); sentry_value_remove_by_key(tx, "op"); sentry_value_remove_by_key(tx, "description"); sentry_value_remove_by_key(tx, "status"); sentry__transaction_decref(opaque_tx); // This takes ownership of the transaction, generates an event ID, merges // scope return sentry__capture_event(tx); fail: sentry__transaction_decref(opaque_tx); return sentry_uuid_nil(); } void sentry_set_transaction_object(sentry_transaction_t *tx) { SENTRY_WITH_SCOPE_MUT (scope) { sentry__span_decref(scope->span); scope->span = NULL; sentry__transaction_decref(scope->transaction_object); sentry__transaction_incref(tx); scope->transaction_object = tx; } } void sentry_set_span(sentry_span_t *span) { SENTRY_WITH_SCOPE_MUT (scope) { sentry__transaction_decref(scope->transaction_object); scope->transaction_object = NULL; sentry__span_decref(scope->span); sentry__span_incref(span); scope->span = span; } } sentry_span_t * sentry_transaction_start_child( sentry_transaction_t *opaque_parent, char *operation, char *description) { if (!opaque_parent || sentry_value_is_null(opaque_parent->inner)) { SENTRY_DEBUG("no transaction available to create a child under"); return NULL; } sentry_value_t parent = opaque_parent->inner; // TODO: consider snapshotting this value during tx creation and storing in // tx and span size_t max_spans = SENTRY_SPANS_MAX; SENTRY_WITH_OPTIONS (options) { max_spans = options->max_spans; } sentry_value_t span = sentry__value_span_new(max_spans, parent, operation, description); return sentry__span_new(opaque_parent, span); } sentry_span_t * sentry_span_start_child( sentry_span_t *opaque_parent, char *operation, char *description) { if (!opaque_parent || sentry_value_is_null(opaque_parent->inner)) { SENTRY_DEBUG("no parent span available to create a child span under"); return NULL; } if (!opaque_parent->transaction) { SENTRY_DEBUG("no root transaction to create a child span under"); return NULL; } sentry_value_t parent = opaque_parent->inner; // TODO: consider snapshotting this value during tx creation and storing in // tx and span size_t max_spans = SENTRY_SPANS_MAX; SENTRY_WITH_OPTIONS (options) { max_spans = options->max_spans; } sentry_value_t span = sentry__value_span_new(max_spans, parent, operation, description); return sentry__span_new(opaque_parent->transaction, span); } void sentry_span_finish(sentry_span_t *opaque_span) { if (!opaque_span || sentry_value_is_null(opaque_span->inner)) { SENTRY_DEBUG("no span to finish"); goto fail; } sentry_transaction_t *opaque_root_transaction = opaque_span->transaction; if (!opaque_root_transaction || sentry_value_is_null(opaque_root_transaction->inner)) { SENTRY_DEBUG( "no root transaction to finish span on, aborting span finish"); goto fail; } sentry_value_t root_transaction = opaque_root_transaction->inner; if (!sentry_value_is_true( sentry_value_get_by_key(root_transaction, "sampled"))) { SENTRY_DEBUG("root transaction is unsampled, dropping span"); goto fail; } if (!sentry_value_is_null( sentry_value_get_by_key(root_transaction, "timestamp"))) { SENTRY_DEBUG("span's root transaction is already finished, aborting " "span finish"); goto fail; } sentry_value_t span = sentry__value_clone(opaque_span->inner); SENTRY_WITH_SCOPE_MUT (scope) { if (scope->span) { sentry_value_t scope_span = scope->span->inner; const char *span_id = sentry_value_as_string( sentry_value_get_by_key(span, "span_id")); const char *scope_span_id = sentry_value_as_string( sentry_value_get_by_key(scope_span, "span_id")); if (sentry__string_eq(span_id, scope_span_id)) { sentry__span_decref(scope->span); scope->span = NULL; } } } // Note that the current API makes it impossible to set a sampled value // that's different from the span's root transaction, but let's just be safe // here. if (!sentry_value_is_true(sentry_value_get_by_key(span, "sampled"))) { SENTRY_DEBUG("span is unsampled, dropping span"); sentry_value_decref(span); goto fail; } if (!sentry_value_is_null(sentry_value_get_by_key(span, "timestamp"))) { SENTRY_DEBUG("span is already finished, aborting span finish"); sentry_value_decref(span); goto fail; } sentry_value_set_by_key(span, "timestamp", sentry__value_new_string_owned( sentry__msec_time_to_iso8601(sentry__msec_time()))); sentry_value_remove_by_key(span, "sampled"); size_t max_spans = SENTRY_SPANS_MAX; SENTRY_WITH_OPTIONS (options) { max_spans = options->max_spans; } sentry_value_t spans = sentry_value_get_by_key(root_transaction, "spans"); if (sentry_value_get_length(spans) >= max_spans) { SENTRY_DEBUG("reached maximum number of spans for transaction, " "discarding span"); sentry_value_decref(span); goto fail; } if (sentry_value_is_null(spans)) { spans = sentry_value_new_list(); sentry_value_set_by_key(root_transaction, "spans", spans); } sentry_value_append(spans, span); sentry__span_decref(opaque_span); return; fail: sentry__span_decref(opaque_span); return; } int sentry_get_crashed_last_run(void) { return g_last_crash; } int sentry_clear_crashed_last_run(void) { bool success = false; sentry_options_t *options = sentry__options_lock(); if (options) { success = sentry__clear_crash_marker(options); } sentry__options_unlock(); return success ? 0 : 1; }