Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
87d411b
Add the random flag
ayush-that Sep 9, 2026
92e1014
Pass the flag along
ayush-that Sep 9, 2026
e9cc1dc
Wire up random IDs
ayush-that Sep 9, 2026
531790a
Update API changelog
ayush-that Sep 9, 2026
b613429
Update SDK changelog
ayush-that Sep 9, 2026
578341d
Update Zipkin changelog
ayush-that Sep 9, 2026
a5d4da4
Keep B3 drops explicit
ayush-that Sep 10, 2026
b3a79ae
Update SDK changelog
ayush-that Sep 10, 2026
1e06a00
Keep B3 bits out of OTLP
ayush-that Sep 10, 2026
cae2a10
Update proto changelog
ayush-that Sep 10, 2026
6f5155a
Start fresh for invalid parents
ayush-that Sep 10, 2026
26d9998
Keep unreleased changelog notes under vNext
ayush-that Sep 18, 2026
d8e110e
Retrigger CI
ayush-that Sep 19, 2026
f2d9e96
Make generators declare randomness
ayush-that Sep 23, 2026
754ac6d
Update SDK changelog
ayush-that Sep 23, 2026
f17a1b9
Merge branch 'main' into shydev/w3c-random-flag
ayush-that Sep 23, 2026
87fb712
Merge main into the random flag branch
ayush-that Sep 25, 2026
d8958f4
Show custom generators the new method
ayush-that Sep 26, 2026
fa2c521
Clear private flags after sampling
ayush-that Sep 26, 2026
e53bae1
Note the sampling flag cleanup
ayush-that Sep 26, 2026
6c8f1cb
Note the trace flag helper
ayush-that Sep 26, 2026
7b584e0
Merge main into the random flag branch
ayush-that Sep 28, 2026
0c5a347
chore: merge main to resolve PR #3718 conflicts
ayush-that Oct 3, 2026
a11ae33
Merge main into the random flag branch
ayush-that Oct 5, 2026
2984822
Merge main into the random flag branch
ayush-that Oct 8, 2026
781d7af
Merge branch 'main' into shydev/w3c-random-flag
ayush-that Oct 13, 2026
b3247c3
Merge main into the random flag branch
ayush-that Oct 16, 2026
59b8284
Merge main into the random flag branch
ayush-that Oct 19, 2026
643ba42
Merge main into the random flag branch
ayush-that Oct 23, 2026
a111cee
Merge branch 'main' into shydev/w3c-random-flag
scottgerring Oct 23, 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
4 changes: 2 additions & 2 deletions opentelemetry-appender-tracing/src/layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -897,7 +897,7 @@ mod tests {
);
assert_eq!(
log.record.trace_context().unwrap().trace_flags.unwrap(),
TraceFlags::SAMPLED
TraceFlags::SAMPLED | TraceFlags::RANDOM
);

// validate attributes.
Expand Down Expand Up @@ -1125,7 +1125,7 @@ mod tests {
);
assert_eq!(
log.record.trace_context().unwrap().trace_flags.unwrap(),
TraceFlags::SAMPLED
TraceFlags::SAMPLED | TraceFlags::RANDOM
);

for attribute in log.record.attributes_iter() {
Expand Down
4 changes: 4 additions & 0 deletions opentelemetry-propagator-b3/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## vNext

- The B3 propagator's internal "deferred" marker no longer overlaps the W3C
`random-trace-id` trace flag; span contexts carrying that flag now inject their
sampling state correctly
([#3270](https://github.com/open-telemetry/opentelemetry-rust/issues/3270)).
## 0.33.1

Released 2026-Oct-08
Expand Down
1 change: 1 addition & 0 deletions opentelemetry-propagator-b3/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ opentelemetry = { workspace = true, features = ["trace"] }

[dev-dependencies]
opentelemetry = { workspace = true, features = ["testing"] }
opentelemetry_sdk = { workspace = true, features = ["trace"] }

[lints]
workspace = true
Expand Down
110 changes: 109 additions & 1 deletion opentelemetry-propagator-b3/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const B3_SPAN_ID_HEADER: &str = "x-b3-spanid";
const B3_SAMPLED_HEADER: &str = "x-b3-sampled";
const B3_PARENT_SPAN_ID_HEADER: &str = "x-b3-parentspanid";

const TRACE_FLAG_DEFERRED: TraceFlags = TraceFlags::new(0x02);
const TRACE_FLAG_DEFERRED: TraceFlags = TraceFlags::new(0x80);
const TRACE_FLAG_DEBUG: TraceFlags = TraceFlags::new(0x04);

static B3_SINGLE_FIELDS: Lazy<[String; 1]> = Lazy::new(|| [B3_SINGLE_HEADER.to_owned()]);
Expand Down Expand Up @@ -312,6 +312,7 @@ impl TextMapPropagator for Propagator {
mod tests {
use super::*;
use opentelemetry::testing::trace::TestSpan;
use opentelemetry_sdk::propagation::TraceContextPropagator;
use std::collections::HashMap;

const TRACE_ID_STR: &str = "4bf92f3577b34da6a3ce929d0e0e4736";
Expand Down Expand Up @@ -403,9 +404,11 @@ mod tests {
fn single_header_inject_data() -> Vec<(&'static str, SpanContext)> {
vec![
("4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-1", SpanContext::new(TraceId::from(TRACE_ID_HEX), SpanId::from(SPAN_ID_HEX), TraceFlags::SAMPLED, true, TraceState::default())),
("4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-1", SpanContext::new(TraceId::from(TRACE_ID_HEX), SpanId::from(SPAN_ID_HEX), TraceFlags::SAMPLED | TraceFlags::RANDOM, true, TraceState::default())),
("4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-d", SpanContext::new(TraceId::from(TRACE_ID_HEX), SpanId::from(SPAN_ID_HEX), TRACE_FLAG_DEBUG, true, TraceState::default())),
("4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7", SpanContext::new(TraceId::from(TRACE_ID_HEX), SpanId::from(SPAN_ID_HEX), TRACE_FLAG_DEFERRED, true, TraceState::default())),
("4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-0", SpanContext::new(TraceId::from(TRACE_ID_HEX), SpanId::from(SPAN_ID_HEX), TraceFlags::default(), true, TraceState::default())),
("4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-0", SpanContext::new(TraceId::from(TRACE_ID_HEX), SpanId::from(SPAN_ID_HEX), TraceFlags::RANDOM, true, TraceState::default())),
("1", SpanContext::new(TraceId::INVALID, SpanId::INVALID, TraceFlags::SAMPLED, true, TraceState::default())),
("0", SpanContext::new(TraceId::INVALID, SpanId::INVALID, TraceFlags::default(), true, TraceState::default())),
]
Expand All @@ -417,9 +420,11 @@ mod tests {
// TraceId, SpanId, isSampled, isDebug
vec![
(Some(TRACE_ID_STR), Some(SPAN_ID_STR), Some("1"), None, SpanContext::new(TraceId::from(TRACE_ID_HEX), SpanId::from(SPAN_ID_HEX), TraceFlags::SAMPLED, true, TraceState::default())),
(Some(TRACE_ID_STR), Some(SPAN_ID_STR), Some("1"), None, SpanContext::new(TraceId::from(TRACE_ID_HEX), SpanId::from(SPAN_ID_HEX), TraceFlags::SAMPLED | TraceFlags::RANDOM, true, TraceState::default())),
(Some(TRACE_ID_STR), Some(SPAN_ID_STR), None, Some("1"), SpanContext::new(TraceId::from(TRACE_ID_HEX), SpanId::from(SPAN_ID_HEX), TRACE_FLAG_DEBUG, true, TraceState::default())),
(Some(TRACE_ID_STR), Some(SPAN_ID_STR), None, None, SpanContext::new(TraceId::from(TRACE_ID_HEX), SpanId::from(SPAN_ID_HEX), TRACE_FLAG_DEFERRED, true, TraceState::default())),
(Some(TRACE_ID_STR), Some(SPAN_ID_STR), Some("0"), None, SpanContext::new(TraceId::from(TRACE_ID_HEX), SpanId::from(SPAN_ID_HEX), TraceFlags::default(), true, TraceState::default())),
(Some(TRACE_ID_STR), Some(SPAN_ID_STR), Some("0"), None, SpanContext::new(TraceId::from(TRACE_ID_HEX), SpanId::from(SPAN_ID_HEX), TraceFlags::RANDOM, true, TraceState::default())),
(None, None, Some("0"), None, SpanContext::empty_context()),
(None, None, Some("1"), None, SpanContext::new(TraceId::INVALID, SpanId::INVALID, TraceFlags::SAMPLED, true, TraceState::default()))
]
Expand Down Expand Up @@ -685,4 +690,107 @@ mod tests {
]
);
}
#[test]
fn w3c_random_flag_injects_sampled_state_via_b3() {
let mut headers = HashMap::new();
headers.insert(
"traceparent".to_string(),
"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-03".to_string(),
);
let cx = TraceContextPropagator::new().extract(&headers);

let mut single = HashMap::new();
Propagator::with_encoding(B3Encoding::SingleHeader).inject_context(&cx, &mut single);
assert_eq!(
single.get(B3_SINGLE_HEADER),
Some(&"4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-1".to_owned())
);

let mut multi = HashMap::new();
Propagator::with_encoding(B3Encoding::MultipleHeader).inject_context(&cx, &mut multi);
assert_eq!(multi.get(B3_SAMPLED_HEADER), Some(&"1".to_owned()));
assert_eq!(multi.get(B3_DEBUG_FLAG_HEADER), None);
}

#[test]
fn local_sampling_decision_clears_b3_deferred_marker() {
use opentelemetry::trace::{Span, Tracer, TracerProvider};
use opentelemetry_sdk::trace::{Sampler, SdkTracerProvider};

let mut incoming = HashMap::new();
incoming.insert(
B3_SINGLE_HEADER.to_string(),
"4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7".to_string(),
);
let parent_cx = Propagator::with_encoding(B3Encoding::SingleHeader).extract(&incoming);
assert_eq!(
parent_cx.span().span_context().trace_flags(),
TRACE_FLAG_DEFERRED
);

for (sampler, sampled) in [(Sampler::AlwaysOff, false), (Sampler::AlwaysOn, true)] {
let provider = SdkTracerProvider::builder().with_sampler(sampler).build();
let child = provider
.tracer("test")
.start_with_context("child", &parent_cx);
assert_eq!(child.is_recording(), sampled);
let span_id = child.span_context().span_id().to_string();
let child_cx = Context::current_with_span(child);
let sampled_flag = if sampled { "1" } else { "0" };

let mut single = HashMap::new();
Propagator::with_encoding(B3Encoding::SingleHeader)
.inject_context(&child_cx, &mut single);
assert_eq!(
single.get(B3_SINGLE_HEADER),
Some(&format!("{TRACE_ID_STR}-{span_id}-{sampled_flag}"))
);

let mut multi = HashMap::new();
Propagator::with_encoding(B3Encoding::MultipleHeader)
.inject_context(&child_cx, &mut multi);
assert_eq!(
multi.get(B3_SAMPLED_HEADER).map(String::as_str),
Some(sampled_flag)
);
assert_eq!(multi.get(B3_DEBUG_FLAG_HEADER), None);
}
}

#[test]
fn b3_private_flags_stay_off_traceparent() {
let w3c = TraceContextPropagator::new();

// Deferred sampling has no sampling state on the wire.
let mut deferred = HashMap::new();
deferred.insert(
B3_SINGLE_HEADER.to_string(),
"4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7".to_string(),
);
let cx = Propagator::with_encoding(B3Encoding::SingleHeader).extract(&deferred);
assert_eq!(cx.span().span_context().trace_flags(), TRACE_FLAG_DEFERRED);
let mut out = HashMap::new();
w3c.inject_context(&cx, &mut out);
assert_eq!(
out.get("traceparent"),
Some(&"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00".to_owned())
);

// Debug sampling is indicated by x-b3-flags set to 1.
let mut debug = HashMap::new();
debug.insert(B3_TRACE_ID_HEADER.to_string(), TRACE_ID_STR.to_string());
debug.insert(B3_SPAN_ID_HEADER.to_string(), SPAN_ID_STR.to_string());
debug.insert(B3_DEBUG_FLAG_HEADER.to_string(), "1".to_string());
let cx = Propagator::with_encoding(B3Encoding::MultipleHeader).extract(&debug);
assert_eq!(
cx.span().span_context().trace_flags(),
TRACE_FLAG_DEBUG | TraceFlags::SAMPLED
);
let mut out = HashMap::new();
w3c.inject_context(&cx, &mut out);
assert_eq!(
out.get("traceparent"),
Some(&"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01".to_owned())
);
}
}
4 changes: 4 additions & 0 deletions opentelemetry-proto/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## vNext

- **Bug fix**: OTLP span, link, and log record `flags` now carry only the W3C-defined
trace flags (`sampled`, `random-trace-id`); propagator-private bits such as the B3
propagator's deferred marker are no longer exported
([#3270](https://github.com/open-telemetry/opentelemetry-rust/issues/3270)).
## 0.33.1

Released 2026-Oct-08
Expand Down
41 changes: 41 additions & 0 deletions opentelemetry-proto/src/transform/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,47 @@ pub(crate) fn to_nanos(time: SystemTime) -> u64 {
.as_nanos() as u64
}

/// Keeps W3C trace flags for OTLP, excluding propagator-private bits.
#[cfg(all(
feature = "gen-tonic-messages",
any(feature = "trace", feature = "logs")
))]
pub(crate) fn w3c_trace_flags(flags: opentelemetry::TraceFlags) -> u32 {
use opentelemetry::TraceFlags;
(flags & (TraceFlags::SAMPLED | TraceFlags::RANDOM)).to_u8() as u32
Comment thread
ayush-that marked this conversation as resolved.
}

#[cfg(all(
test,
feature = "gen-tonic-messages",
any(feature = "trace", feature = "logs")
))]
mod w3c_trace_flags_tests {
use super::w3c_trace_flags;
use opentelemetry::TraceFlags;

#[test]
fn keeps_only_w3c_trace_flags() {
// B3 uses 0x80 for deferred sampling and 0x04 for debug.
for (flags, expected) in [
(0x00, 0x00),
(0x01, 0x01),
(0x02, 0x02),
(0x03, 0x03),
(0x80, 0x00),
(0x84, 0x00),
(0x83, 0x03),
(0xff, 0x03),
] {
assert_eq!(
w3c_trace_flags(TraceFlags::new(flags)),
expected,
"flags {flags:#04x}"
);
}
}
}

#[cfg(feature = "gen-tonic-messages")]
pub mod tonic {
use crate::proto::tonic::common::v1::{
Expand Down
31 changes: 25 additions & 6 deletions opentelemetry-proto/src/transform/logs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub mod tonic {
logs::v1::{LogRecord, ResourceLogs, ScopeLogs, SeverityNumber},
resource::v1::Resource,
},
transform::common::{to_nanos, tonic::ResourceAttributesWithSchema},
transform::common::{to_nanos, tonic::ResourceAttributesWithSchema, w3c_trace_flags},
};
use opentelemetry::logs::{AnyValue as LogsAnyValue, Severity};
use opentelemetry_sdk::logs::LogBatch;
Expand Down Expand Up @@ -112,11 +112,8 @@ pub mod tonic {
body: log_record.body().cloned().map(Into::into),
dropped_attributes_count: 0,
flags: trace_context
.map(|ctx| {
ctx.trace_flags
.map(|flags| flags.to_u8() as u32)
.unwrap_or_default()
})
.and_then(|ctx| ctx.trace_flags)
.map(w3c_trace_flags)
.unwrap_or_default(),
span_id: trace_context
.map(|ctx| ctx.span_id.to_bytes().to_vec())
Expand Down Expand Up @@ -321,6 +318,28 @@ mod tests {
assert_eq!(scope_logs_2.log_records.len(), 1);
}

#[test]
fn log_record_flags_only_carry_w3c_trace_flags() {
use crate::tonic::logs::v1::LogRecord;
use opentelemetry::trace::{SpanId, TraceFlags, TraceId};

// 0x80 is B3's private "deferred" marker; OTLP must only see W3C bits.
for (flags, expected) in [
(TraceFlags::new(0x80), 0x00),
(TraceFlags::new(0x80) | TraceFlags::RANDOM, 0x02),
(TraceFlags::new(0x80) | TraceFlags::SAMPLED, 0x01),
(TraceFlags::SAMPLED | TraceFlags::RANDOM, 0x03),
] {
let (mut record, _scope) = create_test_log_data("test-lib", "Log");
record.set_trace_context(TraceId::from(1), SpanId::from(2), Some(flags));

let otlp: LogRecord = (&record).into();
assert_eq!(otlp.flags, expected, "flags {:#04x}", flags.to_u8());
assert_eq!(otlp.trace_id, TraceId::from(1).to_bytes().to_vec());
assert_eq!(otlp.span_id, SpanId::from(2).to_bytes().to_vec());
}
}

#[test]
fn scope_grouping_same_target_preserves_distinct_versions() {
let (mut first, _) = create_test_log_data("bridge", "first");
Expand Down
Loading
Loading