diff --git a/opentelemetry-datadog/src/exporter/mod.rs b/opentelemetry-datadog/src/exporter/mod.rs index 7f7d57c362..e928c1c396 100644 --- a/opentelemetry-datadog/src/exporter/mod.rs +++ b/opentelemetry-datadog/src/exporter/mod.rs @@ -5,6 +5,7 @@ pub use model::ApiVersion; pub use model::Error; pub use model::FieldMappingFn; +use std::borrow::Cow; use std::fmt::{Debug, Formatter}; use crate::exporter::model::FieldMapping; @@ -165,18 +166,16 @@ impl DatadogPipelineBuilder { let service_name = self.service_name.take(); if let Some(service_name) = service_name { let config = if let Some(mut cfg) = self.trace_config.take() { - cfg.resource = cfg.resource.map(|r| { - let without_service_name = r + cfg.resource = Cow::Owned(Resource::new( + cfg.resource .iter() .filter(|(k, _v)| **k != semcov::resource::SERVICE_NAME) - .map(|(k, v)| KeyValue::new(k.clone(), v.clone())) - .collect::>(); - Arc::new(Resource::new(without_service_name)) - }); + .map(|(k, v)| KeyValue::new(k.clone(), v.clone())), + )); cfg } else { Config { - resource: Some(Arc::new(Resource::empty())), + resource: Cow::Owned(Resource::empty()), ..Default::default() } }; @@ -190,7 +189,7 @@ impl DatadogPipelineBuilder { ( Config { // use a empty resource to prevent TracerProvider to assign a service name. - resource: Some(Arc::new(Resource::empty())), + resource: Cow::Owned(Resource::empty()), ..Default::default() }, service_name, diff --git a/opentelemetry-datadog/src/exporter/model/mod.rs b/opentelemetry-datadog/src/exporter/model/mod.rs index dac2864733..56ed6d2eb9 100644 --- a/opentelemetry-datadog/src/exporter/model/mod.rs +++ b/opentelemetry-datadog/src/exporter/model/mod.rs @@ -179,12 +179,13 @@ impl ApiVersion { #[cfg(test)] pub(crate) mod tests { use super::*; - use opentelemetry::sdk; use opentelemetry::sdk::InstrumentationLibrary; + use opentelemetry::sdk::{self, Resource}; use opentelemetry::{ trace::{SpanContext, SpanId, SpanKind, Status, TraceFlags, TraceId, TraceState}, Key, }; + use std::borrow::Cow; use std::time::{Duration, SystemTime}; fn get_traces() -> Vec> { @@ -221,7 +222,7 @@ pub(crate) mod tests { events, links, status: Status::Ok, - resource: None, + resource: Cow::Owned(Resource::empty()), instrumentation_lib: InstrumentationLibrary::new("component", None, None), } } diff --git a/opentelemetry-jaeger/src/exporter/config/agent.rs b/opentelemetry-jaeger/src/exporter/config/agent.rs index efefc17a9b..28a3f72d96 100644 --- a/opentelemetry-jaeger/src/exporter/config/agent.rs +++ b/opentelemetry-jaeger/src/exporter/config/agent.rs @@ -232,7 +232,6 @@ impl AgentPipeline { let mut builder = sdk::trace::TracerProvider::builder(); let (config, process) = build_config_and_process( - builder.sdk_provided_resource(), self.trace_config.take(), self.transformation_config.service_name.take(), ); @@ -270,7 +269,6 @@ impl AgentPipeline { // build sdk trace config and jaeger process. // some attributes like service name has attributes like service name let (config, process) = build_config_and_process( - builder.sdk_provided_resource(), self.trace_config.take(), self.transformation_config.service_name.take(), ); @@ -312,12 +310,10 @@ impl AgentPipeline { where R: JaegerTraceRuntime, { - let builder = sdk::trace::TracerProvider::builder(); let export_instrument_library = self.transformation_config.export_instrument_library; // build sdk trace config and jaeger process. // some attributes like service name has attributes like service name let (_, process) = build_config_and_process( - builder.sdk_provided_resource(), self.trace_config.take(), self.transformation_config.service_name.take(), ); @@ -331,9 +327,7 @@ impl AgentPipeline { /// Build an jaeger exporter targeting a jaeger agent and running on the sync runtime. pub fn build_sync_agent_exporter(mut self) -> Result { - let builder = sdk::trace::TracerProvider::builder(); let (_, process) = build_config_and_process( - builder.sdk_provided_resource(), self.trace_config.take(), self.transformation_config.service_name.take(), ); diff --git a/opentelemetry-jaeger/src/exporter/config/collector/http_client.rs b/opentelemetry-jaeger/src/exporter/config/collector/http_client.rs index 34b3779d5f..eaf8d989e9 100644 --- a/opentelemetry-jaeger/src/exporter/config/collector/http_client.rs +++ b/opentelemetry-jaeger/src/exporter/config/collector/http_client.rs @@ -179,18 +179,14 @@ mod collector_client_tests { use crate::exporter::thrift::jaeger::Batch; use crate::new_collector_pipeline; use opentelemetry::runtime::Tokio; - use opentelemetry::sdk::Resource; use opentelemetry::trace::TraceError; - use opentelemetry::KeyValue; #[test] fn test_bring_your_own_client() -> Result<(), TraceError> { let invalid_uri_builder = new_collector_pipeline() .with_endpoint("localhost:6831") .with_http_client(test_http_client::TestHttpClient); - let sdk_provided_resource = - Resource::new(vec![KeyValue::new("service.name", "unknown_service")]); - let (_, process) = build_config_and_process(sdk_provided_resource, None, None); + let (_, process) = build_config_and_process(None, None); let mut uploader = invalid_uri_builder.build_uploader::()?; let res = futures_executor::block_on(async { uploader diff --git a/opentelemetry-jaeger/src/exporter/config/collector/mod.rs b/opentelemetry-jaeger/src/exporter/config/collector/mod.rs index 205c142695..8736c575e1 100644 --- a/opentelemetry-jaeger/src/exporter/config/collector/mod.rs +++ b/opentelemetry-jaeger/src/exporter/config/collector/mod.rs @@ -407,7 +407,6 @@ impl CollectorPipeline { // some attributes like service name has attributes like service name let export_instrument_library = self.transformation_config.export_instrument_library; let (config, process) = build_config_and_process( - builder.sdk_provided_resource(), self.trace_config.take(), self.transformation_config.service_name.take(), ); diff --git a/opentelemetry-jaeger/src/exporter/config/mod.rs b/opentelemetry-jaeger/src/exporter/config/mod.rs index a363c0270a..8a576d83d9 100644 --- a/opentelemetry-jaeger/src/exporter/config/mod.rs +++ b/opentelemetry-jaeger/src/exporter/config/mod.rs @@ -10,12 +10,9 @@ //! [jaeger deployment guide]: https://www.jaegertracing.io/docs/1.31/deployment use crate::Process; -use opentelemetry::sdk::trace::Config; -use opentelemetry::sdk::Resource; use opentelemetry::trace::{TraceError, TracerProvider}; use opentelemetry::{global, sdk, KeyValue}; use opentelemetry_semantic_conventions as semcov; -use std::sync::Arc; /// Config a exporter that sends the spans to a [jaeger agent](https://www.jaegertracing.io/docs/1.31/deployment/#agent). pub mod agent; @@ -54,35 +51,25 @@ trait HasRequiredConfig { // There are multiple ways to set the service name. A `service.name` tag will be always added // to the process tags. fn build_config_and_process( - sdk_resource: sdk::Resource, - mut config: Option, + config: Option, service_name_opt: Option, ) -> (sdk::trace::Config, Process) { - let (config, resource) = if let Some(mut config) = config.take() { - let resource = if let Some(resource) = config.resource.replace(Arc::new(Resource::empty())) - { - sdk_resource.merge(resource) - } else { - sdk_resource - }; - - (config, resource) - } else { - (Config::default(), sdk_resource) - }; + let config = config.unwrap_or_default(); let service_name = service_name_opt.unwrap_or_else(|| { - resource + config + .resource .get(semcov::resource::SERVICE_NAME) .map(|v| v.to_string()) .unwrap_or_else(|| "unknown_service".to_string()) }); // merge the tags and resource. Resources take priority. - let mut tags = resource - .into_iter() - .filter(|(key, _)| *key != semcov::resource::SERVICE_NAME) - .map(|(key, value)| KeyValue::new(key, value)) + let mut tags = config + .resource + .iter() + .filter(|(key, _)| **key != semcov::resource::SERVICE_NAME) + .map(|(key, value)| KeyValue::new(key.clone(), value.clone())) .collect::>(); tags.push(KeyValue::new( @@ -101,51 +88,20 @@ mod tests { use opentelemetry::sdk::Resource; use opentelemetry::KeyValue; use std::env; - use std::sync::Arc; #[test] fn test_set_service_name() { let service_name = "halloween_service".to_string(); // set via builder's service name, it has highest priority - let (_, process) = - build_config_and_process(Resource::empty(), None, Some(service_name.clone())); + let (_, process) = build_config_and_process(None, Some(service_name.clone())); assert_eq!(process.service_name, service_name); // make sure the tags in resource are moved to process let trace_config = Config::default() .with_resource(Resource::new(vec![KeyValue::new("test-key", "test-value")])); - let (config, process) = - build_config_and_process(Resource::empty(), Some(trace_config), Some(service_name)); - assert_eq!(config.resource, Some(Arc::new(Resource::empty()))); + let (_, process) = build_config_and_process(Some(trace_config), Some(service_name)); assert_eq!(process.tags.len(), 2); - - // sdk provided resource can override service name if users didn't provided service name to builder - let (_, process) = build_config_and_process( - Resource::new(vec![KeyValue::new("service.name", "halloween_service")]), - None, - None, - ); - assert_eq!(process.service_name, "halloween_service"); - - // users can also provided service.name from config's resource, in this case, it will override the - // sdk provided service name - let trace_config = Config::default().with_resource(Resource::new(vec![KeyValue::new( - "service.name", - "override_service", - )])); - let (_, process) = build_config_and_process( - Resource::new(vec![KeyValue::new("service.name", "halloween_service")]), - Some(trace_config), - None, - ); - - assert_eq!(process.service_name, "override_service"); - assert_eq!(process.tags.len(), 1); - assert_eq!( - process.tags[0], - KeyValue::new("service.name", "override_service") - ); } #[test] diff --git a/opentelemetry-otlp/src/transform/metrics.rs b/opentelemetry-otlp/src/transform/metrics.rs index 1befe5a520..093b96c206 100644 --- a/opentelemetry-otlp/src/transform/metrics.rs +++ b/opentelemetry-otlp/src/transform/metrics.rs @@ -417,7 +417,16 @@ mod tests { // If we changed the sink function to process the input in parallel, we will have to sort other vectors // like data points in Metrics. fn assert_resource_metrics(mut expect: ResourceMetrics, mut actual: ResourceMetrics) { - assert_eq!(expect.resource, actual.resource); + assert_eq!( + expect + .resource + .as_mut() + .map(|r| r.attributes.sort_by_key(|kv| kv.key.to_string())), + actual + .resource + .as_mut() + .map(|r| r.attributes.sort_by_key(|kv| kv.key.to_string())) + ); assert_eq!( expect.instrumentation_library_metrics.len(), actual.instrumentation_library_metrics.len() diff --git a/opentelemetry-proto/src/transform/traces.rs b/opentelemetry-proto/src/transform/traces.rs index 1a4634ffb2..f8990b19ec 100644 --- a/opentelemetry-proto/src/transform/traces.rs +++ b/opentelemetry-proto/src/transform/traces.rs @@ -51,15 +51,13 @@ pub mod tonic { let span_kind: span::SpanKind = source_span.span_kind.into(); ResourceSpans { resource: Some(Resource { - attributes: resource_attributes( - source_span.resource.as_ref().map(AsRef::as_ref), - ) - .0, + attributes: resource_attributes(&source_span.resource).0, dropped_attributes_count: 0, }), schema_url: source_span .resource - .and_then(|resource| resource.schema_url().map(|url| url.to_string())) + .schema_url() + .map(|url| url.to_string()) .unwrap_or_default(), instrumentation_library_spans: vec![InstrumentationLibrarySpans { schema_url: source_span @@ -112,14 +110,11 @@ pub mod tonic { } } - fn resource_attributes(resource: Option<&sdk::Resource>) -> Attributes { + fn resource_attributes(resource: &sdk::Resource) -> Attributes { resource - .map(|res| { - res.iter() - .map(|(k, v)| opentelemetry::KeyValue::new(k.clone(), v.clone())) - .collect::>() - }) - .unwrap_or_default() + .iter() + .map(|(k, v)| opentelemetry::KeyValue::new(k.clone(), v.clone())) + .collect::>() .into() } } @@ -175,10 +170,7 @@ pub mod grpcio { fn from(source_span: SpanData) -> Self { ResourceSpans { resource: SingularPtrField::from(Some(Resource { - attributes: resource_attributes( - source_span.resource.as_ref().map(AsRef::as_ref), - ) - .0, + attributes: resource_attributes(&source_span.resource).0, dropped_attributes_count: 0, ..Default::default() })), @@ -246,15 +238,11 @@ pub mod grpcio { } } - fn resource_attributes(resource: Option<&sdk::Resource>) -> Attributes { + fn resource_attributes(resource: &sdk::Resource) -> Attributes { resource - .map(|resource| { - resource - .iter() - .map(|(k, v)| opentelemetry::KeyValue::new(k.clone(), v.clone())) - .collect::>() - }) - .unwrap_or_default() + .iter() + .map(|(k, v)| opentelemetry::KeyValue::new(k.clone(), v.clone())) + .collect::>() .into() } } diff --git a/opentelemetry-sdk/Cargo.toml b/opentelemetry-sdk/Cargo.toml index 37595ae041..09073ff893 100644 --- a/opentelemetry-sdk/Cargo.toml +++ b/opentelemetry-sdk/Cargo.toml @@ -4,15 +4,17 @@ version = "0.1.0" edition = "2018" [dependencies] -opentelemetry-api = { version = "0.1", path = "../opentelemetry-api/" } async-std = { version = "1.6", features = ["unstable"], optional = true } async-trait = { version = "0.1", optional = true } +crossbeam-channel = { version = "0.5", optional = true } dashmap = { version = "4.0.1", optional = true } fnv = { version = "1.0", optional = true } futures-channel = "0.3" futures-executor = "0.3" futures-util = { version = "0.3", default-features = false, features = ["std", "sink"] } lazy_static = "1.4" +once_cell = "1.10" +opentelemetry-api = { version = "0.1", path = "../opentelemetry-api/" } percent-encoding = { version = "2.0", optional = true } pin-project = { version = "1.0.2", optional = true } rand = { version = "0.8", default-features = false, features = ["std", "std_rng"], optional = true } @@ -20,7 +22,6 @@ serde = { version = "1.0", features = ["derive", "rc"], optional = true } thiserror = "1" tokio = { version = "1.0", default-features = false, features = ["rt", "time"], optional = true } tokio-stream = { version = "0.1", optional = true } -crossbeam-channel = { version = "0.5", optional = true } [package.metadata.docs.rs] all-features = true diff --git a/opentelemetry-sdk/benches/batch_span_processor.rs b/opentelemetry-sdk/benches/batch_span_processor.rs index 1d872739cb..f4ef6e0ed0 100644 --- a/opentelemetry-sdk/benches/batch_span_processor.rs +++ b/opentelemetry-sdk/benches/batch_span_processor.rs @@ -6,6 +6,8 @@ use opentelemetry_sdk::export::trace::SpanData; use opentelemetry_sdk::runtime::Tokio; use opentelemetry_sdk::testing::trace::NoopSpanExporter; use opentelemetry_sdk::trace::{BatchSpanProcessor, EvictedHashMap, EvictedQueue, SpanProcessor}; +use opentelemetry_sdk::Resource; +use std::borrow::Cow; use std::sync::Arc; use std::time::SystemTime; use tokio::runtime::Runtime; @@ -30,7 +32,7 @@ fn get_span_data() -> Vec { events: EvictedQueue::new(12), links: EvictedQueue::new(12), status: Status::Unset, - resource: None, + resource: Cow::Owned(Resource::empty()), instrumentation_lib: Default::default(), }) .collect::>() diff --git a/opentelemetry-sdk/src/export/trace/mod.rs b/opentelemetry-sdk/src/export/trace/mod.rs index 5460afe017..daab93371e 100644 --- a/opentelemetry-sdk/src/export/trace/mod.rs +++ b/opentelemetry-sdk/src/export/trace/mod.rs @@ -1,9 +1,9 @@ //! Trace exporters +use crate::Resource; use async_trait::async_trait; use opentelemetry_api::trace::{Event, Link, SpanContext, SpanId, SpanKind, Status, TraceError}; use std::borrow::Cow; use std::fmt::Debug; -use std::sync::Arc; use std::time::SystemTime; pub mod stdout; @@ -73,7 +73,7 @@ pub struct SpanData { /// Span status pub status: Status, /// Resource contains attributes representing an entity that produced this span. - pub resource: Option>, + pub resource: Cow<'static, Resource>, /// Instrumentation library that produced this span pub instrumentation_lib: crate::InstrumentationLibrary, } diff --git a/opentelemetry-sdk/src/resource/mod.rs b/opentelemetry-sdk/src/resource/mod.rs index ccbf4dbca5..b443773059 100644 --- a/opentelemetry-sdk/src/resource/mod.rs +++ b/opentelemetry-sdk/src/resource/mod.rs @@ -31,14 +31,14 @@ pub use process::ProcessResourceDetector; use opentelemetry_api::attributes; use opentelemetry_api::{Key, KeyValue, Value}; use std::borrow::Cow; -use std::collections::{btree_map, BTreeMap}; +use std::collections::{hash_map, HashMap}; use std::ops::Deref; use std::time::Duration; /// An immutable representation of the entity producing telemetry as attributes. #[derive(Clone, Debug, PartialEq)] pub struct Resource { - attrs: BTreeMap, + attrs: HashMap, schema_url: Option>, } @@ -46,7 +46,10 @@ impl Default for Resource { fn default() -> Self { Self::from_detectors( Duration::from_secs(0), - vec![Box::new(EnvResourceDetector::new())], + vec![ + Box::new(SdkProvidedResourceDetector), + Box::new(EnvResourceDetector::new()), + ], ) } } @@ -196,7 +199,7 @@ impl Resource { /// An owned iterator over the entries of a `Resource`. #[derive(Debug)] -pub struct IntoIter(btree_map::IntoIter); +pub struct IntoIter(hash_map::IntoIter); impl Iterator for IntoIter { type Item = (Key, Value); @@ -217,7 +220,7 @@ impl IntoIterator for Resource { /// An iterator over the entries of a `Resource`. #[derive(Debug)] -pub struct Iter<'a>(btree_map::Iter<'a, Key, Value>); +pub struct Iter<'a>(hash_map::Iter<'a, Key, Value>); impl<'a> Iterator for Iter<'a> { type Item = (&'a Key, &'a Value); @@ -256,14 +259,14 @@ pub trait ResourceDetector { mod tests { use super::*; use crate::resource::EnvResourceDetector; - use std::collections::BTreeMap; + use std::collections::HashMap; use std::{env, time}; #[test] fn new_resource() { let args_with_dupe_keys = vec![KeyValue::new("a", ""), KeyValue::new("a", "final")]; - let mut expected_attrs = BTreeMap::new(); + let mut expected_attrs = HashMap::new(); expected_attrs.insert(Key::new("a"), Value::from("final")); assert_eq!( @@ -289,7 +292,7 @@ mod tests { KeyValue::new("d", ""), ]); - let mut expected_attrs = BTreeMap::new(); + let mut expected_attrs = HashMap::new(); expected_attrs.insert(Key::new("a"), Value::from("a-value")); expected_attrs.insert(Key::new("b"), Value::from("b-value")); expected_attrs.insert(Key::new("c"), Value::from("c-value")); diff --git a/opentelemetry-sdk/src/trace/config.rs b/opentelemetry-sdk/src/trace/config.rs index c4a7f07aaf..65a8daee23 100644 --- a/opentelemetry-sdk/src/trace/config.rs +++ b/opentelemetry-sdk/src/trace/config.rs @@ -3,10 +3,11 @@ //! Configuration represents the global tracing configuration, overrides //! can be set for the default OpenTelemetry limits and Sampler. use crate::trace::{span_limit::SpanLimits, IdGenerator, RandomIdGenerator, Sampler, ShouldSample}; +use crate::Resource; use opentelemetry_api::global::{handle_error, Error}; +use std::borrow::Cow; use std::env; use std::str::FromStr; -use std::sync::Arc; /// Default trace configuration pub fn config() -> Config { @@ -18,12 +19,15 @@ pub fn config() -> Config { pub struct Config { /// The sampler that the sdk should use pub sampler: Box, + /// The id generator that the sdk should use pub id_generator: Box, + /// span limits pub span_limits: SpanLimits, + /// Contains attributes representing an entity that produces telemetry. - pub resource: Option>, + pub resource: Cow<'static, Resource>, } impl Config { @@ -76,20 +80,10 @@ impl Config { } /// Specify the attributes representing the entity that produces telemetry - pub fn with_resource(mut self, resource: crate::Resource) -> Self { - self.resource = Some(Arc::new(resource)); + pub fn with_resource(mut self, resource: Resource) -> Self { + self.resource = Cow::Owned(resource); self } - - /// Use empty resource instead of default resource in this config. - /// - /// Usually if no resource is provided, SDK will assign a default resource - /// to the `TracerProvider`, which could impact the performance. Performance - /// sensitive application can use function to disable such behavior and assign - /// no resource to `TracerProvider`. - pub fn with_no_resource(self) -> Self { - self.with_resource(crate::Resource::empty()) - } } impl Default for Config { @@ -99,7 +93,7 @@ impl Default for Config { sampler: Box::new(Sampler::ParentBased(Box::new(Sampler::AlwaysOn))), id_generator: Box::new(RandomIdGenerator::default()), span_limits: SpanLimits::default(), - resource: None, + resource: Cow::Owned(Resource::default()), }; if let Some(max_attributes_per_span) = env::var("OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT") diff --git a/opentelemetry-sdk/src/trace/provider.rs b/opentelemetry-sdk/src/trace/provider.rs index 824fe43c6e..5a5736271c 100644 --- a/opentelemetry-sdk/src/trace/provider.rs +++ b/opentelemetry-sdk/src/trace/provider.rs @@ -8,17 +8,17 @@ //! propagators) are provided by the `TracerProvider`. `Tracer` instances do //! not duplicate this data to avoid that different `Tracer` instances //! of the `TracerProvider` have different versions of these data. -use crate::resource::{EnvResourceDetector, SdkProvidedResourceDetector}; use crate::trace::{runtime::TraceRuntime, BatchSpanProcessor, SimpleSpanProcessor, Tracer}; use crate::{export::trace::SpanExporter, trace::SpanProcessor}; use crate::{InstrumentationLibrary, Resource}; +use once_cell::sync::OnceCell; use opentelemetry_api::{global, trace::TraceResult}; use std::borrow::Cow; use std::sync::Arc; -use std::time::Duration; /// Default tracer name if empty string is provided. const DEFAULT_COMPONENT_NAME: &str = "rust.opentelemetry.io/sdk/tracer"; +static PROVIDER_RESOURCE: OnceCell = OnceCell::new(); /// TracerProvider inner type #[derive(Debug)] @@ -144,27 +144,10 @@ impl opentelemetry_api::trace::TracerProvider for TracerProvider { } /// Builder for provider attributes. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Builder { processors: Vec>, config: crate::trace::Config, - sdk_provided_resource: Resource, -} - -impl Default for Builder { - fn default() -> Self { - Builder { - processors: Default::default(), - config: Default::default(), - sdk_provided_resource: Resource::from_detectors( - Duration::from_secs(0), - vec![ - Box::new(SdkProvidedResourceDetector), - Box::new(EnvResourceDetector::new()), - ], - ), - } - } } impl Builder { @@ -199,28 +182,29 @@ impl Builder { Builder { config, ..self } } - /// Return the clone of sdk provided resource. - /// - /// See - /// for details. - pub fn sdk_provided_resource(&self) -> Resource { - self.sdk_provided_resource.clone() - } - /// Create a new provider from this configuration. pub fn build(self) -> TracerProvider { let mut config = self.config; - config.resource = match config.resource { - None => Some(Arc::new(self.sdk_provided_resource)), - // User provided resource information has higher priority. - Some(resource) => { - if resource.is_empty() { - None - } else { - Some(Arc::new(self.sdk_provided_resource.merge(resource))) + + // Standard config will contain an owned `Resource` (either sdk default or use supplied) + // we can optimize the common case with a static ref to avoid cloning the underlying + // resource data for each span. + // + // For the uncommon case where there are multiple tracer providers with different resource + // configurations, users can optionally provide their own borrowed static resource. + if matches!(config.resource, Cow::Owned(_)) { + config.resource = match PROVIDER_RESOURCE.try_insert(config.resource.into_owned()) { + Ok(static_resource) => Cow::Borrowed(static_resource), + Err((prev, new)) => { + if prev == &new { + Cow::Borrowed(prev) + } else { + Cow::Owned(new) + } } } - }; + } + TracerProvider { inner: Arc::new(TracerProviderInner { processors: self.processors, @@ -238,6 +222,7 @@ mod tests { use crate::Resource; use opentelemetry_api::trace::{TraceError, TraceResult}; use opentelemetry_api::{Context, Key, KeyValue}; + use std::borrow::Cow; use std::env; use std::sync::Arc; @@ -288,9 +273,11 @@ mod tests { let assert_service_name = |provider: super::TracerProvider, expect: Option<&'static str>| { assert_eq!( - provider.config().resource.as_ref().and_then(|r| r + provider + .config() + .resource .get(Key::from_static_str("service.name")) - .map(|v| v.to_string())), + .map(|v| v.to_string()), expect.map(|s| s.to_string()) ); }; @@ -300,10 +287,10 @@ mod tests { // If user didn't provided a resource, try to get a default from env var let custom_config_provider = super::TracerProvider::builder() .with_config(Config { - resource: Some(Arc::new(Resource::new(vec![KeyValue::new( + resource: Cow::Owned(Resource::new(vec![KeyValue::new( "service.name", "test_service", - )]))), + )])), ..Default::default() }) .build(); @@ -314,11 +301,11 @@ mod tests { let env_resource_provider = super::TracerProvider::builder().build(); assert_eq!( env_resource_provider.config().resource, - Some(Arc::new(Resource::new(vec![ + Cow::Owned(Resource::new(vec![ KeyValue::new("key1", "value1"), KeyValue::new("k3", "value2"), KeyValue::new("service.name", "unknown_service"), - ]))) + ])) ); // When `OTEL_RESOURCE_ATTRIBUTES` is set and also user provided config @@ -328,27 +315,26 @@ mod tests { ); let user_provided_resource_config_provider = super::TracerProvider::builder() .with_config(Config { - resource: Some(Arc::new(Resource::new(vec![KeyValue::new( - "my-custom-key", - "my-custom-value", - )]))), + resource: Cow::Owned(Resource::default().merge(&mut Resource::new(vec![ + KeyValue::new("my-custom-key", "my-custom-value"), + ]))), ..Default::default() }) .build(); assert_eq!( user_provided_resource_config_provider.config().resource, - Some(Arc::new(Resource::new(vec![ + Cow::Owned(Resource::new(vec![ KeyValue::new("my-custom-key", "my-custom-value"), KeyValue::new("k2", "value2"), KeyValue::new("service.name", "unknown_service"), - ]))) + ])) ); env::remove_var("OTEL_RESOURCE_ATTRIBUTES"); // If user provided a resource, it takes priority during collision. let no_service_name = super::TracerProvider::builder() .with_config(Config { - resource: Some(Arc::new(Resource::empty())), + resource: Cow::Owned(Resource::empty()), ..Default::default() }) .build(); diff --git a/opentelemetry-sdk/src/trace/span.rs b/opentelemetry-sdk/src/trace/span.rs index 03383c9322..f9c942981a 100644 --- a/opentelemetry-sdk/src/trace/span.rs +++ b/opentelemetry-sdk/src/trace/span.rs @@ -9,10 +9,10 @@ //! is possible to change its name, set its `Attributes`, and add `Links` and `Events`. //! These cannot be changed after the `Span`'s end time has been set. use crate::trace::SpanLimits; +use crate::Resource; use opentelemetry_api::trace::{Event, SpanContext, SpanId, SpanKind, Status}; use opentelemetry_api::{trace, KeyValue}; use std::borrow::Cow; -use std::sync::Arc; use std::time::SystemTime; /// Single operation within a trace. @@ -74,11 +74,8 @@ impl Span { /// overhead. pub fn exported_data(&self) -> Option { let (span_context, tracer) = (self.span_context.clone(), &self.tracer); - let resource = if let Some(provider) = self.tracer.provider() { - provider.config().resource.clone() - } else { - None - }; + let resource = self.tracer.provider()?.config().resource.clone(); + self.data .as_ref() .map(|data| build_export_data(data.clone(), span_context, resource, tracer)) @@ -221,7 +218,7 @@ impl Drop for Span { fn build_export_data( data: SpanData, span_context: SpanContext, - resource: Option>, + resource: Cow<'static, Resource>, tracer: &crate::trace::Tracer, ) -> crate::export::trace::SpanData { crate::export::trace::SpanData { diff --git a/opentelemetry-zipkin/src/exporter/mod.rs b/opentelemetry-zipkin/src/exporter/mod.rs index 0218e3a325..45d5a6f30d 100644 --- a/opentelemetry-zipkin/src/exporter/mod.rs +++ b/opentelemetry-zipkin/src/exporter/mod.rs @@ -18,6 +18,7 @@ use opentelemetry::{ }; use opentelemetry_http::HttpClient; use opentelemetry_semantic_conventions as semcov; +use std::borrow::Cow; #[cfg(all( not(feature = "reqwest-client"), not(feature = "reqwest-blocking-client"), @@ -25,7 +26,6 @@ use opentelemetry_semantic_conventions as semcov; ))] use std::convert::TryFrom; use std::net::SocketAddr; -use std::sync::Arc; use std::time::Duration; /// Zipkin span exporter @@ -118,18 +118,17 @@ impl ZipkinPipelineBuilder { let service_name = self.service_name.take(); if let Some(service_name) = service_name { let config = if let Some(mut cfg) = self.trace_config.take() { - cfg.resource = cfg.resource.map(|r| { - let without_service_name = r + cfg.resource = Cow::Owned(Resource::new( + cfg.resource .iter() .filter(|(k, _v)| **k != semcov::resource::SERVICE_NAME) .map(|(k, v)| KeyValue::new(k.clone(), v.clone())) - .collect::>(); - Arc::new(Resource::new(without_service_name)) - }); + .collect::>(), + )); cfg } else { Config { - resource: Some(Arc::new(Resource::empty())), + resource: Cow::Owned(Resource::empty()), ..Default::default() } }; @@ -143,7 +142,7 @@ impl ZipkinPipelineBuilder { ( Config { // use a empty resource to prevent TracerProvider to assign a service name. - resource: Some(Arc::new(Resource::empty())), + resource: Cow::Owned(Resource::empty()), ..Default::default() }, Endpoint::new(service_name, self.service_addr), diff --git a/opentelemetry-zipkin/src/exporter/model/span.rs b/opentelemetry-zipkin/src/exporter/model/span.rs index 2360a6f531..8c1e782e51 100644 --- a/opentelemetry-zipkin/src/exporter/model/span.rs +++ b/opentelemetry-zipkin/src/exporter/model/span.rs @@ -61,7 +61,9 @@ mod tests { use crate::exporter::model::{into_zipkin_span, OTEL_ERROR_DESCRIPTION, OTEL_STATUS_CODE}; use opentelemetry::sdk::export::trace::SpanData; use opentelemetry::sdk::trace::{EvictedHashMap, EvictedQueue}; + use opentelemetry::sdk::Resource; use opentelemetry::trace::{SpanContext, SpanId, SpanKind, Status, TraceFlags, TraceId}; + use std::borrow::Cow; use std::collections::HashMap; use std::net::Ipv4Addr; use std::time::SystemTime; @@ -164,7 +166,7 @@ mod tests { events: EvictedQueue::new(20), links: EvictedQueue::new(20), status, - resource: None, + resource: Cow::Owned(Resource::default()), instrumentation_lib: Default::default(), }; let local_endpoint = Endpoint::new("test".into(), None);