opentelemetry/
common.rs

1use std::borrow::{Borrow, Cow};
2use std::sync::Arc;
3use std::{fmt, hash};
4
5use std::hash::{Hash, Hasher};
6
7/// The key part of attribute [KeyValue] pairs.
8///
9/// See the [attribute naming] spec for guidelines.
10///
11/// [attribute naming]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/general/attribute-naming.md
12#[non_exhaustive]
13#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
14pub struct Key(OtelString);
15
16impl Key {
17    /// Create a new `Key`.
18    ///
19    /// # Examples
20    ///
21    /// ```
22    /// use opentelemetry::Key;
23    /// use std::sync::Arc;
24    ///
25    /// let key1 = Key::new("my_static_str");
26    /// let key2 = Key::new(String::from("my_owned_string"));
27    /// let key3 = Key::new(Arc::from("my_ref_counted_str"));
28    /// ```
29    pub fn new(value: impl Into<Key>) -> Self {
30        value.into()
31    }
32
33    /// Create a new const `Key`.
34    pub const fn from_static_str(value: &'static str) -> Self {
35        Key(OtelString::Static(value))
36    }
37
38    /// Returns a reference to the underlying key name
39    pub fn as_str(&self) -> &str {
40        self.0.as_str()
41    }
42}
43
44impl From<&'static str> for Key {
45    /// Convert a `&str` to a `Key`.
46    fn from(key_str: &'static str) -> Self {
47        Key(OtelString::Static(key_str))
48    }
49}
50
51impl From<String> for Key {
52    /// Convert a `String` to a `Key`.
53    fn from(string: String) -> Self {
54        Key(OtelString::Owned(string.into_boxed_str()))
55    }
56}
57
58impl From<Arc<str>> for Key {
59    /// Convert a `String` to a `Key`.
60    fn from(string: Arc<str>) -> Self {
61        Key(OtelString::RefCounted(string))
62    }
63}
64
65impl From<Cow<'static, str>> for Key {
66    /// Convert a `Cow<'static, str>` to a `Key`
67    fn from(string: Cow<'static, str>) -> Self {
68        match string {
69            Cow::Borrowed(s) => Key(OtelString::Static(s)),
70            Cow::Owned(s) => Key(OtelString::Owned(s.into_boxed_str())),
71        }
72    }
73}
74
75impl fmt::Debug for Key {
76    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
77        self.0.fmt(fmt)
78    }
79}
80
81impl From<Key> for String {
82    fn from(key: Key) -> Self {
83        match key.0 {
84            OtelString::Owned(s) => s.to_string(),
85            OtelString::Static(s) => s.to_string(),
86            OtelString::RefCounted(s) => s.to_string(),
87        }
88    }
89}
90
91impl fmt::Display for Key {
92    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
93        match &self.0 {
94            OtelString::Owned(s) => s.fmt(fmt),
95            OtelString::Static(s) => s.fmt(fmt),
96            OtelString::RefCounted(s) => s.fmt(fmt),
97        }
98    }
99}
100
101impl Borrow<str> for Key {
102    fn borrow(&self) -> &str {
103        self.0.as_str()
104    }
105}
106
107impl AsRef<str> for Key {
108    fn as_ref(&self) -> &str {
109        self.0.as_str()
110    }
111}
112
113#[derive(Clone, Debug, Eq)]
114enum OtelString {
115    Owned(Box<str>),
116    Static(&'static str),
117    RefCounted(Arc<str>),
118}
119
120impl OtelString {
121    fn as_str(&self) -> &str {
122        match self {
123            OtelString::Owned(s) => s.as_ref(),
124            OtelString::Static(s) => s,
125            OtelString::RefCounted(s) => s.as_ref(),
126        }
127    }
128}
129
130impl PartialOrd for OtelString {
131    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
132        Some(self.cmp(other))
133    }
134}
135
136impl Ord for OtelString {
137    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
138        self.as_str().cmp(other.as_str())
139    }
140}
141
142impl PartialEq for OtelString {
143    fn eq(&self, other: &Self) -> bool {
144        self.as_str().eq(other.as_str())
145    }
146}
147
148impl hash::Hash for OtelString {
149    fn hash<H: hash::Hasher>(&self, state: &mut H) {
150        self.as_str().hash(state)
151    }
152}
153
154/// A [Value::Array] containing homogeneous values.
155#[non_exhaustive]
156#[derive(Clone, Debug, PartialEq)]
157pub enum Array {
158    /// Array of bools
159    Bool(Vec<bool>),
160    /// Array of integers
161    I64(Vec<i64>),
162    /// Array of floats
163    F64(Vec<f64>),
164    /// Array of strings
165    String(Vec<StringValue>),
166}
167
168impl fmt::Display for Array {
169    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
170        match self {
171            Array::Bool(values) => display_array_str(values, fmt),
172            Array::I64(values) => display_array_str(values, fmt),
173            Array::F64(values) => display_array_str(values, fmt),
174            Array::String(values) => {
175                write!(fmt, "[")?;
176                for (i, t) in values.iter().enumerate() {
177                    if i > 0 {
178                        write!(fmt, ",")?;
179                    }
180                    write!(fmt, "\"{}\"", t)?;
181                }
182                write!(fmt, "]")
183            }
184        }
185    }
186}
187
188fn display_array_str<T: fmt::Display>(slice: &[T], fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
189    write!(fmt, "[")?;
190    for (i, t) in slice.iter().enumerate() {
191        if i > 0 {
192            write!(fmt, ",")?;
193        }
194        write!(fmt, "{}", t)?;
195    }
196    write!(fmt, "]")
197}
198
199macro_rules! into_array {
200    ($(($t:ty, $val:expr),)+) => {
201        $(
202            impl From<$t> for Array {
203                fn from(t: $t) -> Self {
204                    $val(t)
205                }
206            }
207        )+
208    }
209}
210
211into_array!(
212    (Vec<bool>, Array::Bool),
213    (Vec<i64>, Array::I64),
214    (Vec<f64>, Array::F64),
215    (Vec<StringValue>, Array::String),
216);
217
218/// The value part of attribute [KeyValue] pairs.
219#[non_exhaustive]
220#[derive(Clone, Debug, PartialEq)]
221pub enum Value {
222    /// bool values
223    Bool(bool),
224    /// i64 values
225    I64(i64),
226    /// f64 values
227    F64(f64),
228    /// String values
229    String(StringValue),
230    /// Array of homogeneous values
231    Array(Array),
232}
233
234/// Wrapper for string-like values
235#[non_exhaustive]
236#[derive(Clone, PartialEq, Eq, Hash)]
237pub struct StringValue(OtelString);
238
239impl fmt::Debug for StringValue {
240    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241        self.0.fmt(f)
242    }
243}
244
245impl fmt::Display for StringValue {
246    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247        match &self.0 {
248            OtelString::Owned(s) => s.fmt(f),
249            OtelString::Static(s) => s.fmt(f),
250            OtelString::RefCounted(s) => s.fmt(f),
251        }
252    }
253}
254
255impl AsRef<str> for StringValue {
256    fn as_ref(&self) -> &str {
257        self.0.as_str()
258    }
259}
260
261impl StringValue {
262    /// Returns a string slice to this value
263    pub fn as_str(&self) -> &str {
264        self.0.as_str()
265    }
266}
267
268impl From<StringValue> for String {
269    fn from(s: StringValue) -> Self {
270        match s.0 {
271            OtelString::Owned(s) => s.to_string(),
272            OtelString::Static(s) => s.to_string(),
273            OtelString::RefCounted(s) => s.to_string(),
274        }
275    }
276}
277
278impl From<&'static str> for StringValue {
279    fn from(s: &'static str) -> Self {
280        StringValue(OtelString::Static(s))
281    }
282}
283
284impl From<String> for StringValue {
285    fn from(s: String) -> Self {
286        StringValue(OtelString::Owned(s.into_boxed_str()))
287    }
288}
289
290impl From<Arc<str>> for StringValue {
291    fn from(s: Arc<str>) -> Self {
292        StringValue(OtelString::RefCounted(s))
293    }
294}
295
296impl From<Cow<'static, str>> for StringValue {
297    fn from(s: Cow<'static, str>) -> Self {
298        match s {
299            Cow::Owned(s) => StringValue(OtelString::Owned(s.into_boxed_str())),
300            Cow::Borrowed(s) => StringValue(OtelString::Static(s)),
301        }
302    }
303}
304
305impl From<Value> for StringValue {
306    fn from(s: Value) -> Self {
307        match s {
308            Value::Bool(v) => format!("{}", v).into(),
309            Value::I64(v) => format!("{}", v).into(),
310            Value::F64(v) => format!("{}", v).into(),
311            Value::String(v) => v,
312            Value::Array(v) => format!("{}", v).into(),
313        }
314    }
315}
316
317impl Value {
318    /// String representation of the `Value`
319    ///
320    /// This will allocate if the underlying value is not a `String`.
321    pub fn as_str(&self) -> Cow<'_, str> {
322        match self {
323            Value::Bool(v) => format!("{}", v).into(),
324            Value::I64(v) => format!("{}", v).into(),
325            Value::F64(v) => format!("{}", v).into(),
326            Value::String(v) => Cow::Borrowed(v.as_str()),
327            Value::Array(v) => format!("{}", v).into(),
328        }
329    }
330}
331
332macro_rules! from_values {
333   (
334        $(
335            ($t:ty, $val:expr);
336        )+
337    ) => {
338        $(
339            impl From<$t> for Value {
340                fn from(t: $t) -> Self {
341                    $val(t)
342                }
343            }
344        )+
345    }
346}
347
348from_values!(
349    (bool, Value::Bool);
350    (i64, Value::I64);
351    (f64, Value::F64);
352    (StringValue, Value::String);
353);
354
355impl From<&'static str> for Value {
356    fn from(s: &'static str) -> Self {
357        Value::String(s.into())
358    }
359}
360
361impl From<String> for Value {
362    fn from(s: String) -> Self {
363        Value::String(s.into())
364    }
365}
366
367impl From<Arc<str>> for Value {
368    fn from(s: Arc<str>) -> Self {
369        Value::String(s.into())
370    }
371}
372
373impl From<Cow<'static, str>> for Value {
374    fn from(s: Cow<'static, str>) -> Self {
375        Value::String(s.into())
376    }
377}
378
379impl fmt::Display for Value {
380    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
381        match self {
382            Value::Bool(v) => v.fmt(fmt),
383            Value::I64(v) => v.fmt(fmt),
384            Value::F64(v) => v.fmt(fmt),
385            Value::String(v) => fmt.write_str(v.as_str()),
386            Value::Array(v) => v.fmt(fmt),
387        }
388    }
389}
390
391/// A key-value pair describing an attribute.
392#[derive(Clone, Debug, PartialEq)]
393#[non_exhaustive]
394pub struct KeyValue {
395    /// The attribute name
396    pub key: Key,
397
398    /// The attribute value
399    pub value: Value,
400}
401
402impl KeyValue {
403    /// Create a new `KeyValue` pair.
404    pub fn new<K, V>(key: K, value: V) -> Self
405    where
406        K: Into<Key>,
407        V: Into<Value>,
408    {
409        KeyValue {
410            key: key.into(),
411            value: value.into(),
412        }
413    }
414}
415
416struct F64Hashable(f64);
417
418impl PartialEq for F64Hashable {
419    fn eq(&self, other: &Self) -> bool {
420        self.0.to_bits() == other.0.to_bits()
421    }
422}
423
424impl Eq for F64Hashable {}
425
426impl Hash for F64Hashable {
427    fn hash<H: Hasher>(&self, state: &mut H) {
428        self.0.to_bits().hash(state);
429    }
430}
431
432impl Hash for KeyValue {
433    fn hash<H: Hasher>(&self, state: &mut H) {
434        self.key.hash(state);
435        match &self.value {
436            Value::F64(f) => F64Hashable(*f).hash(state),
437            Value::Array(a) => match a {
438                Array::Bool(b) => b.hash(state),
439                Array::I64(i) => i.hash(state),
440                Array::F64(f) => f.iter().for_each(|f| F64Hashable(*f).hash(state)),
441                Array::String(s) => s.hash(state),
442            },
443            Value::Bool(b) => b.hash(state),
444            Value::I64(i) => i.hash(state),
445            Value::String(s) => s.hash(state),
446        };
447    }
448}
449
450impl Eq for KeyValue {}
451
452/// Information about a library or crate providing instrumentation.
453///
454/// An instrumentation scope should be named to follow any naming conventions
455/// of the instrumented library (e.g. 'middleware' for a web framework).
456///
457/// See the [instrumentation libraries] spec for more information.
458///
459/// [instrumentation libraries]: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.9.0/specification/overview.md#instrumentation-libraries
460#[derive(Debug, Default, Clone)]
461#[non_exhaustive]
462pub struct InstrumentationScope {
463    /// The library name.
464    ///
465    /// This should be the name of the crate providing the instrumentation.
466    name: Cow<'static, str>,
467
468    /// The library version.
469    version: Option<Cow<'static, str>>,
470
471    /// [Schema URL] used by this library.
472    ///
473    /// [Schema URL]: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.9.0/specification/schemas/overview.md#schema-url
474    schema_url: Option<Cow<'static, str>>,
475
476    /// Specifies the instrumentation scope attributes to associate with emitted telemetry.
477    attributes: Vec<KeyValue>,
478}
479
480impl PartialEq for InstrumentationScope {
481    fn eq(&self, other: &Self) -> bool {
482        self.name == other.name
483            && self.version == other.version
484            && self.schema_url == other.schema_url
485            && self.attributes.len() == other.attributes.len()
486            && {
487                let mut self_attrs = Vec::from_iter(&self.attributes);
488                let mut other_attrs = Vec::from_iter(&other.attributes);
489                self_attrs.sort_unstable_by(|a, b| a.key.cmp(&b.key));
490                other_attrs.sort_unstable_by(|a, b| a.key.cmp(&b.key));
491                self_attrs == other_attrs
492            }
493    }
494}
495
496impl Eq for InstrumentationScope {}
497
498impl hash::Hash for InstrumentationScope {
499    fn hash<H: hash::Hasher>(&self, state: &mut H) {
500        self.name.hash(state);
501        self.version.hash(state);
502        self.schema_url.hash(state);
503        let mut sorted_attrs = Vec::from_iter(&self.attributes);
504        sorted_attrs.sort_unstable_by(|a, b| a.key.cmp(&b.key));
505        for attribute in sorted_attrs {
506            attribute.hash(state);
507        }
508    }
509}
510
511impl InstrumentationScope {
512    /// Create a new builder to create an [InstrumentationScope]
513    pub fn builder<T: Into<Cow<'static, str>>>(name: T) -> InstrumentationScopeBuilder {
514        InstrumentationScopeBuilder {
515            name: name.into(),
516            version: None,
517            schema_url: None,
518            attributes: None,
519        }
520    }
521
522    /// Returns the instrumentation library name.
523    #[inline]
524    pub fn name(&self) -> &str {
525        &self.name
526    }
527
528    /// Returns the instrumentation library version.
529    #[inline]
530    pub fn version(&self) -> Option<&str> {
531        self.version.as_deref()
532    }
533
534    /// Returns the [Schema URL] used by this library.
535    ///
536    /// [Schema URL]: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.9.0/specification/schemas/overview.md#schema-url
537    #[inline]
538    pub fn schema_url(&self) -> Option<&str> {
539        self.schema_url.as_deref()
540    }
541
542    /// Returns the instrumentation scope attributes to associate with emitted telemetry.
543    #[inline]
544    pub fn attributes(&self) -> impl Iterator<Item = &KeyValue> {
545        self.attributes.iter()
546    }
547}
548
549/// Configuration options for [InstrumentationScope].
550///
551/// An instrumentation scope is a library or crate providing instrumentation.
552/// It should be named to follow any naming conventions of the instrumented
553/// library (e.g. 'middleware' for a web framework).
554///
555/// Apart from the name, all other fields are optional.
556///
557/// See the [instrumentation libraries] spec for more information.
558///
559/// [instrumentation libraries]: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.9.0/specification/overview.md#instrumentation-libraries
560#[derive(Debug)]
561pub struct InstrumentationScopeBuilder {
562    name: Cow<'static, str>,
563    version: Option<Cow<'static, str>>,
564    schema_url: Option<Cow<'static, str>>,
565    attributes: Option<Vec<KeyValue>>,
566}
567
568impl InstrumentationScopeBuilder {
569    /// Configure the version for the instrumentation scope
570    ///
571    /// # Examples
572    ///
573    /// ```
574    /// let scope = opentelemetry::InstrumentationScope::builder("my-crate")
575    ///     .with_version("v0.1.0")
576    ///     .build();
577    /// ```
578    pub fn with_version(mut self, version: impl Into<Cow<'static, str>>) -> Self {
579        self.version = Some(version.into());
580        self
581    }
582
583    /// Configure the Schema URL for the instrumentation scope
584    ///
585    /// # Examples
586    ///
587    /// ```
588    /// let scope = opentelemetry::InstrumentationScope::builder("my-crate")
589    ///     .with_schema_url("https://opentelemetry.io/schemas/1.17.0")
590    ///     .build();
591    /// ```
592    pub fn with_schema_url(mut self, schema_url: impl Into<Cow<'static, str>>) -> Self {
593        self.schema_url = Some(schema_url.into());
594        self
595    }
596
597    /// Configure the attributes for the instrumentation scope
598    ///
599    /// # Examples
600    ///
601    /// ```
602    /// use opentelemetry::KeyValue;
603    ///
604    /// let scope = opentelemetry::InstrumentationScope::builder("my-crate")
605    ///     .with_attributes([KeyValue::new("k", "v")])
606    ///     .build();
607    /// ```
608    pub fn with_attributes<I>(mut self, attributes: I) -> Self
609    where
610        I: IntoIterator<Item = KeyValue>,
611    {
612        self.attributes = Some(attributes.into_iter().collect());
613        self
614    }
615
616    /// Create a new [InstrumentationScope] from this configuration
617    pub fn build(self) -> InstrumentationScope {
618        InstrumentationScope {
619            name: self.name,
620            version: self.version,
621            schema_url: self.schema_url,
622            attributes: self.attributes.unwrap_or_default(),
623        }
624    }
625}
626
627#[cfg(test)]
628mod tests {
629    use std::hash::{Hash, Hasher};
630
631    use crate::{InstrumentationScope, KeyValue};
632
633    use rand::random;
634    use std::collections::hash_map::DefaultHasher;
635    use std::f64;
636
637    #[test]
638    fn kv_float_equality() {
639        let kv1 = KeyValue::new("key", 1.0);
640        let kv2 = KeyValue::new("key", 1.0);
641        assert_eq!(kv1, kv2);
642
643        let kv1 = KeyValue::new("key", 1.0);
644        let kv2 = KeyValue::new("key", 1.01);
645        assert_ne!(kv1, kv2);
646
647        let kv1 = KeyValue::new("key", f64::NAN);
648        let kv2 = KeyValue::new("key", f64::NAN);
649        assert_ne!(kv1, kv2, "NAN is not equal to itself");
650
651        for float_val in [
652            f64::INFINITY,
653            f64::NEG_INFINITY,
654            f64::MAX,
655            f64::MIN,
656            f64::MIN_POSITIVE,
657        ]
658        .iter()
659        {
660            let kv1 = KeyValue::new("key", *float_val);
661            let kv2 = KeyValue::new("key", *float_val);
662            assert_eq!(kv1, kv2);
663        }
664
665        for _ in 0..100 {
666            let random_value = random::<f64>();
667            let kv1 = KeyValue::new("key", random_value);
668            let kv2 = KeyValue::new("key", random_value);
669            assert_eq!(kv1, kv2);
670        }
671    }
672
673    #[test]
674    fn kv_float_hash() {
675        for float_val in [
676            f64::NAN,
677            f64::INFINITY,
678            f64::NEG_INFINITY,
679            f64::MAX,
680            f64::MIN,
681            f64::MIN_POSITIVE,
682        ]
683        .iter()
684        {
685            let kv1 = KeyValue::new("key", *float_val);
686            let kv2 = KeyValue::new("key", *float_val);
687            assert_eq!(hash_helper(&kv1), hash_helper(&kv2));
688        }
689
690        for _ in 0..100 {
691            let random_value = random::<f64>();
692            let kv1 = KeyValue::new("key", random_value);
693            let kv2 = KeyValue::new("key", random_value);
694            assert_eq!(hash_helper(&kv1), hash_helper(&kv2));
695        }
696    }
697
698    fn hash_helper<T: Hash>(item: &T) -> u64 {
699        let mut hasher = DefaultHasher::new();
700        item.hash(&mut hasher);
701        hasher.finish()
702    }
703
704    #[test]
705    fn instrumentation_scope_equality() {
706        let scope1 = InstrumentationScope::builder("my-crate")
707            .with_version("v0.1.0")
708            .with_schema_url("https://opentelemetry.io/schemas/1.17.0")
709            .with_attributes([KeyValue::new("k", "v")])
710            .build();
711        let scope2 = InstrumentationScope::builder("my-crate")
712            .with_version("v0.1.0")
713            .with_schema_url("https://opentelemetry.io/schemas/1.17.0")
714            .with_attributes([KeyValue::new("k", "v")])
715            .build();
716        assert_eq!(scope1, scope2);
717    }
718
719    #[test]
720    fn instrumentation_scope_equality_attributes_diff_order() {
721        let scope1 = InstrumentationScope::builder("my-crate")
722            .with_version("v0.1.0")
723            .with_schema_url("https://opentelemetry.io/schemas/1.17.0")
724            .with_attributes([KeyValue::new("k1", "v1"), KeyValue::new("k2", "v2")])
725            .build();
726        let scope2 = InstrumentationScope::builder("my-crate")
727            .with_version("v0.1.0")
728            .with_schema_url("https://opentelemetry.io/schemas/1.17.0")
729            .with_attributes([KeyValue::new("k2", "v2"), KeyValue::new("k1", "v1")])
730            .build();
731        assert_eq!(scope1, scope2);
732
733        // assert hash are same for both scopes
734        let mut hasher1 = std::collections::hash_map::DefaultHasher::new();
735        scope1.hash(&mut hasher1);
736        let mut hasher2 = std::collections::hash_map::DefaultHasher::new();
737        scope2.hash(&mut hasher2);
738        assert_eq!(hasher1.finish(), hasher2.finish());
739    }
740
741    #[test]
742    fn instrumentation_scope_equality_different_attributes() {
743        let scope1 = InstrumentationScope::builder("my-crate")
744            .with_version("v0.1.0")
745            .with_schema_url("https://opentelemetry.io/schemas/1.17.0")
746            .with_attributes([KeyValue::new("k1", "v1"), KeyValue::new("k2", "v2")])
747            .build();
748        let scope2 = InstrumentationScope::builder("my-crate")
749            .with_version("v0.1.0")
750            .with_schema_url("https://opentelemetry.io/schemas/1.17.0")
751            .with_attributes([KeyValue::new("k2", "v3"), KeyValue::new("k4", "v5")])
752            .build();
753        assert_ne!(scope1, scope2);
754
755        // assert hash are same for both scopes
756        let mut hasher1 = std::collections::hash_map::DefaultHasher::new();
757        scope1.hash(&mut hasher1);
758        let mut hasher2 = std::collections::hash_map::DefaultHasher::new();
759        scope2.hash(&mut hasher2);
760        assert_ne!(hasher1.finish(), hasher2.finish());
761    }
762}