Skip to content

Commit cd60550

Browse files
committed
feat: allow Resource transformation in TracerProviderBuilder
1 parent 353bbb0 commit cd60550

File tree

2 files changed

+211
-0
lines changed

2 files changed

+211
-0
lines changed

opentelemetry-sdk/src/resource/mod.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ impl Resource {
7878
}
7979
}
8080

81+
/// Returns a [ResourceBuilder] initialized from this resource.
82+
pub fn into_builder(self) -> ResourceBuilder {
83+
ResourceBuilder { resource: self }
84+
}
85+
8186
/// Creates an empty resource.
8287
/// This is the basic constructor that initializes a resource with no attributes and no schema URL.
8388
pub(crate) fn empty() -> Self {
@@ -230,6 +235,12 @@ impl Resource {
230235
}
231236
}
232237

238+
impl From<Resource> for ResourceBuilder {
239+
fn from(resource: Resource) -> Self {
240+
resource.into_builder()
241+
}
242+
}
243+
233244
/// An iterator over the entries of a `Resource`.
234245
#[derive(Debug)]
235246
pub struct Iter<'a>(hash_map::Iter<'a, Key, Value>);
@@ -549,4 +560,51 @@ mod tests {
549560
},
550561
)
551562
}
563+
564+
#[test]
565+
fn into_builder() {
566+
// Create a resource with some attributes and schema URL
567+
let original_resource = Resource::from_schema_url(
568+
[
569+
KeyValue::new("service.name", "test-service"),
570+
KeyValue::new("service.version", "1.0.0"),
571+
KeyValue::new("environment", "test"),
572+
],
573+
"http://example.com/schema",
574+
);
575+
576+
// Get a builder from the resource
577+
let builder: ResourceBuilder = original_resource.clone().into();
578+
let rebuilt_resource = builder.build();
579+
580+
// The rebuilt resource should be identical to the original
581+
assert_eq!(original_resource, rebuilt_resource);
582+
assert_eq!(
583+
original_resource.schema_url(),
584+
rebuilt_resource.schema_url()
585+
);
586+
assert_eq!(original_resource.len(), rebuilt_resource.len());
587+
588+
// Verify we can modify the builder and get a different resource
589+
let modified_resource = original_resource
590+
.clone()
591+
.into_builder()
592+
.with_attribute(KeyValue::new("new_key", "new_value"))
593+
.build();
594+
595+
// The modified resource should have the original attributes plus the new one
596+
assert_eq!(modified_resource.len(), original_resource.len() + 1);
597+
assert_eq!(
598+
modified_resource.get(&Key::new("new_key")),
599+
Some(Value::from("new_value"))
600+
);
601+
assert_eq!(
602+
modified_resource.get(&Key::new("service.name")),
603+
Some(Value::from("test-service"))
604+
);
605+
assert_eq!(
606+
modified_resource.schema_url(),
607+
original_resource.schema_url()
608+
);
609+
}
552610
}

opentelemetry-sdk/src/trace/provider.rs

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,25 @@ impl TracerProviderBuilder {
422422
TracerProviderBuilder { resource, ..self }
423423
}
424424

425+
/// Transforms the current [Resource] in this builder using a function.
426+
///
427+
/// The transformed [Resource] represents the entity producing telemetry and
428+
/// is associated with all [Tracer]s the [SdkTracerProvider] will create.
429+
///
430+
/// By default, if this option is not used, the default [Resource] will be used.
431+
///
432+
/// *Note*: Calls to this method are not additive, the returned resource will
433+
/// completely replace the existing [Resource].
434+
///
435+
/// [Tracer]: opentelemetry::trace::Tracer
436+
pub fn transform_resource<F>(self, f: F) -> Self
437+
where
438+
F: FnOnce(Option<&Resource>) -> Resource,
439+
{
440+
let resource = Some(f(self.resource.as_ref()));
441+
TracerProviderBuilder { resource, ..self }
442+
}
443+
425444
/// Create a new provider from this configuration.
426445
pub fn build(self) -> SdkTracerProvider {
427446
let mut config = self.config;
@@ -882,4 +901,138 @@ mod tests {
882901
// Verify that shutdown was only called once, even after drop
883902
assert_eq!(shutdown_count.load(Ordering::SeqCst), 1);
884903
}
904+
905+
#[test]
906+
fn transform_resource_with_none() {
907+
// Test transform_resource when no resource is set initially
908+
let provider = super::SdkTracerProvider::builder()
909+
.transform_resource(|existing| {
910+
assert!(existing.is_none());
911+
Resource::new(vec![KeyValue::new("transformed", "value")])
912+
})
913+
.build();
914+
915+
assert_eq!(
916+
provider.config().resource.get(&Key::new("transformed")),
917+
Some(Value::from("value"))
918+
);
919+
}
920+
921+
#[test]
922+
fn transform_resource_with_existing() {
923+
// Test transform_resource when a resource already exists
924+
let provider = super::SdkTracerProvider::builder()
925+
.with_resource(Resource::new(vec![KeyValue::new("original", "value1")]))
926+
.transform_resource(|existing| {
927+
assert!(existing.is_some());
928+
let existing_resource = existing.unwrap();
929+
assert_eq!(
930+
existing_resource.get(&Key::new("original")),
931+
Some(Value::from("value1"))
932+
);
933+
934+
// Create a new resource that merges with the existing one
935+
existing_resource
936+
.merge(&Resource::new(vec![KeyValue::new("transformed", "value2")]))
937+
})
938+
.build();
939+
940+
// Both original and transformed attributes should be present
941+
assert_eq!(
942+
provider.config().resource.get(&Key::new("original")),
943+
Some(Value::from("value1"))
944+
);
945+
assert_eq!(
946+
provider.config().resource.get(&Key::new("transformed")),
947+
Some(Value::from("value2"))
948+
);
949+
}
950+
951+
#[test]
952+
fn transform_resource_replace_existing() {
953+
// Test transform_resource that completely replaces the existing resource
954+
let provider = super::SdkTracerProvider::builder()
955+
.with_resource(Resource::new(vec![KeyValue::new("original", "value1")]))
956+
.transform_resource(|_existing| {
957+
// Ignore existing and create a completely new resource
958+
Resource::new(vec![KeyValue::new("replaced", "new_value")])
959+
})
960+
.build();
961+
962+
// Only the new resource should be present
963+
assert_eq!(provider.config().resource.get(&Key::new("original")), None);
964+
assert_eq!(
965+
provider.config().resource.get(&Key::new("replaced")),
966+
Some(Value::from("new_value"))
967+
);
968+
}
969+
970+
#[test]
971+
fn transform_resource_multiple_calls() {
972+
// Test multiple calls to transform_resource
973+
let provider = super::SdkTracerProvider::builder()
974+
.with_resource(Resource::new(vec![KeyValue::new("initial", "value")]))
975+
.transform_resource(|existing| {
976+
existing.unwrap().merge(&Resource::new(vec![KeyValue::new(
977+
"first_transform",
978+
"value1",
979+
)]))
980+
})
981+
.transform_resource(|existing| {
982+
existing.unwrap().merge(&Resource::new(vec![KeyValue::new(
983+
"second_transform",
984+
"value2",
985+
)]))
986+
})
987+
.build();
988+
989+
// All attributes should be present
990+
assert_eq!(
991+
provider.config().resource.get(&Key::new("initial")),
992+
Some(Value::from("value"))
993+
);
994+
assert_eq!(
995+
provider.config().resource.get(&Key::new("first_transform")),
996+
Some(Value::from("value1"))
997+
);
998+
assert_eq!(
999+
provider
1000+
.config()
1001+
.resource
1002+
.get(&Key::new("second_transform")),
1003+
Some(Value::from("value2"))
1004+
);
1005+
}
1006+
1007+
#[test]
1008+
fn transform_resource_with_schema_url() {
1009+
// Test transform_resource preserving schema URL
1010+
let provider = super::SdkTracerProvider::builder()
1011+
.with_resource(
1012+
Resource::builder_empty()
1013+
.with_schema_url(
1014+
vec![KeyValue::new("original", "value")],
1015+
"http://example.com/schema",
1016+
)
1017+
.build(),
1018+
)
1019+
.transform_resource(|existing| {
1020+
let existing_resource = existing.unwrap();
1021+
existing_resource.merge(&Resource::new(vec![KeyValue::new("added", "new_value")]))
1022+
})
1023+
.build();
1024+
1025+
assert_eq!(
1026+
provider.config().resource.get(&Key::new("original")),
1027+
Some(Value::from("value"))
1028+
);
1029+
assert_eq!(
1030+
provider.config().resource.get(&Key::new("added")),
1031+
Some(Value::from("new_value"))
1032+
);
1033+
assert_eq!(
1034+
provider.config().resource.schema_url(),
1035+
Some("http://example.com/schema")
1036+
);
1037+
}
8851038
}

0 commit comments

Comments
 (0)