opentelemetry_sdk/metrics/
view.rs1use super::instrument::{Instrument, Stream};
2use glob::Pattern;
3use opentelemetry::{
4 global,
5 metrics::{MetricsError, Result},
6};
7
8fn empty_view(_inst: &Instrument) -> Option<Stream> {
9 None
10}
11
12pub trait View: Send + Sync + 'static {
49 fn match_inst(&self, inst: &Instrument) -> Option<Stream>;
54}
55
56impl<T> View for T
57where
58 T: Fn(&Instrument) -> Option<Stream> + Send + Sync + 'static,
59{
60 fn match_inst(&self, inst: &Instrument) -> Option<Stream> {
61 self(inst)
62 }
63}
64
65impl View for Box<dyn View> {
66 fn match_inst(&self, inst: &Instrument) -> Option<Stream> {
67 (**self).match_inst(inst)
68 }
69}
70
71pub fn new_view(criteria: Instrument, mask: Stream) -> Result<Box<dyn View>> {
104 if criteria.is_empty() {
105 global::handle_error(MetricsError::Config(format!(
106 "no criteria provided, dropping view. mask: {mask:?}"
107 )));
108 return Ok(Box::new(empty_view));
109 }
110 let contains_wildcard = criteria.name.contains(|c| c == '*' || c == '?');
111 let err_msg_criteria = criteria.clone();
112
113 let match_fn: Box<dyn Fn(&Instrument) -> bool + Send + Sync> = if contains_wildcard {
114 if mask.name != "" {
115 global::handle_error(MetricsError::Config(format!(
116 "name replacement for multiple instruments, dropping view, criteria: {criteria:?}, mask: {mask:?}"
117 )));
118 return Ok(Box::new(empty_view));
119 }
120
121 let pattern = criteria.name.clone();
122 let glob_pattern =
123 Pattern::new(&pattern).map_err(|e| MetricsError::Config(e.to_string()))?;
124
125 Box::new(move |i| {
126 glob_pattern.matches(&i.name)
127 && criteria.matches_description(i)
128 && criteria.matches_kind(i)
129 && criteria.matches_unit(i)
130 && criteria.matches_scope(i)
131 })
132 } else {
133 Box::new(move |i| criteria.matches(i))
134 };
135
136 let mut agg = None;
137 if let Some(ma) = &mask.aggregation {
138 match ma.validate() {
139 Ok(_) => agg = Some(ma.clone()),
140 Err(err) => {
141 global::handle_error(MetricsError::Other(format!(
142 "{}, proceeding as if view did not exist. criteria: {:?}, mask: {:?}",
143 err, err_msg_criteria, mask
144 )));
145 return Ok(Box::new(empty_view));
146 }
147 }
148 }
149
150 Ok(Box::new(move |i: &Instrument| -> Option<Stream> {
151 if match_fn(i) {
152 Some(Stream {
153 name: if !mask.name.is_empty() {
154 mask.name.clone()
155 } else {
156 i.name.clone()
157 },
158 description: if !mask.description.is_empty() {
159 mask.description.clone()
160 } else {
161 i.description.clone()
162 },
163 unit: if !mask.unit.is_empty() {
164 mask.unit.clone()
165 } else {
166 i.unit.clone()
167 },
168 aggregation: agg.clone(),
169 allowed_attribute_keys: mask.allowed_attribute_keys.clone(),
170 })
171 } else {
172 None
173 }
174 }))
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180 #[test]
181 fn test_new_view_matching_all() {
182 let criteria = Instrument::new().name("*");
183 let mask = Stream::new();
184
185 let view = new_view(criteria, mask).expect("Expected to create a new view");
186
187 let test_instrument = Instrument::new().name("test_instrument");
188 assert!(
189 view.match_inst(&test_instrument).is_some(),
190 "Expected to match all instruments with * pattern"
191 );
192 }
193
194 #[test]
195 fn test_new_view_exact_match() {
196 let criteria = Instrument::new().name("counter_exact_match");
197 let mask = Stream::new();
198
199 let view = new_view(criteria, mask).expect("Expected to create a new view");
200
201 let matching_instrument = Instrument::new().name("counter_exact_match");
202 assert!(
203 view.match_inst(&matching_instrument).is_some(),
204 "Expected to match instrument with exact name"
205 );
206
207 let non_matching_instrument = Instrument::new().name("counter_non_exact_match");
208 assert!(
209 view.match_inst(&non_matching_instrument).is_none(),
210 "Expected not to match instrument with different name"
211 );
212 }
213
214 #[test]
215 fn test_new_view_with_wildcard_pattern() {
216 let criteria = Instrument::new().name("prefix_*");
217 let mask = Stream::new();
218
219 let view = new_view(criteria, mask).expect("Expected to create a new view");
220
221 let matching_instrument = Instrument::new().name("prefix_counter");
222 assert!(
223 view.match_inst(&matching_instrument).is_some(),
224 "Expected to match instrument with matching prefix"
225 );
226
227 let non_matching_instrument = Instrument::new().name("nonprefix_counter");
228 assert!(
229 view.match_inst(&non_matching_instrument).is_none(),
230 "Expected not to match instrument with different prefix"
231 );
232 }
233
234 #[test]
235 fn test_new_view_wildcard_question_mark() {
236 let criteria = Instrument::new().name("test_?");
237 let mask = Stream::new();
238
239 let view = new_view(criteria, mask).expect("Expected to create a new view");
240
241 let matching_instrument = Instrument::new().name("test_1");
243 assert!(
244 view.match_inst(&matching_instrument).is_some(),
245 "Expected to match instrument with test_? pattern"
246 );
247
248 let non_matching_instrument = Instrument::new().name("test_12");
250 assert!(
251 view.match_inst(&non_matching_instrument).is_none(),
252 "Expected not to match instrument with test_? pattern"
253 );
254 }
255}