Skip to content

Commit 601d297

Browse files
authored
Store parent context in span builder (#399)
The span builder currently holds a parent `SpanContext`, and `Context` information is passed separately when starting a span directly or via a builder. This poses a few problems, first you can assign a context which contains an active span _as well as_ a builder with a parent span context, which can lead to confusion, and secondly we have to construct a wrapper context when sampling even in cases when the passed in context already contains an active span. This patch resolves this by changing the span builder's parent context to instead store a `Context` directly. This allows the builder methods to have a sigle context to look at when searchig for parent span contexts, and allows the wrapper context to onlly be created for sampling when it is facilitating a remote parent.
1 parent 006f475 commit 601d297

File tree

9 files changed

+117
-103
lines changed

9 files changed

+117
-103
lines changed

examples/aws-xray/src/server.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ async fn handle(req: Request<Body>) -> Result<Response<Body>, Infallible> {
2020
.to_str()
2121
.unwrap();
2222

23-
let span = global::tracer("example/server").start_from_context("hello", &parent_context);
23+
let span = global::tracer("example/server").start_with_context("hello", parent_context);
2424
span.add_event(format!("Handling - {}", x_amzn_trace_id), Vec::new());
2525

2626
Ok(Response::new(

examples/grpc/src/server.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ impl Greeter for MyGreeter {
2525
request: Request<HelloRequest>, // Accept request of type HelloRequest
2626
) -> Result<Response<HelloReply>, Status> {
2727
let parent_cx = global::get_text_map_propagator(|prop| prop.extract(request.metadata()));
28-
let span = global::tracer("greeter").start_from_context("Processing reply", &parent_cx);
28+
let span = global::tracer("greeter").start_with_context("Processing reply", parent_cx);
2929
span.set_attribute(KeyValue::new("request", format!("{:?}", request)));
3030

3131
// Return an instance of type HelloReply

examples/http/src/server.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use std::{convert::Infallible, net::SocketAddr};
1313

1414
async fn handle(req: Request<Body>) -> Result<Response<Body>, Infallible> {
1515
let parent_cx = global::get_text_map_propagator(|propagator| propagator.extract(req.headers()));
16-
let span = global::tracer("example/server").start_from_context("hello", &parent_cx);
16+
let span = global::tracer("example/server").start_with_context("hello", parent_cx);
1717
span.add_event("handling this...".to_string(), Vec::new());
1818

1919
Ok(Response::new("Hello, World!".into()))

opentelemetry/src/api/trace/noop.rs

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,13 @@ impl trace::Tracer for NoopTracer {
129129
trace::NoopSpan::new()
130130
}
131131

132-
/// Starts a new `NoopSpan` in a given context.
132+
/// Starts a new `NoopSpan` with a given context.
133133
///
134134
/// If the context contains a valid span context, it is propagated.
135-
fn start_from_context(&self, name: &str, cx: &Context) -> Self::Span {
136-
let builder = self.span_builder(name);
137-
self.build_with_context(builder, cx)
135+
fn start_with_context(&self, name: &str, cx: Context) -> Self::Span {
136+
let mut builder = self.span_builder(name);
137+
builder.parent_context = Some(cx);
138+
self.build(builder)
138139
}
139140

140141
/// Starts a `SpanBuilder`.
@@ -145,18 +146,14 @@ impl trace::Tracer for NoopTracer {
145146
/// Builds a `NoopSpan` from a `SpanBuilder`.
146147
///
147148
/// If the span builder or context contains a valid span context, it is propagated.
148-
fn build_with_context(&self, mut builder: trace::SpanBuilder, cx: &Context) -> Self::Span {
149-
let parent_span_context = builder
150-
.parent_context
151-
.take()
152-
.or_else(|| {
153-
if cx.has_active_span() {
154-
Some(cx.span().span_context().clone())
155-
} else {
156-
None
157-
}
158-
})
159-
.or_else(|| cx.remote_span_context().cloned());
149+
fn build(&self, mut builder: trace::SpanBuilder) -> Self::Span {
150+
let parent_span_context = builder.parent_context.take().and_then(|parent_cx| {
151+
if parent_cx.has_active_span() {
152+
Some(parent_cx.span().span_context().clone())
153+
} else {
154+
parent_cx.remote_span_context().cloned()
155+
}
156+
});
160157
if let Some(span_context) = parent_span_context {
161158
trace::NoopSpan { span_context }
162159
} else {
@@ -190,6 +187,7 @@ impl SpanExporter for NoopSpanExporter {
190187
#[cfg(test)]
191188
mod tests {
192189
use super::*;
190+
use crate::testing::trace::TestSpan;
193191
use crate::trace::{self, Span, Tracer};
194192

195193
fn valid_span_context() -> trace::SpanContext {
@@ -205,15 +203,17 @@ mod tests {
205203
#[test]
206204
fn noop_tracer_defaults_to_invalid_span() {
207205
let tracer = NoopTracer::new();
208-
let span = tracer.start_from_context("foo", &Context::new());
206+
let span = tracer.start_with_context("foo", Context::new());
209207
assert!(!span.span_context().is_valid());
210208
}
211209

212210
#[test]
213211
fn noop_tracer_propagates_valid_span_context_from_builder() {
214212
let tracer = NoopTracer::new();
215-
let builder = tracer.span_builder("foo").with_parent(valid_span_context());
216-
let span = tracer.build_with_context(builder, &Context::new());
213+
let builder = tracer
214+
.span_builder("foo")
215+
.with_parent_context(Context::current_with_span(TestSpan(valid_span_context())));
216+
let span = tracer.build(builder);
217217
assert!(span.span_context().is_valid());
218218
}
219219

@@ -223,15 +223,15 @@ mod tests {
223223
let cx = Context::new().with_span(NoopSpan {
224224
span_context: valid_span_context(),
225225
});
226-
let span = tracer.start_from_context("foo", &cx);
226+
let span = tracer.start_with_context("foo", cx);
227227
assert!(span.span_context().is_valid());
228228
}
229229

230230
#[test]
231231
fn noop_tracer_propagates_valid_span_context_from_remote_span_context() {
232232
let tracer = NoopTracer::new();
233233
let cx = Context::new().with_remote_span_context(valid_span_context());
234-
let span = tracer.start_from_context("foo", &cx);
234+
let span = tracer.start_with_context("foo", cx);
235235
assert!(span.span_context().is_valid());
236236
}
237237
}

opentelemetry/src/api/trace/tracer.rs

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
use crate::sdk;
22
use crate::{
3-
trace::{
4-
Event, Link, Span, SpanContext, SpanId, SpanKind, StatusCode, TraceContextExt, TraceId,
5-
},
3+
trace::{Event, Link, Span, SpanId, SpanKind, StatusCode, TraceContextExt, TraceId},
64
Context, KeyValue,
75
};
86
use std::fmt;
@@ -35,19 +33,20 @@ use std::time::SystemTime;
3533
/// Spans can be created and nested manually:
3634
///
3735
/// ```
38-
/// use opentelemetry::{global, trace::{Span, Tracer}};
36+
/// use opentelemetry::{global, trace::{Span, Tracer, TraceContextExt}, Context};
3937
///
4038
/// let tracer = global::tracer("my-component");
4139
///
4240
/// let parent = tracer.start("foo");
41+
/// let parent_cx = Context::current_with_span(parent);
4342
/// let child = tracer.span_builder("bar")
44-
/// .with_parent(parent.span_context().clone())
43+
/// .with_parent_context(parent_cx.clone())
4544
/// .start(&tracer);
4645
///
4746
/// // ...
4847
///
4948
/// child.end();
50-
/// parent.end();
49+
/// drop(parent_cx) // end parent
5150
/// ```
5251
///
5352
/// Spans can also use the current thread's [`Context`] to track which span is active:
@@ -189,10 +188,10 @@ pub trait Tracer: fmt::Debug + 'static {
189188
/// `is_remote` to true on a parent `SpanContext` so `Span` creation knows if the
190189
/// parent is remote.
191190
fn start(&self, name: &str) -> Self::Span {
192-
self.start_from_context(name, &Context::current())
191+
self.start_with_context(name, Context::current())
193192
}
194193

195-
/// Starts a new `Span` in a given context
194+
/// Starts a new `Span` with a given context
196195
///
197196
/// By default the currently active `Span` is set as the new `Span`'s
198197
/// parent. The `Tracer` MAY provide other default options for newly
@@ -215,20 +214,15 @@ pub trait Tracer: fmt::Debug + 'static {
215214
/// created in another process. Each propagators' deserialization must set
216215
/// `is_remote` to true on a parent `SpanContext` so `Span` creation knows if the
217216
/// parent is remote.
218-
fn start_from_context(&self, name: &str, context: &Context) -> Self::Span;
217+
fn start_with_context(&self, name: &str, context: Context) -> Self::Span;
219218

220219
/// Creates a span builder
221220
///
222221
/// An ergonomic way for attributes to be configured before the `Span` is started.
223222
fn span_builder(&self, name: &str) -> SpanBuilder;
224223

225224
/// Create a span from a `SpanBuilder`
226-
fn build(&self, builder: SpanBuilder) -> Self::Span {
227-
self.build_with_context(builder, &Context::current())
228-
}
229-
230-
/// Create a span from a `SpanBuilder`
231-
fn build_with_context(&self, builder: SpanBuilder, cx: &Context) -> Self::Span;
225+
fn build(&self, builder: SpanBuilder) -> Self::Span;
232226

233227
/// Start a new span and execute the given closure with reference to the span's
234228
/// context.
@@ -335,8 +329,8 @@ pub trait Tracer: fmt::Debug + 'static {
335329
/// ```
336330
#[derive(Clone, Debug, Default)]
337331
pub struct SpanBuilder {
338-
/// Parent `SpanContext`
339-
pub parent_context: Option<SpanContext>,
332+
/// Parent `Context`
333+
pub parent_context: Option<Context>,
340334
/// Trace id, useful for integrations with external tracing systems.
341335
pub trace_id: Option<TraceId>,
342336
/// Span id, useful for integrations with external tracing systems.
@@ -385,7 +379,7 @@ impl SpanBuilder {
385379
}
386380

387381
/// Assign parent context
388-
pub fn with_parent(self, parent_context: SpanContext) -> Self {
382+
pub fn with_parent_context(self, parent_context: Context) -> Self {
389383
SpanBuilder {
390384
parent_context: Some(parent_context),
391385
..self

opentelemetry/src/global/trace.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ impl trace::Tracer for BoxedTracer {
9292
/// trace. A span is said to be a _root span_ if it does not have a parent. Each
9393
/// trace includes a single root span, which is the shared ancestor of all other
9494
/// spans in the trace.
95-
fn start_from_context(&self, name: &str, cx: &Context) -> Self::Span {
95+
fn start_with_context(&self, name: &str, cx: Context) -> Self::Span {
9696
BoxedSpan(self.0.start_with_context_boxed(name, cx))
9797
}
9898

@@ -104,8 +104,8 @@ impl trace::Tracer for BoxedTracer {
104104
}
105105

106106
/// Create a span from a `SpanBuilder`
107-
fn build_with_context(&self, builder: trace::SpanBuilder, cx: &Context) -> Self::Span {
108-
BoxedSpan(self.0.build_with_context_boxed(builder, cx))
107+
fn build(&self, builder: trace::SpanBuilder) -> Self::Span {
108+
BoxedSpan(self.0.build_boxed(builder))
109109
}
110110
}
111111

@@ -120,11 +120,11 @@ pub trait GenericTracer: fmt::Debug + 'static {
120120

121121
/// Returns a trait object so the underlying implementation can be swapped
122122
/// out at runtime.
123-
fn start_with_context_boxed(&self, name: &str, cx: &Context) -> Box<DynSpan>;
123+
fn start_with_context_boxed(&self, name: &str, cx: Context) -> Box<DynSpan>;
124124

125125
/// Returns a trait object so the underlying implementation can be swapped
126126
/// out at runtime.
127-
fn build_with_context_boxed(&self, builder: trace::SpanBuilder, cx: &Context) -> Box<DynSpan>;
127+
fn build_boxed(&self, builder: trace::SpanBuilder) -> Box<DynSpan>;
128128
}
129129

130130
impl<S, T> GenericTracer for T
@@ -139,14 +139,14 @@ where
139139

140140
/// Returns a trait object so the underlying implementation can be swapped
141141
/// out at runtime.
142-
fn start_with_context_boxed(&self, name: &str, cx: &Context) -> Box<DynSpan> {
143-
Box::new(self.start_from_context(name, cx))
142+
fn start_with_context_boxed(&self, name: &str, cx: Context) -> Box<DynSpan> {
143+
Box::new(self.start_with_context(name, cx))
144144
}
145145

146146
/// Returns a trait object so the underlying implementation can be swapped
147147
/// out at runtime.
148-
fn build_with_context_boxed(&self, builder: trace::SpanBuilder, cx: &Context) -> Box<DynSpan> {
149-
Box::new(self.build_with_context(builder, cx))
148+
fn build_boxed(&self, builder: trace::SpanBuilder) -> Box<DynSpan> {
149+
Box::new(self.build(builder))
150150
}
151151
}
152152

opentelemetry/src/sdk/trace/sampler.rs

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -113,19 +113,21 @@ impl ShouldSample for Sampler {
113113
// Never sample the trace
114114
Sampler::AlwaysOff => SamplingDecision::Drop,
115115
// The parent decision if sampled; otherwise the decision of delegate_sampler
116-
Sampler::ParentBased(delegate_sampler) => parent_context.map_or(
117-
delegate_sampler
118-
.should_sample(parent_context, trace_id, name, span_kind, attributes, links)
119-
.decision,
120-
|ctx| {
121-
let parent_span_context = ctx.span().span_context();
122-
if parent_span_context.is_sampled() {
123-
SamplingDecision::RecordAndSample
124-
} else {
125-
SamplingDecision::Drop
126-
}
127-
},
128-
),
116+
Sampler::ParentBased(delegate_sampler) => {
117+
parent_context.filter(|cx| cx.has_active_span()).map_or(
118+
delegate_sampler
119+
.should_sample(parent_context, trace_id, name, span_kind, attributes, links)
120+
.decision,
121+
|ctx| {
122+
let parent_span_context = ctx.span().span_context();
123+
if parent_span_context.is_sampled() {
124+
SamplingDecision::RecordAndSample
125+
} else {
126+
SamplingDecision::Drop
127+
}
128+
},
129+
)
130+
}
129131
// Probabilistically sample the trace.
130132
Sampler::TraceIdRatioBased(prob) => {
131133
if *prob >= 1.0 {
@@ -270,4 +272,20 @@ mod tests {
270272
);
271273
}
272274
}
275+
276+
#[test]
277+
fn filter_parent_sampler_for_active_spans() {
278+
let sampler = Sampler::ParentBased(Box::new(Sampler::AlwaysOn));
279+
let cx = Context::current_with_value("some_value");
280+
let result = sampler.should_sample(
281+
Some(&cx),
282+
TraceId::from_u128(1),
283+
"should sample",
284+
&SpanKind::Internal,
285+
&[],
286+
&[],
287+
);
288+
289+
assert_eq!(result.decision, SamplingDecision::RecordAndSample);
290+
}
273291
}

opentelemetry/src/sdk/trace/span_processor.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -695,10 +695,8 @@ mod tests {
695695
D: Fn(time::Duration) -> DS + 'static + Send + Sync,
696696
DS: Future<Output = ()> + Send + Sync + 'static,
697697
{
698-
async fn export(&mut self, batch: Vec<SpanData>) -> ExportResult {
699-
println!("Accepting {} spans", batch.len());
698+
async fn export(&mut self, _batch: Vec<SpanData>) -> ExportResult {
700699
(self.delay_fn)(self.delay_for).await;
701-
println!("Finish exporting, return result from exporter");
702700
Ok(())
703701
}
704702
}

0 commit comments

Comments
 (0)