1use core::fmt;
2use std::{any::Any, borrow::Cow, collections::HashSet, sync::Arc};
3
4use opentelemetry::{
5 global,
6 metrics::{
7 noop::{NoopAsyncInstrument, NoopRegistration},
8 AsyncInstrument, Callback, CallbackRegistration, Counter, Gauge, Histogram,
9 InstrumentProvider, MetricsError, ObservableCounter, ObservableGauge,
10 ObservableUpDownCounter, Observer as ApiObserver, Result, UpDownCounter,
11 },
12 KeyValue,
13};
14
15use crate::instrumentation::Scope;
16use crate::metrics::{
17 instrument::{
18 Instrument, InstrumentKind, Observable, ObservableId, ResolvedMeasures, EMPTY_MEASURE_MSG,
19 },
20 internal::{self, Number},
21 pipeline::{Pipelines, Resolver},
22};
23
24const INSTRUMENT_NAME_MAX_LENGTH: usize = 255;
26const INSTRUMENT_UNIT_NAME_MAX_LENGTH: usize = 63;
28const INSTRUMENT_NAME_ALLOWED_NON_ALPHANUMERIC_CHARS: [char; 4] = ['_', '.', '-', '/'];
29
30const INSTRUMENT_NAME_EMPTY: &str = "instrument name must be non-empty";
32const INSTRUMENT_NAME_LENGTH: &str = "instrument name must be less than 256 characters";
33const INSTRUMENT_NAME_INVALID_CHAR: &str =
34 "characters in instrument name must be ASCII and belong to the alphanumeric characters, '_', '.', '-' and '/'";
35const INSTRUMENT_NAME_FIRST_ALPHABETIC: &str =
36 "instrument name must start with an alphabetic character";
37const INSTRUMENT_UNIT_LENGTH: &str = "instrument unit must be less than 64 characters";
38const INSTRUMENT_UNIT_INVALID_CHAR: &str = "characters in instrument unit must be ASCII";
39
40pub struct SdkMeter {
50 scope: Scope,
51 pipes: Arc<Pipelines>,
52 u64_resolver: Resolver<u64>,
53 i64_resolver: Resolver<i64>,
54 f64_resolver: Resolver<f64>,
55 validation_policy: InstrumentValidationPolicy,
56}
57
58impl SdkMeter {
59 pub(crate) fn new(scope: Scope, pipes: Arc<Pipelines>) -> Self {
60 let view_cache = Default::default();
61
62 SdkMeter {
63 scope,
64 pipes: Arc::clone(&pipes),
65 u64_resolver: Resolver::new(Arc::clone(&pipes), Arc::clone(&view_cache)),
66 i64_resolver: Resolver::new(Arc::clone(&pipes), Arc::clone(&view_cache)),
67 f64_resolver: Resolver::new(pipes, view_cache),
68 validation_policy: InstrumentValidationPolicy::HandleGlobalAndIgnore,
69 }
70 }
71
72 #[cfg(test)]
73 fn with_validation_policy(self, validation_policy: InstrumentValidationPolicy) -> Self {
74 Self {
75 validation_policy,
76 ..self
77 }
78 }
79}
80
81#[doc(hidden)]
82impl InstrumentProvider for SdkMeter {
83 fn u64_counter(
84 &self,
85 name: Cow<'static, str>,
86 description: Option<Cow<'static, str>>,
87 unit: Option<Cow<'static, str>>,
88 ) -> Result<Counter<u64>> {
89 validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
90 let p = InstrumentResolver::new(self, &self.u64_resolver);
91 p.lookup(InstrumentKind::Counter, name, description, unit)
92 .map(|i| Counter::new(Arc::new(i)))
93 }
94
95 fn f64_counter(
96 &self,
97 name: Cow<'static, str>,
98 description: Option<Cow<'static, str>>,
99 unit: Option<Cow<'static, str>>,
100 ) -> Result<Counter<f64>> {
101 validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
102 let p = InstrumentResolver::new(self, &self.f64_resolver);
103 p.lookup(InstrumentKind::Counter, name, description, unit)
104 .map(|i| Counter::new(Arc::new(i)))
105 }
106
107 fn u64_observable_counter(
108 &self,
109 name: Cow<'static, str>,
110 description: Option<Cow<'static, str>>,
111 unit: Option<Cow<'static, str>>,
112 callbacks: Vec<Callback<u64>>,
113 ) -> Result<ObservableCounter<u64>> {
114 validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
115 let p = InstrumentResolver::new(self, &self.u64_resolver);
116 let ms = p.measures(
117 InstrumentKind::ObservableCounter,
118 name.clone(),
119 description.clone(),
120 unit.clone(),
121 )?;
122 if ms.is_empty() {
123 return Ok(ObservableCounter::new(Arc::new(NoopAsyncInstrument::new())));
124 }
125
126 let observable = Arc::new(Observable::new(
127 self.scope.clone(),
128 InstrumentKind::ObservableCounter,
129 name,
130 description.unwrap_or_default(),
131 unit.unwrap_or_default(),
132 ms,
133 ));
134
135 for callback in callbacks {
136 let cb_inst = Arc::clone(&observable);
137 self.pipes
138 .register_callback(move || callback(cb_inst.as_ref()));
139 }
140
141 Ok(ObservableCounter::new(observable))
142 }
143
144 fn f64_observable_counter(
145 &self,
146 name: Cow<'static, str>,
147 description: Option<Cow<'static, str>>,
148 unit: Option<Cow<'static, str>>,
149 callbacks: Vec<Callback<f64>>,
150 ) -> Result<ObservableCounter<f64>> {
151 validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
152 let p = InstrumentResolver::new(self, &self.f64_resolver);
153 let ms = p.measures(
154 InstrumentKind::ObservableCounter,
155 name.clone(),
156 description.clone(),
157 unit.clone(),
158 )?;
159 if ms.is_empty() {
160 return Ok(ObservableCounter::new(Arc::new(NoopAsyncInstrument::new())));
161 }
162 let observable = Arc::new(Observable::new(
163 self.scope.clone(),
164 InstrumentKind::ObservableCounter,
165 name,
166 description.unwrap_or_default(),
167 unit.unwrap_or_default(),
168 ms,
169 ));
170
171 for callback in callbacks {
172 let cb_inst = Arc::clone(&observable);
173 self.pipes
174 .register_callback(move || callback(cb_inst.as_ref()));
175 }
176
177 Ok(ObservableCounter::new(observable))
178 }
179
180 fn i64_up_down_counter(
181 &self,
182 name: Cow<'static, str>,
183 description: Option<Cow<'static, str>>,
184 unit: Option<Cow<'static, str>>,
185 ) -> Result<UpDownCounter<i64>> {
186 validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
187 let p = InstrumentResolver::new(self, &self.i64_resolver);
188 p.lookup(InstrumentKind::UpDownCounter, name, description, unit)
189 .map(|i| UpDownCounter::new(Arc::new(i)))
190 }
191
192 fn f64_up_down_counter(
193 &self,
194 name: Cow<'static, str>,
195 description: Option<Cow<'static, str>>,
196 unit: Option<Cow<'static, str>>,
197 ) -> Result<UpDownCounter<f64>> {
198 validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
199 let p = InstrumentResolver::new(self, &self.f64_resolver);
200 p.lookup(InstrumentKind::UpDownCounter, name, description, unit)
201 .map(|i| UpDownCounter::new(Arc::new(i)))
202 }
203
204 fn i64_observable_up_down_counter(
205 &self,
206 name: Cow<'static, str>,
207 description: Option<Cow<'static, str>>,
208 unit: Option<Cow<'static, str>>,
209 callbacks: Vec<Callback<i64>>,
210 ) -> Result<ObservableUpDownCounter<i64>> {
211 validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
212 let p = InstrumentResolver::new(self, &self.i64_resolver);
213 let ms = p.measures(
214 InstrumentKind::ObservableUpDownCounter,
215 name.clone(),
216 description.clone(),
217 unit.clone(),
218 )?;
219 if ms.is_empty() {
220 return Ok(ObservableUpDownCounter::new(Arc::new(
221 NoopAsyncInstrument::new(),
222 )));
223 }
224
225 let observable = Arc::new(Observable::new(
226 self.scope.clone(),
227 InstrumentKind::ObservableUpDownCounter,
228 name,
229 description.unwrap_or_default(),
230 unit.unwrap_or_default(),
231 ms,
232 ));
233
234 for callback in callbacks {
235 let cb_inst = Arc::clone(&observable);
236 self.pipes
237 .register_callback(move || callback(cb_inst.as_ref()));
238 }
239
240 Ok(ObservableUpDownCounter::new(observable))
241 }
242
243 fn f64_observable_up_down_counter(
244 &self,
245 name: Cow<'static, str>,
246 description: Option<Cow<'static, str>>,
247 unit: Option<Cow<'static, str>>,
248 callbacks: Vec<Callback<f64>>,
249 ) -> Result<ObservableUpDownCounter<f64>> {
250 validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
251 let p = InstrumentResolver::new(self, &self.f64_resolver);
252 let ms = p.measures(
253 InstrumentKind::ObservableUpDownCounter,
254 name.clone(),
255 description.clone(),
256 unit.clone(),
257 )?;
258 if ms.is_empty() {
259 return Ok(ObservableUpDownCounter::new(Arc::new(
260 NoopAsyncInstrument::new(),
261 )));
262 }
263
264 let observable = Arc::new(Observable::new(
265 self.scope.clone(),
266 InstrumentKind::ObservableUpDownCounter,
267 name,
268 description.unwrap_or_default(),
269 unit.unwrap_or_default(),
270 ms,
271 ));
272
273 for callback in callbacks {
274 let cb_inst = Arc::clone(&observable);
275 self.pipes
276 .register_callback(move || callback(cb_inst.as_ref()));
277 }
278
279 Ok(ObservableUpDownCounter::new(observable))
280 }
281
282 fn u64_gauge(
283 &self,
284 name: Cow<'static, str>,
285 description: Option<Cow<'static, str>>,
286 unit: Option<Cow<'static, str>>,
287 ) -> Result<Gauge<u64>> {
288 validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
289 let p = InstrumentResolver::new(self, &self.u64_resolver);
290 p.lookup(InstrumentKind::Gauge, name, description, unit)
291 .map(|i| Gauge::new(Arc::new(i)))
292 }
293
294 fn f64_gauge(
295 &self,
296 name: Cow<'static, str>,
297 description: Option<Cow<'static, str>>,
298 unit: Option<Cow<'static, str>>,
299 ) -> Result<Gauge<f64>> {
300 validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
301 let p = InstrumentResolver::new(self, &self.f64_resolver);
302 p.lookup(InstrumentKind::Gauge, name, description, unit)
303 .map(|i| Gauge::new(Arc::new(i)))
304 }
305
306 fn i64_gauge(
307 &self,
308 name: Cow<'static, str>,
309 description: Option<Cow<'static, str>>,
310 unit: Option<Cow<'static, str>>,
311 ) -> Result<Gauge<i64>> {
312 validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
313 let p = InstrumentResolver::new(self, &self.i64_resolver);
314 p.lookup(InstrumentKind::Gauge, name, description, unit)
315 .map(|i| Gauge::new(Arc::new(i)))
316 }
317
318 fn u64_observable_gauge(
319 &self,
320 name: Cow<'static, str>,
321 description: Option<Cow<'static, str>>,
322 unit: Option<Cow<'static, str>>,
323 callbacks: Vec<Callback<u64>>,
324 ) -> Result<ObservableGauge<u64>> {
325 validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
326 let p = InstrumentResolver::new(self, &self.u64_resolver);
327 let ms = p.measures(
328 InstrumentKind::ObservableGauge,
329 name.clone(),
330 description.clone(),
331 unit.clone(),
332 )?;
333 if ms.is_empty() {
334 return Ok(ObservableGauge::new(Arc::new(NoopAsyncInstrument::new())));
335 }
336
337 let observable = Arc::new(Observable::new(
338 self.scope.clone(),
339 InstrumentKind::ObservableGauge,
340 name,
341 description.unwrap_or_default(),
342 unit.unwrap_or_default(),
343 ms,
344 ));
345
346 for callback in callbacks {
347 let cb_inst = Arc::clone(&observable);
348 self.pipes
349 .register_callback(move || callback(cb_inst.as_ref()));
350 }
351
352 Ok(ObservableGauge::new(observable))
353 }
354
355 fn i64_observable_gauge(
356 &self,
357 name: Cow<'static, str>,
358 description: Option<Cow<'static, str>>,
359 unit: Option<Cow<'static, str>>,
360 callbacks: Vec<Callback<i64>>,
361 ) -> Result<ObservableGauge<i64>> {
362 validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
363 let p = InstrumentResolver::new(self, &self.i64_resolver);
364 let ms = p.measures(
365 InstrumentKind::ObservableGauge,
366 name.clone(),
367 description.clone(),
368 unit.clone(),
369 )?;
370 if ms.is_empty() {
371 return Ok(ObservableGauge::new(Arc::new(NoopAsyncInstrument::new())));
372 }
373
374 let observable = Arc::new(Observable::new(
375 self.scope.clone(),
376 InstrumentKind::ObservableGauge,
377 name,
378 description.unwrap_or_default(),
379 unit.unwrap_or_default(),
380 ms,
381 ));
382
383 for callback in callbacks {
384 let cb_inst = Arc::clone(&observable);
385 self.pipes
386 .register_callback(move || callback(cb_inst.as_ref()));
387 }
388
389 Ok(ObservableGauge::new(observable))
390 }
391
392 fn f64_observable_gauge(
393 &self,
394 name: Cow<'static, str>,
395 description: Option<Cow<'static, str>>,
396 unit: Option<Cow<'static, str>>,
397 callbacks: Vec<Callback<f64>>,
398 ) -> Result<ObservableGauge<f64>> {
399 validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
400 let p = InstrumentResolver::new(self, &self.f64_resolver);
401 let ms = p.measures(
402 InstrumentKind::ObservableGauge,
403 name.clone(),
404 description.clone(),
405 unit.clone(),
406 )?;
407 if ms.is_empty() {
408 return Ok(ObservableGauge::new(Arc::new(NoopAsyncInstrument::new())));
409 }
410
411 let observable = Arc::new(Observable::new(
412 self.scope.clone(),
413 InstrumentKind::ObservableGauge,
414 name,
415 description.unwrap_or_default(),
416 unit.unwrap_or_default(),
417 ms,
418 ));
419
420 for callback in callbacks {
421 let cb_inst = Arc::clone(&observable);
422 self.pipes
423 .register_callback(move || callback(cb_inst.as_ref()));
424 }
425
426 Ok(ObservableGauge::new(observable))
427 }
428
429 fn f64_histogram(
430 &self,
431 name: Cow<'static, str>,
432 description: Option<Cow<'static, str>>,
433 unit: Option<Cow<'static, str>>,
434 ) -> Result<Histogram<f64>> {
435 validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
436 let p = InstrumentResolver::new(self, &self.f64_resolver);
437 p.lookup(InstrumentKind::Histogram, name, description, unit)
438 .map(|i| Histogram::new(Arc::new(i)))
439 }
440
441 fn u64_histogram(
442 &self,
443 name: Cow<'static, str>,
444 description: Option<Cow<'static, str>>,
445 unit: Option<Cow<'static, str>>,
446 ) -> Result<Histogram<u64>> {
447 validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
448 let p = InstrumentResolver::new(self, &self.u64_resolver);
449 p.lookup(InstrumentKind::Histogram, name, description, unit)
450 .map(|i| Histogram::new(Arc::new(i)))
451 }
452
453 fn register_callback(
454 &self,
455 insts: &[Arc<dyn Any>],
456 callback: Box<dyn Fn(&dyn ApiObserver) + Send + Sync>,
457 ) -> Result<Box<dyn CallbackRegistration>> {
458 if insts.is_empty() {
459 return Ok(Box::new(NoopRegistration::new()));
460 }
461
462 let mut reg = Observer::default();
463 let mut errs = vec![];
464 for inst in insts {
465 if let Some(i64_obs) = inst.downcast_ref::<Observable<i64>>() {
466 if let Err(err) = i64_obs.registerable(&self.scope) {
467 if !err.to_string().contains(EMPTY_MEASURE_MSG) {
468 errs.push(err);
469 }
470 continue;
471 }
472 reg.register_i64(i64_obs.id.clone());
473 } else if let Some(u64_obs) = inst.downcast_ref::<Observable<u64>>() {
474 if let Err(err) = u64_obs.registerable(&self.scope) {
475 if !err.to_string().contains(EMPTY_MEASURE_MSG) {
476 errs.push(err);
477 }
478 continue;
479 }
480 reg.register_u64(u64_obs.id.clone());
481 } else if let Some(f64_obs) = inst.downcast_ref::<Observable<f64>>() {
482 if let Err(err) = f64_obs.registerable(&self.scope) {
483 if !err.to_string().contains(EMPTY_MEASURE_MSG) {
484 errs.push(err);
485 }
486 continue;
487 }
488 reg.register_f64(f64_obs.id.clone());
489 } else {
490 return Err(MetricsError::Other(
492 "invalid observable: from different implementation".into(),
493 ));
494 }
495 }
496
497 if !errs.is_empty() {
498 return Err(MetricsError::Other(format!("{errs:?}")));
499 }
500
501 if reg.is_empty() {
502 return Ok(Box::new(NoopRegistration::new()));
504 }
505
506 self.pipes.register_multi_callback(move || callback(®))
507 }
508}
509
510#[derive(Clone, Copy)]
512enum InstrumentValidationPolicy {
513 HandleGlobalAndIgnore,
514 #[cfg(test)]
516 Strict,
517}
518
519fn validate_instrument_config(
520 name: &str,
521 unit: &Option<Cow<'static, str>>,
522 policy: InstrumentValidationPolicy,
523) -> Result<()> {
524 match validate_instrument_name(name).and_then(|_| validate_instrument_unit(unit)) {
525 Ok(_) => Ok(()),
526 Err(err) => match policy {
527 InstrumentValidationPolicy::HandleGlobalAndIgnore => {
528 global::handle_error(err);
529 Ok(())
530 }
531 #[cfg(test)]
532 InstrumentValidationPolicy::Strict => Err(err),
533 },
534 }
535}
536
537fn validate_instrument_name(name: &str) -> Result<()> {
538 if name.is_empty() {
539 return Err(MetricsError::InvalidInstrumentConfiguration(
540 INSTRUMENT_NAME_EMPTY,
541 ));
542 }
543 if name.len() > INSTRUMENT_NAME_MAX_LENGTH {
544 return Err(MetricsError::InvalidInstrumentConfiguration(
545 INSTRUMENT_NAME_LENGTH,
546 ));
547 }
548 if name.starts_with(|c: char| !c.is_ascii_alphabetic()) {
549 return Err(MetricsError::InvalidInstrumentConfiguration(
550 INSTRUMENT_NAME_FIRST_ALPHABETIC,
551 ));
552 }
553 if name.contains(|c: char| {
554 !c.is_ascii_alphanumeric() && !INSTRUMENT_NAME_ALLOWED_NON_ALPHANUMERIC_CHARS.contains(&c)
555 }) {
556 return Err(MetricsError::InvalidInstrumentConfiguration(
557 INSTRUMENT_NAME_INVALID_CHAR,
558 ));
559 }
560 Ok(())
561}
562
563fn validate_instrument_unit(unit: &Option<Cow<'static, str>>) -> Result<()> {
564 if let Some(unit) = unit {
565 if unit.len() > INSTRUMENT_UNIT_NAME_MAX_LENGTH {
566 return Err(MetricsError::InvalidInstrumentConfiguration(
567 INSTRUMENT_UNIT_LENGTH,
568 ));
569 }
570 if unit.contains(|c: char| !c.is_ascii()) {
571 return Err(MetricsError::InvalidInstrumentConfiguration(
572 INSTRUMENT_UNIT_INVALID_CHAR,
573 ));
574 }
575 }
576 Ok(())
577}
578
579#[derive(Default)]
580struct Observer {
581 f64s: HashSet<ObservableId<f64>>,
582 i64s: HashSet<ObservableId<i64>>,
583 u64s: HashSet<ObservableId<u64>>,
584}
585
586impl Observer {
587 fn is_empty(&self) -> bool {
588 self.f64s.is_empty() && self.i64s.is_empty() && self.u64s.is_empty()
589 }
590
591 pub(crate) fn register_i64(&mut self, id: ObservableId<i64>) {
592 self.i64s.insert(id);
593 }
594
595 pub(crate) fn register_f64(&mut self, id: ObservableId<f64>) {
596 self.f64s.insert(id);
597 }
598
599 pub(crate) fn register_u64(&mut self, id: ObservableId<u64>) {
600 self.u64s.insert(id);
601 }
602}
603
604impl ApiObserver for Observer {
605 fn observe_f64(&self, inst: &dyn AsyncInstrument<f64>, measurement: f64, attrs: &[KeyValue]) {
606 if let Some(f64_obs) = inst.as_any().downcast_ref::<Observable<f64>>() {
607 if self.f64s.contains(&f64_obs.id) {
608 f64_obs.observe(measurement, attrs)
609 } else {
610 global::handle_error(
611 MetricsError::Other(format!("observable instrument not registered for callback, failed to record. name: {}, description: {}, unit: {:?}, number: f64",
612 f64_obs.id.inner.name,
613 f64_obs.id.inner.description,
614 f64_obs.id.inner.unit,
615 )))
616 }
617 } else {
618 global::handle_error(MetricsError::Other(
619 "unknown observable instrument, failed to record.".into(),
620 ))
621 }
622 }
623
624 fn observe_u64(&self, inst: &dyn AsyncInstrument<u64>, measurement: u64, attrs: &[KeyValue]) {
625 if let Some(u64_obs) = inst.as_any().downcast_ref::<Observable<u64>>() {
626 if self.u64s.contains(&u64_obs.id) {
627 u64_obs.observe(measurement, attrs)
628 } else {
629 global::handle_error(
630 MetricsError::Other(format!("observable instrument not registered for callback, failed to record. name: {}, description: {}, unit: {:?}, number: f64",
631 u64_obs.id.inner.name,
632 u64_obs.id.inner.description,
633 u64_obs.id.inner.unit,
634 )))
635 }
636 } else {
637 global::handle_error(MetricsError::Other(
638 "unknown observable instrument, failed to record.".into(),
639 ))
640 }
641 }
642
643 fn observe_i64(&self, inst: &dyn AsyncInstrument<i64>, measurement: i64, attrs: &[KeyValue]) {
644 if let Some(i64_obs) = inst.as_any().downcast_ref::<Observable<i64>>() {
645 if self.i64s.contains(&i64_obs.id) {
646 i64_obs.observe(measurement, attrs)
647 } else {
648 global::handle_error(
649 MetricsError::Other(format!("observable instrument not registered for callback, failed to record. name: {}, description: {}, unit: {:?}, number: f64",
650 i64_obs.id.inner.name,
651 i64_obs.id.inner.description,
652 i64_obs.id.inner.unit,
653 )))
654 }
655 } else {
656 global::handle_error(MetricsError::Other(
657 "unknown observable instrument, failed to record.".into(),
658 ))
659 }
660 }
661}
662
663impl fmt::Debug for SdkMeter {
664 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
665 f.debug_struct("Meter").field("scope", &self.scope).finish()
666 }
667}
668
669struct InstrumentResolver<'a, T> {
671 meter: &'a SdkMeter,
672 resolve: &'a Resolver<T>,
673}
674
675impl<'a, T> InstrumentResolver<'a, T>
676where
677 T: Number<T>,
678{
679 fn new(meter: &'a SdkMeter, resolve: &'a Resolver<T>) -> Self {
680 InstrumentResolver { meter, resolve }
681 }
682
683 fn lookup(
685 &self,
686 kind: InstrumentKind,
687 name: Cow<'static, str>,
688 description: Option<Cow<'static, str>>,
689 unit: Option<Cow<'static, str>>,
690 ) -> Result<ResolvedMeasures<T>> {
691 let aggregators = self.measures(kind, name, description, unit)?;
692 Ok(ResolvedMeasures {
693 measures: aggregators,
694 })
695 }
696
697 fn measures(
698 &self,
699 kind: InstrumentKind,
700 name: Cow<'static, str>,
701 description: Option<Cow<'static, str>>,
702 unit: Option<Cow<'static, str>>,
703 ) -> Result<Vec<Arc<dyn internal::Measure<T>>>> {
704 let inst = Instrument {
705 name,
706 description: description.unwrap_or_default(),
707 unit: unit.unwrap_or_default(),
708 kind: Some(kind),
709 scope: self.meter.scope.clone(),
710 };
711
712 self.resolve.measures(inst)
713 }
714}
715
716#[cfg(test)]
717mod tests {
718 use std::sync::Arc;
719
720 use opentelemetry::metrics::{InstrumentProvider, MeterProvider, MetricsError};
721
722 use super::{
723 InstrumentValidationPolicy, SdkMeter, INSTRUMENT_NAME_FIRST_ALPHABETIC,
724 INSTRUMENT_NAME_INVALID_CHAR, INSTRUMENT_NAME_LENGTH, INSTRUMENT_UNIT_INVALID_CHAR,
725 INSTRUMENT_UNIT_LENGTH,
726 };
727 use crate::{
728 metrics::{pipeline::Pipelines, SdkMeterProvider},
729 Resource, Scope,
730 };
731
732 #[test]
733 #[ignore = "See issue https://github.com/open-telemetry/opentelemetry-rust/issues/1699"]
734 fn test_instrument_creation() {
735 let provider = SdkMeterProvider::builder().build();
736 let meter = provider.meter("test");
737 assert!(meter.u64_counter("test").try_init().is_ok());
738 let result = meter.u64_counter("test with invalid name").try_init();
739 assert!(result.is_err());
741 }
742
743 #[test]
744 fn test_instrument_config_validation() {
745 let meter = SdkMeter::new(
747 Scope::default(),
748 Arc::new(Pipelines::new(Resource::default(), Vec::new(), Vec::new())),
749 )
750 .with_validation_policy(InstrumentValidationPolicy::Strict);
751 let instrument_name_test_cases = vec![
753 ("validateName", ""),
754 ("_startWithNoneAlphabet", INSTRUMENT_NAME_FIRST_ALPHABETIC),
755 ("utf8char锈", INSTRUMENT_NAME_INVALID_CHAR),
756 ("a".repeat(255).leak(), ""),
757 ("a".repeat(256).leak(), INSTRUMENT_NAME_LENGTH),
758 ("invalid name", INSTRUMENT_NAME_INVALID_CHAR),
759 ("allow/slash", ""),
760 ("allow_under_score", ""),
761 ("allow.dots.ok", ""),
762 ];
763 for (name, expected_error) in instrument_name_test_cases {
764 let assert = |result: Result<_, MetricsError>| {
765 if expected_error.is_empty() {
766 assert!(result.is_ok());
767 } else {
768 assert!(matches!(
769 result.unwrap_err(),
770 MetricsError::InvalidInstrumentConfiguration(msg) if msg == expected_error
771 ));
772 }
773 };
774
775 assert(meter.u64_counter(name.into(), None, None).map(|_| ()));
776 assert(meter.f64_counter(name.into(), None, None).map(|_| ()));
777 assert(
778 meter
779 .u64_observable_counter(name.into(), None, None, Vec::new())
780 .map(|_| ()),
781 );
782 assert(
783 meter
784 .f64_observable_counter(name.into(), None, None, Vec::new())
785 .map(|_| ()),
786 );
787 assert(
788 meter
789 .i64_up_down_counter(name.into(), None, None)
790 .map(|_| ()),
791 );
792 assert(
793 meter
794 .f64_up_down_counter(name.into(), None, None)
795 .map(|_| ()),
796 );
797 assert(
798 meter
799 .i64_observable_up_down_counter(name.into(), None, None, Vec::new())
800 .map(|_| ()),
801 );
802 assert(
803 meter
804 .f64_observable_up_down_counter(name.into(), None, None, Vec::new())
805 .map(|_| ()),
806 );
807 assert(meter.u64_gauge(name.into(), None, None).map(|_| ()));
808 assert(meter.f64_gauge(name.into(), None, None).map(|_| ()));
809 assert(meter.i64_gauge(name.into(), None, None).map(|_| ()));
810 assert(
811 meter
812 .u64_observable_gauge(name.into(), None, None, Vec::new())
813 .map(|_| ()),
814 );
815 assert(
816 meter
817 .i64_observable_gauge(name.into(), None, None, Vec::new())
818 .map(|_| ()),
819 );
820 assert(
821 meter
822 .f64_observable_gauge(name.into(), None, None, Vec::new())
823 .map(|_| ()),
824 );
825 assert(meter.f64_histogram(name.into(), None, None).map(|_| ()));
826 assert(meter.u64_histogram(name.into(), None, None).map(|_| ()));
827 }
828
829 let instrument_unit_test_cases = vec![
831 (
832 "0123456789012345678901234567890123456789012345678901234567890123",
833 INSTRUMENT_UNIT_LENGTH,
834 ),
835 ("utf8char锈", INSTRUMENT_UNIT_INVALID_CHAR),
836 ("kb", ""),
837 ("Kb/sec", ""),
838 ("%", ""),
839 ("", ""),
840 ];
841
842 for (unit, expected_error) in instrument_unit_test_cases {
843 let assert = |result: Result<_, MetricsError>| {
844 if expected_error.is_empty() {
845 assert!(result.is_ok());
846 } else {
847 assert!(matches!(
848 result.unwrap_err(),
849 MetricsError::InvalidInstrumentConfiguration(msg) if msg == expected_error
850 ));
851 }
852 };
853 let unit = Some(unit.into());
854 assert(
855 meter
856 .u64_counter("test".into(), None, unit.clone())
857 .map(|_| ()),
858 );
859 assert(
860 meter
861 .f64_counter("test".into(), None, unit.clone())
862 .map(|_| ()),
863 );
864 assert(
865 meter
866 .u64_observable_counter("test".into(), None, unit.clone(), Vec::new())
867 .map(|_| ()),
868 );
869 assert(
870 meter
871 .f64_observable_counter("test".into(), None, unit.clone(), Vec::new())
872 .map(|_| ()),
873 );
874 assert(
875 meter
876 .i64_up_down_counter("test".into(), None, unit.clone())
877 .map(|_| ()),
878 );
879 assert(
880 meter
881 .f64_up_down_counter("test".into(), None, unit.clone())
882 .map(|_| ()),
883 );
884 assert(
885 meter
886 .i64_observable_up_down_counter("test".into(), None, unit.clone(), Vec::new())
887 .map(|_| ()),
888 );
889 assert(
890 meter
891 .f64_observable_up_down_counter("test".into(), None, unit.clone(), Vec::new())
892 .map(|_| ()),
893 );
894 assert(
895 meter
896 .u64_observable_gauge("test".into(), None, unit.clone(), Vec::new())
897 .map(|_| ()),
898 );
899 assert(
900 meter
901 .i64_observable_gauge("test".into(), None, unit.clone(), Vec::new())
902 .map(|_| ()),
903 );
904 assert(
905 meter
906 .f64_observable_gauge("test".into(), None, unit.clone(), Vec::new())
907 .map(|_| ()),
908 );
909 assert(
910 meter
911 .f64_histogram("test".into(), None, unit.clone())
912 .map(|_| ()),
913 );
914 assert(
915 meter
916 .u64_histogram("test".into(), None, unit.clone())
917 .map(|_| ()),
918 );
919 }
920 }
921}