From a430656c17551c33072b6e82f88468ef5be5595d Mon Sep 17 00:00:00 2001 From: andylokandy Date: Sun, 1 Feb 2026 17:58:14 +0800 Subject: [PATCH 1/6] feat: add_link and with_link --- examples/asynchronous.rs | 2 +- examples/synchronous.rs | 2 +- fastrace-opentelemetry/src/lib.rs | 32 ++- fastrace/src/collector/command.rs | 2 +- fastrace/src/collector/global_collector.rs | 262 ++++++------------- fastrace/src/collector/id.rs | 6 +- fastrace/src/collector/mod.rs | 3 +- fastrace/src/event.rs | 7 +- fastrace/src/local/local_collector.rs | 14 +- fastrace/src/local/local_span.rs | 68 ++++- fastrace/src/local/local_span_line.rs | 102 ++++---- fastrace/src/local/local_span_stack.rs | 166 ++++++------ fastrace/src/local/raw_span.rs | 30 +-- fastrace/src/local/span_queue.rs | 30 ++- fastrace/src/span.rs | 277 ++++++++++----------- fastrace/src/util/mod.rs | 8 - fastrace/src/util/tree.rs | 172 ++++++------- fastrace/tests/lib.rs | 170 ++++--------- tests/statically-disable/src/main.rs | 37 +-- 19 files changed, 612 insertions(+), 778 deletions(-) diff --git a/examples/asynchronous.rs b/examples/asynchronous.rs index 3e9407cb..1a029464 100644 --- a/examples/asynchronous.rs +++ b/examples/asynchronous.rs @@ -62,7 +62,7 @@ async fn main() { let f = async { let jhs = { let _span = LocalSpan::enter_with_local_parent("a span") - .with_property(|| ("a property", "a value")); + .with_properties(|| [("a property", "a value")]); parallel_job() }; diff --git a/examples/synchronous.rs b/examples/synchronous.rs index d2bc0d73..a298e177 100644 --- a/examples/synchronous.rs +++ b/examples/synchronous.rs @@ -45,7 +45,7 @@ async fn main() { let _g = root.set_local_parent(); let _span = LocalSpan::enter_with_local_parent("a span") - .with_property(|| ("a property", "a value")); + .with_properties(|| [("a property", "a value")]); for i in 1..=10 { func1(i); diff --git a/fastrace-opentelemetry/src/lib.rs b/fastrace-opentelemetry/src/lib.rs index 31eb2e14..1193295f 100644 --- a/fastrace-opentelemetry/src/lib.rs +++ b/fastrace-opentelemetry/src/lib.rs @@ -33,6 +33,7 @@ use fastrace::prelude::*; use opentelemetry::InstrumentationScope; use opentelemetry::KeyValue; use opentelemetry::trace::Event; +use opentelemetry::trace::Link; use opentelemetry::trace::SpanContext as OtelSpanContext; use opentelemetry::trace::SpanKind; use opentelemetry::trace::Status; @@ -139,7 +140,6 @@ static OTEL_PROPERTIES: LazyLock> = LazyLock::new(|| { ]) }); -/// Convert a list of properties to a list of key-value pairs. fn map_props_to_kvs(props: Vec<(Cow<'static, str>, Cow<'static, str>)>) -> Vec { props .into_iter() @@ -148,7 +148,6 @@ fn map_props_to_kvs(props: Vec<(Cow<'static, str>, Cow<'static, str>)>) -> Vec) -> SpanEvents { let mut queue = SpanEvents::default(); queue.events.reserve(events.len()); @@ -167,6 +166,31 @@ fn map_events(events: Vec) -> SpanEvents { queue } +fn map_links(links: Vec) -> SpanLinks { + let links = links + .into_iter() + .map(|link| { + let trace_flags = if link.sampled { + TraceFlags::SAMPLED + } else { + TraceFlags::default() + }; + let span_context = OtelSpanContext::new( + link.trace_id.0.into(), + link.span_id.0.into(), + trace_flags, + false, + TraceState::default(), + ); + Link::with_context(span_context) + }) + .collect(); + + let mut span_links = SpanLinks::default(); + span_links.links = links; + span_links +} + trait DynSpanExporter: Send + Sync + Debug { fn export( &self, @@ -209,6 +233,7 @@ impl OpenTelemetryReporter { name, properties, events, + links, }| { let parent_span_id = parent_id.0.into(); let span_kind = span_kind(&properties); @@ -221,6 +246,7 @@ impl OpenTelemetryReporter { + Duration::from_nanos(begin_time_unix_ns + duration_ns); let attributes = map_props_to_kvs(properties); let events = map_events(events); + let links = map_links(links); SpanData { span_context: OtelSpanContext::new( @@ -239,7 +265,7 @@ impl OpenTelemetryReporter { attributes, dropped_attributes_count: 0, events, - links: SpanLinks::default(), + links, status, instrumentation_scope, } diff --git a/fastrace/src/collector/command.rs b/fastrace/src/collector/command.rs index ab6dcab1..145870df 100644 --- a/fastrace/src/collector/command.rs +++ b/fastrace/src/collector/command.rs @@ -1,7 +1,7 @@ // Copyright 2020 TiKV Project Authors. Licensed under Apache-2.0. +use crate::collector::CollectToken; use crate::collector::SpanSet; -use crate::util::CollectToken; #[derive(Debug)] pub enum CollectCommand { diff --git a/fastrace/src/collector/global_collector.rs b/fastrace/src/collector/global_collector.rs index abe2bf78..c4099f80 100644 --- a/fastrace/src/collector/global_collector.rs +++ b/fastrace/src/collector/global_collector.rs @@ -3,7 +3,6 @@ use std::borrow::Cow; use std::cell::UnsafeCell; use std::collections::HashMap; -use std::sync::Arc; use std::sync::LazyLock; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicUsize; @@ -13,6 +12,7 @@ use fastant::Anchor; use fastant::Instant; use parking_lot::Mutex; +use crate::collector::CollectToken; use crate::collector::Config; use crate::collector::EventRecord; use crate::collector::SpanContext; @@ -28,7 +28,6 @@ use crate::collector::command::SubmitSpans; use crate::local::local_collector::LocalSpansInner; use crate::local::raw_span::RawKind; use crate::local::raw_span::RawSpan; -use crate::util::CollectToken; use crate::util::command_bus::CommandBus; use crate::util::command_bus::CommandSender; @@ -134,37 +133,8 @@ impl GlobalCollect { send_command(CollectCommand::DropCollect(DropCollect { collect_id })); } - // Note that: relationships are not built completely for now so a further job is needed. - // - // Every `SpanSet` has its own root spans whose `raw_span.parent_id` are equal to - // `SpanId::default()`. - // - // Every root span can have multiple parents where mainly comes from `Span::enter_with_parents`. - // Those parents are recorded into `CollectToken` which has several `CollectTokenItem`s. Look - // into a `CollectTokenItem`, `parent_ids` can be found. - // - // For example, we have a `SpanSet::LocalSpansInner` and a `CollectToken` as follows: - // - // SpanSet::LocalSpansInner::spans CollectToken::parent_ids - // +------+-----------+-----+ +------------+------------+ - // | id | parent_id | ... | | collect_id | parent_ids | - // +------+-----------+-----+ +------------+------------+ - // | 43 | 545 | ... | | 1212 | 7 | - // | 15 | default | ... | <- root span | 874 | 321 | - // | 545 | 15 | ... | | 915 | 413 | - // | 70 | default | ... | <- root span +------------+------------+ - // +------+-----------+-----+ - // - // There is a many-to-many mapping. Span#15 has parents Span#7, Span#321 and Span#413, so does - // Span#70. - // - // So the expected further job mentioned above is: - // * Copy `SpanSet` to the same number of copies as `CollectTokenItem`s, one `SpanSet` to one - // `CollectTokenItem` - // * Amend `raw_span.parent_id` of root spans in `SpanSet` to `parent_ids` of `CollectTokenItem` - pub fn submit_spans(&self, spans: SpanSet, mut collect_token: CollectToken) { - collect_token.retain(|item| item.is_sampled); - if !collect_token.is_empty() { + pub fn submit_spans(&self, spans: SpanSet, collect_token: CollectToken) { + if collect_token.is_sampled { send_command(CollectCommand::SubmitSpans(SubmitSpans { spans, collect_token, @@ -173,26 +143,10 @@ impl GlobalCollect { } } -enum SpanCollection { - Owned { - spans: SpanSet, - trace_id: TraceId, - parent_id: SpanId, - }, - Shared { - spans: Arc, - trace_id: TraceId, - parent_id: SpanId, - }, -} - -impl SpanCollection { - fn trace_id(&self) -> TraceId { - match self { - SpanCollection::Owned { trace_id, .. } => *trace_id, - SpanCollection::Shared { trace_id, .. } => *trace_id, - } - } +struct SpanCollection { + spans: SpanSet, + trace_id: TraceId, + parent_id: SpanId, } #[derive(Default)] @@ -303,49 +257,22 @@ impl GlobalCollector { collect_token, } in self.submit_spans.drain(..) { - debug_assert!(!collect_token.is_empty()); - - if collect_token.len() == 1 { - let item = collect_token[0]; - if let Some(active_collector) = self.active_collectors.get_mut(&item.collect_id) { - if !active_collector.canceled { - active_collector - .span_collections - .push(SpanCollection::Owned { - spans, - trace_id: item.trace_id, - parent_id: item.parent_id, - }); - } - } else { - self.stale_spans.push(SpanCollection::Owned { + if let Some(active_collector) = + self.active_collectors.get_mut(&collect_token.collect_id) + { + if !active_collector.canceled { + active_collector.span_collections.push(SpanCollection { spans, - trace_id: item.trace_id, - parent_id: item.parent_id, + trace_id: collect_token.trace_id, + parent_id: collect_token.parent_id, }); } } else { - let spans = Arc::new(spans); - for item in &collect_token { - if let Some(active_collector) = self.active_collectors.get_mut(&item.collect_id) - { - if !active_collector.canceled { - active_collector - .span_collections - .push(SpanCollection::Shared { - spans: spans.clone(), - trace_id: item.trace_id, - parent_id: item.parent_id, - }); - } - } else { - self.stale_spans.push(SpanCollection::Shared { - spans: spans.clone(), - trace_id: item.trace_id, - parent_id: item.parent_id, - }); - } - } + self.stale_spans.push(SpanCollection { + spans, + trace_id: collect_token.trace_id, + parent_id: collect_token.parent_id, + }); } } @@ -365,12 +292,9 @@ impl GlobalCollector { } } - self.stale_spans.sort_by_key(|spans| spans.trace_id()); + self.stale_spans.sort_by_key(|spans| spans.trace_id); - for spans in self - .stale_spans - .chunk_by(|a, b| a.trace_id() == b.trace_id()) - { + for spans in self.stale_spans.chunk_by(|a, b| a.trace_id == b.trace_id) { postprocess_span_collection( spans, &anchor, @@ -406,6 +330,7 @@ impl LocalSpansInner { enum DanglingItem { Event(EventRecord), Properties(Vec<(Cow<'static, str>, Cow<'static, str>)>), + Links(Vec), } fn postprocess_span_collection<'a>( @@ -417,67 +342,31 @@ fn postprocess_span_collection<'a>( let committed_len = committed_records.len(); for span_collection in span_collections { - match span_collection { - SpanCollection::Owned { - spans, - trace_id, - parent_id, - } => match spans { - SpanSet::Span(raw_span) => amend_span( - raw_span, - *trace_id, - *parent_id, - committed_records, - danglings, - anchor, - ), - SpanSet::LocalSpansInner(local_spans) => amend_local_span( - local_spans, - *trace_id, - *parent_id, - committed_records, - danglings, - anchor, - ), - SpanSet::SharedLocalSpans(local_spans) => amend_local_span( - local_spans, - *trace_id, - *parent_id, - committed_records, - danglings, - anchor, - ), - }, - SpanCollection::Shared { - spans, - trace_id, - parent_id, - } => match &**spans { - SpanSet::Span(raw_span) => amend_span( - raw_span, - *trace_id, - *parent_id, - committed_records, - danglings, - anchor, - ), - SpanSet::LocalSpansInner(local_spans) => amend_local_span( - local_spans, - *trace_id, - *parent_id, - committed_records, - danglings, - anchor, - ), - SpanSet::SharedLocalSpans(local_spans) => amend_local_span( - local_spans, - *trace_id, - *parent_id, - committed_records, - danglings, - anchor, - ), - }, + match &span_collection.spans { + SpanSet::Span(raw_span) => amend_span( + raw_span, + span_collection.trace_id, + span_collection.parent_id, + committed_records, + danglings, + anchor, + ), + SpanSet::LocalSpansInner(local_spans) => amend_local_span( + local_spans, + span_collection.trace_id, + span_collection.parent_id, + committed_records, + danglings, + anchor, + ), + SpanSet::SharedLocalSpans(local_spans) => amend_local_span( + local_spans, + span_collection.trace_id, + span_collection.parent_id, + committed_records, + danglings, + anchor, + ), } } @@ -509,12 +398,9 @@ fn amend_local_span( begin_time_unix_ns, duration_ns: end_time_unix_ns.saturating_sub(begin_time_unix_ns), name: span.name.clone(), - properties: span - .properties - .as_ref() - .map(|p| p.to_vec()) - .unwrap_or_default(), + properties: span.properties.clone(), events: vec![], + links: span.links.clone(), }); } RawKind::Event => { @@ -522,11 +408,7 @@ fn amend_local_span( let event = EventRecord { name: span.name.clone(), timestamp_unix_ns: begin_time_unix_ns, - properties: span - .properties - .as_ref() - .map(|p| p.to_vec()) - .unwrap_or_default(), + properties: span.properties.clone(), }; dangling .entry(parent_id) @@ -537,12 +419,13 @@ fn amend_local_span( dangling .entry(parent_id) .or_default() - .push(DanglingItem::Properties( - span.properties - .as_ref() - .map(|p| p.to_vec()) - .unwrap_or_default(), - )); + .push(DanglingItem::Properties(span.properties.clone())); + } + RawKind::Link => { + dangling + .entry(parent_id) + .or_default() + .push(DanglingItem::Links(span.links.clone())); } } } @@ -567,12 +450,9 @@ fn amend_span( begin_time_unix_ns, duration_ns: end_time_unix_ns.saturating_sub(begin_time_unix_ns), name: span.name.clone(), - properties: span - .properties - .as_ref() - .map(|p| p.to_vec()) - .unwrap_or_default(), + properties: span.properties.clone(), events: vec![], + links: span.links.clone(), }); } RawKind::Event => { @@ -580,11 +460,7 @@ fn amend_span( let event = EventRecord { name: span.name.clone(), timestamp_unix_ns: begin_time_unix_ns, - properties: span - .properties - .as_ref() - .map(|p| p.to_vec()) - .unwrap_or_default(), + properties: span.properties.clone(), }; dangling .entry(parent_id) @@ -595,12 +471,15 @@ fn amend_span( dangling .entry(parent_id) .or_default() - .push(DanglingItem::Properties( - span.properties - .as_ref() - .map(|p| p.to_vec()) - .unwrap_or_default(), - )); + .push(DanglingItem::Properties(span.properties.clone())); + } + RawKind::Link => { + if !span.links.is_empty() { + dangling + .entry(parent_id) + .or_default() + .push(DanglingItem::Links(span.links.clone())); + } } } } @@ -620,6 +499,9 @@ fn mount_danglings(records: &mut [SpanRecord], danglings: &mut HashMap { record.properties.extend(properties); } + DanglingItem::Links(links) => { + record.links.extend(links); + } } } } diff --git a/fastrace/src/collector/id.rs b/fastrace/src/collector/id.rs index 1e19166f..a27273de 100644 --- a/fastrace/src/collector/id.rs +++ b/fastrace/src/collector/id.rs @@ -129,7 +129,7 @@ impl<'de> serde::Deserialize<'de> for SpanId { /// /// [`TraceId`]: crate::collector::TraceId /// [`SpanId`]: crate::collector::SpanId -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct SpanContext { pub trace_id: TraceId, pub span_id: SpanId, @@ -215,7 +215,7 @@ impl SpanContext { #[cfg(feature = "enable")] { let inner = span.inner.as_ref()?; - let collect_token = inner.issue_collect_token().next()?; + let collect_token = inner.issue_collect_token(); Some(Self { trace_id: collect_token.trace_id, @@ -249,7 +249,7 @@ impl SpanContext { let stack = LOCAL_SPAN_STACK.try_with(Rc::clone).ok()?; let mut stack = stack.borrow_mut(); - let collect_token = stack.current_collect_token()?[0]; + let collect_token = stack.current_collect_token()?; Some(Self { trace_id: collect_token.trace_id, diff --git a/fastrace/src/collector/mod.rs b/fastrace/src/collector/mod.rs index e98179e7..b0e4584f 100644 --- a/fastrace/src/collector/mod.rs +++ b/fastrace/src/collector/mod.rs @@ -52,6 +52,7 @@ pub struct SpanRecord { pub name: Cow<'static, str>, pub properties: Vec<(Cow<'static, str>, Cow<'static, str>)>, pub events: Vec, + pub links: Vec, } /// A record of an event that occurred during the execution of a span. @@ -64,7 +65,7 @@ pub struct EventRecord { #[doc(hidden)] #[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub struct CollectTokenItem { +pub struct CollectToken { pub trace_id: TraceId, pub parent_id: SpanId, pub collect_id: usize, diff --git a/fastrace/src/event.rs b/fastrace/src/event.rs index 6de94783..eba8f841 100644 --- a/fastrace/src/event.rs +++ b/fastrace/src/event.rs @@ -9,7 +9,7 @@ use crate::util::Properties; /// An event that represents a single point in time during the execution of a span. pub struct Event { pub(crate) name: Cow<'static, str>, - pub(crate) properties: Option, + pub(crate) properties: Properties, } impl Event { @@ -26,7 +26,7 @@ impl Event { pub fn new(name: impl Into>) -> Self { Event { name: name.into(), - properties: None, + properties: Properties::default(), } } @@ -39,7 +39,7 @@ impl Event { /// ``` /// use fastrace::prelude::*; /// - /// LocalSpan::add_event(Event::new("event").with_property(|| ("key", "value"))); + /// LocalSpan::add_event(Event::new("event").with_properties(|| [("key", "value")])); /// ``` #[inline] pub fn with_property(self, property: F) -> Self @@ -71,7 +71,6 @@ impl Event { #[cfg(feature = "enable")] { self.properties - .get_or_insert_with(Properties::default) .extend(properties().into_iter().map(|(k, v)| (k.into(), v.into()))) } self diff --git a/fastrace/src/local/local_collector.rs b/fastrace/src/local/local_collector.rs index c973fe9d..5b3f6cf7 100644 --- a/fastrace/src/local/local_collector.rs +++ b/fastrace/src/local/local_collector.rs @@ -6,12 +6,12 @@ use std::sync::Arc; use fastant::Instant; +use crate::collector::CollectToken; use crate::local::local_span_stack::LOCAL_SPAN_STACK; use crate::local::local_span_stack::LocalSpanStack; use crate::local::local_span_stack::SpanLineHandle; use crate::prelude::SpanContext; use crate::prelude::SpanRecord; -use crate::util::CollectToken; use crate::util::RawSpans; /// A collector to collect [`LocalSpan`]. @@ -238,7 +238,7 @@ impl LocalSpans { #[cfg(test)] mod tests { use super::*; - use crate::collector::CollectTokenItem; + use crate::collector::CollectToken; use crate::collector::SpanId; use crate::prelude::LocalSpan; use crate::prelude::TraceId; @@ -252,21 +252,21 @@ mod tests { let span1 = stack.borrow_mut().enter_span("span1").unwrap(); { - let token2 = CollectTokenItem { + let token2 = CollectToken { trace_id: TraceId(1234), parent_id: SpanId::default(), collect_id: 42, is_root: false, is_sampled: true, }; - let collector2 = LocalCollector::new(Some(token2.into()), stack.clone()); + let collector2 = LocalCollector::new(Some(token2), stack.clone()); let span2 = stack.borrow_mut().enter_span("span2").unwrap(); let span3 = stack.borrow_mut().enter_span("span3").unwrap(); stack.borrow_mut().exit_span(span3); stack.borrow_mut().exit_span(span2); let (spans, token) = collector2.collect_spans_and_token(); - assert_eq!(token.unwrap().as_slice(), &[token2]); + assert_eq!(token.unwrap(), token2); assert_eq!( tree_str_from_raw_spans(spans.spans), r" @@ -292,14 +292,14 @@ span1 [] let span1 = stack.borrow_mut().enter_span("span1").unwrap(); { - let token2 = CollectTokenItem { + let token2 = CollectToken { trace_id: TraceId(1234), parent_id: SpanId::default(), collect_id: 42, is_root: false, is_sampled: true, }; - let collector2 = LocalCollector::new(Some(token2.into()), stack.clone()); + let collector2 = LocalCollector::new(Some(token2), stack.clone()); let span2 = stack.borrow_mut().enter_span("span2").unwrap(); let span3 = stack.borrow_mut().enter_span("span3").unwrap(); stack.borrow_mut().exit_span(span3); diff --git a/fastrace/src/local/local_span.rs b/fastrace/src/local/local_span.rs index f56ee04f..6fc6d2a6 100644 --- a/fastrace/src/local/local_span.rs +++ b/fastrace/src/local/local_span.rs @@ -6,6 +6,7 @@ use std::fmt; use std::rc::Rc; use crate::Event; +use crate::collector::SpanContext; use crate::local::local_span_line::LocalSpanHandle; use crate::local::local_span_stack::LOCAL_SPAN_STACK; use crate::local::local_span_stack::LocalSpanStack; @@ -84,6 +85,33 @@ impl LocalSpan { self.with_properties(|| [property()]) } + /// Add a link to the `LocalSpan` and return the modified `LocalSpan`. + /// + /// Links allow a span to reference additional related spans without establishing + /// a strict parent-child relationship. + /// + /// # Examples + /// + /// ``` + /// use fastrace::prelude::*; + /// + /// let root = Span::root("root", SpanContext::random()); + /// let link = SpanContext::new(TraceId(1), SpanId(2)); + /// let _g = root.set_local_parent(); + /// + /// let _span = LocalSpan::enter_with_local_parent("child").with_link(link); + /// ``` + #[inline] + pub fn with_link(self, link: SpanContext) -> Self { + #[cfg(feature = "enable")] + if let Some(LocalSpanInner { stack, span_handle }) = &self.inner { + let span_stack = &mut *stack.borrow_mut(); + span_stack.with_link(span_handle, link); + } + + self + } + /// Add multiple properties to the `LocalSpan` and return the modified `LocalSpan`. /// /// # Examples @@ -134,6 +162,34 @@ impl LocalSpan { Self::add_properties(|| [property()]) } + /// Add a link to the current local parent. + /// + /// # Examples + /// + /// ``` + /// use fastrace::prelude::*; + /// + /// let root = Span::root("root", SpanContext::random()); + /// let link = SpanContext::new(TraceId(1), SpanId(2)); + /// let _g = root.set_local_parent(); + /// + /// let _span = LocalSpan::enter_with_local_parent("child"); + /// LocalSpan::add_link(link); + /// ``` + #[inline] + pub fn add_link(link: SpanContext) { + #[cfg(feature = "enable")] + { + LOCAL_SPAN_STACK + .try_with(|s| { + let span_stack = &mut s.borrow_mut(); + span_stack.add_link(link); + Some(()) + }) + .ok(); + } + } + /// Add multiple properties to the current local parent. /// /// # Examples @@ -217,7 +273,7 @@ impl Drop for LocalSpan { #[cfg(test)] mod tests { use super::*; - use crate::collector::CollectTokenItem; + use crate::collector::CollectToken; use crate::collector::SpanId; use crate::local::LocalCollector; use crate::prelude::TraceId; @@ -227,14 +283,14 @@ mod tests { fn local_span_basic() { let stack = Rc::new(RefCell::new(LocalSpanStack::with_capacity(16))); - let token = CollectTokenItem { + let token = CollectToken { trace_id: TraceId(1234), parent_id: SpanId::default(), collect_id: 42, is_root: false, is_sampled: true, }; - let collector = LocalCollector::new(Some(token.into()), stack.clone()); + let collector = LocalCollector::new(Some(token), stack.clone()); { let _g = LocalSpan::enter_with_stack("span1", stack.clone()); @@ -245,7 +301,7 @@ mod tests { } let (spans, collect_token) = collector.collect_spans_and_token(); - assert_eq!(collect_token.unwrap().as_slice(), &[token]); + assert_eq!(collect_token.unwrap(), token); assert_eq!( tree_str_from_raw_spans(spans.spans), r#" @@ -265,14 +321,14 @@ span1 [] fn drop_out_of_order() { let stack = Rc::new(RefCell::new(LocalSpanStack::with_capacity(16))); - let token = CollectTokenItem { + let token = CollectToken { trace_id: TraceId(1234), parent_id: SpanId::default(), collect_id: 42, is_root: false, is_sampled: true, }; - let collector = LocalCollector::new(Some(token.into()), stack.clone()); + let collector = LocalCollector::new(Some(token), stack.clone()); { let span1 = LocalSpan::enter_with_stack("span1", stack.clone()); diff --git a/fastrace/src/local/local_span_line.rs b/fastrace/src/local/local_span_line.rs index bea3c787..c285b4db 100644 --- a/fastrace/src/local/local_span_line.rs +++ b/fastrace/src/local/local_span_line.rs @@ -3,10 +3,10 @@ use std::borrow::Cow; use crate::Event; -use crate::collector::CollectTokenItem; +use crate::collector::CollectToken; +use crate::collector::SpanContext; use crate::local::span_queue::SpanHandle; use crate::local::span_queue::SpanQueue; -use crate::util::CollectToken; use crate::util::RawSpans; pub struct SpanLine { @@ -23,7 +23,7 @@ impl SpanLine { collect_token: Option, ) -> Self { let is_sampled = match &collect_token { - Some(token) => token.iter().any(|item| item.is_sampled), + Some(token) => token.is_sampled, None => true, }; @@ -83,6 +83,15 @@ impl SpanLine { self.span_queue.add_properties(properties()); } + #[inline] + pub fn add_link(&mut self, link: SpanContext) { + if !self.is_sampled { + return; + } + + self.span_queue.add_link(link); + } + #[inline] pub fn with_properties(&mut self, handle: &LocalSpanHandle, properties: F) where @@ -101,22 +110,28 @@ impl SpanLine { } } + #[inline] + pub fn with_link(&mut self, handle: &LocalSpanHandle, link: SpanContext) { + if !self.is_sampled { + return; + } + + if self.epoch == handle.span_line_epoch { + self.span_queue.with_link(&handle.span_handle, link); + } + } + #[inline] pub fn current_collect_token(&self) -> Option { - self.collect_token.as_ref().map(|collect_token| { - collect_token - .iter() - .map(|item| CollectTokenItem { - trace_id: item.trace_id, - parent_id: self - .span_queue - .current_parent_id() - .unwrap_or(item.parent_id), - collect_id: item.collect_id, - is_root: item.is_root, - is_sampled: item.is_sampled, - }) - .collect() + self.collect_token.as_ref().map(|item| CollectToken { + trace_id: item.trace_id, + parent_id: self + .span_queue + .current_parent_id() + .unwrap_or(item.parent_id), + collect_id: item.collect_id, + is_root: item.is_root, + is_sampled: item.is_sampled, }) } @@ -169,52 +184,34 @@ span1 [] #[test] fn current_collect_token() { - let token1 = CollectTokenItem { + let token = CollectToken { trace_id: TraceId(1234), parent_id: SpanId::default(), collect_id: 42, is_root: false, is_sampled: true, }; - let token2 = CollectTokenItem { - trace_id: TraceId(1235), - parent_id: SpanId::default(), - collect_id: 43, - is_root: false, - is_sampled: true, - }; - let token = [token1, token2].into_iter().collect(); let mut span_line = SpanLine::new(16, 1, Some(token)); let current_token = span_line.current_collect_token().unwrap(); - assert_eq!(current_token.as_slice(), &[token1, token2]); + assert_eq!(current_token, token); let span = span_line.start_span("span").unwrap(); let current_token = span_line.current_collect_token().unwrap(); - assert_eq!(current_token.len(), 2); - assert_eq!(current_token.as_slice(), &[ - CollectTokenItem { - trace_id: TraceId(1234), - parent_id: span_line.span_queue.current_parent_id().unwrap(), - collect_id: 42, - is_root: false, - is_sampled: true, - }, - CollectTokenItem { - trace_id: TraceId(1235), - parent_id: span_line.span_queue.current_parent_id().unwrap(), - collect_id: 43, - is_root: false, - is_sampled: true, - } - ]); + assert_eq!(current_token, CollectToken { + trace_id: TraceId(1234), + parent_id: span_line.span_queue.current_parent_id().unwrap(), + collect_id: 42, + is_root: false, + is_sampled: true, + }); span_line.finish_span(span); let current_token = span_line.current_collect_token().unwrap(); - assert_eq!(current_token.as_slice(), &[token1, token2]); + assert_eq!(current_token, token); let (spans, collect_token) = span_line.collect(1).unwrap(); - assert_eq!(collect_token.unwrap().as_slice(), &[token1, token2]); + assert_eq!(collect_token.unwrap(), token); assert_eq!( tree_str_from_raw_spans(spans), r#" @@ -236,7 +233,7 @@ span [] let raw_spans = span_line1.collect(1).unwrap().0; assert_eq!(raw_spans.len(), 1); - assert_eq!(raw_spans[0].properties, None); + assert!(raw_spans[0].properties.is_empty()); let raw_spans = span_line2.collect(2).unwrap().0; assert!(raw_spans.is_empty()); @@ -244,14 +241,14 @@ span [] #[test] fn unmatched_epoch_finish_span() { - let item = CollectTokenItem { + let item = CollectToken { trace_id: TraceId(1234), parent_id: SpanId::default(), collect_id: 42, is_root: false, is_sampled: true, }; - let mut span_line1 = SpanLine::new(16, 1, Some(item.into())); + let mut span_line1 = SpanLine::new(16, 1, Some(item)); let mut span_line2 = SpanLine::new(16, 2, None); assert_eq!(span_line1.span_line_epoch(), 1); assert_eq!(span_line2.span_line_epoch(), 2); @@ -262,14 +259,11 @@ span [] let token_after_finish = span_line1.current_collect_token().unwrap(); // the span failed to finish - assert_eq!( - token_before_finish.as_slice(), - token_after_finish.as_slice() - ); + assert_eq!(token_before_finish, token_after_finish); let (spans, collect_token) = span_line1.collect(1).unwrap(); let collect_token = collect_token.unwrap(); - assert_eq!(collect_token.as_slice(), &[item]); + assert_eq!(collect_token, item); assert_eq!(spans.len(), 1); let (spans, collect_token) = span_line2.collect(2).unwrap(); diff --git a/fastrace/src/local/local_span_stack.rs b/fastrace/src/local/local_span_stack.rs index e609b76e..aa390899 100644 --- a/fastrace/src/local/local_span_stack.rs +++ b/fastrace/src/local/local_span_stack.rs @@ -5,9 +5,10 @@ use std::cell::RefCell; use std::rc::Rc; use crate::Event; +use crate::collector::CollectToken; +use crate::collector::SpanContext; use crate::local::local_span_line::LocalSpanHandle; use crate::local::local_span_line::SpanLine; -use crate::util::CollectToken; use crate::util::RawSpans; const DEFAULT_SPAN_STACK_SIZE: usize = 4096; @@ -57,6 +58,13 @@ impl LocalSpanStack { } } + #[inline] + pub fn add_link(&mut self, link: SpanContext) { + if let Some(span_line) = self.current_span_line() { + span_line.add_link(link); + } + } + /// Register a new span line to the span stack. If succeed, return a span line epoch which can /// be used to unregister the span line via [`LocalSpanStack::unregister_and_collect`]. If /// the size of the span stack is greater than the `capacity`, registration will fail @@ -128,6 +136,18 @@ impl LocalSpanStack { } } + #[inline] + pub fn with_link(&mut self, local_span_handle: &LocalSpanHandle, link: SpanContext) { + debug_assert!(self.current_span_line().is_some()); + if let Some(span_line) = self.current_span_line() { + debug_assert_eq!( + span_line.span_line_epoch(), + local_span_handle.span_line_epoch + ); + span_line.with_link(local_span_handle, link); + } + } + pub fn current_collect_token(&mut self) -> Option { let span_line = self.current_span_line()?; span_line.current_collect_token() @@ -146,7 +166,7 @@ pub struct SpanLineHandle { #[cfg(test)] mod tests { use super::*; - use crate::collector::CollectTokenItem; + use crate::collector::CollectToken; use crate::collector::SpanId; use crate::prelude::TraceId; use crate::util::tree::tree_str_from_raw_spans; @@ -155,14 +175,14 @@ mod tests { fn span_stack_basic() { let mut span_stack = LocalSpanStack::with_capacity(16); - let token1 = CollectTokenItem { + let token1 = CollectToken { trace_id: TraceId(1234), parent_id: SpanId::default(), collect_id: 42, is_root: false, is_sampled: true, }; - let span_line1 = span_stack.register_span_line(Some(token1.into())).unwrap(); + let span_line1 = span_stack.register_span_line(Some(token1)).unwrap(); { { let span1 = span_stack.enter_span("span1").unwrap(); @@ -173,14 +193,14 @@ mod tests { span_stack.exit_span(span1); } - let token2 = CollectTokenItem { + let token2 = CollectToken { trace_id: TraceId(1235), parent_id: SpanId::default(), collect_id: 48, is_root: false, is_sampled: true, }; - let span_line2 = span_stack.register_span_line(Some(token2.into())).unwrap(); + let span_line2 = span_stack.register_span_line(Some(token2)).unwrap(); { let span3 = span_stack.enter_span("span3").unwrap(); { @@ -191,7 +211,7 @@ mod tests { } let (spans, collect_token) = span_stack.unregister_and_collect(span_line2).unwrap(); - assert_eq!(collect_token.unwrap().as_slice(), &[token2]); + assert_eq!(collect_token.unwrap(), token2); assert_eq!( tree_str_from_raw_spans(spans), r" @@ -202,7 +222,7 @@ span3 [] } let (spans, collect_token) = span_stack.unregister_and_collect(span_line1).unwrap(); - assert_eq!(collect_token.unwrap().as_slice(), &[token1]); + assert_eq!(collect_token.unwrap(), token1); assert_eq!( tree_str_from_raw_spans(spans), r" @@ -221,32 +241,26 @@ span1 [] let span_line2 = span_stack.register_span_line(None).unwrap(); { let span_line3 = span_stack - .register_span_line(Some( - CollectTokenItem { - trace_id: TraceId(1234), - parent_id: SpanId::default(), - collect_id: 42, - is_root: false, - is_sampled: true, - } - .into(), - )) + .register_span_line(Some(CollectToken { + trace_id: TraceId(1234), + parent_id: SpanId::default(), + collect_id: 42, + is_root: false, + is_sampled: true, + })) .unwrap(); { let span_line4 = span_stack.register_span_line(None).unwrap(); { assert!( span_stack - .register_span_line(Some( - CollectTokenItem { - trace_id: TraceId(1235), - parent_id: SpanId::default(), - collect_id: 43, - is_root: false, - is_sampled: true, - } - .into() - )) + .register_span_line(Some(CollectToken { + trace_id: TraceId(1235), + parent_id: SpanId::default(), + collect_id: 43, + is_root: false, + is_sampled: true, + })) .is_none() ); assert!(span_stack.register_span_line(None).is_none()); @@ -258,16 +272,13 @@ span1 [] { assert!( span_stack - .register_span_line(Some( - CollectTokenItem { - trace_id: TraceId(1236), - parent_id: SpanId::default(), - collect_id: 44, - is_root: false, - is_sampled: true, - } - .into() - )) + .register_span_line(Some(CollectToken { + trace_id: TraceId(1236), + parent_id: SpanId::default(), + collect_id: 44, + is_root: false, + is_sampled: true, + })) .is_none() ); assert!(span_stack.register_span_line(None).is_none()); @@ -285,53 +296,45 @@ span1 [] fn current_collect_token() { let mut span_stack = LocalSpanStack::with_capacity(16); assert!(span_stack.current_collect_token().is_none()); - let token1 = CollectTokenItem { + let token1 = CollectToken { trace_id: TraceId(1), parent_id: SpanId(1), collect_id: 1, is_root: false, is_sampled: true, }; - let span_line1 = span_stack.register_span_line(Some(token1.into())).unwrap(); - assert_eq!(span_stack.current_collect_token().unwrap().as_slice(), &[ - token1 - ]); + let span_line1 = span_stack.register_span_line(Some(token1)).unwrap(); + assert_eq!(span_stack.current_collect_token().unwrap(), token1); { let span_line2 = span_stack.register_span_line(None).unwrap(); assert!(span_stack.current_collect_token().is_none()); { - let token3 = CollectTokenItem { + let token3 = CollectToken { trace_id: TraceId(3), parent_id: SpanId(3), collect_id: 3, is_root: false, is_sampled: true, }; - let span_line3 = span_stack.register_span_line(Some(token3.into())).unwrap(); - assert_eq!(span_stack.current_collect_token().unwrap().as_slice(), &[ - token3 - ]); + let span_line3 = span_stack.register_span_line(Some(token3)).unwrap(); + assert_eq!(span_stack.current_collect_token().unwrap(), token3); let _ = span_stack.unregister_and_collect(span_line3).unwrap(); } assert!(span_stack.current_collect_token().is_none()); let _ = span_stack.unregister_and_collect(span_line2).unwrap(); - let token4 = CollectTokenItem { + let token4 = CollectToken { trace_id: TraceId(4), parent_id: SpanId(4), collect_id: 4, is_root: false, is_sampled: true, }; - let span_line4 = span_stack.register_span_line(Some(token4.into())).unwrap(); - assert_eq!(span_stack.current_collect_token().unwrap().as_slice(), &[ - token4 - ]); + let span_line4 = span_stack.register_span_line(Some(token4)).unwrap(); + assert_eq!(span_stack.current_collect_token().unwrap(), token4); let _ = span_stack.unregister_and_collect(span_line4).unwrap(); } - assert_eq!(span_stack.current_collect_token().unwrap().as_slice(), &[ - token1 - ]); + assert_eq!(span_stack.current_collect_token().unwrap(), token1); let _ = span_stack.unregister_and_collect(span_line1).unwrap(); assert!(span_stack.current_collect_token().is_none()); } @@ -344,16 +347,13 @@ span1 [] let span1 = span_stack.enter_span("span1").unwrap(); { let span_line2 = span_stack - .register_span_line(Some( - CollectTokenItem { - trace_id: TraceId(1234), - parent_id: SpanId::default(), - collect_id: 42, - is_root: false, - is_sampled: true, - } - .into(), - )) + .register_span_line(Some(CollectToken { + trace_id: TraceId(1234), + parent_id: SpanId::default(), + collect_id: 42, + is_root: false, + is_sampled: true, + })) .unwrap(); span_stack.exit_span(span1); let _ = span_stack.unregister_and_collect(span_line2).unwrap(); @@ -369,16 +369,13 @@ span1 [] let span1 = span_stack.enter_span("span1").unwrap(); { let span_line2 = span_stack - .register_span_line(Some( - CollectTokenItem { - trace_id: TraceId(1234), - parent_id: SpanId::default(), - collect_id: 42, - is_root: false, - is_sampled: true, - } - .into(), - )) + .register_span_line(Some(CollectToken { + trace_id: TraceId(1234), + parent_id: SpanId::default(), + collect_id: 42, + is_root: false, + is_sampled: true, + })) .unwrap(); span_stack.with_properties(&span1, || [("k1", "v1")]); let _ = span_stack.unregister_and_collect(span_line2).unwrap(); @@ -394,16 +391,13 @@ span1 [] let span_line1 = span_stack.register_span_line(None).unwrap(); { let span_line2 = span_stack - .register_span_line(Some( - CollectTokenItem { - trace_id: TraceId(1234), - parent_id: SpanId::default(), - collect_id: 42, - is_root: false, - is_sampled: true, - } - .into(), - )) + .register_span_line(Some(CollectToken { + trace_id: TraceId(1234), + parent_id: SpanId::default(), + collect_id: 42, + is_root: false, + is_sampled: true, + })) .unwrap(); let _ = span_stack.unregister_and_collect(span_line1).unwrap(); let _ = span_stack.unregister_and_collect(span_line2).unwrap(); diff --git a/fastrace/src/local/raw_span.rs b/fastrace/src/local/raw_span.rs index 4956571d..4a9a4943 100644 --- a/fastrace/src/local/raw_span.rs +++ b/fastrace/src/local/raw_span.rs @@ -4,6 +4,7 @@ use std::borrow::Cow; use fastant::Instant; +use crate::collector::SpanContext; use crate::collector::SpanId; use crate::util::Properties; @@ -12,15 +13,17 @@ pub enum RawKind { Span, Event, Properties, + Link, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct RawSpan { pub id: SpanId, pub parent_id: Option, pub begin_instant: Instant, pub name: Cow<'static, str>, - pub properties: Option, + pub properties: Properties, + pub links: Vec, pub raw_kind: RawKind, // Will write this field at post processing @@ -41,7 +44,8 @@ impl RawSpan { parent_id, begin_instant, name: name.into(), - properties: None, + properties: Properties::default(), + links: Vec::new(), raw_kind, end_instant: Instant::ZERO, } @@ -52,23 +56,3 @@ impl RawSpan { self.end_instant = end_instant; } } - -impl Clone for RawSpan { - fn clone(&self) -> Self { - let properties = self.properties.as_ref().map(|properties| { - let mut new_properties = Properties::default(); - new_properties.extend(properties.iter().cloned()); - new_properties - }); - - RawSpan { - id: self.id, - parent_id: self.parent_id, - begin_instant: self.begin_instant, - name: self.name.clone(), - properties, - raw_kind: self.raw_kind, - end_instant: self.end_instant, - } - } -} diff --git a/fastrace/src/local/span_queue.rs b/fastrace/src/local/span_queue.rs index 910152d0..ee5decdf 100644 --- a/fastrace/src/local/span_queue.rs +++ b/fastrace/src/local/span_queue.rs @@ -6,9 +6,9 @@ use fastant::Instant; use super::raw_span::RawKind; use crate::Event; +use crate::collector::SpanContext; use crate::collector::SpanId; use crate::local::raw_span::RawSpan; -use crate::util::Properties; use crate::util::RawSpans; pub struct SpanQueue { @@ -83,6 +83,24 @@ impl SpanQueue { self.span_queue.push(span); } + #[inline] + pub fn add_link(&mut self, link: SpanContext) { + if self.span_queue.len() >= self.capacity { + return; + } + + let mut span = RawSpan::begin_with( + SpanId::next_id(), + self.next_parent_id, + Instant::ZERO, + Cow::Borrowed(""), + RawKind::Link, + ); + span.links.push(link); + + self.span_queue.push(span); + } + #[inline] pub fn add_properties(&mut self, properties: I) where @@ -102,7 +120,6 @@ impl SpanQueue { RawKind::Properties, ); span.properties - .get_or_insert_with(Properties::default) .extend(properties.into_iter().map(|(k, v)| (k.into(), v.into()))); self.span_queue.push(span); @@ -119,10 +136,17 @@ impl SpanQueue { let span = &mut self.span_queue[span_handle.index]; span.properties - .get_or_insert_with(Properties::default) .extend(properties.into_iter().map(|(k, v)| (k.into(), v.into()))); } + #[inline] + pub fn with_link(&mut self, span_handle: &SpanHandle, link: SpanContext) { + debug_assert!(span_handle.index < self.span_queue.len()); + + let span = &mut self.span_queue[span_handle.index]; + span.links.push(link); + } + #[inline] pub fn take_queue(self) -> RawSpans { self.span_queue diff --git a/fastrace/src/span.rs b/fastrace/src/span.rs index e0736416..657426df 100644 --- a/fastrace/src/span.rs +++ b/fastrace/src/span.rs @@ -9,22 +9,20 @@ use std::time::Duration; use fastant::Instant; -use crate::Event; -use crate::collector::CollectTokenItem; +use crate::collector::global_collector::NOT_SAMPLED_COLLECT_ID; +use crate::collector::CollectToken; use crate::collector::GlobalCollect; use crate::collector::SpanContext; use crate::collector::SpanId; use crate::collector::SpanSet; -use crate::collector::global_collector::NOT_SAMPLED_COLLECT_ID; -use crate::local::LocalCollector; -use crate::local::LocalSpans; use crate::local::local_collector::LocalSpansInner; -use crate::local::local_span_stack::LOCAL_SPAN_STACK; use crate::local::local_span_stack::LocalSpanStack; +use crate::local::local_span_stack::LOCAL_SPAN_STACK; use crate::local::raw_span::RawKind; use crate::local::raw_span::RawSpan; -use crate::util::CollectToken; -use crate::util::Properties; +use crate::local::LocalCollector; +use crate::local::LocalSpans; +use crate::Event; /// A thread-safe span. #[must_use] @@ -93,14 +91,13 @@ impl Span { NOT_SAMPLED_COLLECT_ID }; - let token = CollectTokenItem { + let token = CollectToken { trace_id: parent.trace_id, parent_id: parent.span_id, collect_id, is_root: true, is_sampled: parent.sampled, - } - .into(); + }; Self::new(token, name, Some(collect_id)) } @@ -126,7 +123,7 @@ impl Span { #[cfg(feature = "enable")] { match &parent.inner { - Some(_inner) => Self::enter_with_parents(name, [parent]), + Some(inner) => Self::new(inner.issue_collect_token(), name, None), None => Span::noop(), } } @@ -134,13 +131,10 @@ impl Span { /// Create a new child span associated with multiple parent spans. /// - /// This function is particularly useful when a single operation amalgamates multiple requests. - /// It enables the creation of a unique child span that is interconnected with all the parent - /// spans related to the requests, thereby obviating the need to generate individual child - /// spans for each parent span. + /// This API is deprecated. The first non-noop parent becomes the primary parent; any additional + /// parents are attached as links. /// - /// The newly created child span, and its children, will have a replica for each trace of parent - /// spans. + /// Use [`Span::enter_with_parent`] plus [`Span::with_link`] instead. /// /// # Examples /// @@ -150,8 +144,12 @@ impl Span { /// let parent1 = Span::root("parent1", SpanContext::random()); /// let parent2 = Span::root("parent2", SpanContext::random()); /// - /// let child = Span::enter_with_parents("child", [&parent1, &parent2]); + /// let child = Span::enter_with_parent("child", &parent1) + /// .with_link(SpanContext::from_span(&parent2).unwrap()); #[inline] + #[deprecated( + note = "Multiple-parent spans are deprecated. Use `enter_with_parent` and link other parents with `with_link`." + )] pub fn enter_with_parents<'a>( name: impl Into>, parents: impl IntoIterator, @@ -163,12 +161,35 @@ impl Span { #[cfg(feature = "enable")] { - let token = parents - .into_iter() - .filter_map(|span| span.inner.as_ref()) - .flat_map(|inner| inner.issue_collect_token()) - .collect(); - Self::new(token, name, None) + let mut main: Option<&Span> = None; + let mut links = Vec::new(); + for parent in parents.into_iter() { + if parent.inner.is_none() { + continue; + } + + if main.is_none() { + main = Some(parent); + } else if let Some(ctx) = SpanContext::from_span(parent) { + links.push(ctx); + } + } + + let Some(main) = main else { + return Self::noop(); + }; + + let mut span = Self::new( + main.inner.as_ref().unwrap().issue_collect_token(), + name, + None, + ); + + for link in links { + span = span.with_link(link); + } + + span } } @@ -360,6 +381,58 @@ impl Span { } } + /// Adds a link to another span context and returns the modified `Span`. + /// + /// Links allow a span to reference additional related spans without establishing + /// a strict parent-child relationship. + /// + /// # Examples + /// + /// ``` + /// use fastrace::prelude::*; + /// + /// let root = Span::root("root", SpanContext::random()); + /// let other = Span::root("other", SpanContext::random()); + /// let link = SpanContext::from_span(&other).unwrap(); + /// + /// let _span = Span::enter_with_parent("child", &root).with_link(link); + /// ``` + #[inline] + pub fn with_link(mut self, link: SpanContext) -> Self { + #[cfg(feature = "enable")] + if let Some(inner) = self.inner.as_mut() { + inner.add_link(link); + } + self + } + + /// Adds a link to another span context after the span has been created. + /// + /// # Examples + /// + /// ``` + /// use fastrace::prelude::*; + /// + /// let root = Span::root("root", SpanContext::random()); + /// let other = Span::root("other", SpanContext::random()); + /// let link = SpanContext::from_span(&other).unwrap(); + /// + /// let child = Span::enter_with_parent("child", &root); + /// child.add_link(link); + /// ``` + #[inline] + pub fn add_link(&self, link: SpanContext) { + #[cfg(feature = "enable")] + { + let mut span = Span::enter_with_parent("", self); + if let Some(mut inner) = span.inner.take() { + inner.raw_span.raw_kind = RawKind::Link; + inner.raw_span.links.push(link); + inner.submit_spans(); + } + } + } + /// Attach a collection of [`LocalSpan`] instances as child spans to the current span. /// /// This method allows you to associate previously collected `LocalSpan` instances with the @@ -518,13 +591,17 @@ impl SpanInner { { self.raw_span .properties - .get_or_insert_with(Properties::default) .extend(properties().into_iter().map(|(k, v)| (k.into(), v.into()))); } + #[inline] + fn add_link(&mut self, link: SpanContext) { + self.raw_span.links.push(link); + } + #[inline] fn capture_local_spans(&self, stack: Rc>) -> LocalParentGuard { - let token = self.issue_collect_token().collect(); + let token = self.issue_collect_token(); let collector = LocalCollector::new(Some(token), stack); LocalParentGuard::new(collector, self.collect.clone()) @@ -538,21 +615,19 @@ impl SpanInner { self.collect.submit_spans( SpanSet::SharedLocalSpans(local_spans), - self.issue_collect_token().collect(), + self.issue_collect_token(), ); } #[inline] - pub(crate) fn issue_collect_token(&self) -> impl Iterator + '_ { - self.collect_token - .iter() - .map(move |collect_item| CollectTokenItem { - trace_id: collect_item.trace_id, - parent_id: self.raw_span.id, - collect_id: collect_item.collect_id, - is_root: false, - is_sampled: collect_item.is_sampled, - }) + pub(crate) fn issue_collect_token(&self) -> CollectToken { + CollectToken { + trace_id: self.collect_token.trace_id, + parent_id: self.raw_span.id, + collect_id: self.collect_token.collect_id, + is_root: false, + is_sampled: self.collect_token.is_sampled, + } } #[inline] @@ -566,7 +641,7 @@ impl Drop for Span { fn drop(&mut self) { #[cfg(feature = "enable")] if let Some(mut inner) = self.inner.take() { - if inner.collect_token.iter().any(|token| token.is_sampled) { + if inner.collect_token.is_sampled { let collect_id = inner.collect_id.take(); let collect = inner.collect.clone(); @@ -617,7 +692,7 @@ impl Drop for LocalParentGuard { let (spans, token) = inner.collector.collect_spans_and_token(); debug_assert!(token.is_some()); if let Some(token) = token { - if token.iter().any(|token| token.is_sampled) { + if token.is_sampled { inner .collect .submit_spans(SpanSet::LocalSpansInner(spans), token); @@ -649,12 +724,12 @@ fn current_collect() -> GlobalCollect { #[cfg(test)] mod tests { - use std::sync::Mutex; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; + use std::sync::Mutex; - use mockall::Sequence; use mockall::predicate; + use mockall::Sequence; use rand::rng; use rand::seq::SliceRandom; @@ -694,16 +769,13 @@ mod tests { .in_sequence(&mut seq) .with( predicate::always(), - predicate::eq::( - CollectTokenItem { - trace_id: TraceId(12), - parent_id: SpanId::default(), - collect_id: 42, - is_root: true, - is_sampled: true, - } - .into(), - ), + predicate::eq::(CollectToken { + trace_id: TraceId(12), + parent_id: SpanId::default(), + collect_id: 42, + is_root: true, + is_sampled: true, + }), ) .return_const(()); mock.expect_drop_collect() @@ -744,7 +816,7 @@ mod tests { mock.expect_submit_spans() .times(1) .in_sequence(&mut seq) - .withf(|_, collect_token| collect_token.len() == 1 && collect_token[0].collect_id == 42) + .withf(|_, collect_token| collect_token.collect_id == 42) .returning({ let span_sets = span_sets.clone(); move |span_set, token| span_sets.lock().unwrap().push((span_set, token)) @@ -805,7 +877,7 @@ root [] mock.expect_submit_spans() .times(4) .in_sequence(&mut seq) - .withf(|_, collect_token| collect_token.len() == 1 && collect_token[0].collect_id == 42) + .withf(|_, collect_token| collect_token.collect_id == 42) .returning({ let span_sets = span_sets.clone(); move |span_set, token| span_sets.lock().unwrap().push((span_set, token)) @@ -834,101 +906,6 @@ root [] ); } - #[test] - fn span_with_parents() { - crate::set_reporter(ConsoleReporter, crate::collector::Config::default()); - - let routine = || { - let parent_ctx = SpanContext::random(); - let parent1 = Span::root("parent1", parent_ctx); - let parent2 = Span::root("parent2", parent_ctx); - let parent3 = Span::root("parent3", parent_ctx); - let parent4 = Span::root("parent4", parent_ctx); - let parent5 = Span::root("parent5", parent_ctx); - let child1 = Span::enter_with_parent("child1", &parent5); - let child2 = Span::enter_with_parents("child2", [ - &parent1, &parent2, &parent3, &parent4, &parent5, &child1, - ]) - .with_property(|| ("k1", "v1")); - - crossbeam::scope(move |scope| { - let mut rng = rng(); - let mut spans = [child1, child2]; - spans.shuffle(&mut rng); - for span in spans { - scope.spawn(|_| drop(span)); - } - }) - .unwrap(); - crossbeam::scope(move |scope| { - let mut rng = rng(); - let mut spans = [parent1, parent2, parent3, parent4, parent5]; - spans.shuffle(&mut rng); - for span in spans { - scope.spawn(|_| drop(span)); - } - }) - .unwrap(); - - fastrace::flush(); - }; - - let mut mock = MockGlobalCollect::new(); - let mut seq = Sequence::new(); - let span_sets = Arc::new(Mutex::new(Vec::new())); - mock.expect_start_collect() - .times(5) - .in_sequence(&mut seq) - .returning({ - let id = Arc::new(AtomicUsize::new(1)); - move || id.fetch_add(1, Ordering::SeqCst) - }); - mock.expect_submit_spans() - .times(7) - .in_sequence(&mut seq) - .returning({ - let span_sets = span_sets.clone(); - move |span_set, token| span_sets.lock().unwrap().push((span_set, token)) - }); - mock.expect_drop_collect() - .times(5) - .with(predicate::in_iter([1_usize, 2, 3, 4, 5])) - .return_const(()); - - let mock = Arc::new(mock); - set_mock_collect(mock); - - routine(); - - let span_sets = std::mem::take(&mut *span_sets.lock().unwrap()); - assert_eq!( - tree_str_from_span_sets(span_sets.as_slice()), - r#" -#1 -parent1 [] - child2 [("k1", "v1")] - -#2 -parent2 [] - child2 [("k1", "v1")] - -#3 -parent3 [] - child2 [("k1", "v1")] - -#4 -parent4 [] - child2 [("k1", "v1")] - -#5 -parent5 [] - child1 [] - child2 [("k1", "v1")] - child2 [("k1", "v1")] -"# - ); - } - #[test] fn span_push_child_spans() { crate::set_reporter(ConsoleReporter, crate::collector::Config::default()); @@ -1051,7 +1028,7 @@ parent5 [] mock.expect_submit_spans() .times(5) .in_sequence(&mut seq) - .withf(|_, collect_token| collect_token.len() == 1 && collect_token[0].collect_id == 42) + .withf(|_, collect_token| collect_token.collect_id == 42) .returning({ let span_sets = span_sets.clone(); move |span_set, token| span_sets.lock().unwrap().push((span_set, token)) diff --git a/fastrace/src/util/mod.rs b/fastrace/src/util/mod.rs index ad5ee35c..48a07c0f 100644 --- a/fastrace/src/util/mod.rs +++ b/fastrace/src/util/mod.rs @@ -7,15 +7,7 @@ pub mod tree; use std::borrow::Cow; -use crate::collector::CollectTokenItem; use crate::local::raw_span::RawSpan; pub type RawSpans = Vec; -pub type CollectToken = Vec; pub type Properties = Vec<(Cow<'static, str>, Cow<'static, str>)>; - -impl From for CollectToken { - fn from(item: CollectTokenItem) -> Self { - vec![item] - } -} diff --git a/fastrace/src/util/tree.rs b/fastrace/src/util/tree.rs index 90a193c0..63b77d3c 100644 --- a/fastrace/src/util/tree.rs +++ b/fastrace/src/util/tree.rs @@ -5,10 +5,10 @@ use std::collections::HashMap; use std::fmt; +use crate::collector::CollectToken; use crate::collector::SpanId; use crate::collector::SpanRecord; use crate::collector::SpanSet; -use crate::util::CollectToken; use crate::util::RawSpans; type TreeChildren = HashMap< @@ -76,14 +76,9 @@ impl Tree { span.name.to_string(), vec![], span.properties - .as_ref() - .map(|properties| { - properties - .iter() - .map(|(k, v)| (k.to_string(), v.to_string())) - .collect() - }) - .unwrap_or_default(), + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(), vec![], ), ); @@ -118,84 +113,79 @@ impl Tree { >::new(); for (span_set, token) in span_sets { - for item in token.iter() { - collect - .entry(item.collect_id) - .or_default() - .insert(Some(SpanId(0)), ("".into(), vec![], vec![], vec![])); - match span_set { - SpanSet::Span(span) => { - collect.entry(item.collect_id).or_default().insert( + collect + .entry(token.collect_id) + .or_default() + .insert(Some(SpanId(0)), ("".into(), vec![], vec![], vec![])); + match span_set { + SpanSet::Span(span) => { + collect.entry(token.collect_id).or_default().insert( + Some(span.id), + ( + span.name.to_string(), + vec![], + span.properties + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(), + vec![], + ), + ); + } + SpanSet::LocalSpansInner(spans) => { + for span in spans.spans.iter() { + collect.entry(token.collect_id).or_default().insert( Some(span.id), ( span.name.to_string(), vec![], span.properties - .as_ref() - .map(|properties| { - properties - .iter() - .map(|(k, v)| (k.to_string(), v.to_string())) - .collect() - }) - .unwrap_or_default(), + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(), vec![], ), ); } - SpanSet::LocalSpansInner(spans) => { - for span in spans.spans.iter() { - collect.entry(item.collect_id).or_default().insert( - Some(span.id), - ( - span.name.to_string(), - vec![], - span.properties - .as_ref() - .map(|properties| { - properties - .iter() - .map(|(k, v)| (k.to_string(), v.to_string())) - .collect() - }) - .unwrap_or_default(), - vec![], - ), - ); - } - } - SpanSet::SharedLocalSpans(spans) => { - for span in spans.spans.iter() { - collect.entry(item.collect_id).or_default().insert( - Some(span.id), - ( - span.name.to_string(), - vec![], - span.properties - .as_ref() - .map(|properties| { - properties - .iter() - .map(|(k, v)| (k.to_string(), v.to_string())) - .collect() - }) - .unwrap_or_default(), - vec![], - ), - ); - } + } + SpanSet::SharedLocalSpans(spans) => { + for span in spans.spans.iter() { + collect.entry(token.collect_id).or_default().insert( + Some(span.id), + ( + span.name.to_string(), + vec![], + span.properties + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(), + vec![], + ), + ); } } } } for (span_set, token) in span_sets { - for item in token.iter() { - match span_set { - SpanSet::Span(span) => { - let parent_id = span.parent_id.unwrap_or(item.parent_id); + match span_set { + SpanSet::Span(span) => { + let parent_id = span.parent_id.unwrap_or(token.parent_id); + collect + .get_mut(&token.collect_id) + .as_mut() + .unwrap() + .get_mut(&Some(parent_id)) + .as_mut() + .unwrap() + .1 + .push(span.id); + } + SpanSet::LocalSpansInner(spans) => { + for span in spans.spans.iter() { + let parent_id = span.parent_id.unwrap_or(token.parent_id); collect - .get_mut(&item.collect_id) + .get_mut(&token.collect_id) .as_mut() .unwrap() .get_mut(&Some(parent_id)) @@ -204,33 +194,19 @@ impl Tree { .1 .push(span.id); } - SpanSet::LocalSpansInner(spans) => { - for span in spans.spans.iter() { - let parent_id = span.parent_id.unwrap_or(item.parent_id); - collect - .get_mut(&item.collect_id) - .as_mut() - .unwrap() - .get_mut(&Some(parent_id)) - .as_mut() - .unwrap() - .1 - .push(span.id); - } - } - SpanSet::SharedLocalSpans(spans) => { - for span in spans.spans.iter() { - let parent_id = span.parent_id.unwrap_or(item.parent_id); - collect - .get_mut(&item.collect_id) - .as_mut() - .unwrap() - .get_mut(&Some(parent_id)) - .as_mut() - .unwrap() - .1 - .push(span.id); - } + } + SpanSet::SharedLocalSpans(spans) => { + for span in spans.spans.iter() { + let parent_id = span.parent_id.unwrap_or(token.parent_id); + collect + .get_mut(&token.collect_id) + .as_mut() + .unwrap() + .get_mut(&Some(parent_id)) + .as_mut() + .unwrap() + .1 + .push(span.id); } } } diff --git a/fastrace/tests/lib.rs b/fastrace/tests/lib.rs index 393ab589..2164c071 100644 --- a/fastrace/tests/lib.rs +++ b/fastrace/tests/lib.rs @@ -60,6 +60,54 @@ fn single_thread_single_span() { "###); } +#[test] +#[serial] +fn span_links() { + let (reporter, collected_spans) = TestReporter::new(); + fastrace::set_reporter(reporter, Config::default()); + + let root1 = Span::root("root1", SpanContext::new(TraceId(1), SpanId(0))); + let root2 = Span::root("root2", SpanContext::new(TraceId(2), SpanId(0))); + let link = SpanContext::from_span(&root2).unwrap(); + + let child = Span::enter_with_parent("child", &root1).with_link(link); + child.add_link(link); + + drop(child); + drop(root1); + drop(root2); + + fastrace::flush(); + + let spans = collected_spans.lock(); + let child_record = spans.iter().find(|span| span.name == "child").unwrap(); + assert_eq!(child_record.links, vec![link, link]); +} + +#[test] +#[serial] +fn local_span_links() { + let (reporter, collected_spans) = TestReporter::new(); + fastrace::set_reporter(reporter, Config::default()); + + let root = Span::root("root", SpanContext::new(TraceId(1), SpanId(0))); + let link1 = SpanContext::new(TraceId(2), SpanId(1)); + let link2 = SpanContext::new(TraceId(3), SpanId(2)); + + { + let _g = root.set_local_parent(); + let _span = LocalSpan::enter_with_local_parent("local").with_link(link1); + LocalSpan::add_link(link2); + } + + drop(root); + fastrace::flush(); + + let spans = collected_spans.lock(); + let local_record = spans.iter().find(|span| span.name == "local").unwrap(); + assert_eq!(local_record.links, vec![link1, link2]); +} + #[test] #[serial] fn single_thread_multiple_spans() { @@ -192,128 +240,6 @@ fn multiple_threads_single_span() { "###); } -#[test] -#[serial] -fn multiple_threads_multiple_spans() { - let (reporter, collected_spans) = TestReporter::new(); - fastrace::set_reporter(reporter, Config::default()); - - crossbeam::scope(|scope| { - let root1 = Span::root("root1", SpanContext::new(TraceId(12), SpanId(0))); - let root2 = Span::root("root2", SpanContext::new(TraceId(13), SpanId(0))); - let local_collector = LocalCollector::start(); - - let mut handles = vec![]; - - for _ in 0..4 { - let merged = Span::enter_with_parents("merged", vec![&root1, &root2]); - let _g = merged.set_local_parent(); - let _local = LocalSpan::enter_with_local_parent("local"); - let h = scope.spawn(move |_| { - let local_collector = LocalCollector::start(); - - four_spans(); - - let local_spans = local_collector.collect(); - merged.push_child_spans(local_spans); - }); - - handles.push(h); - } - - four_spans(); - - handles.into_iter().for_each(|h| h.join().unwrap()); - - let local_spans = local_collector.collect(); - root1.push_child_spans(local_spans.clone()); - root2.push_child_spans(local_spans); - }) - .unwrap(); - - fastrace::flush(); - - let graph1 = tree_str_from_span_records( - collected_spans - .lock() - .iter() - .filter(|s| s.trace_id == TraceId(12)) - .cloned() - .collect(), - ); - insta::assert_snapshot!(graph1, @r###" - root1 [] - iter-span-0 [("tmp_property", "tmp_value")] - iter-span-1 [("tmp_property", "tmp_value")] - merged [] - iter-span-0 [("tmp_property", "tmp_value")] - iter-span-1 [("tmp_property", "tmp_value")] - local [] - rec-span [] - rec-span [] - merged [] - iter-span-0 [("tmp_property", "tmp_value")] - iter-span-1 [("tmp_property", "tmp_value")] - local [] - rec-span [] - rec-span [] - merged [] - iter-span-0 [("tmp_property", "tmp_value")] - iter-span-1 [("tmp_property", "tmp_value")] - local [] - rec-span [] - rec-span [] - merged [] - iter-span-0 [("tmp_property", "tmp_value")] - iter-span-1 [("tmp_property", "tmp_value")] - local [] - rec-span [] - rec-span [] - rec-span [] - rec-span [] - "###); - - let graph2 = tree_str_from_span_records( - collected_spans - .lock() - .iter() - .filter(|s| s.trace_id == TraceId(13)) - .cloned() - .collect(), - ); - insta::assert_snapshot!(graph2, @r###" - root2 [] - iter-span-0 [("tmp_property", "tmp_value")] - iter-span-1 [("tmp_property", "tmp_value")] - merged [] - iter-span-0 [("tmp_property", "tmp_value")] - iter-span-1 [("tmp_property", "tmp_value")] - local [] - rec-span [] - rec-span [] - merged [] - iter-span-0 [("tmp_property", "tmp_value")] - iter-span-1 [("tmp_property", "tmp_value")] - local [] - rec-span [] - rec-span [] - merged [] - iter-span-0 [("tmp_property", "tmp_value")] - iter-span-1 [("tmp_property", "tmp_value")] - local [] - rec-span [] - rec-span [] - merged [] - iter-span-0 [("tmp_property", "tmp_value")] - iter-span-1 [("tmp_property", "tmp_value")] - local [] - rec-span [] - rec-span [] - rec-span [] - rec-span [] - "###); -} - #[test] #[serial] fn multiple_spans_without_local_spans() { diff --git a/tests/statically-disable/src/main.rs b/tests/statically-disable/src/main.rs index 0f925136..9457b341 100644 --- a/tests/statically-disable/src/main.rs +++ b/tests/statically-disable/src/main.rs @@ -40,34 +40,37 @@ fn main() { ); let root = Span::root("root", SpanContext::new(TraceId(0), SpanId(0))) - .with_property(|| ("k1", "v1")) - .with_properties(|| [("k2", "v2")]); - - root.add_property(|| ("k3", "v3")); - root.add_properties(|| [("k4", "v4")]); + .with_property(|| ("k0", "v0")) + .with_properties(|| [("k1", "v1")]) + .with_link(SpanContext::new(TraceId(1), SpanId(1))); + root.add_property(|| ("k1.5", "v1.5")); + root.add_properties(|| [("k2", "v2")]); root.add_event( Event::new("event") - .with_property(|| ("k1", "v1")) - .with_properties(|| [("k2", "v2")]), + .with_property(|| ("k0", "v0")) + .with_properties(|| [("k1", "v1")]), ); + root.add_link(SpanContext::new(TraceId(1), SpanId(1))); let _g = root.set_local_parent(); LocalSpan::add_event( Event::new("event") - .with_property(|| ("k1", "v1")) - .with_properties(|| [("k2", "v2")]), + .with_property(|| ("k0", "v0")) + .with_properties(|| [("k1", "v1")]), ); - let _span1 = LocalSpan::enter_with_local_parent("span1") - .with_property(|| ("k", "v")) + let _span1 = LocalSpan::enter_with_local_parent("span1").with_property(|| ("k0", "v0")); + let _span2 = LocalSpan::enter_with_local_parent("span2") + .with_property(|| ("k1", "v1")) .with_properties(|| [("k", "v")]); + let _span3 = LocalSpan::enter_with_local_parent("span3") + .with_link(SpanContext::new(TraceId(1), SpanId(1))); - let _span2 = LocalSpan::enter_with_local_parent("span2"); - - LocalSpan::add_property(|| ("k", "v")); + LocalSpan::add_property(|| ("k0", "v0")); LocalSpan::add_properties(|| [("k", "v")]); + LocalSpan::add_link(SpanContext::new(TraceId(1), SpanId(1))); let local_collector = LocalCollector::start(); let _ = LocalSpan::enter_with_local_parent("span3"); @@ -76,12 +79,12 @@ fn main() { let span3 = Span::enter_with_parent("span3", &root); let span4 = Span::enter_with_local_parent("span4"); - let span5 = Span::enter_with_parents("span5", [&root, &span3, &span4]); - span5.push_child_spans(local_spans); + span4.push_child_spans(local_spans); assert!(SpanContext::current_local_parent().is_none()); - assert!(SpanContext::from_span(&span5).is_none()); + assert!(SpanContext::from_span(&span3).is_none()); + assert!(SpanContext::from_span(&span4).is_none()); assert!(root.elapsed().is_none()); From 29f45be31db6d77c2e4507b9c8e1923c371ec8eb Mon Sep 17 00:00:00 2001 From: andylokandy Date: Sun, 1 Feb 2026 17:59:43 +0800 Subject: [PATCH 2/6] fix --- fastrace/src/span.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/fastrace/src/span.rs b/fastrace/src/span.rs index 657426df..c66efc5e 100644 --- a/fastrace/src/span.rs +++ b/fastrace/src/span.rs @@ -9,20 +9,20 @@ use std::time::Duration; use fastant::Instant; -use crate::collector::global_collector::NOT_SAMPLED_COLLECT_ID; +use crate::Event; use crate::collector::CollectToken; use crate::collector::GlobalCollect; use crate::collector::SpanContext; use crate::collector::SpanId; use crate::collector::SpanSet; +use crate::collector::global_collector::NOT_SAMPLED_COLLECT_ID; +use crate::local::LocalCollector; +use crate::local::LocalSpans; use crate::local::local_collector::LocalSpansInner; -use crate::local::local_span_stack::LocalSpanStack; use crate::local::local_span_stack::LOCAL_SPAN_STACK; +use crate::local::local_span_stack::LocalSpanStack; use crate::local::raw_span::RawKind; use crate::local::raw_span::RawSpan; -use crate::local::LocalCollector; -use crate::local::LocalSpans; -use crate::Event; /// A thread-safe span. #[must_use] @@ -724,12 +724,12 @@ fn current_collect() -> GlobalCollect { #[cfg(test)] mod tests { + use std::sync::Mutex; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; - use std::sync::Mutex; - use mockall::predicate; use mockall::Sequence; + use mockall::predicate; use rand::rng; use rand::seq::SliceRandom; From 6cdba9aed7c2aa72b1de37d44038002b85514a96 Mon Sep 17 00:00:00 2001 From: andylokandy Date: Mon, 23 Feb 2026 16:29:04 +0800 Subject: [PATCH 3/6] fix --- fastrace/src/event.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastrace/src/event.rs b/fastrace/src/event.rs index eba8f841..e462d09e 100644 --- a/fastrace/src/event.rs +++ b/fastrace/src/event.rs @@ -39,7 +39,7 @@ impl Event { /// ``` /// use fastrace::prelude::*; /// - /// LocalSpan::add_event(Event::new("event").with_properties(|| [("key", "value")])); + /// /// LocalSpan::add_event(Event::new("event").with_property(|| ("key", "value"))); /// ``` #[inline] pub fn with_property(self, property: F) -> Self From 88d8c6aef135d1aafe1ca518f8dd859e145a22a1 Mon Sep 17 00:00:00 2001 From: andylokandy Date: Mon, 23 Feb 2026 16:35:30 +0800 Subject: [PATCH 4/6] fix --- fastrace/src/collector/global_collector.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/fastrace/src/collector/global_collector.rs b/fastrace/src/collector/global_collector.rs index c4099f80..2b7f606f 100644 --- a/fastrace/src/collector/global_collector.rs +++ b/fastrace/src/collector/global_collector.rs @@ -474,12 +474,10 @@ fn amend_span( .push(DanglingItem::Properties(span.properties.clone())); } RawKind::Link => { - if !span.links.is_empty() { - dangling - .entry(parent_id) - .or_default() - .push(DanglingItem::Links(span.links.clone())); - } + dangling + .entry(parent_id) + .or_default() + .push(DanglingItem::Links(span.links.clone())); } } } From 721e18adca6f41b49362a5621ad9725f23fe6040 Mon Sep 17 00:00:00 2001 From: andylokandy Date: Mon, 23 Feb 2026 18:51:07 +0800 Subject: [PATCH 5/6] fix --- fastrace/src/event.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastrace/src/event.rs b/fastrace/src/event.rs index e462d09e..04f4b48a 100644 --- a/fastrace/src/event.rs +++ b/fastrace/src/event.rs @@ -39,7 +39,7 @@ impl Event { /// ``` /// use fastrace::prelude::*; /// - /// /// LocalSpan::add_event(Event::new("event").with_property(|| ("key", "value"))); + /// LocalSpan::add_event(Event::new("event").with_property(|| ("key", "value"))); /// ``` #[inline] pub fn with_property(self, property: F) -> Self From b4632a7a940f3e51f1768d60d9cd5a088cf0ea62 Mon Sep 17 00:00:00 2001 From: andylokandy Date: Mon, 23 Feb 2026 19:09:00 +0800 Subject: [PATCH 6/6] fix --- examples/asynchronous.rs | 2 +- examples/synchronous.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/asynchronous.rs b/examples/asynchronous.rs index 1a029464..3e9407cb 100644 --- a/examples/asynchronous.rs +++ b/examples/asynchronous.rs @@ -62,7 +62,7 @@ async fn main() { let f = async { let jhs = { let _span = LocalSpan::enter_with_local_parent("a span") - .with_properties(|| [("a property", "a value")]); + .with_property(|| ("a property", "a value")); parallel_job() }; diff --git a/examples/synchronous.rs b/examples/synchronous.rs index a298e177..d2bc0d73 100644 --- a/examples/synchronous.rs +++ b/examples/synchronous.rs @@ -45,7 +45,7 @@ async fn main() { let _g = root.set_local_parent(); let _span = LocalSpan::enter_with_local_parent("a span") - .with_properties(|| [("a property", "a value")]); + .with_property(|| ("a property", "a value")); for i in 1..=10 { func1(i);