Skip to main content

rocketmq_observability/
attributes.rs

1// Copyright 2023 The RocketMQ Rust Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::config::ObservabilityConfig;
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct Attribute {
19    pub key: &'static str,
20    pub value: String,
21}
22
23pub fn base_attributes(config: &ObservabilityConfig) -> Vec<Attribute> {
24    let mut attributes = vec![
25        Attribute::new("cluster", &config.cluster),
26        Attribute::new("node_type", &config.node_type),
27    ];
28
29    if !config.node_id.is_empty() {
30        attributes.push(Attribute::new("node_id", &config.node_id));
31    }
32
33    attributes
34}
35
36impl Attribute {
37    pub fn new(key: &'static str, value: impl Into<String>) -> Self {
38        Self {
39            key,
40            value: value.into(),
41        }
42    }
43}
44
45/// Inline capacity that covers the component attribute sets recorded on hot paths.
46#[cfg(feature = "otel-metrics")]
47type KeyValues = smallvec::SmallVec<[opentelemetry::KeyValue; 4]>;
48
49/// Attributes recorded with a metric measurement.
50///
51/// Build a set once and reuse it for repeated measurements: recording hands the prepared
52/// attributes to the metrics backend without converting them again. Without the `otel-metrics`
53/// feature metrics are not recorded, so added attributes are discarded.
54#[derive(Debug, Clone, Default, PartialEq)]
55pub struct MetricAttributes {
56    #[cfg(feature = "otel-metrics")]
57    key_values: KeyValues,
58}
59
60impl MetricAttributes {
61    /// Creates an empty attribute set.
62    pub fn new() -> Self {
63        Self::default()
64    }
65
66    /// Returns the set with a string attribute appended.
67    #[must_use]
68    pub fn with(self, key: &'static str, value: impl Into<String>) -> Self {
69        #[cfg(feature = "otel-metrics")]
70        {
71            let mut attributes = self;
72            attributes
73                .key_values
74                .push(opentelemetry::KeyValue::new(key, value.into()));
75            attributes
76        }
77        #[cfg(not(feature = "otel-metrics"))]
78        {
79            let _ = (key, value);
80            self
81        }
82    }
83}
84
85#[cfg(feature = "otel-metrics")]
86impl MetricAttributes {
87    /// Wraps attributes that were already collected, reusing the vector allocation.
88    pub(crate) fn from_key_values(key_values: Vec<opentelemetry::KeyValue>) -> Self {
89        Self {
90            key_values: KeyValues::from_vec(key_values),
91        }
92    }
93
94    /// Wraps a fixed attribute list; up to the inline capacity it stays off the heap.
95    pub(crate) fn from_array<const N: usize>(key_values: [opentelemetry::KeyValue; N]) -> Self {
96        Self {
97            key_values: key_values.into_iter().collect(),
98        }
99    }
100
101    pub(crate) fn as_key_values(&self) -> &[opentelemetry::KeyValue] {
102        &self.key_values
103    }
104
105    pub(crate) fn push(&mut self, key_value: opentelemetry::KeyValue) {
106        self.key_values.push(key_value);
107    }
108
109    pub(crate) fn extend(&mut self, key_values: impl IntoIterator<Item = opentelemetry::KeyValue>) {
110        self.key_values.extend(key_values);
111    }
112}
113
114#[cfg(all(test, feature = "otel-metrics"))]
115mod tests {
116    use opentelemetry::KeyValue;
117
118    use super::MetricAttributes;
119
120    #[test]
121    fn with_appends_string_attributes_in_order() {
122        let attributes = MetricAttributes::new()
123            .with("topic", "TopicA")
124            .with("group", String::from("GroupA"));
125
126        assert_eq!(
127            attributes.as_key_values(),
128            &[KeyValue::new("topic", "TopicA"), KeyValue::new("group", "GroupA")]
129        );
130    }
131}