Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

**Features**:

- Add support for outgoing W3C traceparent header propagation with the `propagate_traceparent` option. ([#1394](https://github.com/getsentry/sentry-native/pull/1394))

**Fixes**:

- Use proper SDK name determination for structured logs `sdk.name` attribute. ([#1399](https://github.com/getsentry/sentry-native/pull/1399))
Expand Down
20 changes: 20 additions & 0 deletions include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -1890,6 +1890,26 @@ SENTRY_EXPERIMENTAL_API void sentry_options_set_traces_sampler(
sentry_options_t *opts, sentry_traces_sampler_function callback,
void *user_data);

/**
* Enables or disables propagation of the W3C Trace Context `traceparent`
* header.
*
* When enabled, the `traceparent` header will be included alongside the
* `sentry-trace` header in outgoing HTTP requests for distributed tracing
* interoperability with OpenTelemetry (OTel) services.
*
* This is disabled by default.
*/
SENTRY_EXPERIMENTAL_API void sentry_options_set_propagate_traceparent(
sentry_options_t *opts, int propagate_traceparent);

/**
* Returns whether W3C Trace Context `traceparent` header propagation is
* enabled.
*/
SENTRY_EXPERIMENTAL_API int sentry_options_get_propagate_traceparent(
const sentry_options_t *opts);

/**
* Enables or disables the structured logging feature.
* When disabled, all calls to sentry_logger_X() are no-ops.
Expand Down
14 changes: 14 additions & 0 deletions src/sentry_options.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ sentry_options_new(void)
opts->attach_screenshot = false;
opts->crashpad_wait_for_upload = false;
opts->enable_logging_when_crashed = true;
opts->propagate_traceparent = false;
opts->symbolize_stacktraces =
// AIX doesn't have reliable debug IDs for server-side symbolication,
// and the diversity of Android makes it infeasible to have access to debug
Expand Down Expand Up @@ -718,3 +719,16 @@ sentry_options_set_handler_strategy(
}

#endif // SENTRY_PLATFORM_LINUX

void
sentry_options_set_propagate_traceparent(
sentry_options_t *opts, int propagate_traceparent)
{
opts->propagate_traceparent = !!propagate_traceparent;
}

int
sentry_options_get_propagate_traceparent(const sentry_options_t *opts)
{
return opts->propagate_traceparent;
}
1 change: 1 addition & 0 deletions src/sentry_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ struct sentry_options_s {
bool attach_screenshot;
bool crashpad_wait_for_upload;
bool enable_logging_when_crashed;
bool propagate_traceparent;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of these days, we should convert all these flags to bitfields. But not in this PR.


sentry_attachment_t *attachments;
sentry_run_t *run;
Expand Down
72 changes: 52 additions & 20 deletions src/sentry_tracing.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#include "sentry_tracing.h"
#include "sentry.h"
#include "sentry_alloc.h"
#include "sentry_core.h"
#include "sentry_logger.h"
#include "sentry_options.h"
#include "sentry_scope.h"
#include "sentry_slice.h"
#include "sentry_string.h"
Expand Down Expand Up @@ -229,29 +231,26 @@ is_valid_span_id(const char *span_id)
return is_valid_id(span_id, 16, "span id");
}

void
sentry_transaction_context_update_from_header_n(
sentry_transaction_context_t *tx_ctx, const char *key, size_t key_len,
const char *value, size_t value_len)
static bool
compare_header_key(
const char *key, size_t key_len, const char *expected, size_t expected_len)
{
if (!tx_ctx) {
return;
if (key_len != expected_len) {
return false;
}

// do case-insensitive header key comparison
const char sentry_trace[] = "sentry-trace";
const size_t sentry_trace_len = sizeof(sentry_trace) - 1;
if (key_len != sentry_trace_len) {
return;
}
for (size_t i = 0; i < sentry_trace_len; i++) {
if (tolower(key[i]) != sentry_trace[i]) {
return;
for (size_t i = 0; i < expected_len; i++) {
if (tolower(key[i]) != expected[i]) {
return false;
}
}
return true;
}

// https://develop.sentry.dev/sdk/performance/#header-sentry-trace
// sentry-trace = traceid-spanid(-sampled)?
static void
parse_sentry_trace(
sentry_transaction_context_t *tx_ctx, const char *value, size_t value_len)
{
// Parse sentry-trace header: traceid-spanid(-sampled)?
const char *trace_id_start = value;
const char *trace_id_end = memchr(trace_id_start, '-', value_len);
if (!trace_id_end) {
Expand Down Expand Up @@ -297,6 +296,25 @@ sentry_transaction_context_update_from_header_n(
sentry_value_set_by_key(inner, "sampled", sentry_value_new_bool(sampled));
}

void
sentry_transaction_context_update_from_header_n(
sentry_transaction_context_t *tx_ctx, const char *key, size_t key_len,
const char *value, size_t value_len)
{
if (!tx_ctx) {
return;
}

// do case-insensitive header key comparison
const char sentry_trace[] = "sentry-trace";
const size_t sentry_trace_len = sizeof(sentry_trace) - 1;
bool is_sentry_trace
= compare_header_key(key, key_len, sentry_trace, sentry_trace_len);
if (is_sentry_trace) {
parse_sentry_trace(tx_ctx, value, value_len);
}
}

void
sentry_transaction_context_update_from_header(
sentry_transaction_context_t *tx_ctx, const char *key, const char *value)
Expand Down Expand Up @@ -771,15 +789,29 @@ sentry__span_iter_headers(sentry_value_t span,
return;
}

char buf[64];
// (32 char trace_id)-(16-char span_id)-(0|1) + null terminator
char buf[SENTRY_TRACE_LEN + 1];
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);

// TODO propagate dsc into outgoing bagage header
// https://develop.sentry.dev/sdk/telemetry/traces/dynamic-sampling-context/#baggage-header

callback("sentry-trace", buf, userdata);
SENTRY_WITH_OPTIONS (options) {
if (options->propagate_traceparent) {
// 00-(32 char trace_id)-(16-char span_id)-(00|01) + null terminator
char traceparent[SENTRY_W3C_TRACEPARENT_LEN + 1];
snprintf(traceparent, sizeof(traceparent), "00-%s-%s-%s",
sentry_value_as_string(trace_id),
sentry_value_as_string(span_id),
sentry_value_is_true(sampled) ? "01" : "00");
// emit as lowercase as described on the W3C spec
// https://www.w3.org/TR/trace-context/#header-name
callback("traceparent", traceparent, userdata);
}
}
}

void
Expand Down
8 changes: 8 additions & 0 deletions src/sentry_tracing.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@
#include "sentry_slice.h"
#include "sentry_value.h"

// W3C traceparent header: 00-<traceId>-<spanId>-<flags>
// length: 00-32char-16char-02char
#define SENTRY_W3C_TRACEPARENT_LEN 55

// sentry-trace header: <traceId>-<spanId>-<sampled>
// length: 32char-16char-01char
#define SENTRY_TRACE_LEN 51

/**
* A span.
*/
Expand Down
130 changes: 130 additions & 0 deletions tests/unit/test_tracing.c
Original file line number Diff line number Diff line change
Expand Up @@ -1815,5 +1815,135 @@ SENTRY_TEST(propagation_context_init)
sentry_close();
}

typedef struct {
int sentry_trace_found;
int traceparent_found;
char sentry_trace_value[64];
char traceparent_value[64];
} traceparent_header_collector_t;

static void
collect_traceparent_headers(const char *key, const char *value, void *userdata)
{
traceparent_header_collector_t *collector
= (traceparent_header_collector_t *)userdata;
if (strcmp(key, "sentry-trace") == 0) {
collector->sentry_trace_found = 1;
const size_t value_len = sizeof(collector->sentry_trace_value) - 1;
strncpy(collector->sentry_trace_value, value, value_len);
collector->sentry_trace_value[value_len] = '\0';
} else if (strcmp(key, "traceparent") == 0) {
collector->traceparent_found = 1;
const size_t value_len = sizeof(collector->traceparent_value) - 1;
strncpy(collector->traceparent_value, value, value_len);
collector->traceparent_value[value_len] = '\0';
}
}

SENTRY_TEST(traceparent_header_disabled_by_default)
{
SENTRY_TEST_OPTIONS_NEW(options);
sentry_options_set_dsn(options, "https://[email protected]/42");
sentry_options_set_traces_sample_rate(options, 1.0);
// Note: not enabling traceparent propagation
sentry_init(options);

sentry_transaction_context_t *tx_ctx
= sentry_transaction_context_new("test", "test");
sentry_transaction_t *tx
= sentry_transaction_start(tx_ctx, sentry_value_new_null());

traceparent_header_collector_t collector = { 0 };
sentry_transaction_iter_headers(
tx, collect_traceparent_headers, &collector);

// Should have sentry-trace but not traceparent
TEST_CHECK(collector.sentry_trace_found);
TEST_CHECK(!collector.traceparent_found);

TEST_CHECK_INT_EQUAL(
strlen(collector.sentry_trace_value), SENTRY_TRACE_LEN);

sentry_transaction_finish(tx);
sentry_close();
}

SENTRY_TEST(traceparent_header_generation)
{
SENTRY_TEST_OPTIONS_NEW(options);
sentry_options_set_dsn(options, "https://[email protected]/42");
sentry_options_set_traces_sample_rate(options, 1.0);
sentry_options_set_propagate_traceparent(options, 1);
sentry_init(options);

sentry_transaction_context_t *tx_ctx
= sentry_transaction_context_new("test", "test");
sentry_transaction_t *tx
= sentry_transaction_start(tx_ctx, sentry_value_new_null());

// Test transaction header generation
traceparent_header_collector_t collector = { 0 };
sentry_transaction_iter_headers(
tx, collect_traceparent_headers, &collector);

// Should have both headers
TEST_CHECK(collector.sentry_trace_found);
TEST_CHECK(collector.traceparent_found);

// Verify expected traceparent length
TEST_CHECK_INT_EQUAL(
strlen(collector.traceparent_value), SENTRY_W3C_TRACEPARENT_LEN);
// Verify traceparent format: starts with "00-"
TEST_CHECK(strncmp(collector.traceparent_value, "00-", 3) == 0);

// Extract components from both headers to verify consistency
const char *trace_id = sentry_value_as_string(
sentry_value_get_by_key(tx->inner, "trace_id"));
const char *tx_span_id
= sentry_value_as_string(sentry_value_get_by_key(tx->inner, "span_id"));

// Verify sentry-trace contains the correct trace and span IDs
TEST_CHECK(strstr(collector.sentry_trace_value, trace_id) != NULL);
TEST_CHECK(strstr(collector.sentry_trace_value, tx_span_id) != NULL);

// Verify traceparent contains the correct trace and span IDs
TEST_CHECK(strstr(collector.traceparent_value, trace_id) != NULL);
TEST_CHECK(strstr(collector.traceparent_value, tx_span_id) != NULL);

// Test span header generation
sentry_span_t *span
= sentry_transaction_start_child(tx, "child", "child-span");

traceparent_header_collector_t span_collector = { 0 };
sentry_span_iter_headers(
span, collect_traceparent_headers, &span_collector);

// Should have both headers for spans too
TEST_CHECK(span_collector.sentry_trace_found);
TEST_CHECK(span_collector.traceparent_found);

// Verify traceparent format for spans
TEST_CHECK(strncmp(span_collector.traceparent_value, "00-", 3) == 0);

// Extract components from both headers to verify consistency
const char *span_trace_id = sentry_value_as_string(
sentry_value_get_by_key(span->inner, "trace_id"));
const char *span_id = sentry_value_as_string(
sentry_value_get_by_key(span->inner, "span_id"));

// Verify sentry-trace contains the correct trace and span IDs
TEST_CHECK(
strstr(span_collector.sentry_trace_value, span_trace_id) != NULL);
TEST_CHECK(strstr(span_collector.sentry_trace_value, span_id) != NULL);

// Verify traceparent contains the correct trace and span IDs
TEST_CHECK(strstr(span_collector.traceparent_value, span_trace_id) != NULL);
TEST_CHECK(strstr(span_collector.traceparent_value, span_id) != NULL);

sentry_span_finish(span);
sentry_transaction_finish(tx);
sentry_close();
}

#undef IS_NULL
#undef CHECK_STRING_PROPERTY
2 changes: 2 additions & 0 deletions tests/unit/tests.inc
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ XX(stack_guarantee_auto_init)
XX(symbolizer)
XX(task_queue)
XX(thread_without_name_still_valid)
XX(traceparent_header_disabled_by_default)
XX(traceparent_header_generation)
XX(transaction_name_backfill_on_finish)
XX(transactions_skip_before_send)
XX(transport_sampling_transactions)
Expand Down
Loading