Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

**Features**:
- Add `traceparent` header support. ([#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
147 changes: 128 additions & 19 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,104 @@ sentry_transaction_context_update_from_header_n(
sentry_value_set_by_key(inner, "sampled", sentry_value_new_bool(sampled));
}

static void
parse_traceparent(
sentry_transaction_context_t *tx_ctx, const char *value, size_t value_len)
{
// Parse W3C traceparent header: 00-<traceId>-<spanId>-<flags>
if (value_len != 55) { // length: 00-32char-16char-02char
SENTRY_WARN("invalid traceparent format: length mismatch");
return;
}

// Check version
if (value[0] != '0' || value[1] != '0' || value[2] != '-') {
SENTRY_WARN("invalid traceparent format: unsupported version "
"or missing delimiter");
return;
}

// Extract trace ID (32 hex chars)
const char *trace_id_start = value + 3;
if (value[35] != '-') {
SENTRY_WARN("invalid traceparent format: missing delimiter "
"after trace ID");
return;
}

char *trace_id_str = sentry__string_clone_n(trace_id_start, 32);
if (!is_valid_trace_id(trace_id_str)) {
sentry_free(trace_id_str);
return;
}

// Extract span ID (16 hex chars)
const char *span_id_start = value + 36;
if (value[52] != '-') {
SENTRY_WARN("invalid traceparent format: missing delimiter "
"after span ID");
sentry_free(trace_id_str);
return;
}

char *span_id_str = sentry__string_clone_n(span_id_start, 16);
if (!is_valid_span_id(span_id_str)) {
sentry_free(trace_id_str);
sentry_free(span_id_str);
return;
}

// Extract flags (2 hex chars)
const char *flags_start = value + 53;

// Parse sampled flag from the last bit of flags
char flags_str[3] = { flags_start[0], flags_start[1], '\0' };
unsigned int flags_value = 0;
if (sscanf(flags_str, "%02x", &flags_value) != 1) {
SENTRY_WARN("invalid traceparent format: invalid flags");
sentry_free(trace_id_str);
sentry_free(span_id_str);
return;
}

bool sampled = (flags_value & 0x01) != 0;

sentry_value_t inner = tx_ctx->inner;
sentry_value_set_by_key(
inner, "trace_id", sentry__value_new_string_owned(trace_id_str));
sentry_value_set_by_key(
inner, "parent_span_id", sentry__value_new_string_owned(span_id_str));
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);
return;
}

// Check for traceparent header: 00-<traceId>-<spanId>-<sampled>
const char traceparent[] = "traceparent";
const size_t traceparent_len = sizeof(traceparent) - 1;
bool is_traceparent
= compare_header_key(key, key_len, traceparent, traceparent_len);
if (is_traceparent) {
parse_traceparent(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,7 +868,8 @@ 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[52];
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");
Expand All @@ -780,6 +878,17 @@ sentry__span_iter_headers(sentry_value_t span,
// 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[56];
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");
callback("traceparent", traceparent, userdata);
}
}
}

void
Expand Down
Loading
Loading