Skip to content

Commit fdf6401

Browse files
authored
feat(common): add schema_url to resource. (open-telemetry#775)
* feat(common): add schema_url to resource. * doc(common): update links in doc. * fix(common): typo * fix(common): return Option for schema_url * fix(common): use &str for better performance. * make clippy happy
1 parent 74d0c2d commit fdf6401

File tree

4 files changed

+119
-10
lines changed

4 files changed

+119
-10
lines changed

opentelemetry-otlp/src/transform/metrics.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub(crate) mod tonic {
1414
ArrayAggregator, HistogramAggregator, LastValueAggregator, MinMaxSumCountAggregator,
1515
SumAggregator,
1616
};
17+
use opentelemetry::sdk::InstrumentationLibrary;
1718
use opentelemetry_proto::tonic::metrics::v1::DataPointFlags;
1819
use opentelemetry_proto::tonic::FromNumber;
1920
use opentelemetry_proto::tonic::{
@@ -28,7 +29,6 @@ pub(crate) mod tonic {
2829

2930
use crate::to_nanos;
3031
use crate::transform::{CheckpointedMetrics, ResourceWrapper};
31-
use opentelemetry::sdk::InstrumentationLibrary;
3232
use std::collections::{BTreeMap, HashMap};
3333

3434
pub(crate) fn record_to_metric(
@@ -212,14 +212,21 @@ pub(crate) mod tonic {
212212
resource_metrics: sink_map
213213
.into_iter()
214214
.map(|(resource, metric_map)| ResourceMetrics {
215+
schema_url: resource
216+
.schema_url()
217+
.map(|s| s.to_string())
218+
.unwrap_or_default(),
215219
resource: Some(resource.into()),
216-
schema_url: "".to_string(), // todo: replace with actual schema url.
217220
instrumentation_library_metrics: metric_map
218221
.into_iter()
219222
.map(
220223
|(instrumentation_library, metrics)| InstrumentationLibraryMetrics {
224+
schema_url: instrumentation_library
225+
.schema_url
226+
.clone()
227+
.unwrap_or_default()
228+
.to_string(),
221229
instrumentation_library: Some(instrumentation_library.into()),
222-
schema_url: "".to_string(), // todo: replace with actual schema url.
223230
metrics: metrics
224231
.into_iter()
225232
.map(|(_k, v)| v)
@@ -385,7 +392,7 @@ mod tests {
385392
version: instrumentation_version.unwrap_or("").to_string(),
386393
},
387394
),
388-
schema_url: "".to_string(), // todo: replace with actual schema url.
395+
schema_url: "".to_string(),
389396
metrics: metrics
390397
.into_iter()
391398
.map(|(name, data_points)| get_metric_with_name(name, data_points))
@@ -394,7 +401,7 @@ mod tests {
394401
}
395402
ResourceMetrics {
396403
resource: Some(resource),
397-
schema_url: "".to_string(), // todo: replace with actual schema url.
404+
schema_url: "".to_string(),
398405
instrumentation_library_metrics,
399406
}
400407
}

opentelemetry-otlp/src/transform/resource.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ impl PartialOrd for ResourceWrapper {
2525
}
2626
}
2727

28+
impl ResourceWrapper {
29+
#[cfg(all(feature = "grpc-tonic", feature = "metrics"))]
30+
// it's currently only used by metrics. Trace set this in opentelemtry-proto
31+
pub(crate) fn schema_url(&self) -> Option<&str> {
32+
self.0.schema_url()
33+
}
34+
}
35+
2836
#[cfg(feature = "grpc-tonic")]
2937
impl From<ResourceWrapper> for Resource {
3038
fn from(resource: ResourceWrapper) -> Self {

opentelemetry-proto/src/transform/traces.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,10 @@ pub mod tonic {
5757
.0,
5858
dropped_attributes_count: 0,
5959
}),
60-
schema_url: "".to_string(), // todo: replace with actual schema url.
60+
schema_url: source_span
61+
.resource
62+
.and_then(|resource| resource.schema_url().map(|url| url.to_string()))
63+
.unwrap_or_default(),
6164
instrumentation_library_spans: vec![InstrumentationLibrarySpans {
6265
schema_url: source_span
6366
.instrumentation_lib

opentelemetry-sdk/src/resource/mod.rs

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pub use process::ProcessResourceDetector;
3030
#[cfg(feature = "metrics")]
3131
use opentelemetry_api::attributes;
3232
use opentelemetry_api::{Key, KeyValue, Value};
33+
use std::borrow::Cow;
3334
use std::collections::{btree_map, BTreeMap};
3435
use std::ops::Deref;
3536
use std::time::Duration;
@@ -38,6 +39,7 @@ use std::time::Duration;
3839
#[derive(Clone, Debug, PartialEq)]
3940
pub struct Resource {
4041
attrs: BTreeMap<Key, Value>,
42+
schema_url: Option<Cow<'static, str>>,
4143
}
4244

4345
impl Default for Resource {
@@ -54,6 +56,7 @@ impl Resource {
5456
pub fn empty() -> Self {
5557
Self {
5658
attrs: Default::default(),
59+
schema_url: None,
5760
}
5861
}
5962

@@ -71,6 +74,24 @@ impl Resource {
7174
resource
7275
}
7376

77+
/// Create a new `Resource` from a key value pairs and [schema url].
78+
///
79+
/// Values are de-duplicated by key, and the first key-value pair with a non-empty string value
80+
/// will be retained.
81+
///
82+
/// schema_url must be a valid URL using HTTP or HTTPS protocol.
83+
///
84+
/// [schema url]: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.9.0/specification/schemas/overview.md#schema-url
85+
pub fn from_schema_url<KV, S>(kvs: KV, schema_url: S) -> Self
86+
where
87+
KV: IntoIterator<Item = KeyValue>,
88+
S: Into<Cow<'static, str>>,
89+
{
90+
let mut resource = Self::new(kvs);
91+
resource.schema_url = Some(schema_url.into());
92+
resource
93+
}
94+
7495
/// Create a new `Resource` from resource detectors.
7596
///
7697
/// timeout will be applied to each detector.
@@ -89,8 +110,19 @@ impl Resource {
89110

90111
/// Create a new `Resource` by combining two resources.
91112
///
113+
/// ### Key value pairs
92114
/// Keys from the `other` resource have priority over keys from this resource, even if the
93115
/// updated value is empty.
116+
///
117+
/// ### [Schema url]
118+
/// If both of the resource are not empty. Schema url is determined by the following rules, in order:
119+
/// 1. If this resource has a schema url, it will be used.
120+
/// 2. If this resource does not have a schema url, and the other resource has a schema url, it will be used.
121+
/// 3. If both resources have a schema url and it's the same, it will be used.
122+
/// 4. If both resources have a schema url and it's different, the schema url will be empty.
123+
/// 5. If both resources do not have a schema url, the schema url will be empty.
124+
///
125+
/// [Schema url]: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.9.0/specification/schemas/overview.md#schema-url
94126
pub fn merge<T: Deref<Target = Self>>(&self, other: T) -> Self {
95127
if self.attrs.is_empty() {
96128
return other.clone();
@@ -109,9 +141,31 @@ impl Resource {
109141
resource.attrs.insert(k.clone(), v.clone());
110142
}
111143

144+
if self.schema_url == other.schema_url {
145+
resource.schema_url = self.schema_url.clone();
146+
} else if self.schema_url.is_none() {
147+
// if the other resource has schema url, use it.
148+
if other.schema_url.is_some() {
149+
resource.schema_url = other.schema_url.clone();
150+
}
151+
// else empty schema url.
152+
} else {
153+
// if self has schema url, use it.
154+
if other.schema_url.is_none() {
155+
resource.schema_url = self.schema_url.clone();
156+
}
157+
}
158+
112159
resource
113160
}
114161

162+
/// Return the [schema url] of the resource. If the resource does not have a schema url, return `None`.
163+
///
164+
/// [schema url]: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.9.0/specification/schemas/overview.md#schema-url
165+
pub fn schema_url(&self) -> Option<&str> {
166+
self.schema_url.as_ref().map(|s| s.as_ref())
167+
}
168+
115169
/// Returns the number of attributes for this resource
116170
pub fn len(&self) -> usize {
117171
self.attrs.len()
@@ -215,13 +269,14 @@ mod tests {
215269
assert_eq!(
216270
Resource::new(args_with_dupe_keys),
217271
Resource {
218-
attrs: expected_attrs
272+
attrs: expected_attrs,
273+
schema_url: None,
219274
}
220275
);
221276
}
222277

223278
#[test]
224-
fn merge_resource() {
279+
fn merge_resource_key_value_pairs() {
225280
let resource_a = Resource::new(vec![
226281
KeyValue::new("a", ""),
227282
KeyValue::new("b", "b-value"),
@@ -243,11 +298,47 @@ mod tests {
243298
assert_eq!(
244299
resource_a.merge(&resource_b),
245300
Resource {
246-
attrs: expected_attrs
301+
attrs: expected_attrs,
302+
schema_url: None,
247303
}
248304
);
249305
}
250306

307+
#[test]
308+
fn merge_resource_schema_url() {
309+
// if both resources contains key value pairs
310+
let test_cases = vec![
311+
(Some("http://schema/a"), None, Some("http://schema/a")),
312+
(Some("http://schema/a"), Some("http://schema/b"), None),
313+
(None, Some("http://schema/b"), Some("http://schema/b")),
314+
(
315+
Some("http://schema/a"),
316+
Some("http://schema/a"),
317+
Some("http://schema/a"),
318+
),
319+
(None, None, None),
320+
];
321+
322+
for (schema_url, other_schema_url, expect_schema_url) in test_cases.into_iter() {
323+
let mut resource = Resource::new(vec![KeyValue::new("key", "")]);
324+
resource.schema_url = schema_url.map(Into::into);
325+
326+
let mut other_resource = Resource::new(vec![KeyValue::new("key", "")]);
327+
other_resource.schema_url = other_schema_url.map(Into::into);
328+
329+
assert_eq!(
330+
resource.merge(&other_resource).schema_url,
331+
expect_schema_url.map(Into::into)
332+
);
333+
}
334+
335+
// if only one resource contains key value pairs
336+
let resource = Resource::from_schema_url(vec![], "http://schema/a");
337+
let other_resource = Resource::new(vec![KeyValue::new("key", "")]);
338+
339+
assert_eq!(resource.merge(&other_resource).schema_url, None);
340+
}
341+
251342
#[test]
252343
fn detect_resource() {
253344
env::set_var("OTEL_RESOURCE_ATTRIBUTES", "key=value, k = v , a= x, a=z");
@@ -262,7 +353,7 @@ mod tests {
262353
KeyValue::new("key", "value"),
263354
KeyValue::new("k", "v"),
264355
KeyValue::new("a", "x"),
265-
KeyValue::new("a", "z")
356+
KeyValue::new("a", "z"),
266357
])
267358
)
268359
}

0 commit comments

Comments
 (0)