Skip to content

Commit

Permalink
feat: Implement distributed trace propagation (NATIVE-304) (#657)
Browse files Browse the repository at this point in the history
This implements the `iter_headers` and something analogous to the `continue_from_headers` APIs.
  • Loading branch information
Swatinem authored Jan 18, 2022
1 parent 0e94e49 commit e39edcb
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 1 deletion.
35 changes: 35 additions & 0 deletions include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -1335,6 +1335,17 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_sampled(
SENTRY_EXPERIMENTAL_API void sentry_transaction_context_remove_sampled(
sentry_transaction_context_t *tx_cxt);

/**
* Update the Transaction Context with the given HTTP header key/value pair.
*
* This is used to propagate distributed tracing metadata from upstream
* services. Therefore, the headers of incoming requests should be fed into this
* function so that sentry is able to continue a trace that was started by an
* upstream service.
*/
SENTRY_EXPERIMENTAL_API void sentry_transaction_context_update_from_header(
sentry_transaction_context_t *tx_cxt, const char *key, const char *value);

/**
* Starts a new Transaction based on the provided context, restored from an
* external integration (i.e. a span from a different SDK) or manually
Expand Down Expand Up @@ -1624,6 +1635,30 @@ SENTRY_EXPERIMENTAL_API void sentry_span_set_status(
SENTRY_EXPERIMENTAL_API void sentry_transaction_set_status(
sentry_transaction_t *tx, sentry_span_status_t status);

/**
* Type of the `iter_headers` callback.
*
* The callback is being called with HTTP header key/value pairs.
* These headers can be attached to outgoing HTTP requests to propagate
* distributed tracing metadata to downstream services.
*
*/
typedef void (*sentry_iter_headers_function_t)(
const char *key, const char *value, void *userdata);

/**
* Iterates the distributed tracing HTTP headers for the given span.
*/
SENTRY_EXPERIMENTAL_API void sentry_span_iter_headers(sentry_span_t *span,
sentry_iter_headers_function_t callback, void *userdata);

/**
* Iterates the distributed tracing HTTP headers for the given transaction.
*/
SENTRY_EXPERIMENTAL_API void sentry_transaction_iter_headers(
sentry_transaction_t *tx, sentry_iter_headers_function_t callback,
void *userdata);

#endif

#ifdef __cplusplus
Expand Down
75 changes: 75 additions & 0 deletions src/sentry_tracing.c
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,47 @@ sentry_transaction_context_remove_sampled(sentry_transaction_context_t *tx_cxt)
sentry_value_remove_by_key(tx_cxt->inner, "sampled");
}

void
sentry_transaction_context_update_from_header(
sentry_transaction_context_t *tx_cxt, const char *key, const char *value)
{
if (!sentry__string_eq(key, "sentry-trace")) {
return;
}

// https://develop.sentry.dev/sdk/performance/#header-sentry-trace
// sentry-trace = traceid-spanid(-sampled)?
const char *trace_id_start = value;
const char *trace_id_end = strchr(trace_id_start, '-');
if (!trace_id_end) {
return;
}

sentry_value_t inner = tx_cxt->inner;

char *s
= sentry__string_clonen(trace_id_start, trace_id_end - trace_id_start);
sentry_value_t trace_id = sentry__value_new_string_owned(s);
sentry_value_set_by_key(inner, "trace_id", trace_id);

const char *span_id_start = trace_id_end + 1;
const char *span_id_end = strchr(span_id_start, '-');
if (!span_id_end) {
// no sampled flag
sentry_value_t parent_span_id = sentry_value_new_string(span_id_start);
sentry_value_set_by_key(inner, "parent_span_id", parent_span_id);
return;
}
// else: we have a sampled flag

s = sentry__string_clonen(span_id_start, span_id_end - span_id_start);
sentry_value_t parent_span_id = sentry__value_new_string_owned(s);
sentry_value_set_by_key(inner, "parent_span_id", parent_span_id);

bool sampled = *(span_id_end + 1) == '1';
sentry_value_set_by_key(inner, "sampled", sentry_value_new_bool(sampled));
}

sentry_transaction_t *
sentry__transaction_new(sentry_value_t inner)
{
Expand Down Expand Up @@ -435,3 +476,37 @@ sentry_transaction_set_status(
{
set_status(tx->inner, status);
}

static void
sentry__span_iter_headers(sentry_value_t span,
sentry_iter_headers_function_t callback, void *userdata)
{
sentry_value_t trace_id = sentry_value_get_by_key(span, "trace_id");
sentry_value_t span_id = sentry_value_get_by_key(span, "span_id");
sentry_value_t sampled = sentry_value_get_by_key(span, "sampled");

if (sentry_value_is_null(trace_id) || sentry_value_is_null(span_id)) {
return;
}

char buf[64];
snprintf(buf, sizeof(buf), "%s-%s-%s", sentry_value_as_string(trace_id),
sentry_value_as_string(span_id),
sentry_value_is_true(sampled) ? "1" : "0");

callback("sentry-trace", buf, userdata);
}

void
sentry_span_iter_headers(sentry_span_t *span,
sentry_iter_headers_function_t callback, void *userdata)
{
sentry__span_iter_headers(span->inner, callback, userdata);
}

void
sentry_transaction_iter_headers(sentry_transaction_t *tx,
sentry_iter_headers_function_t callback, void *userdata)
{
sentry__span_iter_headers(tx->inner, callback, userdata);
}
98 changes: 97 additions & 1 deletion tests/unit/test_tracing.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "sentry_scope.h"
#include "sentry_testsupport.h"

#include "sentry_scope.h"
#include "sentry_string.h"
#include "sentry_tracing.h"
#include "sentry_uuid.h"

Expand Down Expand Up @@ -610,5 +612,99 @@ SENTRY_TEST(drop_unfinished_spans)
TEST_CHECK_INT_EQUAL(called_transport, 1);
}

static void
forward_headers_to(const char *key, const char *value, void *userdata)
{
sentry_transaction_context_t *tx_ctx
= (sentry_transaction_context_t *)userdata;

sentry_transaction_context_update_from_header(tx_ctx, key, value);
}

SENTRY_TEST(distributed_headers)
{
sentry_options_t *options = sentry_options_new();
sentry_options_set_dsn(options, "https://foo@sentry.invalid/42");

sentry_options_set_traces_sample_rate(options, 1.0);
sentry_options_set_max_spans(options, 2);
sentry_init(options);

sentry_transaction_context_t *tx_ctx
= sentry_transaction_context_new("wow!", NULL);
sentry_transaction_t *tx = sentry_transaction_start(tx_ctx);

const char *trace_id = sentry_value_as_string(
sentry_value_get_by_key(tx->inner, "trace_id"));
TEST_CHECK(!sentry__string_eq(trace_id, ""));

const char *span_id
= sentry_value_as_string(sentry_value_get_by_key(tx->inner, "span_id"));
TEST_CHECK(!sentry__string_eq(span_id, ""));

// check transaction
tx_ctx = sentry_transaction_context_new("distributed!", NULL);
sentry_transaction_iter_headers(tx, forward_headers_to, (void *)tx_ctx);
sentry_transaction_t *dist_tx = sentry_transaction_start(tx_ctx);

const char *dist_trace_id = sentry_value_as_string(
sentry_value_get_by_key(dist_tx->inner, "trace_id"));
TEST_CHECK_STRING_EQUAL(dist_trace_id, trace_id);

const char *parent_span_id = sentry_value_as_string(
sentry_value_get_by_key(dist_tx->inner, "parent_span_id"));
TEST_CHECK_STRING_EQUAL(parent_span_id, span_id);

sentry__transaction_decref(dist_tx);

// check span
sentry_span_t *child = sentry_transaction_start_child(tx, "honk", "goose");

span_id = sentry_value_as_string(
sentry_value_get_by_key(child->inner, "span_id"));
TEST_CHECK(!sentry__string_eq(span_id, ""));

tx_ctx = sentry_transaction_context_new("distributed!", NULL);
sentry_span_iter_headers(child, forward_headers_to, (void *)tx_ctx);
dist_tx = sentry_transaction_start(tx_ctx);

dist_trace_id = sentry_value_as_string(
sentry_value_get_by_key(dist_tx->inner, "trace_id"));
TEST_CHECK_STRING_EQUAL(dist_trace_id, trace_id);

parent_span_id = sentry_value_as_string(
sentry_value_get_by_key(dist_tx->inner, "parent_span_id"));
TEST_CHECK_STRING_EQUAL(parent_span_id, span_id);

TEST_CHECK(sentry_value_is_true(
sentry_value_get_by_key(dist_tx->inner, "sampled")));

sentry__transaction_decref(dist_tx);
sentry__span_free(child);
sentry__transaction_decref(tx);

// check sampled flag
tx_ctx = sentry_transaction_context_new("wow!", NULL);
sentry_transaction_context_set_sampled(tx_ctx, 0);
tx = sentry_transaction_start(tx_ctx);

tx_ctx = sentry_transaction_context_new("distributed!", NULL);
sentry_transaction_iter_headers(tx, forward_headers_to, (void *)tx_ctx);
dist_tx = sentry_transaction_start(tx_ctx);

TEST_CHECK(!sentry_value_is_true(
sentry_value_get_by_key(dist_tx->inner, "sampled")));

sentry__transaction_decref(dist_tx);

// TODO: Check the sampled flag on a child span as well, but I think we
// don't create one if the transaction is not sampled? Well, here is the
// reason why we should!

sentry__transaction_decref(tx);

sentry_close();
}

#undef IS_NULL
#undef CHECK_STRING_PROPERTY
1 change: 1 addition & 0 deletions tests/unit/tests.inc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ XX(concurrent_init)
XX(concurrent_uninit)
XX(count_sampled_events)
XX(custom_logger)
XX(distributed_headers)
XX(drop_unfinished_spans)
XX(dsn_parsing_complete)
XX(dsn_parsing_invalid)
Expand Down

0 comments on commit e39edcb

Please sign in to comment.