Skip to content

Commit f01e8f4

Browse files
martintmklalitbcijothomas
authored
Calls to with_resource for signal builders (Metrics, Logs, Traces) are now additive (#2677)
Co-authored-by: Lalit Kumar Bhasin <[email protected]> Co-authored-by: Cijo Thomas <[email protected]>
1 parent dc9a5c8 commit f01e8f4

File tree

5 files changed

+150
-22
lines changed

5 files changed

+150
-22
lines changed

opentelemetry-sdk/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
## vNext
44

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

79
## 0.28.0
810

opentelemetry-sdk/src/logs/logger_provider.rs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,11 +235,16 @@ impl LoggerProviderBuilder {
235235
}
236236

237237
/// The `Resource` to be associated with this Provider.
238+
///
239+
/// *Note*: Calls to this method are additive, each call merges the provided
240+
/// resource with the previous one.
238241
pub fn with_resource(self, resource: Resource) -> Self {
239-
LoggerProviderBuilder {
240-
resource: Some(resource),
241-
..self
242-
}
242+
let resource = match self.resource {
243+
Some(existing) => Some(existing.merge(&resource)),
244+
None => Some(resource),
245+
};
246+
247+
LoggerProviderBuilder { resource, ..self }
243248
}
244249

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

720+
#[test]
721+
fn with_resource_multiple_calls_ensure_additive() {
722+
let builder = SdkLoggerProvider::builder()
723+
.with_resource(Resource::new(vec![KeyValue::new("key1", "value1")]))
724+
.with_resource(Resource::new(vec![KeyValue::new("key2", "value2")]))
725+
.with_resource(
726+
Resource::builder_empty()
727+
.with_schema_url(vec![], "http://example.com")
728+
.build(),
729+
)
730+
.with_resource(Resource::new(vec![KeyValue::new("key3", "value3")]));
731+
732+
let resource = builder.resource.unwrap();
733+
734+
assert_eq!(
735+
resource.get(&Key::from_static_str("key1")),
736+
Some(Value::from("value1"))
737+
);
738+
assert_eq!(
739+
resource.get(&Key::from_static_str("key2")),
740+
Some(Value::from("value2"))
741+
);
742+
assert_eq!(
743+
resource.get(&Key::from_static_str("key3")),
744+
Some(Value::from("value3"))
745+
);
746+
assert_eq!(resource.schema_url(), Some("http://example.com"));
747+
}
748+
715749
#[derive(Debug)]
716750
pub(crate) struct LazyLogProcessor {
717751
shutdown_called: Arc<Mutex<bool>>,

opentelemetry-sdk/src/metrics/meter_provider.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,9 +233,16 @@ impl MeterProviderBuilder {
233233
///
234234
/// By default, if this option is not used, the default [Resource] will be used.
235235
///
236+
/// *Note*: Calls to this method are additive, each call merges the provided
237+
/// resource with the previous one.
238+
///
236239
/// [Meter]: opentelemetry::metrics::Meter
237240
pub fn with_resource(mut self, resource: Resource) -> Self {
238-
self.resource = Some(resource);
241+
self.resource = match self.resource {
242+
Some(existing) => Some(existing.merge(&resource)),
243+
None => Some(resource),
244+
};
245+
239246
self
240247
}
241248

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

554562
assert_eq!(provider.inner.meters.lock().unwrap().len(), 5);
555563
}
564+
565+
#[test]
566+
fn with_resource_multiple_calls_ensure_additive() {
567+
let builder = SdkMeterProvider::builder()
568+
.with_resource(Resource::new(vec![KeyValue::new("key1", "value1")]))
569+
.with_resource(Resource::new(vec![KeyValue::new("key2", "value2")]))
570+
.with_resource(
571+
Resource::builder_empty()
572+
.with_schema_url(vec![], "http://example.com")
573+
.build(),
574+
)
575+
.with_resource(Resource::new(vec![KeyValue::new("key3", "value3")]));
576+
577+
let resource = builder.resource.unwrap();
578+
579+
assert_eq!(
580+
resource.get(&Key::from_static_str("key1")),
581+
Some(Value::from("value1"))
582+
);
583+
assert_eq!(
584+
resource.get(&Key::from_static_str("key2")),
585+
Some(Value::from("value2"))
586+
);
587+
assert_eq!(
588+
resource.get(&Key::from_static_str("key3")),
589+
Some(Value::from("value3"))
590+
);
591+
assert_eq!(resource.schema_url(), Some("http://example.com"));
592+
}
556593
}

opentelemetry-sdk/src/resource/mod.rs

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -169,11 +169,11 @@ impl Resource {
169169
/// 5. If both resources do not have a schema url, the schema url will be empty.
170170
///
171171
/// [Schema url]: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.9.0/specification/schemas/overview.md#schema-url
172-
fn merge<T: Deref<Target = Self>>(&self, other: T) -> Self {
173-
if self.is_empty() {
172+
pub(crate) fn merge<T: Deref<Target = Self>>(&self, other: T) -> Self {
173+
if self.is_empty() && self.schema_url().is_none() {
174174
return other.clone();
175175
}
176-
if other.is_empty() {
176+
if other.is_empty() && other.schema_url().is_none() {
177177
return self.clone();
178178
}
179179
let mut combined_attrs = self.inner.attrs.clone();
@@ -419,18 +419,29 @@ mod tests {
419419
}
420420

421421
#[rstest]
422-
#[case(vec![], vec![KeyValue::new("key", "b")], "http://schema/a", None)]
423-
#[case(vec![KeyValue::new("key", "a")], vec![KeyValue::new("key", "b")], "http://schema/a", Some("http://schema/a"))]
424-
fn merge_resource_with_missing_attribtes(
422+
#[case(vec![], vec![KeyValue::new("key", "b")], Some("http://schema/a"), None, Some("http://schema/a"))]
423+
#[case(vec![KeyValue::new("key", "a")], vec![KeyValue::new("key", "b")], Some("http://schema/a"), None, Some("http://schema/a"))]
424+
#[case(vec![KeyValue::new("key", "a")], vec![KeyValue::new("key", "b")], Some("http://schema/a"), None, Some("http://schema/a"))]
425+
#[case(vec![KeyValue::new("key", "a")], vec![KeyValue::new("key", "b")], Some("http://schema/a"), Some("http://schema/b"), None)]
426+
#[case(vec![KeyValue::new("key", "a")], vec![KeyValue::new("key", "b")], None, Some("http://schema/b"), Some("http://schema/b"))]
427+
fn merge_resource_with_missing_attributes(
425428
#[case] key_values_a: Vec<KeyValue>,
426429
#[case] key_values_b: Vec<KeyValue>,
427-
#[case] schema_url: &'static str,
430+
#[case] schema_url_a: Option<&'static str>,
431+
#[case] schema_url_b: Option<&'static str>,
428432
#[case] expected_schema_url: Option<&'static str>,
429433
) {
430-
let resource = Resource::from_schema_url(key_values_a, schema_url);
431-
let other_resource = Resource::builder_empty()
432-
.with_attributes(key_values_b)
433-
.build();
434+
let resource = match schema_url_a {
435+
Some(schema) => Resource::from_schema_url(key_values_a, schema),
436+
None => Resource::new(key_values_a),
437+
};
438+
439+
let other_resource = match schema_url_b {
440+
Some(schema) => Resource::builder_empty()
441+
.with_schema_url(key_values_b, schema)
442+
.build(),
443+
None => Resource::new(key_values_b),
444+
};
434445

435446
assert_eq!(
436447
resource.merge(&other_resource).schema_url(),

opentelemetry-sdk/src/trace/provider.rs

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ impl opentelemetry::trace::TracerProvider for SdkTracerProvider {
293293
pub struct TracerProviderBuilder {
294294
processors: Vec<Box<dyn SpanProcessor>>,
295295
config: crate::trace::Config,
296+
resource: Option<Resource>,
296297
}
297298

298299
impl TracerProviderBuilder {
@@ -410,18 +411,28 @@ impl TracerProviderBuilder {
410411
///
411412
/// By default, if this option is not used, the default [Resource] will be used.
412413
///
414+
/// *Note*: Calls to this method are additive, each call merges the provided
415+
/// resource with the previous one.
416+
///
413417
/// [Tracer]: opentelemetry::trace::Tracer
414418
pub fn with_resource(self, resource: Resource) -> Self {
415-
TracerProviderBuilder {
416-
config: self.config.with_resource(resource),
417-
..self
418-
}
419+
let resource = match self.resource {
420+
Some(existing) => Some(existing.merge(&resource)),
421+
None => Some(resource),
422+
};
423+
424+
TracerProviderBuilder { resource, ..self }
419425
}
420426

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

431+
// Now, we can update the config with the resource.
432+
if let Some(resource) = self.resource {
433+
config = config.with_resource(resource);
434+
};
435+
425436
// Standard config will contain an owned [`Resource`] (either sdk default or use supplied)
426437
// we can optimize the common case with a static ref to avoid cloning the underlying
427438
// resource data for each span.
@@ -462,8 +473,8 @@ mod tests {
462473
SERVICE_NAME, TELEMETRY_SDK_LANGUAGE, TELEMETRY_SDK_NAME, TELEMETRY_SDK_VERSION,
463474
};
464475
use crate::trace::provider::TracerProviderInner;
465-
use crate::trace::SpanData;
466476
use crate::trace::{Config, Span, SpanProcessor};
477+
use crate::trace::{SdkTracerProvider, SpanData};
467478
use crate::Resource;
468479
use opentelemetry::trace::{Tracer, TracerProvider};
469480
use opentelemetry::{Context, Key, KeyValue, Value};
@@ -730,6 +741,39 @@ mod tests {
730741
assert!(test_tracer_1.provider().is_shutdown());
731742
}
732743

744+
#[test]
745+
fn with_resource_multiple_calls_ensure_additive() {
746+
let resource = SdkTracerProvider::builder()
747+
.with_resource(Resource::new(vec![KeyValue::new("key1", "value1")]))
748+
.with_resource(Resource::new(vec![KeyValue::new("key2", "value2")]))
749+
.with_resource(
750+
Resource::builder_empty()
751+
.with_schema_url(vec![], "http://example.com")
752+
.build(),
753+
)
754+
.with_resource(Resource::new(vec![KeyValue::new("key3", "value3")]))
755+
.build()
756+
.inner
757+
.config
758+
.resource
759+
.clone()
760+
.into_owned();
761+
762+
assert_eq!(
763+
resource.get(&Key::from_static_str("key1")),
764+
Some(Value::from("value1"))
765+
);
766+
assert_eq!(
767+
resource.get(&Key::from_static_str("key2")),
768+
Some(Value::from("value2"))
769+
);
770+
assert_eq!(
771+
resource.get(&Key::from_static_str("key3")),
772+
Some(Value::from("value3"))
773+
);
774+
assert_eq!(resource.schema_url(), Some("http://example.com"));
775+
}
776+
733777
#[derive(Debug)]
734778
struct CountingShutdownProcessor {
735779
shutdown_count: Arc<AtomicU32>,

0 commit comments

Comments
 (0)