Skip to content

Commit 3bda893

Browse files
jtescherhawkw
andauthored
opentelemetry: Add PreSampledTracer interface (tokio-rs#962)
## Motivation Currently the `OpenTelemetryLayer` is coupled to the opentelemetry SDK in a way that makes using alternate API implementations difficult (they would have to use the SDK's sampler and id generator) as well as causing duplication when specifying sampler configuration (it must currently be set on both the tracer provider configuration as well as the layer configuration, which is difficult to remember to keep in sync). ## Solution This change creates an interface for working with pre-sampled tracers, and implements it for the current opentelemetry SDK. This simplifies the layer as it no longer needs to track an id generator or sampler, and allows other otel API implementations to implement the trait rather than rely on the current SDK. Co-authored-by: Eliza Weisman <eliza@buoyant.io>
1 parent 9a2ae71 commit 3bda893

File tree

4 files changed

+155
-128
lines changed

4 files changed

+155
-128
lines changed

tracing-opentelemetry/src/layer.rs

Lines changed: 34 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use opentelemetry::api::{Context as OtelContext, IdGenerator, TraceContextExt};
2-
use opentelemetry::{api, sdk};
1+
use crate::PreSampledTracer;
2+
use opentelemetry::api::{self, Context as OtelContext, TraceContextExt};
33
use std::any::TypeId;
44
use std::fmt;
55
use std::marker;
@@ -20,10 +20,8 @@ static SPAN_KIND_FIELD: &str = "otel.kind";
2020
///
2121
/// [OpenTelemetry]: https://opentelemetry.io
2222
/// [tracing]: https://github.com/tokio-rs/tracing
23-
pub struct OpenTelemetryLayer<S, T: api::Tracer> {
23+
pub struct OpenTelemetryLayer<S, T> {
2424
tracer: T,
25-
sampler: Box<dyn sdk::ShouldSample>,
26-
id_generator: sdk::IdGenerator,
2725

2826
get_context: WithContext,
2927
_registry: marker::PhantomData<S>,
@@ -34,7 +32,7 @@ where
3432
S: Subscriber + for<'span> LookupSpan<'span>,
3533
{
3634
fn default() -> Self {
37-
OpenTelemetryLayer::new(api::NoopTracer {}, sdk::Sampler::AlwaysOn)
35+
OpenTelemetryLayer::new(api::NoopTracer {})
3836
}
3937
}
4038

@@ -50,8 +48,7 @@ where
5048
///
5149
/// // Use the tracing subscriber `Registry`, or any other subscriber
5250
/// // that impls `LookupSpan`
53-
/// let subscriber = Registry::default()
54-
/// .with(tracing_opentelemetry::layer());
51+
/// let subscriber = Registry::default().with(tracing_opentelemetry::layer());
5552
/// # drop(subscriber);
5653
/// ```
5754
pub fn layer<S>() -> OpenTelemetryLayer<S, api::NoopTracer>
@@ -70,7 +67,7 @@ pub(crate) struct WithContext(
7067
fn(
7168
&tracing::Dispatch,
7269
&span::Id,
73-
f: &mut dyn FnMut(&mut api::SpanBuilder, &dyn sdk::ShouldSample),
70+
f: &mut dyn FnMut(&mut api::SpanBuilder, &dyn PreSampledTracer),
7471
),
7572
);
7673

@@ -81,63 +78,12 @@ impl WithContext {
8178
&self,
8279
dispatch: &'a tracing::Dispatch,
8380
id: &span::Id,
84-
mut f: impl FnMut(&mut api::SpanBuilder, &dyn sdk::ShouldSample),
81+
mut f: impl FnMut(&mut api::SpanBuilder, &dyn PreSampledTracer),
8582
) {
8683
(self.0)(dispatch, id, &mut f)
8784
}
8885
}
8986

90-
pub(crate) fn build_span_context(
91-
builder: &mut api::SpanBuilder,
92-
sampler: &dyn sdk::ShouldSample,
93-
) -> api::SpanContext {
94-
let span_id = builder.span_id.expect("Builders must have id");
95-
let (trace_id, trace_flags) = builder
96-
.parent_context
97-
.as_ref()
98-
.filter(|parent_context| parent_context.is_valid())
99-
.map(|parent_context| (parent_context.trace_id(), parent_context.trace_flags()))
100-
.unwrap_or_else(|| {
101-
let trace_id = builder.trace_id.expect("trace_id should exist");
102-
103-
// ensure sampling decision is recorded so all span contexts have consistent flags
104-
let sampling_decision = if let Some(result) = builder.sampling_result.as_ref() {
105-
result.decision.clone()
106-
} else {
107-
let mut result = sampler.should_sample(
108-
builder.parent_context.as_ref(),
109-
trace_id,
110-
&builder.name,
111-
builder
112-
.span_kind
113-
.as_ref()
114-
.unwrap_or(&api::SpanKind::Internal),
115-
builder.attributes.as_ref().unwrap_or(&Vec::new()),
116-
builder.links.as_ref().unwrap_or(&Vec::new()),
117-
);
118-
119-
// Record additional attributes resulting from sampling
120-
if let Some(attributes) = &mut builder.attributes {
121-
attributes.append(&mut result.attributes)
122-
} else {
123-
builder.attributes = Some(result.attributes);
124-
}
125-
126-
result.decision
127-
};
128-
129-
let trace_flags = if sampling_decision == sdk::SamplingDecision::RecordAndSampled {
130-
api::TRACE_FLAG_SAMPLED
131-
} else {
132-
0
133-
};
134-
135-
(trace_id, trace_flags)
136-
});
137-
138-
api::SpanContext::new(trace_id, span_id, trace_flags, false)
139-
}
140-
14187
fn str_to_span_kind(s: &str) -> Option<api::SpanKind> {
14288
if s.eq_ignore_ascii_case("SERVER") {
14389
Some(api::SpanKind::Server)
@@ -235,13 +181,12 @@ impl<'a> field::Visit for SpanAttributeVisitor<'a> {
235181
impl<S, T> OpenTelemetryLayer<S, T>
236182
where
237183
S: Subscriber + for<'span> LookupSpan<'span>,
238-
T: api::Tracer + 'static,
184+
T: api::Tracer + PreSampledTracer + 'static,
239185
{
240-
/// Set the [`Tracer`] and [`Sampler`] that this layer will use to produce and
241-
/// track OpenTelemetry [`Span`]s.
186+
/// Set the [`Tracer`] that this layer will use to produce and track
187+
/// OpenTelemetry [`Span`]s.
242188
///
243189
/// [`Tracer`]: https://docs.rs/opentelemetry/latest/opentelemetry/api/trace/tracer/trait.Tracer.html
244-
/// [`Sampler`]: https://docs.rs/opentelemetry/latest/opentelemetry/api/trace/sampler/trait.Sampler.html
245190
/// [`Span`]: https://docs.rs/opentelemetry/latest/opentelemetry/api/trace/span/trait.Span.html
246191
///
247192
/// # Examples
@@ -273,26 +218,17 @@ where
273218
/// // Get a tracer from the provider for a component
274219
/// let tracer = provider.get_tracer("component-name");
275220
///
276-
/// // The probability sampler can be used to export a percentage of spans
277-
/// let sampler = sdk::Sampler::Probability(0.33);
278-
///
279221
/// // Create a layer with the configured tracer
280-
/// let otel_layer = OpenTelemetryLayer::new(tracer, sampler);
222+
/// let otel_layer = OpenTelemetryLayer::new(tracer);
281223
///
282224
/// // Use the tracing subscriber `Registry`, or any other subscriber
283225
/// // that impls `LookupSpan`
284-
/// let subscriber = Registry::default()
285-
/// .with(otel_layer);
226+
/// let subscriber = Registry::default().with(otel_layer);
286227
/// # drop(subscriber);
287228
/// ```
288-
pub fn new<Sampler>(tracer: T, sampler: Sampler) -> Self
289-
where
290-
Sampler: sdk::ShouldSample + 'static,
291-
{
229+
pub fn new(tracer: T) -> Self {
292230
OpenTelemetryLayer {
293231
tracer,
294-
sampler: Box::new(sampler),
295-
id_generator: sdk::IdGenerator::default(),
296232
get_context: WithContext(Self::get_context),
297233
_registry: marker::PhantomData,
298234
}
@@ -343,52 +279,15 @@ where
343279
/// ```
344280
pub fn with_tracer<Tracer>(self, tracer: Tracer) -> OpenTelemetryLayer<S, Tracer>
345281
where
346-
Tracer: api::Tracer + 'static,
282+
Tracer: api::Tracer + PreSampledTracer + 'static,
347283
{
348284
OpenTelemetryLayer {
349285
tracer,
350-
sampler: self.sampler,
351-
id_generator: self.id_generator,
352286
get_context: WithContext(OpenTelemetryLayer::<S, Tracer>::get_context),
353287
_registry: self._registry,
354288
}
355289
}
356290

357-
/// Set the [`Sampler`] to configure the logic around which [`Span`]s are
358-
/// exported.
359-
///
360-
/// [`Sampler`]: https://docs.rs/opentelemetry/latest/opentelemetry/api/trace/sampler/trait.Sampler.html
361-
/// [`Span`]: https://docs.rs/opentelemetry/latest/opentelemetry/api/trace/span/trait.Span.html
362-
///
363-
/// # Examples
364-
///
365-
/// ```rust,no_run
366-
/// use opentelemetry::sdk;
367-
/// use tracing_subscriber::layer::SubscriberExt;
368-
/// use tracing_subscriber::Registry;
369-
///
370-
/// // The probability sampler can be used to export a percentage of spans
371-
/// let sampler = sdk::Sampler::Probability(0.33);
372-
///
373-
/// // Create a layer with the configured sampler
374-
/// let otel_layer = tracing_opentelemetry::layer().with_sampler(sampler);
375-
///
376-
/// // Use the tracing subscriber `Registry`, or any other subscriber
377-
/// // that impls `LookupSpan`
378-
/// let subscriber = Registry::default()
379-
/// .with(otel_layer);
380-
/// # drop(subscriber);
381-
/// ```
382-
pub fn with_sampler<Sampler>(self, sampler: Sampler) -> Self
383-
where
384-
Sampler: sdk::ShouldSample + 'static,
385-
{
386-
OpenTelemetryLayer {
387-
sampler: Box::new(sampler),
388-
..self
389-
}
390-
}
391-
392291
/// Retrieve the parent OpenTelemetry [`SpanContext`] from the current
393292
/// tracing [`span`] through the [`Registry`]. This [`SpanContext`]
394293
/// links spans to their parent for proper hierarchical visualization.
@@ -407,14 +306,14 @@ where
407306
let mut extensions = span.extensions_mut();
408307
extensions
409308
.get_mut::<api::SpanBuilder>()
410-
.map(|builder| build_span_context(builder, self.sampler.as_ref()))
309+
.map(|builder| self.tracer.sampled_span_context(builder))
411310
// Else if the span is inferred from context, look up any available current span.
412311
} else if attrs.is_contextual() {
413312
ctx.lookup_current().and_then(|span| {
414313
let mut extensions = span.extensions_mut();
415314
extensions
416315
.get_mut::<api::SpanBuilder>()
417-
.map(|builder| build_span_context(builder, self.sampler.as_ref()))
316+
.map(|builder| self.tracer.sampled_span_context(builder))
418317
})
419318
// Explicit root spans should have no parent context.
420319
} else {
@@ -425,7 +324,7 @@ where
425324
fn get_context(
426325
dispatch: &tracing::Dispatch,
427326
id: &span::Id,
428-
f: &mut dyn FnMut(&mut api::SpanBuilder, &dyn sdk::ShouldSample),
327+
f: &mut dyn FnMut(&mut api::SpanBuilder, &dyn PreSampledTracer),
429328
) {
430329
let subscriber = dispatch
431330
.downcast_ref::<S>()
@@ -439,15 +338,15 @@ where
439338

440339
let mut extensions = span.extensions_mut();
441340
if let Some(builder) = extensions.get_mut::<api::SpanBuilder>() {
442-
f(builder, layer.sampler.as_ref());
341+
f(builder, &layer.tracer);
443342
}
444343
}
445344
}
446345

447346
impl<S, T> Layer<S> for OpenTelemetryLayer<S, T>
448347
where
449348
S: Subscriber + for<'span> LookupSpan<'span>,
450-
T: api::Tracer + 'static,
349+
T: api::Tracer + PreSampledTracer + 'static,
451350
{
452351
/// Creates an [OpenTelemetry `Span`] for the corresponding [tracing `Span`].
453352
///
@@ -462,7 +361,7 @@ where
462361
.span_builder(attrs.metadata().name())
463362
.with_start_time(SystemTime::now())
464363
// Eagerly assign span id so children have stable parent id
465-
.with_span_id(self.id_generator.new_span_id());
364+
.with_span_id(self.tracer.new_span_id());
466365

467366
// Set optional parent span context from attrs
468367
builder.parent_context = self.parent_span_context(attrs, &ctx);
@@ -473,7 +372,7 @@ where
473372
if existing_otel_span_context.is_valid() {
474373
builder.trace_id = Some(existing_otel_span_context.trace_id());
475374
} else {
476-
builder.trace_id = Some(self.id_generator.new_trace_id());
375+
builder.trace_id = Some(self.tracer.new_trace_id());
477376
}
478377
}
479378

@@ -507,7 +406,7 @@ where
507406
.get_mut::<api::SpanBuilder>()
508407
.expect("Missing SpanBuilder span extensions");
509408

510-
let follows_context = build_span_context(follows_builder, self.sampler.as_ref());
409+
let follows_context = self.tracer.sampled_span_context(follows_builder);
511410
let follows_link = api::Link::new(follows_context, Vec::new());
512411
if let Some(ref mut links) = builder.links {
513412
links.push(follows_link);
@@ -613,6 +512,18 @@ mod tests {
613512
}
614513
}
615514

515+
impl PreSampledTracer for TestTracer {
516+
fn sampled_span_context(&self, _builder: &mut api::SpanBuilder) -> api::SpanContext {
517+
api::SpanContext::empty_context()
518+
}
519+
fn new_trace_id(&self) -> api::TraceId {
520+
api::TraceId::invalid()
521+
}
522+
fn new_span_id(&self) -> api::SpanId {
523+
api::SpanId::invalid()
524+
}
525+
}
526+
616527
#[derive(Debug, Clone)]
617528
struct TestSpan(api::SpanContext);
618529
impl api::Span for TestSpan {

tracing-opentelemetry/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
//! may still occur on the path to 1.0. You can follow the changes via the
4747
//! [spec repository] to track progress toward stabilization.
4848
//!
49-
//! [spec repository]: (https://github.com/open-telemetry/opentelemetry-specification)
49+
//! [spec repository]: https://github.com/open-telemetry/opentelemetry-specification
5050
//!
5151
//! ## Examples
5252
//!
@@ -100,6 +100,9 @@
100100
mod layer;
101101
/// Span extension which enables OpenTelemetry context management.
102102
mod span_ext;
103+
/// Protocols for OpenTelemetry Tracers that are compatible with Tracing
104+
mod tracer;
103105

104106
pub use layer::{layer, OpenTelemetryLayer};
105107
pub use span_ext::OpenTelemetrySpanExt;
108+
pub use tracer::PreSampledTracer;

tracing-opentelemetry/src/span_ext.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::layer::{build_span_context, WithContext};
1+
use crate::layer::WithContext;
22
use opentelemetry::api;
33
use opentelemetry::api::TraceContextExt;
44

@@ -76,7 +76,7 @@ impl OpenTelemetrySpanExt for tracing::Span {
7676
fn set_parent(&self, parent_context: &api::Context) {
7777
self.with_subscriber(move |(id, subscriber)| {
7878
if let Some(get_context) = subscriber.downcast_ref::<WithContext>() {
79-
get_context.with_context(subscriber, id, move |builder, _sampler| {
79+
get_context.with_context(subscriber, id, move |builder, _tracer| {
8080
builder.parent_context = parent_context.remote_span_context().cloned()
8181
});
8282
}
@@ -87,8 +87,8 @@ impl OpenTelemetrySpanExt for tracing::Span {
8787
let mut span_context = None;
8888
self.with_subscriber(|(id, subscriber)| {
8989
if let Some(get_context) = subscriber.downcast_ref::<WithContext>() {
90-
get_context.with_context(subscriber, id, |builder, sampler| {
91-
span_context = Some(build_span_context(builder, sampler));
90+
get_context.with_context(subscriber, id, |builder, tracer| {
91+
span_context = Some(tracer.sampled_span_context(builder));
9292
})
9393
}
9494
});

0 commit comments

Comments
 (0)