Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## Unreleased

**Features**:
- Add `traceparent` header support. ([#1394](https://github.com/getsentry/sentry-native/pull/1394))

## 0.11.1

**Features**:
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
199 changes: 152 additions & 47 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 @@ -241,60 +243,153 @@ sentry_transaction_context_update_from_header_n(
// 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;
if (key_len == sentry_trace_len) {
bool is_sentry_trace = true;
for (size_t i = 0; i < sentry_trace_len; i++) {
if (tolower(key[i]) != sentry_trace[i]) {
is_sentry_trace = false;
break;
}
}
}

// 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 = memchr(trace_id_start, '-', value_len);
if (!trace_id_end) {
SENTRY_WARN("invalid trace id format in given header");
return;
}

sentry_value_t inner = tx_ctx->inner;

char *s = sentry__string_clone_n(
trace_id_start, (size_t)(trace_id_end - trace_id_start));
if (!is_valid_trace_id(s)) {
sentry_free(s);
return;
}
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);
if (!is_valid_span_id(sentry_value_as_string(parent_span_id))) {
sentry_value_decref(parent_span_id);
if (is_sentry_trace) {
// 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) {
SENTRY_WARN("invalid trace id format in given header");
return;
}

sentry_value_t inner = tx_ctx->inner;

char *s = sentry__string_clone_n(
trace_id_start, (size_t)(trace_id_end - trace_id_start));
if (!is_valid_trace_id(s)) {
sentry_free(s);
return;
}
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);
if (!is_valid_span_id(sentry_value_as_string(parent_span_id))) {
sentry_value_decref(parent_span_id);
return;
}
sentry_value_set_by_key(
inner, "parent_span_id", parent_span_id);
return;
}
// else: we have a sampled flag

s = sentry__string_clone_n(
span_id_start, (size_t)(span_id_end - span_id_start));
if (!is_valid_span_id(s)) {
sentry_free(s);
return;
}
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));
return;
}
sentry_value_set_by_key(inner, "parent_span_id", parent_span_id);
return;
}
// else: we have a sampled flag

s = sentry__string_clone_n(
span_id_start, (size_t)(span_id_end - span_id_start));
if (!is_valid_span_id(s)) {
sentry_free(s);
return;
}
sentry_value_t parent_span_id = sentry__value_new_string_owned(s);
sentry_value_set_by_key(inner, "parent_span_id", parent_span_id);
// Check for traceparent header: 00-<traceId>-<spanId>-<sampled>
const char traceparent[] = "traceparent";
const size_t traceparent_len = sizeof(traceparent) - 1;
if (key_len == traceparent_len) {
bool is_traceparent = true;
for (size_t i = 0; i < traceparent_len; i++) {
if (tolower(key[i]) != traceparent[i]) {
is_traceparent = false;
break;
}
}

bool sampled = *(span_id_end + 1) == '1';
sentry_value_set_by_key(inner, "sampled", sentry_value_new_bool(sampled));
if (is_traceparent) {
// Parse W3C traceparent header: 00-<traceId>-<spanId>-<flags>
if (value_len < 55) { // Minimum length: 00-32char-16char-02char
SENTRY_WARN("invalid traceparent format: too short");
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;
if (value_len < 55) {
SENTRY_WARN("invalid traceparent format: missing flags");
sentry_free(trace_id_str);
sentry_free(span_id_str);
return;
}

// 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
Expand Down Expand Up @@ -780,6 +875,16 @@ 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) {
char traceparent[64];
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