Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
956a803
ref(utils): extract W3C baggage iterator and percent-decode
jpnurmi Apr 22, 2026
0298c91
ref(value): Add key/value foreach helper
jpnurmi May 4, 2026
0d99e48
feat(options): add strict trace continuation option
jpnurmi Apr 22, 2026
489bec5
feat(options): add org_id option
jpnurmi Apr 22, 2026
a780b83
fix(scope): rebuild DSC when the propagation trace changes
jpnurmi Apr 22, 2026
2c54b98
feat(tracing): resolve effective org_id for DSC
jpnurmi Apr 22, 2026
03adabf
feat(tracing): parse incoming baggage header
jpnurmi Apr 22, 2026
3ac3f5d
ref(scope): extract DSC builder as a scope helper
jpnurmi Apr 22, 2026
e77d560
feat(tracing): apply strict trace continuation decision
jpnurmi Apr 22, 2026
0f6acb0
feat(tracing): Emit outgoing baggage from scope DSC
jpnurmi Apr 22, 2026
3c0dd04
test(tracing): cover strict trace continuation
jpnurmi Apr 22, 2026
9b66c4d
fix(tracing): freeze DSC from incoming trace
jpnurmi Apr 22, 2026
e46a43d
fix(tracing): drop upstream sampling decision on fork
jpnurmi Apr 22, 2026
b87d5a0
fix(tracing): use locale-independent isalnum in baggage encoder
jpnurmi Apr 22, 2026
f097da5
test(tracing): Add strict continuation integration coverage
jpnurmi May 4, 2026
7ef9315
Update CHANGELOG.md
jpnurmi Apr 22, 2026
8cff161
ref(tracing): Remove unused org ID getter
jpnurmi May 4, 2026
a00313f
ref(tracing): Rename scope DSC helpers
jpnurmi May 4, 2026
a4f8744
ref(tracing): Rename trace continuation predicate
jpnurmi May 4, 2026
2794991
ref(tracing): Move continuation org lookup into predicate
jpnurmi May 4, 2026
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
**Features**:

- Auto-populate `event.user.id` with a persistent per-installation UUID when no explicit user ID is set. ([#1661](https://github.com/getsentry/sentry-native/pull/1661))
- Add [strict trace continuation](https://develop.sentry.dev/sdk/foundations/trace-propagation/#strict-trace-continuation) via `sentry_options_set_strict_trace_continuation`. ([#1663](https://github.com/getsentry/sentry-native/pull/1663))

## 0.14.0

Expand Down
22 changes: 22 additions & 0 deletions examples/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,14 @@ main(int argc, char **argv)
sentry_options_set_traces_sample_rate(options, 1.0);
}

if (has_arg(argc, argv, "org-id")) {
sentry_options_set_org_id(options, "123456");
}

if (has_arg(argc, argv, "strict-trace-continuation")) {
sentry_options_set_strict_trace_continuation(options, 1);
}

if (has_arg(argc, argv, "child-spans")) {
sentry_options_set_max_spans(options, 5);
}
Expand Down Expand Up @@ -1162,6 +1170,20 @@ main(int argc, char **argv)
sentry_transaction_context_update_from_header(
tx_ctx, "sentry-trace", trace_header);
}
if (has_arg(argc, argv, "incoming-trace")) {
sentry_transaction_context_update_from_header(tx_ctx,
"sentry-trace",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-bbbbbbbbbbbbbbbb-1");
}
if (has_arg(argc, argv, "incoming-baggage")) {
sentry_transaction_context_update_from_header(tx_ctx, "baggage",
"sentry-org_id=123456,sentry-environment=upstream,"
"sentry-release=upstream-app%401.0");
}
if (has_arg(argc, argv, "incoming-baggage-mismatch")) {
sentry_transaction_context_update_from_header(tx_ctx, "baggage",
"sentry-org_id=99999,sentry-environment=upstream");
}

sentry_transaction_t *tx
= sentry_transaction_start(tx_ctx, custom_sampling_ctx);
Expand Down
39 changes: 39 additions & 0 deletions include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -2328,6 +2328,41 @@ SENTRY_EXPERIMENTAL_API void sentry_options_set_propagate_traceparent(
SENTRY_EXPERIMENTAL_API int sentry_options_get_propagate_traceparent(
const sentry_options_t *opts);

/**
* Overrides the organization ID derived from the DSN host
* (e.g. `o123456.ingest.sentry.io` → `123456`). Typically only required for
* self-hosted setups where the DSN host does not encode the organization ID.
*
* The value is passed through as a string; no validation is performed.
*/
SENTRY_EXPERIMENTAL_API void sentry_options_set_org_id(
sentry_options_t *opts, const char *org_id);
SENTRY_EXPERIMENTAL_API void sentry_options_set_org_id_n(
sentry_options_t *opts, const char *org_id, size_t org_id_len);

/**
* Enables or disables strict trace continuation.
*
* Controls whether to continue an incoming trace when either the trace or the
* SDK has an organization ID (derived from the DSN), but not both. When set
* to true, a new trace is started in that case; when false, the incoming
* trace is continued. If both organization IDs are present and differ, the
* trace is never continued regardless of this setting.
*
* See
* https://develop.sentry.dev/sdk/foundations/trace-propagation/#strict-trace-continuation
*
* This is disabled by default.
*/
SENTRY_EXPERIMENTAL_API void sentry_options_set_strict_trace_continuation(
sentry_options_t *opts, int strict_trace_continuation);

/**
* Returns whether strict trace continuation is enabled.
*/
SENTRY_EXPERIMENTAL_API int sentry_options_get_strict_trace_continuation(
const sentry_options_t *opts);

/**
* Enables or disables the structured logging feature.
* When disabled, all calls to `sentry_log_X()` are no-ops.
Expand Down Expand Up @@ -2904,6 +2939,10 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_remove_sampled(
* 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.
*
* Recognized header keys are `sentry-trace` and `baggage` (case-insensitive);
* other keys are ignored. Feed both when available so that strict trace
* continuation can consult the incoming `sentry-org_id`.
*/
SENTRY_EXPERIMENTAL_API void sentry_transaction_context_update_from_header(
sentry_transaction_context_t *tx_ctx, const char *key, const char *value);
Expand Down
87 changes: 49 additions & 38 deletions src/sentry_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,39 +98,6 @@ generate_propagation_context(sentry_value_t propagation_context)
sentry_value_get_by_key(propagation_context, "trace"));
}

static void
set_dynamic_sampling_context(
const sentry_options_t *options, sentry_scope_t *scope)
{
sentry_value_decref(scope->dynamic_sampling_context);
// add the Dynamic Sampling Context to the `trace` header
sentry_value_t dsc = sentry_value_new_object();

if (options->dsn) {
sentry_value_set_by_key(dsc, "public_key",
sentry_value_new_string(options->dsn->public_key));
sentry_value_set_by_key(
dsc, "org_id", sentry_value_new_string(options->dsn->org_id));
}
sentry_value_set_by_key(dsc, "sample_rate",
sentry_value_new_double(options->traces_sample_rate));
if (options->traces_sampler) {
sentry_value_set_by_key(
dsc, "sample_rate", sentry_value_new_double(1.0));
}
sentry_value_t sample_rand = sentry_value_get_by_key(
sentry_value_get_by_key(scope->propagation_context, "trace"),
"sample_rand");
sentry_value_set_by_key(dsc, "sample_rand", sample_rand);
sentry_value_incref(sample_rand);
sentry_value_set_by_key(
dsc, "release", sentry_value_new_string(scope->release));
sentry_value_set_by_key(
dsc, "environment", sentry_value_new_string(scope->environment));

scope->dynamic_sampling_context = dsc;
}

#if defined(SENTRY_PLATFORM_NX) || defined(SENTRY_PLATFORM_PS)
int
sentry__native_init(sentry_options_t *options)
Expand Down Expand Up @@ -250,7 +217,7 @@ sentry_init(sentry_options_t *options)
sentry__ringbuffer_set_max_size(
scope->breadcrumbs, options->max_breadcrumbs);

set_dynamic_sampling_context(options, scope);
sentry__scope_update_dsc(scope, options);
}
if (backend && backend->user_consent_changed_func) {
backend->user_consent_changed_func(backend);
Expand Down Expand Up @@ -1206,15 +1173,24 @@ sentry_set_trace_n(const char *trace_id, size_t trace_id_len,
sentry__generate_sample_rand(context);

sentry__set_propagation_context("trace", context);

SENTRY_WITH_OPTIONS (options) {
SENTRY_WITH_SCOPE_MUT (scope) {
sentry__scope_update_dsc(scope, options);
}
}
}
}

void
sentry_regenerate_trace(void)
{
SENTRY_WITH_SCOPE_MUT (scope) {
generate_propagation_context(scope->propagation_context);
scope->trace_managed = false;
SENTRY_WITH_OPTIONS (options) {
SENTRY_WITH_SCOPE_MUT (scope) {
generate_propagation_context(scope->propagation_context);
scope->trace_managed = false;
sentry__scope_update_dsc(scope, options);
}
Comment thread
jpnurmi marked this conversation as resolved.
}
}

Expand Down Expand Up @@ -1286,6 +1262,41 @@ sentry_transaction_start_ts(sentry_transaction_context_t *opaque_tx_ctx,
sentry_value_remove_by_key(tx, "timestamp");

sentry__value_merge_objects(tx, tx_ctx);

sentry_value_t incoming = sentry_value_get_by_key(tx, "incoming_dsc");
if (!sentry_value_is_null(incoming)) {
SENTRY_WITH_OPTIONS (options) {
SENTRY_WITH_SCOPE_MUT (scope) {
if (sentry__trace_can_continue(incoming, options)) {
// Freeze only when the upstream actually sent DSC values;
// a sentry-trace-only signal leaves incoming empty, in
// which case the SDK builds its own DSC.
if (sentry_value_get_length(incoming) > 0) {
sentry__scope_freeze_dsc(scope, incoming);
} else {
sentry__scope_update_dsc(scope, options);
}
} else {
// Fork: ignore upstream trace, become head of a new trace.
// Regenerate the scope's propagation context so events
// captured outside this transaction also carry the new
// trace_id, and align the tx's trace_id with it.
generate_propagation_context(scope->propagation_context);
sentry_value_t scope_trace_id = sentry_value_get_by_key(
sentry_value_get_by_key(
scope->propagation_context, "trace"),
"trace_id");
sentry_value_incref(scope_trace_id);
sentry_value_set_by_key(tx, "trace_id", scope_trace_id);
sentry_value_remove_by_key(tx, "parent_span_id");
sentry_value_remove_by_key(tx, "sampled");
sentry__scope_update_dsc(scope, options);
}
}
}
}
sentry_value_remove_by_key(tx, "incoming_dsc");

double sample_rand = 1.0;
SENTRY_WITH_SCOPE (scope) {
sample_rand = sentry_value_as_double(sentry_value_get_by_key(
Expand All @@ -1295,7 +1306,7 @@ sentry_transaction_start_ts(sentry_transaction_context_t *opaque_tx_ctx,
sentry_sampling_context_t sampling_ctx
= { opaque_tx_ctx, custom_sampling_ctx, NULL, sample_rand };

bool should_sample = sentry__should_send_transaction(tx_ctx, &sampling_ctx);
bool should_sample = sentry__should_send_transaction(tx, &sampling_ctx);
sentry_value_set_by_key(
tx, "sampled", sentry_value_new_bool(should_sample));
sentry_value_decref(custom_sampling_ctx);
Expand Down
42 changes: 42 additions & 0 deletions src/sentry_options.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ sentry_options_new(void)
opts->enable_logging_when_crashed = true;
#endif
opts->propagate_traceparent = false;
opts->strict_trace_continuation = false;
Comment thread
jpnurmi marked this conversation as resolved.
opts->crashpad_limit_stack_capture_to_sp = false;
opts->enable_metrics = true;
opts->enable_logs = true;
Expand Down Expand Up @@ -124,6 +125,7 @@ sentry_options_free(sentry_options_t *opts)
sentry_free(opts->dist);
sentry_free(opts->proxy);
sentry_free(opts->ca_certs);
sentry_free(opts->org_id);
sentry_free(opts->transport_thread_name);
sentry__path_free(opts->database_path);
sentry__path_free(opts->handler_path);
Expand Down Expand Up @@ -220,6 +222,33 @@ sentry_options_get_dsn(const sentry_options_t *opts)
return opts->dsn ? opts->dsn->raw : NULL;
}

void
sentry_options_set_org_id_n(
sentry_options_t *opts, const char *org_id, size_t org_id_len)
{
sentry_free(opts->org_id);
opts->org_id = sentry__string_clone_n(org_id, org_id_len);
}

void
sentry_options_set_org_id(sentry_options_t *opts, const char *org_id)
{
sentry_free(opts->org_id);
opts->org_id = sentry__string_clone(org_id);
}

const char *
sentry__options_get_org_id(const sentry_options_t *opts)
{
if (opts->org_id && *opts->org_id) {
return opts->org_id;
}
if (opts->dsn && opts->dsn->org_id && *opts->dsn->org_id) {
return opts->dsn->org_id;
}
return NULL;
}

void
sentry_options_set_sample_rate(sentry_options_t *opts, double sample_rate)
{
Expand Down Expand Up @@ -937,6 +966,19 @@ sentry_options_get_propagate_traceparent(const sentry_options_t *opts)
return opts->propagate_traceparent;
}

void
sentry_options_set_strict_trace_continuation(
sentry_options_t *opts, int strict_trace_continuation)
{
opts->strict_trace_continuation = !!strict_trace_continuation;
}

int
sentry_options_get_strict_trace_continuation(const sentry_options_t *opts)
{
return opts->strict_trace_continuation;
}

void
sentry_options_set_send_client_reports(sentry_options_t *opts, int val)
{
Expand Down
9 changes: 9 additions & 0 deletions src/sentry_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ struct sentry_options_s {
bool crashpad_wait_for_upload;
bool enable_logging_when_crashed;
bool propagate_traceparent;
bool strict_trace_continuation;
bool crashpad_limit_stack_capture_to_sp;
bool cache_keep;

Expand All @@ -72,6 +73,7 @@ struct sentry_options_s {
double traces_sample_rate;
sentry_traces_sampler_function traces_sampler;
void *traces_sampler_data;
char *org_id;
size_t max_spans;
bool enable_logs;
// takes the first varg as a `sentry_value_t` object containing attributes
Expand Down Expand Up @@ -108,4 +110,11 @@ struct sentry_options_s {
*/
sentry_options_t *sentry__options_incref(sentry_options_t *options);

/**
* Returns the organization ID used for trace propagation: the `org_id` option
* if set and non-empty, otherwise the DSN-derived value if non-empty,
* otherwise NULL.
*/
const char *sentry__options_get_org_id(const sentry_options_t *options);

#endif
43 changes: 43 additions & 0 deletions src/sentry_scope.c
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,49 @@ sentry__scope_free(sentry_scope_t *scope)
sentry_free(scope);
}

void
sentry__scope_freeze_dsc(sentry_scope_t *scope, sentry_value_t incoming)
{
sentry_value_decref(scope->dynamic_sampling_context);
sentry_value_t dsc = sentry_value_new_object();
sentry__value_merge_objects(dsc, incoming);
Comment thread
sentry[bot] marked this conversation as resolved.
sentry_value_freeze(dsc);
scope->dynamic_sampling_context = dsc;
}

void
sentry__scope_update_dsc(sentry_scope_t *scope, const sentry_options_t *options)
{
sentry_value_decref(scope->dynamic_sampling_context);
sentry_value_t dsc = sentry_value_new_object();

if (options->dsn) {
sentry_value_set_by_key(dsc, "public_key",
sentry_value_new_string(options->dsn->public_key));
}
const char *org_id = sentry__options_get_org_id(options);
if (org_id) {
sentry_value_set_by_key(dsc, "org_id", sentry_value_new_string(org_id));
}
sentry_value_set_by_key(dsc, "sample_rate",
sentry_value_new_double(options->traces_sample_rate));
if (options->traces_sampler) {
sentry_value_set_by_key(
dsc, "sample_rate", sentry_value_new_double(1.0));
}
sentry_value_t sample_rand = sentry_value_get_by_key(
sentry_value_get_by_key(scope->propagation_context, "trace"),
"sample_rand");
sentry_value_set_by_key(dsc, "sample_rand", sample_rand);
sentry_value_incref(sample_rand);
sentry_value_set_by_key(
dsc, "release", sentry_value_new_string(scope->release));
sentry_value_set_by_key(
dsc, "environment", sentry_value_new_string(scope->environment));

scope->dynamic_sampling_context = dsc;
}

#if !defined(SENTRY_PLATFORM_NX)
static void
sentry__foreach_stacktrace(
Expand Down
15 changes: 15 additions & 0 deletions src/sentry_scope.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,21 @@ void sentry__scope_remove_attribute_n(
for (sentry_scope_t *Scope = sentry__scope_lock(); Scope; \
sentry__scope_unlock(), Scope = NULL)

/**
* Rebuilds the scope's dynamic sampling context (DSC) from the SDK options
* and the current propagation context. The previous DSC is discarded.
*/
void sentry__scope_update_dsc(
sentry_scope_t *scope, const sentry_options_t *options);

/**
* Replaces the scope's dynamic sampling context (DSC) with a verbatim copy
* of the incoming object. Used when continuing an upstream trace: per the
* trace-propagation spec, the receiving SDK MUST treat the incoming DSC as
* frozen and propagate its values "as is".
*/
void sentry__scope_freeze_dsc(sentry_scope_t *scope, sentry_value_t incoming);

/**
* Adds scoped attributes to the telemetry attributes object.
*/
Expand Down
Loading
Loading