Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion opentelemetry-sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

## vNext

- *Breaking* Make `force_flush()` in `PushMetricExporter` synchronous
- *Breaking*: Calls to `MeterProviderBuilder::with_resource`, `TracerProviderBuilder::with_resource`,
`LoggerProviderBuilder::with_resource` are now additive ([#2677](https://github.com/open-telemetry/opentelemetry-rust/pull/2677)).
- *Breaking*: Make `force_flush()` in `PushMetricExporter` synchronous

## 0.28.0

Expand Down
42 changes: 38 additions & 4 deletions opentelemetry-sdk/src/logs/logger_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,11 +235,16 @@ impl LoggerProviderBuilder {
}

/// The `Resource` to be associated with this Provider.
///
/// *Note*: Calls to this method are additive, each call merges the provided
/// resource with the previous one.
pub fn with_resource(self, resource: Resource) -> Self {
LoggerProviderBuilder {
resource: Some(resource),
..self
}
let resource = match self.resource {
Some(existing) => Some(existing.merge(&resource)),
None => Some(resource),
};

LoggerProviderBuilder { resource, ..self }
}

/// Create a new provider from this configuration.
Expand Down Expand Up @@ -712,6 +717,35 @@ mod tests {
assert_eq!(scoped_logger.instrumentation_scope().name(), "");
}

#[test]
fn with_resource_multiple_calls_ensure_additive() {
let builder = SdkLoggerProvider::builder()
.with_resource(Resource::new(vec![KeyValue::new("key1", "value1")]))
.with_resource(Resource::new(vec![KeyValue::new("key2", "value2")]))
.with_resource(
Resource::builder_empty()
.with_schema_url(vec![], "http://example.com")
.build(),
)
.with_resource(Resource::new(vec![KeyValue::new("key3", "value3")]));

let resource = builder.resource.unwrap();

assert_eq!(
resource.get(&Key::from_static_str("key1")),
Some(Value::from("value1"))
);
assert_eq!(
resource.get(&Key::from_static_str("key2")),
Some(Value::from("value2"))
);
assert_eq!(
resource.get(&Key::from_static_str("key3")),
Some(Value::from("value3"))
);
assert_eq!(resource.schema_url(), Some("http://example.com"));
}

#[derive(Debug)]
pub(crate) struct LazyLogProcessor {
shutdown_called: Arc<Mutex<bool>>,
Expand Down
39 changes: 38 additions & 1 deletion opentelemetry-sdk/src/metrics/meter_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,16 @@ impl MeterProviderBuilder {
///
/// By default, if this option is not used, the default [Resource] will be used.
///
/// *Note*: Calls to this method are additive, each call merges the provided
/// resource with the previous one.
///
/// [Meter]: opentelemetry::metrics::Meter
pub fn with_resource(mut self, resource: Resource) -> Self {
self.resource = Some(resource);
self.resource = match self.resource {
Some(existing) => Some(existing.merge(&resource)),
None => Some(resource),
};

self
}

Expand Down Expand Up @@ -322,6 +329,7 @@ impl fmt::Debug for MeterProviderBuilder {
#[cfg(all(test, feature = "testing"))]
mod tests {
use crate::error::OTelSdkError;
use crate::metrics::SdkMeterProvider;
use crate::resource::{
SERVICE_NAME, TELEMETRY_SDK_LANGUAGE, TELEMETRY_SDK_NAME, TELEMETRY_SDK_VERSION,
};
Expand Down Expand Up @@ -553,4 +561,33 @@ mod tests {

assert_eq!(provider.inner.meters.lock().unwrap().len(), 5);
}

#[test]
fn with_resource_multiple_calls_ensure_additive() {
let builder = SdkMeterProvider::builder()
.with_resource(Resource::new(vec![KeyValue::new("key1", "value1")]))
.with_resource(Resource::new(vec![KeyValue::new("key2", "value2")]))
.with_resource(
Resource::builder_empty()
.with_schema_url(vec![], "http://example.com")
.build(),
)
.with_resource(Resource::new(vec![KeyValue::new("key3", "value3")]));

let resource = builder.resource.unwrap();

assert_eq!(
resource.get(&Key::from_static_str("key1")),
Some(Value::from("value1"))
);
assert_eq!(
resource.get(&Key::from_static_str("key2")),
Some(Value::from("value2"))
);
assert_eq!(
resource.get(&Key::from_static_str("key3")),
Some(Value::from("value3"))
);
assert_eq!(resource.schema_url(), Some("http://example.com"));
}
}
33 changes: 22 additions & 11 deletions opentelemetry-sdk/src/resource/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,11 @@ impl Resource {
/// 5. If both resources do not have a schema url, the schema url will be empty.
///
/// [Schema url]: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.9.0/specification/schemas/overview.md#schema-url
fn merge<T: Deref<Target = Self>>(&self, other: T) -> Self {
if self.is_empty() {
pub(crate) fn merge<T: Deref<Target = Self>>(&self, other: T) -> Self {
if self.is_empty() && self.schema_url().is_none() {
return other.clone();
}
if other.is_empty() {
if other.is_empty() && other.schema_url().is_none() {
return self.clone();
}
let mut combined_attrs = self.inner.attrs.clone();
Expand Down Expand Up @@ -419,18 +419,29 @@ mod tests {
}

#[rstest]
#[case(vec![], vec![KeyValue::new("key", "b")], "http://schema/a", None)]
#[case(vec![KeyValue::new("key", "a")], vec![KeyValue::new("key", "b")], "http://schema/a", Some("http://schema/a"))]
fn merge_resource_with_missing_attribtes(
#[case(vec![], vec![KeyValue::new("key", "b")], Some("http://schema/a"), None, Some("http://schema/a"))]
#[case(vec![KeyValue::new("key", "a")], vec![KeyValue::new("key", "b")], Some("http://schema/a"), None, Some("http://schema/a"))]
#[case(vec![KeyValue::new("key", "a")], vec![KeyValue::new("key", "b")], Some("http://schema/a"), None, Some("http://schema/a"))]
#[case(vec![KeyValue::new("key", "a")], vec![KeyValue::new("key", "b")], Some("http://schema/a"), Some("http://schema/b"), None)]
#[case(vec![KeyValue::new("key", "a")], vec![KeyValue::new("key", "b")], None, Some("http://schema/b"), Some("http://schema/b"))]
fn merge_resource_with_missing_attributes(
#[case] key_values_a: Vec<KeyValue>,
#[case] key_values_b: Vec<KeyValue>,
#[case] schema_url: &'static str,
#[case] schema_url_a: Option<&'static str>,
#[case] schema_url_b: Option<&'static str>,
#[case] expected_schema_url: Option<&'static str>,
) {
let resource = Resource::from_schema_url(key_values_a, schema_url);
let other_resource = Resource::builder_empty()
.with_attributes(key_values_b)
.build();
let resource = match schema_url_a {
Some(schema) => Resource::from_schema_url(key_values_a, schema),
None => Resource::new(key_values_a),
};

let other_resource = match schema_url_b {
Some(schema) => Resource::builder_empty()
.with_schema_url(key_values_b, schema)
.build(),
None => Resource::new(key_values_b),
};

assert_eq!(
resource.merge(&other_resource).schema_url(),
Expand Down
54 changes: 49 additions & 5 deletions opentelemetry-sdk/src/trace/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ impl opentelemetry::trace::TracerProvider for SdkTracerProvider {
pub struct TracerProviderBuilder {
processors: Vec<Box<dyn SpanProcessor>>,
config: crate::trace::Config,
resource: Option<Resource>,
}

impl TracerProviderBuilder {
Expand Down Expand Up @@ -410,18 +411,28 @@ impl TracerProviderBuilder {
///
/// By default, if this option is not used, the default [Resource] will be used.
///
/// *Note*: Calls to this method are additive, each call merges the provided
/// resource with the previous one.
///
/// [Tracer]: opentelemetry::trace::Tracer
pub fn with_resource(self, resource: Resource) -> Self {
TracerProviderBuilder {
config: self.config.with_resource(resource),
..self
}
let resource = match self.resource {
Some(existing) => Some(existing.merge(&resource)),
None => Some(resource),
};

TracerProviderBuilder { resource, ..self }
}

/// Create a new provider from this configuration.
pub fn build(self) -> SdkTracerProvider {
let mut config = self.config;

// Now, we can update the config with the resource.
if let Some(resource) = self.resource {
config = config.with_resource(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.
Expand Down Expand Up @@ -462,8 +473,8 @@ mod tests {
SERVICE_NAME, TELEMETRY_SDK_LANGUAGE, TELEMETRY_SDK_NAME, TELEMETRY_SDK_VERSION,
};
use crate::trace::provider::TracerProviderInner;
use crate::trace::SpanData;
use crate::trace::{Config, Span, SpanProcessor};
use crate::trace::{SdkTracerProvider, SpanData};
use crate::Resource;
use opentelemetry::trace::{Tracer, TracerProvider};
use opentelemetry::{Context, Key, KeyValue, Value};
Expand Down Expand Up @@ -730,6 +741,39 @@ mod tests {
assert!(test_tracer_1.provider().is_shutdown());
}

#[test]
fn with_resource_multiple_calls_ensure_additive() {
let resource = SdkTracerProvider::builder()
.with_resource(Resource::new(vec![KeyValue::new("key1", "value1")]))
.with_resource(Resource::new(vec![KeyValue::new("key2", "value2")]))
.with_resource(
Resource::builder_empty()
.with_schema_url(vec![], "http://example.com")
.build(),
)
.with_resource(Resource::new(vec![KeyValue::new("key3", "value3")]))
.build()
.inner
.config
.resource
.clone()
.into_owned();

assert_eq!(
resource.get(&Key::from_static_str("key1")),
Some(Value::from("value1"))
);
assert_eq!(
resource.get(&Key::from_static_str("key2")),
Some(Value::from("value2"))
);
assert_eq!(
resource.get(&Key::from_static_str("key3")),
Some(Value::from("value3"))
);
assert_eq!(resource.schema_url(), Some("http://example.com"));
}

#[derive(Debug)]
struct CountingShutdownProcessor {
shutdown_count: Arc<AtomicU32>,
Expand Down