opentelemetry_sdk/metrics/
meter.rs

1#![allow(dead_code)]
2use core::fmt;
3use std::{borrow::Cow, sync::Arc};
4
5use opentelemetry::{
6    metrics::{
7        AsyncInstrumentBuilder, Counter, Gauge, Histogram, HistogramBuilder, InstrumentBuilder,
8        InstrumentProvider, ObservableCounter, ObservableGauge, ObservableUpDownCounter,
9        UpDownCounter,
10    },
11    otel_error, InstrumentationScope,
12};
13
14use crate::metrics::{
15    instrument::{Instrument, InstrumentKind, Observable, ResolvedMeasures},
16    internal::{self, Number},
17    pipeline::{Pipelines, Resolver},
18    MetricError, MetricResult,
19};
20
21use super::noop::NoopSyncInstrument;
22
23// maximum length of instrument name
24const INSTRUMENT_NAME_MAX_LENGTH: usize = 255;
25// maximum length of instrument unit name
26const INSTRUMENT_UNIT_NAME_MAX_LENGTH: usize = 63;
27// Characters allowed in instrument name
28const INSTRUMENT_NAME_ALLOWED_NON_ALPHANUMERIC_CHARS: [char; 4] = ['_', '.', '-', '/'];
29
30// instrument name validation error strings
31const 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";
37
38// instrument unit validation error strings
39const INSTRUMENT_UNIT_LENGTH: &str = "instrument unit must be less than 64 characters";
40const INSTRUMENT_UNIT_INVALID_CHAR: &str = "characters in instrument unit must be ASCII";
41
42/// Handles the creation and coordination of all metric instruments.
43///
44/// A meter represents a single instrumentation scope; all metric telemetry
45/// produced by an instrumentation scope will use metric instruments from a
46/// single meter.
47///
48/// See the [Meter API] docs for usage.
49///
50/// [Meter API]: opentelemetry::metrics::Meter
51pub(crate) struct SdkMeter {
52    scope: InstrumentationScope,
53    pipes: Arc<Pipelines>,
54    u64_resolver: Resolver<u64>,
55    i64_resolver: Resolver<i64>,
56    f64_resolver: Resolver<f64>,
57}
58
59impl SdkMeter {
60    pub(crate) fn new(scope: InstrumentationScope, pipes: Arc<Pipelines>) -> Self {
61        let view_cache = Default::default();
62
63        SdkMeter {
64            scope,
65            pipes: Arc::clone(&pipes),
66            u64_resolver: Resolver::new(Arc::clone(&pipes), Arc::clone(&view_cache)),
67            i64_resolver: Resolver::new(Arc::clone(&pipes), Arc::clone(&view_cache)),
68            f64_resolver: Resolver::new(pipes, view_cache),
69        }
70    }
71
72    fn create_counter<T>(
73        &self,
74        builder: InstrumentBuilder<'_, Counter<T>>,
75        resolver: &InstrumentResolver<'_, T>,
76    ) -> Counter<T>
77    where
78        T: Number,
79    {
80        let validation_result = validate_instrument_config(builder.name.as_ref(), &builder.unit);
81        if let Err(err) = validation_result {
82            otel_error!(
83                name: "InstrumentCreationFailed",
84                meter_name = self.scope.name(),
85                instrument_name = builder.name.as_ref(),
86                message = "Measurements from this Counter will be ignored.",
87                reason = format!("{}", err)
88            );
89            return Counter::new(Arc::new(NoopSyncInstrument::new()));
90        }
91
92        match resolver
93            .lookup(
94                InstrumentKind::Counter,
95                builder.name.clone(),
96                builder.description,
97                builder.unit,
98                None,
99            )
100            .map(|i| Counter::new(Arc::new(i)))
101        {
102            Ok(counter) => counter,
103            Err(err) => {
104                otel_error!(
105                    name: "InstrumentCreationFailed",
106                    meter_name = self.scope.name(),
107                    instrument_name = builder.name.as_ref(),
108                    message = "Measurements from this Counter will be ignored.",
109                    reason = format!("{}", err)
110                );
111                Counter::new(Arc::new(NoopSyncInstrument::new()))
112            }
113        }
114    }
115
116    fn create_observable_counter<T>(
117        &self,
118        builder: AsyncInstrumentBuilder<'_, ObservableCounter<T>, T>,
119        resolver: &InstrumentResolver<'_, T>,
120    ) -> ObservableCounter<T>
121    where
122        T: Number,
123    {
124        let validation_result = validate_instrument_config(builder.name.as_ref(), &builder.unit);
125        if let Err(err) = validation_result {
126            otel_error!(
127                name: "InstrumentCreationFailed", 
128                meter_name = self.scope.name(),
129                instrument_name = builder.name.as_ref(),
130                message = "Callbacks for this ObservableCounter will not be invoked.",
131                reason = format!("{}", err));
132            return ObservableCounter::new();
133        }
134
135        match resolver.measures(
136            InstrumentKind::ObservableCounter,
137            builder.name.clone(),
138            builder.description,
139            builder.unit,
140            None,
141        ) {
142            Ok(ms) => {
143                if ms.is_empty() {
144                    otel_error!(
145                        name: "InstrumentCreationFailed",
146                        meter_name = self.scope.name(),
147                        instrument_name = builder.name.as_ref(),
148                        message = "Callbacks for this ObservableCounter will not be invoked. Check View Configuration."
149                    );
150                    return ObservableCounter::new();
151                }
152
153                let observable = Arc::new(Observable::new(ms));
154
155                for callback in builder.callbacks {
156                    let cb_inst = Arc::clone(&observable);
157                    self.pipes
158                        .register_callback(move || callback(cb_inst.as_ref()));
159                }
160
161                ObservableCounter::new()
162            }
163            Err(err) => {
164                otel_error!(
165                    name: "InstrumentCreationFailed",
166                    meter_name = self.scope.name(),
167                    instrument_name = builder.name.as_ref(),
168                    message = "Callbacks for this ObservableCounter will not be invoked.",
169                    reason = format!("{}", err));
170                ObservableCounter::new()
171            }
172        }
173    }
174
175    fn create_observable_updown_counter<T>(
176        &self,
177        builder: AsyncInstrumentBuilder<'_, ObservableUpDownCounter<T>, T>,
178        resolver: &InstrumentResolver<'_, T>,
179    ) -> ObservableUpDownCounter<T>
180    where
181        T: Number,
182    {
183        let validation_result = validate_instrument_config(builder.name.as_ref(), &builder.unit);
184        if let Err(err) = validation_result {
185            otel_error!(
186                name: "InstrumentCreationFailed", 
187                meter_name = self.scope.name(),
188                instrument_name = builder.name.as_ref(),
189                message = "Callbacks for this ObservableUpDownCounter will not be invoked.",
190                reason = format!("{}", err));
191            return ObservableUpDownCounter::new();
192        }
193
194        match resolver.measures(
195            InstrumentKind::ObservableUpDownCounter,
196            builder.name.clone(),
197            builder.description,
198            builder.unit,
199            None,
200        ) {
201            Ok(ms) => {
202                if ms.is_empty() {
203                    otel_error!(
204                        name: "InstrumentCreationFailed",
205                        meter_name = self.scope.name(),
206                        instrument_name = builder.name.as_ref(),
207                        message = "Callbacks for this ObservableUpDownCounter will not be invoked. Check View Configuration."
208                    );
209                    return ObservableUpDownCounter::new();
210                }
211
212                let observable = Arc::new(Observable::new(ms));
213
214                for callback in builder.callbacks {
215                    let cb_inst = Arc::clone(&observable);
216                    self.pipes
217                        .register_callback(move || callback(cb_inst.as_ref()));
218                }
219
220                ObservableUpDownCounter::new()
221            }
222            Err(err) => {
223                otel_error!(
224                    name: "InstrumentCreationFailed",
225                    meter_name = self.scope.name(),
226                    instrument_name = builder.name.as_ref(),
227                    message = "Callbacks for this ObservableUpDownCounter will not be invoked.",
228                    reason = format!("{}", err));
229                ObservableUpDownCounter::new()
230            }
231        }
232    }
233
234    fn create_observable_gauge<T>(
235        &self,
236        builder: AsyncInstrumentBuilder<'_, ObservableGauge<T>, T>,
237        resolver: &InstrumentResolver<'_, T>,
238    ) -> ObservableGauge<T>
239    where
240        T: Number,
241    {
242        let validation_result = validate_instrument_config(builder.name.as_ref(), &builder.unit);
243        if let Err(err) = validation_result {
244            otel_error!(
245                name: "InstrumentCreationFailed", 
246                meter_name = self.scope.name(),
247                instrument_name = builder.name.as_ref(),
248                message = "Callbacks for this ObservableGauge will not be invoked.",
249                reason = format!("{}", err));
250            return ObservableGauge::new();
251        }
252
253        match resolver.measures(
254            InstrumentKind::ObservableGauge,
255            builder.name.clone(),
256            builder.description,
257            builder.unit,
258            None,
259        ) {
260            Ok(ms) => {
261                if ms.is_empty() {
262                    otel_error!(
263                        name: "InstrumentCreationFailed",
264                        meter_name = self.scope.name(),
265                        instrument_name = builder.name.as_ref(),
266                        message = "Callbacks for this ObservableGauge will not be invoked. Check View Configuration."
267                    );
268                    return ObservableGauge::new();
269                }
270
271                let observable = Arc::new(Observable::new(ms));
272
273                for callback in builder.callbacks {
274                    let cb_inst = Arc::clone(&observable);
275                    self.pipes
276                        .register_callback(move || callback(cb_inst.as_ref()));
277                }
278
279                ObservableGauge::new()
280            }
281            Err(err) => {
282                otel_error!(
283                    name: "InstrumentCreationFailed",
284                    meter_name = self.scope.name(),
285                    instrument_name = builder.name.as_ref(),
286                    message = "Callbacks for this ObservableGauge will not be invoked.",
287                    reason = format!("{}", err));
288                ObservableGauge::new()
289            }
290        }
291    }
292
293    fn create_updown_counter<T>(
294        &self,
295        builder: InstrumentBuilder<'_, UpDownCounter<T>>,
296        resolver: &InstrumentResolver<'_, T>,
297    ) -> UpDownCounter<T>
298    where
299        T: Number,
300    {
301        let validation_result = validate_instrument_config(builder.name.as_ref(), &builder.unit);
302        if let Err(err) = validation_result {
303            otel_error!(
304                name: "InstrumentCreationFailed",
305                meter_name = self.scope.name(),
306                instrument_name = builder.name.as_ref(),
307                message = "Measurements from this UpDownCounter will be ignored.",
308                reason = format!("{}", err)
309            );
310            return UpDownCounter::new(Arc::new(NoopSyncInstrument::new()));
311        }
312
313        match resolver
314            .lookup(
315                InstrumentKind::UpDownCounter,
316                builder.name.clone(),
317                builder.description,
318                builder.unit,
319                None,
320            )
321            .map(|i| UpDownCounter::new(Arc::new(i)))
322        {
323            Ok(updown_counter) => updown_counter,
324            Err(err) => {
325                otel_error!(
326                    name: "InstrumentCreationFailed",
327                    meter_name = self.scope.name(),
328                    instrument_name = builder.name.as_ref(),
329                    message = "Measurements from this UpDownCounter will be ignored.",
330                    reason = format!("{}", err)
331                );
332                UpDownCounter::new(Arc::new(NoopSyncInstrument::new()))
333            }
334        }
335    }
336
337    fn create_gauge<T>(
338        &self,
339        builder: InstrumentBuilder<'_, Gauge<T>>,
340        resolver: &InstrumentResolver<'_, T>,
341    ) -> Gauge<T>
342    where
343        T: Number,
344    {
345        let validation_result = validate_instrument_config(builder.name.as_ref(), &builder.unit);
346        if let Err(err) = validation_result {
347            otel_error!(
348                name: "InstrumentCreationFailed",
349                meter_name = self.scope.name(),
350                instrument_name = builder.name.as_ref(),
351                message = "Measurements from this Gauge will be ignored.",
352                reason = format!("{}", err)
353            );
354            return Gauge::new(Arc::new(NoopSyncInstrument::new()));
355        }
356
357        match resolver
358            .lookup(
359                InstrumentKind::Gauge,
360                builder.name.clone(),
361                builder.description,
362                builder.unit,
363                None,
364            )
365            .map(|i| Gauge::new(Arc::new(i)))
366        {
367            Ok(gauge) => gauge,
368            Err(err) => {
369                otel_error!(
370                    name: "InstrumentCreationFailed",
371                    meter_name = self.scope.name(),
372                    instrument_name = builder.name.as_ref(),
373                    message = "Measurements from this Gauge will be ignored.",
374                    reason = format!("{}", err)
375                );
376                Gauge::new(Arc::new(NoopSyncInstrument::new()))
377            }
378        }
379    }
380
381    fn create_histogram<T>(
382        &self,
383        builder: HistogramBuilder<'_, Histogram<T>>,
384        resolver: &InstrumentResolver<'_, T>,
385    ) -> Histogram<T>
386    where
387        T: Number,
388    {
389        let validation_result = validate_instrument_config(builder.name.as_ref(), &builder.unit);
390        if let Err(err) = validation_result {
391            otel_error!(
392                name: "InstrumentCreationFailed",
393                meter_name = self.scope.name(),
394                instrument_name = builder.name.as_ref(),
395                message = "Measurements from this Histogram will be ignored.",
396                reason = format!("{}", err)
397            );
398            return Histogram::new(Arc::new(NoopSyncInstrument::new()));
399        }
400
401        if let Some(ref boundaries) = builder.boundaries {
402            let validation_result = validate_bucket_boundaries(boundaries);
403            if let Err(err) = validation_result {
404                // TODO: Include the buckets too in the error message.
405                // TODO: This validation is not done when Views are used to
406                // provide boundaries, and that should be fixed.
407                otel_error!(
408                    name: "InstrumentCreationFailed",
409                    meter_name = self.scope.name(),
410                    instrument_name = builder.name.as_ref(),
411                    message = "Measurements from this Histogram will be ignored.",
412                    reason = format!("{}", err)
413                );
414                return Histogram::new(Arc::new(NoopSyncInstrument::new()));
415            }
416        }
417
418        match resolver
419            .lookup(
420                InstrumentKind::Histogram,
421                builder.name.clone(),
422                builder.description,
423                builder.unit,
424                builder.boundaries,
425            )
426            .map(|i| Histogram::new(Arc::new(i)))
427        {
428            Ok(histogram) => histogram,
429            Err(err) => {
430                otel_error!(
431                    name: "InstrumentCreationFailed",
432                    meter_name = self.scope.name(),
433                    instrument_name = builder.name.as_ref(),
434                    message = "Measurements from this Histogram will be ignored.",
435                    reason = format!("{}", err)
436                );
437                Histogram::new(Arc::new(NoopSyncInstrument::new()))
438            }
439        }
440    }
441}
442
443#[doc(hidden)]
444impl InstrumentProvider for SdkMeter {
445    fn u64_counter(&self, builder: InstrumentBuilder<'_, Counter<u64>>) -> Counter<u64> {
446        let resolver = InstrumentResolver::new(self, &self.u64_resolver);
447        self.create_counter(builder, &resolver)
448    }
449
450    fn f64_counter(&self, builder: InstrumentBuilder<'_, Counter<f64>>) -> Counter<f64> {
451        let resolver = InstrumentResolver::new(self, &self.f64_resolver);
452        self.create_counter(builder, &resolver)
453    }
454
455    fn u64_observable_counter(
456        &self,
457        builder: AsyncInstrumentBuilder<'_, ObservableCounter<u64>, u64>,
458    ) -> ObservableCounter<u64> {
459        let resolver = InstrumentResolver::new(self, &self.u64_resolver);
460        self.create_observable_counter(builder, &resolver)
461    }
462
463    fn f64_observable_counter(
464        &self,
465        builder: AsyncInstrumentBuilder<'_, ObservableCounter<f64>, f64>,
466    ) -> ObservableCounter<f64> {
467        let resolver = InstrumentResolver::new(self, &self.f64_resolver);
468        self.create_observable_counter(builder, &resolver)
469    }
470
471    fn i64_up_down_counter(
472        &self,
473        builder: InstrumentBuilder<'_, UpDownCounter<i64>>,
474    ) -> UpDownCounter<i64> {
475        let resolver = InstrumentResolver::new(self, &self.i64_resolver);
476        self.create_updown_counter(builder, &resolver)
477    }
478
479    fn f64_up_down_counter(
480        &self,
481        builder: InstrumentBuilder<'_, UpDownCounter<f64>>,
482    ) -> UpDownCounter<f64> {
483        let resolver = InstrumentResolver::new(self, &self.f64_resolver);
484        self.create_updown_counter(builder, &resolver)
485    }
486
487    fn i64_observable_up_down_counter(
488        &self,
489        builder: AsyncInstrumentBuilder<'_, ObservableUpDownCounter<i64>, i64>,
490    ) -> ObservableUpDownCounter<i64> {
491        let resolver = InstrumentResolver::new(self, &self.i64_resolver);
492        self.create_observable_updown_counter(builder, &resolver)
493    }
494
495    fn f64_observable_up_down_counter(
496        &self,
497        builder: AsyncInstrumentBuilder<'_, ObservableUpDownCounter<f64>, f64>,
498    ) -> ObservableUpDownCounter<f64> {
499        let resolver = InstrumentResolver::new(self, &self.f64_resolver);
500        self.create_observable_updown_counter(builder, &resolver)
501    }
502
503    fn u64_gauge(&self, builder: InstrumentBuilder<'_, Gauge<u64>>) -> Gauge<u64> {
504        let resolver = InstrumentResolver::new(self, &self.u64_resolver);
505        self.create_gauge(builder, &resolver)
506    }
507
508    fn f64_gauge(&self, builder: InstrumentBuilder<'_, Gauge<f64>>) -> Gauge<f64> {
509        let resolver = InstrumentResolver::new(self, &self.f64_resolver);
510        self.create_gauge(builder, &resolver)
511    }
512
513    fn i64_gauge(&self, builder: InstrumentBuilder<'_, Gauge<i64>>) -> Gauge<i64> {
514        let resolver = InstrumentResolver::new(self, &self.i64_resolver);
515        self.create_gauge(builder, &resolver)
516    }
517
518    fn u64_observable_gauge(
519        &self,
520        builder: AsyncInstrumentBuilder<'_, ObservableGauge<u64>, u64>,
521    ) -> ObservableGauge<u64> {
522        let resolver = InstrumentResolver::new(self, &self.u64_resolver);
523        self.create_observable_gauge(builder, &resolver)
524    }
525
526    fn i64_observable_gauge(
527        &self,
528        builder: AsyncInstrumentBuilder<'_, ObservableGauge<i64>, i64>,
529    ) -> ObservableGauge<i64> {
530        let resolver = InstrumentResolver::new(self, &self.i64_resolver);
531        self.create_observable_gauge(builder, &resolver)
532    }
533
534    fn f64_observable_gauge(
535        &self,
536        builder: AsyncInstrumentBuilder<'_, ObservableGauge<f64>, f64>,
537    ) -> ObservableGauge<f64> {
538        let resolver = InstrumentResolver::new(self, &self.f64_resolver);
539        self.create_observable_gauge(builder, &resolver)
540    }
541
542    fn f64_histogram(&self, builder: HistogramBuilder<'_, Histogram<f64>>) -> Histogram<f64> {
543        let resolver = InstrumentResolver::new(self, &self.f64_resolver);
544        self.create_histogram(builder, &resolver)
545    }
546
547    fn u64_histogram(&self, builder: HistogramBuilder<'_, Histogram<u64>>) -> Histogram<u64> {
548        let resolver = InstrumentResolver::new(self, &self.u64_resolver);
549        self.create_histogram(builder, &resolver)
550    }
551}
552
553fn validate_instrument_config(name: &str, unit: &Option<Cow<'static, str>>) -> MetricResult<()> {
554    validate_instrument_name(name).and_then(|_| validate_instrument_unit(unit))
555}
556
557fn validate_bucket_boundaries(boundaries: &[f64]) -> MetricResult<()> {
558    // Validate boundaries do not contain f64::NAN, f64::INFINITY, or f64::NEG_INFINITY
559    for boundary in boundaries {
560        if boundary.is_nan() || boundary.is_infinite() {
561            return Err(MetricError::InvalidInstrumentConfiguration(
562                "Bucket boundaries must not contain NaN, +Inf, or -Inf",
563            ));
564        }
565    }
566
567    // validate that buckets are sorted and non-duplicate
568    for i in 1..boundaries.len() {
569        if boundaries[i] <= boundaries[i - 1] {
570            return Err(MetricError::InvalidInstrumentConfiguration(
571                "Bucket boundaries must be sorted and non-duplicate",
572            ));
573        }
574    }
575
576    Ok(())
577}
578
579#[cfg(feature = "experimental_metrics_disable_name_validation")]
580fn validate_instrument_name(_name: &str) -> MetricResult<()> {
581    // No name restrictions when name validation is disabled
582    Ok(())
583}
584
585#[cfg(not(feature = "experimental_metrics_disable_name_validation"))]
586fn validate_instrument_name(name: &str) -> MetricResult<()> {
587    if name.is_empty() {
588        return Err(MetricError::InvalidInstrumentConfiguration(
589            INSTRUMENT_NAME_EMPTY,
590        ));
591    }
592    if name.len() > INSTRUMENT_NAME_MAX_LENGTH {
593        return Err(MetricError::InvalidInstrumentConfiguration(
594            INSTRUMENT_NAME_LENGTH,
595        ));
596    }
597
598    if name.starts_with(|c: char| !c.is_ascii_alphabetic()) {
599        return Err(MetricError::InvalidInstrumentConfiguration(
600            INSTRUMENT_NAME_FIRST_ALPHABETIC,
601        ));
602    }
603    if name.contains(|c: char| {
604        !c.is_ascii_alphanumeric() && !INSTRUMENT_NAME_ALLOWED_NON_ALPHANUMERIC_CHARS.contains(&c)
605    }) {
606        return Err(MetricError::InvalidInstrumentConfiguration(
607            INSTRUMENT_NAME_INVALID_CHAR,
608        ));
609    }
610    Ok(())
611}
612
613fn validate_instrument_unit(unit: &Option<Cow<'static, str>>) -> MetricResult<()> {
614    if let Some(unit) = unit {
615        if unit.len() > INSTRUMENT_UNIT_NAME_MAX_LENGTH {
616            return Err(MetricError::InvalidInstrumentConfiguration(
617                INSTRUMENT_UNIT_LENGTH,
618            ));
619        }
620        if unit.contains(|c: char| !c.is_ascii()) {
621            return Err(MetricError::InvalidInstrumentConfiguration(
622                INSTRUMENT_UNIT_INVALID_CHAR,
623            ));
624        }
625    }
626    Ok(())
627}
628
629impl fmt::Debug for SdkMeter {
630    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
631        f.debug_struct("Meter").field("scope", &self.scope).finish()
632    }
633}
634
635/// Provides all OpenTelemetry instruments.
636struct InstrumentResolver<'a, T> {
637    meter: &'a SdkMeter,
638    resolve: &'a Resolver<T>,
639}
640
641impl<'a, T> InstrumentResolver<'a, T>
642where
643    T: Number,
644{
645    fn new(meter: &'a SdkMeter, resolve: &'a Resolver<T>) -> Self {
646        InstrumentResolver { meter, resolve }
647    }
648
649    /// lookup returns the resolved measures.
650    fn lookup(
651        &self,
652        kind: InstrumentKind,
653        name: Cow<'static, str>,
654        description: Option<Cow<'static, str>>,
655        unit: Option<Cow<'static, str>>,
656        boundaries: Option<Vec<f64>>,
657    ) -> MetricResult<ResolvedMeasures<T>> {
658        let aggregators = self.measures(kind, name, description, unit, boundaries)?;
659        Ok(ResolvedMeasures {
660            measures: aggregators,
661        })
662    }
663
664    fn measures(
665        &self,
666        kind: InstrumentKind,
667        name: Cow<'static, str>,
668        description: Option<Cow<'static, str>>,
669        unit: Option<Cow<'static, str>>,
670        boundaries: Option<Vec<f64>>,
671    ) -> MetricResult<Vec<Arc<dyn internal::Measure<T>>>> {
672        let inst = Instrument {
673            name,
674            description: description.unwrap_or_default(),
675            unit: unit.unwrap_or_default(),
676            kind: Some(kind),
677            scope: self.meter.scope.clone(),
678        };
679
680        self.resolve.measures(inst, boundaries)
681    }
682}
683
684#[allow(unused_imports)]
685#[cfg(test)]
686mod tests {
687    use std::borrow::Cow;
688
689    use crate::metrics::MetricError;
690
691    use super::{
692        validate_instrument_name, validate_instrument_unit, INSTRUMENT_NAME_EMPTY,
693        INSTRUMENT_NAME_FIRST_ALPHABETIC, INSTRUMENT_NAME_INVALID_CHAR, INSTRUMENT_NAME_LENGTH,
694        INSTRUMENT_UNIT_INVALID_CHAR, INSTRUMENT_UNIT_LENGTH,
695    };
696
697    #[test]
698    #[cfg(not(feature = "experimental_metrics_disable_name_validation"))]
699    fn instrument_name_validation() {
700        // (name, expected error)
701        let instrument_name_test_cases = vec![
702            ("validateName", ""),
703            ("_startWithNoneAlphabet", INSTRUMENT_NAME_FIRST_ALPHABETIC),
704            ("utf8char锈", INSTRUMENT_NAME_INVALID_CHAR),
705            ("a".repeat(255).leak(), ""),
706            ("a".repeat(256).leak(), INSTRUMENT_NAME_LENGTH),
707            ("invalid name", INSTRUMENT_NAME_INVALID_CHAR),
708            ("allow/slash", ""),
709            ("allow_under_score", ""),
710            ("allow.dots.ok", ""),
711            ("", INSTRUMENT_NAME_EMPTY),
712            ("\\allow\\slash /sec", INSTRUMENT_NAME_FIRST_ALPHABETIC),
713            ("\\allow\\$$slash /sec", INSTRUMENT_NAME_FIRST_ALPHABETIC),
714            ("Total $ Count", INSTRUMENT_NAME_INVALID_CHAR),
715            (
716                "\\test\\UsagePercent(Total) > 80%",
717                INSTRUMENT_NAME_FIRST_ALPHABETIC,
718            ),
719            ("/not / allowed", INSTRUMENT_NAME_FIRST_ALPHABETIC),
720        ];
721        for (name, expected_error) in instrument_name_test_cases {
722            let assert = |result: Result<_, MetricError>| {
723                if expected_error.is_empty() {
724                    assert!(result.is_ok());
725                } else {
726                    assert!(matches!(
727                        result.unwrap_err(),
728                        MetricError::InvalidInstrumentConfiguration(msg) if msg == expected_error
729                    ));
730                }
731            };
732
733            assert(validate_instrument_name(name).map(|_| ()));
734        }
735    }
736
737    #[test]
738    #[cfg(feature = "experimental_metrics_disable_name_validation")]
739    fn instrument_name_validation_disabled() {
740        // (name, expected error)
741        let instrument_name_test_cases = vec![
742            ("validateName", ""),
743            ("_startWithNoneAlphabet", ""),
744            ("utf8char锈", ""),
745            ("a".repeat(255).leak(), ""),
746            ("a".repeat(256).leak(), ""),
747            ("invalid name", ""),
748            ("allow/slash", ""),
749            ("allow_under_score", ""),
750            ("allow.dots.ok", ""),
751            ("", ""),
752            ("\\allow\\slash /sec", ""),
753            ("\\allow\\$$slash /sec", ""),
754            ("Total $ Count", ""),
755            ("\\test\\UsagePercent(Total) > 80%", ""),
756            ("/not / allowed", ""),
757        ];
758        for (name, expected_error) in instrument_name_test_cases {
759            let assert = |result: Result<_, MetricError>| {
760                if expected_error.is_empty() {
761                    assert!(result.is_ok());
762                } else {
763                    assert!(matches!(
764                        result.unwrap_err(),
765                        MetricError::InvalidInstrumentConfiguration(msg) if msg == expected_error
766                    ));
767                }
768            };
769
770            assert(validate_instrument_name(name).map(|_| ()));
771        }
772    }
773
774    #[test]
775    fn instrument_unit_validation() {
776        // (unit, expected error)
777        let instrument_unit_test_cases = vec![
778            (
779                "0123456789012345678901234567890123456789012345678901234567890123",
780                INSTRUMENT_UNIT_LENGTH,
781            ),
782            ("utf8char锈", INSTRUMENT_UNIT_INVALID_CHAR),
783            ("kb", ""),
784            ("Kb/sec", ""),
785            ("%", ""),
786            ("", ""),
787        ];
788
789        for (unit, expected_error) in instrument_unit_test_cases {
790            let assert = |result: Result<_, MetricError>| {
791                if expected_error.is_empty() {
792                    assert!(result.is_ok());
793                } else {
794                    assert!(matches!(
795                        result.unwrap_err(),
796                        MetricError::InvalidInstrumentConfiguration(msg) if msg == expected_error
797                    ));
798                }
799            };
800            let unit: Option<Cow<'static, str>> = Some(unit.into());
801
802            assert(validate_instrument_unit(&unit).map(|_| ()));
803        }
804    }
805}