Skip to content

Commit 0841b9a

Browse files
authored
[trace] Allow ShouldSample implementation to modify trace state. (#237)
Add trace_state into SamplingResult. See open-telemetry/opentelemetry-specification#988 for details.
1 parent 0979c06 commit 0841b9a

File tree

2 files changed

+88
-9
lines changed

2 files changed

+88
-9
lines changed

src/sdk/trace/sampler.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
//! MUST NOT allow this combination.
3939
4040
use crate::api;
41+
use crate::api::TraceState;
4142

4243
/// The `ShouldSample` interface allows implementations to provide samplers
4344
/// which will return a sampling `SamplingResult` based on information that
@@ -63,6 +64,8 @@ pub struct SamplingResult {
6364
pub decision: SamplingDecision,
6465
/// Extra attributes added by this result
6566
pub attributes: Vec<api::KeyValue>,
67+
/// Trace state from parent context, might be modified by sampler
68+
pub trace_state: TraceState,
6669
}
6770

6871
/// Decision about whether or not to sample
@@ -142,6 +145,11 @@ impl ShouldSample for Sampler {
142145
decision,
143146
// No extra attributes ever set by the SDK samplers.
144147
attributes: Vec::new(),
148+
// all sampler in SDK will not modify trace state.
149+
trace_state: match parent_context {
150+
Some(ctx) => ctx.trace_state().clone(),
151+
None => TraceState::default(),
152+
},
145153
}
146154
}
147155
}

src/sdk/trace/tracer.rs

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ impl Tracer {
6565
span_kind: &api::SpanKind,
6666
attributes: &[api::KeyValue],
6767
links: &[api::Link],
68-
) -> Option<(u8, Vec<api::KeyValue>)> {
68+
) -> Option<(u8, Vec<api::KeyValue>, TraceState)> {
6969
let provider = self.provider()?;
7070
let sampler = &provider.config().default_sampler;
7171
let sampling_result =
@@ -78,7 +78,7 @@ impl Tracer {
7878
&self,
7979
sampling_result: sdk::SamplingResult,
8080
parent_context: Option<&api::SpanContext>,
81-
) -> Option<(u8, Vec<api::KeyValue>)> {
81+
) -> Option<(u8, Vec<api::KeyValue>, TraceState)> {
8282
match sampling_result {
8383
sdk::SamplingResult {
8484
decision: sdk::SamplingDecision::NotRecord,
@@ -87,16 +87,26 @@ impl Tracer {
8787
sdk::SamplingResult {
8888
decision: sdk::SamplingDecision::Record,
8989
attributes,
90+
trace_state,
9091
} => {
9192
let trace_flags = parent_context.map(|ctx| ctx.trace_flags()).unwrap_or(0);
92-
Some((trace_flags & !api::TRACE_FLAG_SAMPLED, attributes))
93+
Some((
94+
trace_flags & !api::TRACE_FLAG_SAMPLED,
95+
attributes,
96+
trace_state,
97+
))
9398
}
9499
sdk::SamplingResult {
95100
decision: sdk::SamplingDecision::RecordAndSampled,
96101
attributes,
102+
trace_state,
97103
} => {
98104
let trace_flags = parent_context.map(|ctx| ctx.trace_flags()).unwrap_or(0);
99-
Some((trace_flags | api::TRACE_FLAG_SAMPLED, attributes))
105+
Some((
106+
trace_flags | api::TRACE_FLAG_SAMPLED,
107+
attributes,
108+
trace_state,
109+
))
100110
}
101111
}
102112
}
@@ -163,7 +173,7 @@ impl api::Tracer for Tracer {
163173
.or_else(|| cx.remote_span_context().cloned())
164174
.filter(|cx| cx.is_valid());
165175
// Build context for sampling decision
166-
let (no_parent, trace_id, parent_span_id, remote_parent, parent_trace_flags, trace_state) =
176+
let (no_parent, trace_id, parent_span_id, remote_parent, parent_trace_flags) =
167177
parent_span_context
168178
.as_ref()
169179
.map(|ctx| {
@@ -173,7 +183,6 @@ impl api::Tracer for Tracer {
173183
ctx.span_id(),
174184
ctx.is_remote(),
175185
ctx.trace_flags(),
176-
ctx.trace_state().clone(),
177186
)
178187
})
179188
.unwrap_or((
@@ -184,7 +193,6 @@ impl api::Tracer for Tracer {
184193
api::SpanId::invalid(),
185194
false,
186195
0,
187-
TraceState::default(),
188196
));
189197

190198
// There are 3 paths for sampling.
@@ -207,11 +215,17 @@ impl api::Tracer for Tracer {
207215
// has parent that is local: use parent if sampled, or don't record.
208216
parent_span_context
209217
.filter(|span_context| span_context.is_sampled())
210-
.map(|_| (parent_trace_flags, Vec::new()))
218+
.map(|span_context| {
219+
(
220+
parent_trace_flags,
221+
Vec::new(),
222+
span_context.trace_state().clone(),
223+
)
224+
})
211225
};
212226

213227
// Build optional inner context, `None` if not recording.
214-
let inner = sampling_decision.map(move |(trace_flags, mut extra_attrs)| {
228+
let inner = sampling_decision.map(move |(trace_flags, mut extra_attrs, trace_state)| {
215229
attribute_options.append(&mut extra_attrs);
216230
let mut attributes = sdk::EvictedHashMap::new(config.max_attributes_per_span);
217231
for attribute in attribute_options {
@@ -263,3 +277,60 @@ impl api::Tracer for Tracer {
263277
sdk::Span::new(span_id, inner, self.clone())
264278
}
265279
}
280+
281+
#[cfg(test)]
282+
mod tests {
283+
use crate::api::{
284+
Context, KeyValue, Link, Span, SpanBuilder, SpanContext, SpanId, SpanKind, TraceId,
285+
TraceState, Tracer, TracerProvider, TRACE_FLAG_SAMPLED,
286+
};
287+
use crate::sdk;
288+
use crate::sdk::{Config, SamplingDecision, SamplingResult, ShouldSample};
289+
290+
#[derive(Debug)]
291+
struct TestSampler {}
292+
293+
impl ShouldSample for TestSampler {
294+
fn should_sample(
295+
&self,
296+
parent_context: Option<&SpanContext>,
297+
_trace_id: TraceId,
298+
_name: &str,
299+
_span_kind: &SpanKind,
300+
_attributes: &[KeyValue],
301+
_links: &[Link],
302+
) -> SamplingResult {
303+
let trace_state = parent_context.unwrap().trace_state().clone();
304+
SamplingResult {
305+
decision: SamplingDecision::RecordAndSampled,
306+
attributes: Vec::new(),
307+
trace_state: trace_state.insert("foo".into(), "notbar".into()).unwrap(),
308+
}
309+
}
310+
}
311+
312+
#[test]
313+
fn allow_sampler_to_change_trace_state() {
314+
// Setup
315+
let sampler = TestSampler {};
316+
let config = Config::default().with_default_sampler(sampler);
317+
let tracer_provider = sdk::TracerProvider::builder().with_config(config).build();
318+
let tracer = tracer_provider.get_tracer("test", None);
319+
let context = Context::default();
320+
let trace_state = TraceState::from_key_value(vec![("foo", "bar")]).unwrap();
321+
let mut span_builder = SpanBuilder::default();
322+
span_builder.parent_context = Some(SpanContext::new(
323+
TraceId::from_u128(128),
324+
SpanId::from_u64(64),
325+
TRACE_FLAG_SAMPLED,
326+
true,
327+
trace_state,
328+
));
329+
330+
// Test sampler should change trace state
331+
let span = tracer.build_with_context(span_builder, &context);
332+
let span_context = span.span_context();
333+
let expected = span_context.trace_state();
334+
assert_eq!(expected.get("foo"), Some("notbar"))
335+
}
336+
}

0 commit comments

Comments
 (0)