use crate::sdk::trace::SpanLimits;
use crate::sdk::{
trace::{
provider::{TracerProvider, TracerProviderInner},
span::{Span, SpanData},
Config, EvictedHashMap, EvictedQueue, SamplingDecision, SamplingResult,
},
InstrumentationLibrary,
};
use crate::trace::{
Link, SpanBuilder, SpanContext, SpanId, SpanKind, StatusCode, TraceContextExt, TraceFlags,
TraceId, TraceState,
};
use crate::{Context, KeyValue};
use std::borrow::Cow;
use std::fmt;
use std::sync::Weak;
#[derive(Clone)]
pub struct Tracer {
instrumentation_lib: InstrumentationLibrary,
provider: Weak<TracerProviderInner>,
}
impl fmt::Debug for Tracer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Tracer")
.field("name", &self.instrumentation_lib.name)
.field("version", &self.instrumentation_lib.version)
.finish()
}
}
impl Tracer {
pub(crate) fn new(
instrumentation_lib: InstrumentationLibrary,
provider: Weak<TracerProviderInner>,
) -> Self {
Tracer {
instrumentation_lib,
provider,
}
}
pub fn provider(&self) -> Option<TracerProvider> {
self.provider.upgrade().map(TracerProvider::new)
}
pub fn instrumentation_library(&self) -> &InstrumentationLibrary {
&self.instrumentation_lib
}
#[allow(clippy::too_many_arguments)]
fn make_sampling_decision(
&self,
parent_cx: &Context,
trace_id: TraceId,
name: &str,
span_kind: &SpanKind,
attributes: &[KeyValue],
links: &[Link],
config: &Config,
instrumentation_library: &InstrumentationLibrary,
) -> Option<(TraceFlags, Vec<KeyValue>, TraceState)> {
let sampling_result = config.sampler.should_sample(
Some(parent_cx),
trace_id,
name,
span_kind,
attributes,
links,
instrumentation_library,
);
self.process_sampling_result(sampling_result, parent_cx)
}
fn process_sampling_result(
&self,
sampling_result: SamplingResult,
parent_cx: &Context,
) -> Option<(TraceFlags, Vec<KeyValue>, TraceState)> {
match sampling_result {
SamplingResult {
decision: SamplingDecision::Drop,
..
} => None,
SamplingResult {
decision: SamplingDecision::RecordOnly,
attributes,
trace_state,
} => {
let trace_flags = parent_cx.span().span_context().trace_flags();
Some((trace_flags.with_sampled(false), attributes, trace_state))
}
SamplingResult {
decision: SamplingDecision::RecordAndSample,
attributes,
trace_state,
} => {
let trace_flags = parent_cx.span().span_context().trace_flags();
Some((trace_flags.with_sampled(true), attributes, trace_state))
}
}
}
}
impl crate::trace::Tracer for Tracer {
type Span = Span;
fn start_with_context<T>(&self, name: T, parent_cx: &Context) -> Self::Span
where
T: Into<Cow<'static, str>>,
{
self.build_with_context(SpanBuilder::from_name(name), parent_cx)
}
fn span_builder<T>(&self, name: T) -> SpanBuilder
where
T: Into<Cow<'static, str>>,
{
SpanBuilder::from_name(name)
}
fn build_with_context(&self, mut builder: SpanBuilder, parent_cx: &Context) -> Self::Span {
let provider = self.provider();
if provider.is_none() {
return Span::new(
SpanContext::empty_context(),
None,
self.clone(),
SpanLimits::default(),
);
}
let provider = provider.unwrap();
let config = provider.config();
let span_limits = config.span_limits;
let span_id = builder
.span_id
.take()
.unwrap_or_else(|| config.id_generator.new_span_id());
let span_kind = builder.span_kind.take().unwrap_or(SpanKind::Internal);
let mut attribute_options = builder.attributes.take().unwrap_or_default();
let mut link_options = builder.links.take();
let mut no_parent = true;
let mut remote_parent = false;
let mut parent_span_id = SpanId::INVALID;
let mut parent_trace_flags = TraceFlags::default();
let trace_id;
let parent_span = if parent_cx.has_active_span() {
Some(parent_cx.span())
} else {
None
};
if let Some(sc) = parent_span.as_ref().map(|parent| parent.span_context()) {
no_parent = false;
remote_parent = sc.is_remote();
parent_span_id = sc.span_id();
parent_trace_flags = sc.trace_flags();
trace_id = sc.trace_id();
} else {
trace_id = builder
.trace_id
.unwrap_or_else(|| config.id_generator.new_trace_id());
};
let sampling_decision = if let Some(sampling_result) = builder.sampling_result.take() {
self.process_sampling_result(sampling_result, parent_cx)
} else if no_parent || remote_parent {
self.make_sampling_decision(
parent_cx,
trace_id,
&builder.name,
&span_kind,
&attribute_options,
link_options.as_deref().unwrap_or(&[]),
provider.config(),
&self.instrumentation_lib,
)
} else {
parent_span
.filter(|span| span.span_context().is_sampled())
.map(|span| {
(
parent_trace_flags,
Vec::new(),
span.span_context().trace_state().clone(),
)
})
};
let SpanBuilder {
name,
start_time,
end_time,
events,
status_code,
status_message,
..
} = builder;
let mut span = if let Some((flags, mut extra_attrs, trace_state)) = sampling_decision {
if !extra_attrs.is_empty() {
attribute_options.append(&mut extra_attrs);
}
let mut attributes =
EvictedHashMap::new(span_limits.max_attributes_per_span, attribute_options.len());
for attribute in attribute_options {
attributes.insert(attribute);
}
let mut links = EvictedQueue::new(span_limits.max_links_per_span);
if let Some(link_options) = &mut link_options {
let link_attributes_limit = span_limits.max_attributes_per_link as usize;
for link in link_options.iter_mut() {
let dropped_attributes_count =
link.attributes.len().saturating_sub(link_attributes_limit);
link.attributes.truncate(link_attributes_limit);
link.dropped_attributes_count = dropped_attributes_count as u32;
}
links.append_vec(link_options);
}
let start_time = start_time.unwrap_or_else(crate::time::now);
let end_time = end_time.unwrap_or(start_time);
let mut events_queue = EvictedQueue::new(span_limits.max_events_per_span);
if let Some(mut events) = events {
let event_attributes_limit = span_limits.max_attributes_per_event as usize;
for event in events.iter_mut() {
let dropped_attributes_count = event
.attributes
.len()
.saturating_sub(event_attributes_limit);
event.attributes.truncate(event_attributes_limit);
event.dropped_attributes_count = dropped_attributes_count as u32;
}
events_queue.append_vec(&mut events);
}
let status_code = status_code.unwrap_or(StatusCode::Unset);
let status_message = status_message.unwrap_or(Cow::Borrowed(""));
let span_context = SpanContext::new(trace_id, span_id, flags, false, trace_state);
Span::new(
span_context,
Some(SpanData {
parent_span_id,
span_kind,
name,
start_time,
end_time,
attributes,
events: events_queue,
links,
status_code,
status_message,
}),
self.clone(),
span_limits,
)
} else {
let span_context = SpanContext::new(
trace_id,
span_id,
TraceFlags::default(),
false,
Default::default(),
);
Span::new(span_context, None, self.clone(), span_limits)
};
for processor in provider.span_processors() {
processor.on_start(&mut span, parent_cx)
}
span
}
}
#[cfg(all(test, feature = "testing", feature = "trace"))]
mod tests {
use crate::{
sdk::{
self,
trace::{Config, Sampler, SamplingDecision, SamplingResult, ShouldSample},
InstrumentationLibrary,
},
testing::trace::TestSpan,
trace::{
Link, Span, SpanContext, SpanId, SpanKind, TraceContextExt, TraceFlags, TraceId,
TraceState, Tracer, TracerProvider,
},
Context, KeyValue,
};
#[derive(Debug)]
struct TestSampler {}
impl ShouldSample for TestSampler {
fn should_sample(
&self,
parent_context: Option<&Context>,
_trace_id: TraceId,
_name: &str,
_span_kind: &SpanKind,
_attributes: &[KeyValue],
_links: &[Link],
_instrumentation_library: &InstrumentationLibrary,
) -> SamplingResult {
let trace_state = parent_context
.unwrap()
.span()
.span_context()
.trace_state()
.clone();
SamplingResult {
decision: SamplingDecision::RecordAndSample,
attributes: Vec::new(),
trace_state: trace_state.insert("foo", "notbar").unwrap(),
}
}
}
#[test]
fn allow_sampler_to_change_trace_state() {
let sampler = TestSampler {};
let config = Config::default().with_sampler(sampler);
let tracer_provider = sdk::trace::TracerProvider::builder()
.with_config(config)
.build();
let tracer = tracer_provider.tracer("test");
let trace_state = TraceState::from_key_value(vec![("foo", "bar")]).unwrap();
let parent_context = Context::new().with_span(TestSpan(SpanContext::new(
TraceId::from_u128(128),
SpanId::from_u64(64),
TraceFlags::SAMPLED,
true,
trace_state,
)));
let span = tracer.start_with_context("foo", &parent_context);
let span_context = span.span_context();
let expected = span_context.trace_state();
assert_eq!(expected.get("foo"), Some("notbar"))
}
#[test]
fn drop_parent_based_children() {
let sampler = Sampler::ParentBased(Box::new(Sampler::AlwaysOn));
let config = Config::default().with_sampler(sampler);
let tracer_provider = sdk::trace::TracerProvider::builder()
.with_config(config)
.build();
let context = Context::current_with_span(TestSpan(SpanContext::empty_context()));
let tracer = tracer_provider.tracer("test");
let span = tracer.start_with_context("must_not_be_sampled", &context);
assert!(!span.span_context().is_sampled());
}
#[test]
fn uses_current_context_for_builders_if_unset() {
let sampler = Sampler::ParentBased(Box::new(Sampler::AlwaysOn));
let config = Config::default().with_sampler(sampler);
let tracer_provider = sdk::trace::TracerProvider::builder()
.with_config(config)
.build();
let tracer = tracer_provider.tracer("test");
let _attached = Context::current_with_span(TestSpan(SpanContext::empty_context())).attach();
let span = tracer.span_builder("must_not_be_sampled").start(&tracer);
assert!(!span.span_context().is_sampled());
let _attached = Context::current()
.with_remote_span_context(SpanContext::new(
TraceId::from_u128(1),
SpanId::from_u64(1),
TraceFlags::default(),
true,
Default::default(),
))
.attach();
let span = tracer.span_builder("must_not_be_sampled").start(&tracer);
assert!(!span.span_context().is_sampled());
}
}