kicad/thirdparty/sentry-native/src/sentry_core.c

1033 lines
30 KiB
C

#include "sentry_boot.h"
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#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("<unlabeled transaction>"));
}
// 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;
}