|
20 | 20 | //! |
21 | 21 | //! The OS and Process resource detectors are packaged separately in the |
22 | 22 | //! [`opentelemetry-resource-detector` crate](https://github.com/open-telemetry/opentelemetry-rust-contrib/tree/main/opentelemetry-resource-detectors). |
23 | | -mod builder; |
24 | 23 | mod env; |
25 | 24 | mod telemetry; |
26 | 25 |
|
27 | 26 | mod attributes; |
28 | 27 | pub(crate) use attributes::*; |
29 | 28 |
|
30 | | -pub use builder::ResourceBuilder; |
31 | 29 | pub use env::EnvResourceDetector; |
32 | 30 | pub use env::SdkProvidedResourceDetector; |
33 | 31 | pub use telemetry::TelemetryResourceDetector; |
@@ -63,27 +61,23 @@ impl Default for Resource { |
63 | 61 | } |
64 | 62 | } |
65 | 63 |
|
66 | | -impl From<ResourceBuilder> for Resource { |
67 | | - fn from(value: ResourceBuilder) -> Self { |
68 | | - value.build() |
69 | | - } |
70 | | -} |
71 | | - |
72 | 64 | impl Resource { |
73 | 65 | /// Creates a Builder that allows you to configure multiple aspects of the Resource. |
74 | 66 | /// |
75 | | - /// If you want to start from a [Resource::default()] see [Resource::builder_default()]. |
76 | | - /// |
77 | | - /// Starts with a [Resource::empty()]. |
| 67 | + /// Starts with a [Resource::default()]. |
78 | 68 | pub fn builder() -> ResourceBuilder { |
79 | | - ResourceBuilder::new() |
| 69 | + ResourceBuilder { |
| 70 | + resource: Resource::default(), |
| 71 | + } |
80 | 72 | } |
81 | 73 |
|
82 | 74 | /// Creates a Builder that allows you to configure multiple aspects of the Resource. |
83 | 75 | /// |
84 | | - /// Starts with a [Resource::default()]. |
85 | | - pub fn builder_default() -> ResourceBuilder { |
86 | | - ResourceBuilder::default() |
| 76 | + /// Starts with a [Resource::empty()]. |
| 77 | + pub fn builder_empty() -> ResourceBuilder { |
| 78 | + ResourceBuilder { |
| 79 | + resource: Resource::empty(), |
| 80 | + } |
87 | 81 | } |
88 | 82 |
|
89 | 83 | /// Creates an empty resource. |
@@ -286,6 +280,65 @@ pub trait ResourceDetector { |
286 | 280 | fn detect(&self) -> Resource; |
287 | 281 | } |
288 | 282 |
|
| 283 | +/// Builder for [Resource] |
| 284 | +#[derive(Debug)] |
| 285 | +pub struct ResourceBuilder { |
| 286 | + resource: Resource, |
| 287 | +} |
| 288 | + |
| 289 | +impl ResourceBuilder { |
| 290 | + /// Add a single [ResourceDetector] to your resource. |
| 291 | + pub fn with_detector(self, detector: Box<dyn ResourceDetector>) -> Self { |
| 292 | + self.with_detectors(vec![detector]) |
| 293 | + } |
| 294 | + |
| 295 | + /// Add multiple [ResourceDetector] to your resource. |
| 296 | + pub fn with_detectors(mut self, detectors: Vec<Box<dyn ResourceDetector>>) -> Self { |
| 297 | + self.resource = self.resource.merge(&Resource::from_detectors(detectors)); |
| 298 | + self |
| 299 | + } |
| 300 | + |
| 301 | + /// Add a [KeyValue] to the resource. |
| 302 | + pub fn with_attribute(self, kv: KeyValue) -> Self { |
| 303 | + self.with_attributes(vec![kv]) |
| 304 | + } |
| 305 | + |
| 306 | + /// Add multiple [KeyValue]s to the resource. |
| 307 | + pub fn with_attributes<T: IntoIterator<Item = KeyValue>>(mut self, kvs: T) -> Self { |
| 308 | + self.resource = self.resource.merge(&Resource::new(kvs)); |
| 309 | + self |
| 310 | + } |
| 311 | + |
| 312 | + /// Add `service.name` resource attribute. |
| 313 | + pub fn with_service_name(self, name: impl Into<Value>) -> Self { |
| 314 | + self.with_attribute(KeyValue::new(SERVICE_NAME, name.into())) |
| 315 | + } |
| 316 | + |
| 317 | + /// This will merge the provided `schema_url` with the current state of the Resource being built. It |
| 318 | + /// will use the following rules to determine which `schema_url` should be used. |
| 319 | + /// |
| 320 | + /// ### [Schema url] |
| 321 | + /// Schema url is determined by the following rules, in order: |
| 322 | + /// 1. If the current builder resource doesn't have a `schema_url`, the provided `schema_url` will be used. |
| 323 | + /// 2. If the current builder resource has a `schema_url`, and the provided `schema_url` is different from the builder resource, `schema_url` will be empty. |
| 324 | + /// 3. If the provided `schema_url` is the same as the current builder resource, it will be used. |
| 325 | + /// |
| 326 | + /// [Schema url]: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.9.0/specification/schemas/overview.md#schema-url |
| 327 | + pub fn with_schema_url<KV, S>(mut self, attributes: KV, schema_url: S) -> Self |
| 328 | + where |
| 329 | + KV: IntoIterator<Item = KeyValue>, |
| 330 | + S: Into<Cow<'static, str>>, |
| 331 | + { |
| 332 | + self.resource = Resource::from_schema_url(attributes, schema_url).merge(&self.resource); |
| 333 | + self |
| 334 | + } |
| 335 | + |
| 336 | + /// Create a [Resource] with the options provided to the [ResourceBuilder]. |
| 337 | + pub fn build(self) -> Resource { |
| 338 | + self.resource |
| 339 | + } |
| 340 | +} |
| 341 | + |
289 | 342 | #[cfg(test)] |
290 | 343 | mod tests { |
291 | 344 | use rstest::rstest; |
@@ -411,4 +464,81 @@ mod tests { |
411 | 464 | }, |
412 | 465 | ) |
413 | 466 | } |
| 467 | + |
| 468 | + #[rstest] |
| 469 | + #[case(Some("http://schema/a"), Some("http://schema/b"), None)] |
| 470 | + #[case(None, Some("http://schema/b"), Some("http://schema/b"))] |
| 471 | + #[case( |
| 472 | + Some("http://schema/a"), |
| 473 | + Some("http://schema/a"), |
| 474 | + Some("http://schema/a") |
| 475 | + )] |
| 476 | + fn builder_with_schema_url( |
| 477 | + #[case] schema_url_a: Option<&'static str>, |
| 478 | + #[case] schema_url_b: Option<&'static str>, |
| 479 | + #[case] expected_schema_url: Option<&'static str>, |
| 480 | + ) { |
| 481 | + let base_builder = if let Some(url) = schema_url_a { |
| 482 | + ResourceBuilder { |
| 483 | + resource: Resource::from_schema_url(vec![KeyValue::new("key", "")], url), |
| 484 | + } |
| 485 | + } else { |
| 486 | + ResourceBuilder { |
| 487 | + resource: Resource::empty(), |
| 488 | + } |
| 489 | + }; |
| 490 | + |
| 491 | + let resource = base_builder |
| 492 | + .with_schema_url( |
| 493 | + vec![KeyValue::new("key", "")], |
| 494 | + schema_url_b.expect("should always be Some for this test"), |
| 495 | + ) |
| 496 | + .build(); |
| 497 | + |
| 498 | + assert_eq!( |
| 499 | + resource.schema_url().map(|s| s as &str), |
| 500 | + expected_schema_url, |
| 501 | + "Merging schema_url_a {:?} with schema_url_b {:?} did not yield expected result {:?}", |
| 502 | + schema_url_a, |
| 503 | + schema_url_b, |
| 504 | + expected_schema_url |
| 505 | + ); |
| 506 | + } |
| 507 | + |
| 508 | + #[test] |
| 509 | + fn builder_detect_resource() { |
| 510 | + temp_env::with_vars( |
| 511 | + [ |
| 512 | + ( |
| 513 | + "OTEL_RESOURCE_ATTRIBUTES", |
| 514 | + Some("key=value, k = v , a= x, a=z"), |
| 515 | + ), |
| 516 | + ("IRRELEVANT", Some("20200810")), |
| 517 | + ], |
| 518 | + || { |
| 519 | + let resource = Resource::builder_empty() |
| 520 | + .with_detector(Box::new(EnvResourceDetector::new())) |
| 521 | + .with_service_name("testing_service") |
| 522 | + .with_attribute(KeyValue::new("test1", "test_value")) |
| 523 | + .with_attributes(vec![ |
| 524 | + KeyValue::new("test1", "test_value1"), |
| 525 | + KeyValue::new("test2", "test_value2"), |
| 526 | + ]) |
| 527 | + .build(); |
| 528 | + |
| 529 | + assert_eq!( |
| 530 | + resource, |
| 531 | + Resource::new(vec![ |
| 532 | + KeyValue::new("key", "value"), |
| 533 | + KeyValue::new("test1", "test_value1"), |
| 534 | + KeyValue::new("test2", "test_value2"), |
| 535 | + KeyValue::new(SERVICE_NAME, "testing_service"), |
| 536 | + KeyValue::new("k", "v"), |
| 537 | + KeyValue::new("a", "x"), |
| 538 | + KeyValue::new("a", "z"), |
| 539 | + ]) |
| 540 | + ) |
| 541 | + }, |
| 542 | + ) |
| 543 | + } |
414 | 544 | } |
0 commit comments