1use super::instrument::{Instrument, Stream};
2use glob::Pattern;
3use opentelemetry::{
4 global,
5 metrics::{MetricsError, Result},
6};
78fn empty_view(_inst: &Instrument) -> Option<Stream> {
9None
10}
1112/// Used to customize the metrics that are output by the SDK.
13///
14/// Here are some examples when a [View] might be needed:
15///
16/// * Customize which Instruments are to be processed/ignored. For example, an
17/// instrumented library can provide both temperature and humidity, but the
18/// application developer might only want temperature.
19/// * Customize the aggregation - if the default aggregation associated with the
20/// [Instrument] does not meet the needs of the user. For example, an HTTP client
21/// library might expose HTTP client request duration as Histogram by default,
22/// but the application developer might only want the total count of outgoing
23/// requests.
24/// * Customize which attribute(s) are to be reported on metrics. For example,
25/// an HTTP server library might expose HTTP verb (e.g. GET, POST) and HTTP
26/// status code (e.g. 200, 301, 404). The application developer might only care
27/// about HTTP status code (e.g. reporting the total count of HTTP requests for
28/// each HTTP status code). There could also be extreme scenarios in which the
29/// application developer does not need any attributes (e.g. just get the total
30/// count of all incoming requests).
31///
32/// # Example Custom View
33///
34/// View is implemented for all `Fn(&Instrument) -> Option<Stream>`.
35///
36/// ```
37/// use opentelemetry_sdk::metrics::{Instrument, SdkMeterProvider, Stream};
38///
39/// // return streams for the given instrument
40/// let my_view = |i: &Instrument| {
41/// // return Some(Stream) or
42/// None
43/// };
44///
45/// let provider = SdkMeterProvider::builder().with_view(my_view).build();
46/// # drop(provider)
47/// ```
48pub trait View: Send + Sync + 'static {
49/// Defines how data should be collected for certain instruments.
50 ///
51 /// Return [Stream] to use for matching [Instrument]s,
52 /// otherwise if there is no match, return `None`.
53fn match_inst(&self, inst: &Instrument) -> Option<Stream>;
54}
5556impl<T> View for T
57where
58T: Fn(&Instrument) -> Option<Stream> + Send + Sync + 'static,
59{
60fn match_inst(&self, inst: &Instrument) -> Option<Stream> {
61self(inst)
62 }
63}
6465impl View for Box<dyn View> {
66fn match_inst(&self, inst: &Instrument) -> Option<Stream> {
67 (**self).match_inst(inst)
68 }
69}
7071/// Creates a [View] that applies the [Stream] mask for all instruments that
72/// match criteria.
73///
74/// The returned [View] will only apply the mask if all non-empty fields of
75/// criteria match the corresponding [Instrument] passed to the view. If all
76/// fields of the criteria are their default values, a view that matches no
77/// instruments is returned. If you need to match an empty-value field, create a
78/// [View] directly.
79///
80/// The [Instrument::name] field of criteria supports wildcard pattern matching.
81/// The wildcard `*` is recognized as matching zero or more characters, and `?`
82/// is recognized as matching exactly one character. For example, a pattern of
83/// `*` will match all instrument names.
84///
85/// The [Stream] mask only applies updates for non-empty fields. By default, the
86/// [Instrument] the [View] matches against will be use for the name,
87/// description, and unit of the returned [Stream] and no `aggregation` or
88/// `allowed_attribute_keys` are set. All non-empty fields of mask are used
89/// instead of the default. If you need to set a an empty value in the returned
90/// stream, create a custom [View] directly.
91///
92/// # Example
93///
94/// ```
95/// use opentelemetry_sdk::metrics::{new_view, Aggregation, Instrument, Stream};
96///
97/// let criteria = Instrument::new().name("counter_*");
98/// let mask = Stream::new().aggregation(Aggregation::Sum);
99///
100/// let view = new_view(criteria, mask);
101/// # drop(view);
102/// ```
103pub fn new_view(criteria: Instrument, mask: Stream) -> Result<Box<dyn View>> {
104if criteria.is_empty() {
105 global::handle_error(MetricsError::Config(format!(
106"no criteria provided, dropping view. mask: {mask:?}"
107)));
108return Ok(Box::new(empty_view));
109 }
110let contains_wildcard = criteria.name.contains(|c| c == '*' || c == '?');
111let err_msg_criteria = criteria.clone();
112113let match_fn: Box<dyn Fn(&Instrument) -> bool + Send + Sync> = if contains_wildcard {
114if mask.name != "" {
115 global::handle_error(MetricsError::Config(format!(
116"name replacement for multiple instruments, dropping view, criteria: {criteria:?}, mask: {mask:?}"
117)));
118return Ok(Box::new(empty_view));
119 }
120121let pattern = criteria.name.clone();
122let glob_pattern =
123 Pattern::new(&pattern).map_err(|e| MetricsError::Config(e.to_string()))?;
124125 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 };
135136let mut agg = None;
137if let Some(ma) = &mask.aggregation {
138match ma.validate() {
139Ok(_) => agg = Some(ma.clone()),
140Err(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 )));
145return Ok(Box::new(empty_view));
146 }
147 }
148 }
149150Ok(Box::new(move |i: &Instrument| -> Option<Stream> {
151if match_fn(i) {
152Some(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 {
172None
173}
174 }))
175}
176177#[cfg(test)]
178mod tests {
179use super::*;
180#[test]
181fn test_new_view_matching_all() {
182let criteria = Instrument::new().name("*");
183let mask = Stream::new();
184185let view = new_view(criteria, mask).expect("Expected to create a new view");
186187let test_instrument = Instrument::new().name("test_instrument");
188assert!(
189 view.match_inst(&test_instrument).is_some(),
190"Expected to match all instruments with * pattern"
191);
192 }
193194#[test]
195fn test_new_view_exact_match() {
196let criteria = Instrument::new().name("counter_exact_match");
197let mask = Stream::new();
198199let view = new_view(criteria, mask).expect("Expected to create a new view");
200201let matching_instrument = Instrument::new().name("counter_exact_match");
202assert!(
203 view.match_inst(&matching_instrument).is_some(),
204"Expected to match instrument with exact name"
205);
206207let non_matching_instrument = Instrument::new().name("counter_non_exact_match");
208assert!(
209 view.match_inst(&non_matching_instrument).is_none(),
210"Expected not to match instrument with different name"
211);
212 }
213214#[test]
215fn test_new_view_with_wildcard_pattern() {
216let criteria = Instrument::new().name("prefix_*");
217let mask = Stream::new();
218219let view = new_view(criteria, mask).expect("Expected to create a new view");
220221let matching_instrument = Instrument::new().name("prefix_counter");
222assert!(
223 view.match_inst(&matching_instrument).is_some(),
224"Expected to match instrument with matching prefix"
225);
226227let non_matching_instrument = Instrument::new().name("nonprefix_counter");
228assert!(
229 view.match_inst(&non_matching_instrument).is_none(),
230"Expected not to match instrument with different prefix"
231);
232 }
233234#[test]
235fn test_new_view_wildcard_question_mark() {
236let criteria = Instrument::new().name("test_?");
237let mask = Stream::new();
238239let view = new_view(criteria, mask).expect("Expected to create a new view");
240241// Instrument name that should match the pattern "test_?".
242let matching_instrument = Instrument::new().name("test_1");
243assert!(
244 view.match_inst(&matching_instrument).is_some(),
245"Expected to match instrument with test_? pattern"
246);
247248// Instrument name that should not match the pattern "test_?".
249let non_matching_instrument = Instrument::new().name("test_12");
250assert!(
251 view.match_inst(&non_matching_instrument).is_none(),
252"Expected not to match instrument with test_? pattern"
253);
254 }
255}