diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6dc330f8f..0510a9fe5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -34,6 +34,8 @@ sentry_target_sources_cwd(sentry sentry_symbolizer.h sentry_sync.c sentry_sync.h + sentry_tracing.c + sentry_tracing.h sentry_transport.c sentry_transport.h sentry_utils.c diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 5f8992e7e..5052f023f 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -7,6 +7,7 @@ #include "sentry_string.h" #include "sentry_symbolizer.h" #include "sentry_sync.h" +#include "sentry_tracing.h" #include #ifdef SENTRY_BACKEND_CRASHPAD @@ -73,6 +74,7 @@ get_scope(void) g_scope.breadcrumbs = sentry_value_new_list(); g_scope.level = SENTRY_LEVEL_ERROR; g_scope.client_sdk = get_client_sdk(); + g_scope.span = sentry_value_new_null(); g_scope_initialized = true; @@ -93,6 +95,7 @@ sentry__scope_cleanup(void) sentry_value_decref(g_scope.contexts); sentry_value_decref(g_scope.breadcrumbs); sentry_value_decref(g_scope.client_sdk); + sentry_value_decref(g_scope.span); } sentry__mutex_unlock(&g_lock); } @@ -115,7 +118,7 @@ sentry__scope_flush_unlock() { sentry__scope_unlock(); SENTRY_WITH_OPTIONS (options) { - // we try to unlock the scope/session lock as soon as possible. The + // we try to unlock the scope as soon as possible. The // backend will do its own `WITH_SCOPE` internally. if (options->backend && options->backend->flush_scope_func) { options->backend->flush_scope_func(options->backend, options); @@ -224,6 +227,14 @@ sentry__symbolize_stacktrace(sentry_value_t stacktrace) } } +void +sentry__scope_set_span(sentry_value_t span) +{ + // TODO: implement this function and get rid of this line. + (void)span; + return; +} + void sentry__scope_apply_to_event(const sentry_scope_t *scope, const sentry_options_t *options, sentry_value_t event, @@ -269,7 +280,15 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, // TODO: these should merge PLACE_CLONED_VALUE("tags", scope->tags); PLACE_CLONED_VALUE("extra", scope->extra); - PLACE_CLONED_VALUE("contexts", scope->contexts); + + // TODO: better, more thorough deep merging + sentry_value_t contexts = sentry__value_clone(scope->contexts); + sentry_value_t trace = sentry__span_get_trace_context(scope->span); + if (!sentry_value_is_null(trace)) { + sentry_value_set_by_key(contexts, "trace", trace); + } + PLACE_VALUE("contexts", contexts); + sentry_value_decref(contexts); if (mode & SENTRY_SCOPE_BREADCRUMBS) { PLACE_CLONED_VALUE("breadcrumbs", scope->breadcrumbs); @@ -288,7 +307,9 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, sentry__foreach_stacktrace(event, sentry__symbolize_stacktrace); } +#undef PLACE_CLONED_VALUE +#undef PLACE_VALUE #undef PLACE_STRING -#undef IS_NULL #undef SET +#undef IS_NULL } diff --git a/src/sentry_scope.h b/src/sentry_scope.h index 250ff8200..393cf349d 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -19,6 +19,12 @@ typedef struct sentry_scope_s { sentry_value_t breadcrumbs; sentry_level_t level; sentry_value_t client_sdk; + // Not to be confused with transaction, which is a legacy value. This is + // also known as a transaction, but to maintain consistency with other SDKs + // and to avoid a conflict with the existing transaction field this is named + // span. Whenever possible, `transaction` should pull its value from the + // `name` property nested in this field. + sentry_value_t span; } sentry_scope_t; /** @@ -69,6 +75,12 @@ 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); +/** + * Sets the span (actually transaction) on the scope. An internal way to pass + * around contextual information needed from a transaction into other events. + */ +void sentry__scope_set_span(sentry_value_t span); + /** * These are convenience macros to automatically lock/unlock a scope inside a * code block. diff --git a/src/sentry_tracing.c b/src/sentry_tracing.c new file mode 100644 index 000000000..73e23c0d8 --- /dev/null +++ b/src/sentry_tracing.c @@ -0,0 +1,33 @@ +#include "sentry_sync.h" + +sentry_value_t +sentry__span_get_trace_context(sentry_value_t span) +{ + if (sentry_value_is_null(span) + || sentry_value_is_null(sentry_value_get_by_key(span, "trace_id")) + || sentry_value_is_null(sentry_value_get_by_key(span, "span_id"))) { + return sentry_value_new_null(); + } + + sentry_value_t trace_context = sentry_value_new_object(); + +#define PLACE_VALUE(Key, Source) \ + do { \ + sentry_value_t src = sentry_value_get_by_key(Source, Key); \ + if (!sentry_value_is_null(src)) { \ + sentry_value_incref(src); \ + sentry_value_set_by_key(trace_context, Key, src); \ + } \ + } while (0) + + PLACE_VALUE("trace_id", span); + PLACE_VALUE("span_id", span); + PLACE_VALUE("parent_span_id", span); + PLACE_VALUE("op", span); + PLACE_VALUE("description", span); + PLACE_VALUE("status", span); + + return trace_context; + +#undef PLACE_VALUE +} diff --git a/src/sentry_tracing.h b/src/sentry_tracing.h new file mode 100644 index 000000000..d99afe828 --- /dev/null +++ b/src/sentry_tracing.h @@ -0,0 +1,14 @@ +#ifndef SENTRY_TRACING_H_INCLUDED +#define SENTRY_TRACING_H_INCLUDED + +#include "sentry_boot.h" +#include "sentry_value.h" + +/** + * Returns an object containing tracing information extracted from a + * transaction (/span) which should be included in an event. + * See https://develop.sentry.dev/sdk/event-payloads/transaction/#examples + */ +sentry_value_t sentry__span_get_trace_context(sentry_value_t span); + +#endif diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 86ffecaff..91b908ba9 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -33,6 +33,7 @@ add_executable(sentry_test_unit test_slice.c test_symbolizer.c test_sync.c + test_tracing.c test_uninit.c test_unwinder.c test_utils.c diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c new file mode 100644 index 000000000..34cb331c9 --- /dev/null +++ b/tests/unit/test_tracing.c @@ -0,0 +1,35 @@ +#include "sentry_testsupport.h" +#include "sentry_tracing.h" +#include "sentry_uuid.h" + +SENTRY_TEST(basic_tracing_context) +{ + sentry_value_t span = sentry_value_new_object(); + TEST_CHECK(sentry_value_is_null(sentry__span_get_trace_context(span))); + + sentry_value_set_by_key(span, "op", sentry_value_new_string("honk.beep")); + TEST_CHECK(sentry_value_is_null(sentry__span_get_trace_context(span))); + + sentry_uuid_t trace_id = sentry_uuid_new_v4(); + sentry_value_set_by_key( + span, "trace_id", sentry__value_new_internal_uuid(&trace_id)); + TEST_CHECK(sentry_value_is_null(sentry__span_get_trace_context(span))); + + sentry_uuid_t span_id = sentry_uuid_new_v4(); + sentry_value_set_by_key( + span, "span_id", sentry__value_new_span_uuid(&span_id)); + + sentry_value_t trace_context = sentry__span_get_trace_context(span); + TEST_CHECK(!sentry_value_is_null(trace_context)); + TEST_CHECK(!sentry_value_is_null( + sentry_value_get_by_key(trace_context, "trace_id"))); + TEST_CHECK(!sentry_value_is_null( + sentry_value_get_by_key(trace_context, "span_id"))); + + const char *span_op + = sentry_value_as_string(sentry_value_get_by_key(trace_context, "op")); + TEST_CHECK_STRING_EQUAL(span_op, "honk.beep"); + + sentry_value_decref(trace_context); + sentry_value_decref(span); +} diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 51615be37..5ba245f18 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -4,6 +4,7 @@ XX(basic_function_transport) XX(basic_http_request_preparation_for_event) XX(basic_http_request_preparation_for_event_with_attachment) XX(basic_http_request_preparation_for_minidump) +XX(basic_tracing_context) XX(buildid_fallback) XX(concurrent_init) XX(count_sampled_events)