Skip to content

Commit 5d60bff

Browse files
feat: outgoing traceparent header support (#1394)
* initial traceparent support * CHANGELOG.md update * consolidate tests * remove redundant value_len check * check for exact length * reuse header key comparison + extract parsing per header type * static internal helper * add size bounds + checks for headers * only accept 00 or 01 sampling flag * Apply suggestion from @supervacuus Co-authored-by: Mischan Toosarani-Hausberger <[email protected]> * Apply suggestion from @supervacuus Co-authored-by: Mischan Toosarani-Hausberger <[email protected]> * Apply suggestion from @supervacuus Co-authored-by: Mischan Toosarani-Hausberger <[email protected]> * Apply suggestion from @supervacuus Co-authored-by: Mischan Toosarani-Hausberger <[email protected]> * fix(traceparent): only support outgoing for now (#1406) * cleanup * remove feature of parsing incoming traceparent * re-add len macros * add tests to verify consistency of trace and span IDs across headers for transactions and spans * use trace header sizes in header propagation * update CHANGELOG.md --------- Co-authored-by: Mischan Toosarani-Hausberger <[email protected]>
1 parent a51cddd commit 5d60bff

File tree

8 files changed

+231
-20
lines changed

8 files changed

+231
-20
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
**Features**:
6+
7+
- Add support for outgoing W3C traceparent header propagation with the `propagate_traceparent` option. ([#1394](https://github.com/getsentry/sentry-native/pull/1394))
8+
59
**Fixes**:
610

711
- Use proper SDK name determination for structured logs `sdk.name` attribute. ([#1399](https://github.com/getsentry/sentry-native/pull/1399))

include/sentry.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1890,6 +1890,26 @@ SENTRY_EXPERIMENTAL_API void sentry_options_set_traces_sampler(
18901890
sentry_options_t *opts, sentry_traces_sampler_function callback,
18911891
void *user_data);
18921892

1893+
/**
1894+
* Enables or disables propagation of the W3C Trace Context `traceparent`
1895+
* header.
1896+
*
1897+
* When enabled, the `traceparent` header will be included alongside the
1898+
* `sentry-trace` header in outgoing HTTP requests for distributed tracing
1899+
* interoperability with OpenTelemetry (OTel) services.
1900+
*
1901+
* This is disabled by default.
1902+
*/
1903+
SENTRY_EXPERIMENTAL_API void sentry_options_set_propagate_traceparent(
1904+
sentry_options_t *opts, int propagate_traceparent);
1905+
1906+
/**
1907+
* Returns whether W3C Trace Context `traceparent` header propagation is
1908+
* enabled.
1909+
*/
1910+
SENTRY_EXPERIMENTAL_API int sentry_options_get_propagate_traceparent(
1911+
const sentry_options_t *opts);
1912+
18931913
/**
18941914
* Enables or disables the structured logging feature.
18951915
* When disabled, all calls to sentry_logger_X() are no-ops.

src/sentry_options.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ sentry_options_new(void)
5151
opts->attach_screenshot = false;
5252
opts->crashpad_wait_for_upload = false;
5353
opts->enable_logging_when_crashed = true;
54+
opts->propagate_traceparent = false;
5455
opts->symbolize_stacktraces =
5556
// AIX doesn't have reliable debug IDs for server-side symbolication,
5657
// and the diversity of Android makes it infeasible to have access to debug
@@ -718,3 +719,16 @@ sentry_options_set_handler_strategy(
718719
}
719720

720721
#endif // SENTRY_PLATFORM_LINUX
722+
723+
void
724+
sentry_options_set_propagate_traceparent(
725+
sentry_options_t *opts, int propagate_traceparent)
726+
{
727+
opts->propagate_traceparent = !!propagate_traceparent;
728+
}
729+
730+
int
731+
sentry_options_get_propagate_traceparent(const sentry_options_t *opts)
732+
{
733+
return opts->propagate_traceparent;
734+
}

src/sentry_options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ struct sentry_options_s {
4242
bool attach_screenshot;
4343
bool crashpad_wait_for_upload;
4444
bool enable_logging_when_crashed;
45+
bool propagate_traceparent;
4546

4647
sentry_attachment_t *attachments;
4748
sentry_run_t *run;

src/sentry_tracing.c

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#include "sentry_tracing.h"
22
#include "sentry.h"
33
#include "sentry_alloc.h"
4+
#include "sentry_core.h"
45
#include "sentry_logger.h"
6+
#include "sentry_options.h"
57
#include "sentry_scope.h"
68
#include "sentry_slice.h"
79
#include "sentry_string.h"
@@ -229,29 +231,26 @@ is_valid_span_id(const char *span_id)
229231
return is_valid_id(span_id, 16, "span id");
230232
}
231233

232-
void
233-
sentry_transaction_context_update_from_header_n(
234-
sentry_transaction_context_t *tx_ctx, const char *key, size_t key_len,
235-
const char *value, size_t value_len)
234+
static bool
235+
compare_header_key(
236+
const char *key, size_t key_len, const char *expected, size_t expected_len)
236237
{
237-
if (!tx_ctx) {
238-
return;
238+
if (key_len != expected_len) {
239+
return false;
239240
}
240-
241-
// do case-insensitive header key comparison
242-
const char sentry_trace[] = "sentry-trace";
243-
const size_t sentry_trace_len = sizeof(sentry_trace) - 1;
244-
if (key_len != sentry_trace_len) {
245-
return;
246-
}
247-
for (size_t i = 0; i < sentry_trace_len; i++) {
248-
if (tolower(key[i]) != sentry_trace[i]) {
249-
return;
241+
for (size_t i = 0; i < expected_len; i++) {
242+
if (tolower(key[i]) != expected[i]) {
243+
return false;
250244
}
251245
}
246+
return true;
247+
}
252248

253-
// https://develop.sentry.dev/sdk/performance/#header-sentry-trace
254-
// sentry-trace = traceid-spanid(-sampled)?
249+
static void
250+
parse_sentry_trace(
251+
sentry_transaction_context_t *tx_ctx, const char *value, size_t value_len)
252+
{
253+
// Parse sentry-trace header: traceid-spanid(-sampled)?
255254
const char *trace_id_start = value;
256255
const char *trace_id_end = memchr(trace_id_start, '-', value_len);
257256
if (!trace_id_end) {
@@ -297,6 +296,25 @@ sentry_transaction_context_update_from_header_n(
297296
sentry_value_set_by_key(inner, "sampled", sentry_value_new_bool(sampled));
298297
}
299298

299+
void
300+
sentry_transaction_context_update_from_header_n(
301+
sentry_transaction_context_t *tx_ctx, const char *key, size_t key_len,
302+
const char *value, size_t value_len)
303+
{
304+
if (!tx_ctx) {
305+
return;
306+
}
307+
308+
// do case-insensitive header key comparison
309+
const char sentry_trace[] = "sentry-trace";
310+
const size_t sentry_trace_len = sizeof(sentry_trace) - 1;
311+
bool is_sentry_trace
312+
= compare_header_key(key, key_len, sentry_trace, sentry_trace_len);
313+
if (is_sentry_trace) {
314+
parse_sentry_trace(tx_ctx, value, value_len);
315+
}
316+
}
317+
300318
void
301319
sentry_transaction_context_update_from_header(
302320
sentry_transaction_context_t *tx_ctx, const char *key, const char *value)
@@ -771,15 +789,29 @@ sentry__span_iter_headers(sentry_value_t span,
771789
return;
772790
}
773791

774-
char buf[64];
792+
// (32 char trace_id)-(16-char span_id)-(0|1) + null terminator
793+
char buf[SENTRY_TRACE_LEN + 1];
775794
snprintf(buf, sizeof(buf), "%s-%s-%s", sentry_value_as_string(trace_id),
776795
sentry_value_as_string(span_id),
777796
sentry_value_is_true(sampled) ? "1" : "0");
797+
callback("sentry-trace", buf, userdata);
778798

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

782-
callback("sentry-trace", buf, userdata);
802+
SENTRY_WITH_OPTIONS (options) {
803+
if (options->propagate_traceparent) {
804+
// 00-(32 char trace_id)-(16-char span_id)-(00|01) + null terminator
805+
char traceparent[SENTRY_W3C_TRACEPARENT_LEN + 1];
806+
snprintf(traceparent, sizeof(traceparent), "00-%s-%s-%s",
807+
sentry_value_as_string(trace_id),
808+
sentry_value_as_string(span_id),
809+
sentry_value_is_true(sampled) ? "01" : "00");
810+
// emit as lowercase as described on the W3C spec
811+
// https://www.w3.org/TR/trace-context/#header-name
812+
callback("traceparent", traceparent, userdata);
813+
}
814+
}
783815
}
784816

785817
void

src/sentry_tracing.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@
44
#include "sentry_slice.h"
55
#include "sentry_value.h"
66

7+
// W3C traceparent header: 00-<traceId>-<spanId>-<flags>
8+
// length: 00-32char-16char-02char
9+
#define SENTRY_W3C_TRACEPARENT_LEN 55
10+
11+
// sentry-trace header: <traceId>-<spanId>-<sampled>
12+
// length: 32char-16char-01char
13+
#define SENTRY_TRACE_LEN 51
14+
715
/**
816
* A span.
917
*/

tests/unit/test_tracing.c

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1815,5 +1815,135 @@ SENTRY_TEST(propagation_context_init)
18151815
sentry_close();
18161816
}
18171817

1818+
typedef struct {
1819+
int sentry_trace_found;
1820+
int traceparent_found;
1821+
char sentry_trace_value[64];
1822+
char traceparent_value[64];
1823+
} traceparent_header_collector_t;
1824+
1825+
static void
1826+
collect_traceparent_headers(const char *key, const char *value, void *userdata)
1827+
{
1828+
traceparent_header_collector_t *collector
1829+
= (traceparent_header_collector_t *)userdata;
1830+
if (strcmp(key, "sentry-trace") == 0) {
1831+
collector->sentry_trace_found = 1;
1832+
const size_t value_len = sizeof(collector->sentry_trace_value) - 1;
1833+
strncpy(collector->sentry_trace_value, value, value_len);
1834+
collector->sentry_trace_value[value_len] = '\0';
1835+
} else if (strcmp(key, "traceparent") == 0) {
1836+
collector->traceparent_found = 1;
1837+
const size_t value_len = sizeof(collector->traceparent_value) - 1;
1838+
strncpy(collector->traceparent_value, value, value_len);
1839+
collector->traceparent_value[value_len] = '\0';
1840+
}
1841+
}
1842+
1843+
SENTRY_TEST(traceparent_header_disabled_by_default)
1844+
{
1845+
SENTRY_TEST_OPTIONS_NEW(options);
1846+
sentry_options_set_dsn(options, "https://[email protected]/42");
1847+
sentry_options_set_traces_sample_rate(options, 1.0);
1848+
// Note: not enabling traceparent propagation
1849+
sentry_init(options);
1850+
1851+
sentry_transaction_context_t *tx_ctx
1852+
= sentry_transaction_context_new("test", "test");
1853+
sentry_transaction_t *tx
1854+
= sentry_transaction_start(tx_ctx, sentry_value_new_null());
1855+
1856+
traceparent_header_collector_t collector = { 0 };
1857+
sentry_transaction_iter_headers(
1858+
tx, collect_traceparent_headers, &collector);
1859+
1860+
// Should have sentry-trace but not traceparent
1861+
TEST_CHECK(collector.sentry_trace_found);
1862+
TEST_CHECK(!collector.traceparent_found);
1863+
1864+
TEST_CHECK_INT_EQUAL(
1865+
strlen(collector.sentry_trace_value), SENTRY_TRACE_LEN);
1866+
1867+
sentry_transaction_finish(tx);
1868+
sentry_close();
1869+
}
1870+
1871+
SENTRY_TEST(traceparent_header_generation)
1872+
{
1873+
SENTRY_TEST_OPTIONS_NEW(options);
1874+
sentry_options_set_dsn(options, "https://[email protected]/42");
1875+
sentry_options_set_traces_sample_rate(options, 1.0);
1876+
sentry_options_set_propagate_traceparent(options, 1);
1877+
sentry_init(options);
1878+
1879+
sentry_transaction_context_t *tx_ctx
1880+
= sentry_transaction_context_new("test", "test");
1881+
sentry_transaction_t *tx
1882+
= sentry_transaction_start(tx_ctx, sentry_value_new_null());
1883+
1884+
// Test transaction header generation
1885+
traceparent_header_collector_t collector = { 0 };
1886+
sentry_transaction_iter_headers(
1887+
tx, collect_traceparent_headers, &collector);
1888+
1889+
// Should have both headers
1890+
TEST_CHECK(collector.sentry_trace_found);
1891+
TEST_CHECK(collector.traceparent_found);
1892+
1893+
// Verify expected traceparent length
1894+
TEST_CHECK_INT_EQUAL(
1895+
strlen(collector.traceparent_value), SENTRY_W3C_TRACEPARENT_LEN);
1896+
// Verify traceparent format: starts with "00-"
1897+
TEST_CHECK(strncmp(collector.traceparent_value, "00-", 3) == 0);
1898+
1899+
// Extract components from both headers to verify consistency
1900+
const char *trace_id = sentry_value_as_string(
1901+
sentry_value_get_by_key(tx->inner, "trace_id"));
1902+
const char *tx_span_id
1903+
= sentry_value_as_string(sentry_value_get_by_key(tx->inner, "span_id"));
1904+
1905+
// Verify sentry-trace contains the correct trace and span IDs
1906+
TEST_CHECK(strstr(collector.sentry_trace_value, trace_id) != NULL);
1907+
TEST_CHECK(strstr(collector.sentry_trace_value, tx_span_id) != NULL);
1908+
1909+
// Verify traceparent contains the correct trace and span IDs
1910+
TEST_CHECK(strstr(collector.traceparent_value, trace_id) != NULL);
1911+
TEST_CHECK(strstr(collector.traceparent_value, tx_span_id) != NULL);
1912+
1913+
// Test span header generation
1914+
sentry_span_t *span
1915+
= sentry_transaction_start_child(tx, "child", "child-span");
1916+
1917+
traceparent_header_collector_t span_collector = { 0 };
1918+
sentry_span_iter_headers(
1919+
span, collect_traceparent_headers, &span_collector);
1920+
1921+
// Should have both headers for spans too
1922+
TEST_CHECK(span_collector.sentry_trace_found);
1923+
TEST_CHECK(span_collector.traceparent_found);
1924+
1925+
// Verify traceparent format for spans
1926+
TEST_CHECK(strncmp(span_collector.traceparent_value, "00-", 3) == 0);
1927+
1928+
// Extract components from both headers to verify consistency
1929+
const char *span_trace_id = sentry_value_as_string(
1930+
sentry_value_get_by_key(span->inner, "trace_id"));
1931+
const char *span_id = sentry_value_as_string(
1932+
sentry_value_get_by_key(span->inner, "span_id"));
1933+
1934+
// Verify sentry-trace contains the correct trace and span IDs
1935+
TEST_CHECK(
1936+
strstr(span_collector.sentry_trace_value, span_trace_id) != NULL);
1937+
TEST_CHECK(strstr(span_collector.sentry_trace_value, span_id) != NULL);
1938+
1939+
// Verify traceparent contains the correct trace and span IDs
1940+
TEST_CHECK(strstr(span_collector.traceparent_value, span_trace_id) != NULL);
1941+
TEST_CHECK(strstr(span_collector.traceparent_value, span_id) != NULL);
1942+
1943+
sentry_span_finish(span);
1944+
sentry_transaction_finish(tx);
1945+
sentry_close();
1946+
}
1947+
18181948
#undef IS_NULL
18191949
#undef CHECK_STRING_PROPERTY

tests/unit/tests.inc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@ XX(stack_guarantee_auto_init)
162162
XX(symbolizer)
163163
XX(task_queue)
164164
XX(thread_without_name_still_valid)
165+
XX(traceparent_header_disabled_by_default)
166+
XX(traceparent_header_generation)
165167
XX(transaction_name_backfill_on_finish)
166168
XX(transactions_skip_before_send)
167169
XX(transport_sampling_transactions)

0 commit comments

Comments
 (0)