diff --git a/opentelemetry-sdk/CHANGELOG.md b/opentelemetry-sdk/CHANGELOG.md index 6be068e8cd..c8f1d0622c 100644 --- a/opentelemetry-sdk/CHANGELOG.md +++ b/opentelemetry-sdk/CHANGELOG.md @@ -13,6 +13,7 @@ - **Breaking** The SDK `testing` feature is now runtime agnostic. [#3407][3407] - `TokioSpanExporter` and `new_tokio_test_exporter` have been renamed to `TestSpanExporter` and `new_test_exporter`. - The following transitive dependencies and features have been removed: `tokio/rt`, `tokio/time`, `tokio/macros`, `tokio/rt-multi-thread`, `tokio-stream`, `experimental_async_runtime` +- Store `InstrumentationScope` in `Arc` internally in `SdkTracer`, making tracer clones cheaper (Arc refcount increment instead of deep copy). - Add 32-bit platform support by using `portable-atomic` for `AtomicI64` and `AtomicU64` in the metrics module. This enables compilation on 32-bit ARM targets (e.g., `armv5te-unknown-linux-gnueabi`, `armv7-unknown-linux-gnueabihf`). - `Aggregation` enum and `StreamBuilder::with_aggregation()` are now stable and no longer require the `spec_unstable_metrics_views` feature flag. - Fix `service.name` Resource attribute fallback to follow OpenTelemetry diff --git a/opentelemetry-sdk/src/trace/span.rs b/opentelemetry-sdk/src/trace/span.rs index 4753650d53..09cc7474c2 100644 --- a/opentelemetry-sdk/src/trace/span.rs +++ b/opentelemetry-sdk/src/trace/span.rs @@ -709,6 +709,42 @@ mod tests { assert_eq!(event_vec.len(), DEFAULT_MAX_EVENT_PER_SPAN as usize); } + #[test] + fn multiple_processors_receive_span_data() { + use crate::trace::InMemorySpanExporterBuilder; + + let exporter1 = InMemorySpanExporterBuilder::new().build(); + let exporter2 = InMemorySpanExporterBuilder::new().build(); + + let provider = crate::trace::SdkTracerProvider::builder() + .with_simple_exporter(exporter1.clone()) + .with_simple_exporter(exporter2.clone()) + .build(); + + let tracer = provider.tracer("test"); + let mut span = tracer.start("multi_processor_span"); + span.set_attribute(KeyValue::new("key", "value")); + span.end(); + + let spans1 = exporter1.get_finished_spans().unwrap(); + let spans2 = exporter2.get_finished_spans().unwrap(); + + assert_eq!(spans1.len(), 1); + assert_eq!(spans2.len(), 1); + assert_eq!(spans1[0].name, "multi_processor_span"); + assert_eq!(spans2[0].name, "multi_processor_span"); + assert_eq!(spans1[0].attributes, spans2[0].attributes); + // Verify instrumentation scope is correctly propagated to both processors + assert_eq!(spans1[0].instrumentation_scope.name(), "test"); + assert_eq!(spans2[0].instrumentation_scope.name(), "test"); + assert_eq!( + spans1[0].instrumentation_scope, + spans2[0].instrumentation_scope + ); + + let _ = provider.shutdown(); + } + #[test] fn test_span_exported_data() { let provider = crate::trace::SdkTracerProvider::builder() diff --git a/opentelemetry-sdk/src/trace/tracer.rs b/opentelemetry-sdk/src/trace/tracer.rs index 279992b56b..bb454defb9 100644 --- a/opentelemetry-sdk/src/trace/tracer.rs +++ b/opentelemetry-sdk/src/trace/tracer.rs @@ -17,11 +17,12 @@ use opentelemetry::{ Context, InstrumentationScope, KeyValue, }; use std::fmt; +use std::sync::Arc; /// `Tracer` implementation to create and manage spans #[derive(Clone)] pub struct SdkTracer { - scope: InstrumentationScope, + scope: Arc, provider: SdkTracerProvider, } @@ -39,7 +40,10 @@ impl fmt::Debug for SdkTracer { impl SdkTracer { /// Create a new tracer (used internally by `TracerProvider`s). pub(crate) fn new(scope: InstrumentationScope, provider: SdkTracerProvider) -> Self { - SdkTracer { scope, provider } + SdkTracer { + scope: Arc::new(scope), + provider, + } } /// TracerProvider associated with this tracer.