Skip to main content

prometheus_filtered/
lib.rs

1// Copyright (c) 2026 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4//! Drop-in replacement for the `prometheus` crate with optional per-metric
5//! filtering.
6//!
7//! Replace `use prometheus::*` with `use prometheus_filtered::*` and set
8//! `METRICS_FILTER` (or call `Registry::with_filter`) to control which metrics
9//! are registered.
10//!
11//! Filter syntax: comma-separated `pattern=on|off` directives, last-match
12//! wins. A bare `off` or `on` sets the global default. A pattern matches if
13//! it is a prefix of the metric name OR is a component/prefix of the calling
14//! module path (e.g. `traffic_controller` matches
15//! `iota_core::traffic_controller::metrics`).
16//!
17//! Examples:
18//! - `METRICS_FILTER=off,authority=on`
19//! - `METRICS_FILTER=authority=off`
20
21use std::sync::Arc;
22
23/// Re-exported under a hidden alias so `$crate::prometheus::xxx!` works
24/// inside `#[macro_export]` macros without requiring callers to depend
25/// directly on the `prometheus` crate.
26#[doc(hidden)]
27pub use prometheus;
28// Re-export prometheus primitives that require no wrapping.
29pub use prometheus::{
30    DEFAULT_BUCKETS, Encoder, Error, HistogramOpts, Opts, PROTOBUF_FORMAT, ProtobufEncoder, Result,
31    TextEncoder, exponential_buckets, gather, histogram_opts, linear_buckets, opts, proto,
32};
33use tracing::warn;
34
35// ---------------------------------------------------------------------------
36// core sub-module
37// ---------------------------------------------------------------------------
38
39/// Mirrors `prometheus::core` and provides `GenericGauge`/`GenericCounter`
40/// wrappers compatible with prometheus's own generic types.
41///
42/// `crate::IntGauge`, `crate::Gauge`, `crate::IntCounter`, and
43/// `crate::Counter` are type aliases for concrete instantiations of these
44/// types, so `Option<IntGauge>` and `Option<GenericGauge<AtomicI64>>` are
45/// the same type.
46pub mod core {
47    use std::mem::ManuallyDrop;
48
49    pub use prometheus::core::{
50        Atomic, AtomicF64, AtomicI64, AtomicU64, Collector, Desc, Describer, Metric,
51        MetricVecBuilder, Number,
52    };
53
54    macro_rules! impl_generic_metric_traits {
55        ($T:ident) => {
56            impl<P: Atomic> Clone for $T<P> {
57                fn clone(&self) -> Self {
58                    Self(self.0.clone())
59                }
60            }
61
62            impl<P: Atomic> std::fmt::Debug for $T<P> {
63                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64                    write!(f, "{}", stringify!($T))?;
65                    if self.0.is_none() {
66                        write!(f, "(disabled)")?;
67                    }
68                    Ok(())
69                }
70            }
71
72            impl<P: Atomic> prometheus::core::Collector for $T<P> {
73                fn desc(&self) -> Vec<&Desc> {
74                    self.0
75                        .as_ref()
76                        .map(|inner| inner.desc())
77                        .unwrap_or_default()
78                }
79
80                fn collect(&self) -> Vec<prometheus::proto::MetricFamily> {
81                    self.0
82                        .as_ref()
83                        .map(|inner| inner.collect())
84                        .unwrap_or_default()
85                }
86            }
87        };
88    }
89
90    macro_rules! impl_metric_traits {
91        ($T:ident) => {
92            impl Clone for $T {
93                fn clone(&self) -> Self {
94                    Self(self.0.clone())
95                }
96            }
97
98            impl std::fmt::Debug for $T {
99                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100                    write!(f, "{}", stringify!($T))?;
101                    if self.0.is_none() {
102                        write!(f, "(disabled)")?;
103                    }
104                    Ok(())
105                }
106            }
107
108            impl prometheus::core::Collector for $T {
109                fn desc(&self) -> Vec<&Desc> {
110                    self.0
111                        .as_ref()
112                        .map(|inner| inner.desc())
113                        .unwrap_or_default()
114                }
115
116                fn collect(&self) -> Vec<prometheus::proto::MetricFamily> {
117                    self.0
118                        .as_ref()
119                        .map(|inner| inner.collect())
120                        .unwrap_or_default()
121                }
122            }
123        };
124    }
125
126    macro_rules! impl_generic_metric_vec {
127        ($T:ident, $M:ident) => {
128            impl<P: Atomic> $T<P> {
129                pub fn new_some(inner: prometheus::core::$T<P>) -> Self {
130                    Self(Some(inner))
131                }
132
133                pub fn new_none() -> Self {
134                    Self(None)
135                }
136
137                #[inline]
138                pub fn with_label_values<V>(&self, vals: &[V]) -> $M<P>
139                where
140                    V: AsRef<str> + std::fmt::Debug,
141                {
142                    $M::<P>(self.0.as_ref().map(|inner| inner.with_label_values(vals)))
143                }
144
145                #[inline]
146                pub fn remove_label_values<V>(&self, vals: &[V]) -> super::Result<()>
147                where
148                    V: AsRef<str> + std::fmt::Debug,
149                {
150                    self.0
151                        .as_ref()
152                        .map(|inner| inner.remove_label_values(vals))
153                        .unwrap_or(Ok(()))
154                }
155
156                #[inline]
157                pub fn get_metric_with<V, S: std::hash::BuildHasher>(
158                    &self,
159                    labels: &std::collections::HashMap<&str, V, S>,
160                ) -> super::Result<$M<P>>
161                where
162                    V: AsRef<str> + std::fmt::Debug,
163                {
164                    self.0
165                        .as_ref()
166                        .map(|inner| inner.get_metric_with(labels).map($M::<P>::new_some))
167                        .unwrap_or(Ok($M::<P>::new_none()))
168                }
169
170                #[inline]
171                pub fn get_metric_with_label_values<V>(&self, vals: &[V]) -> super::Result<$M<P>>
172                where
173                    V: AsRef<str> + std::fmt::Debug,
174                {
175                    self.0
176                        .as_ref()
177                        .map(|inner| {
178                            inner
179                                .get_metric_with_label_values(vals)
180                                .map($M::<P>::new_some)
181                        })
182                        .unwrap_or(Ok($M::<P>::new_none()))
183                }
184
185                #[inline]
186                pub fn reset(&self) {
187                    if let Some(v) = &self.0 {
188                        v.reset();
189                    }
190                }
191            }
192        };
193    }
194
195    pub struct GenericCounter<P: Atomic>(Option<prometheus::core::GenericCounter<P>>);
196
197    impl<P: Atomic> GenericCounter<P> {
198        pub fn new_some(inner: prometheus::core::GenericCounter<P>) -> Self {
199            Self(Some(inner))
200        }
201
202        pub fn new_none() -> Self {
203            Self(None)
204        }
205
206        pub fn new(name: &str, help: &str) -> prometheus::Result<Self> {
207            prometheus::core::GenericCounter::new(name, help).map(Self::new_some)
208        }
209
210        pub fn with_opts(opts: super::Opts) -> super::Result<Self> {
211            prometheus::core::GenericCounter::with_opts(opts).map(Self::new_some)
212        }
213
214        #[inline]
215        pub fn get(&self) -> P::T {
216            self.0
217                .as_ref()
218                .map(|inner| inner.get())
219                .unwrap_or(<P::T>::from_i64(0))
220        }
221
222        #[inline]
223        pub fn inc(&self) {
224            if let Some(inner) = &self.0 {
225                inner.inc();
226            }
227        }
228
229        #[inline]
230        pub fn inc_by(&self, v: <P as Atomic>::T) {
231            if let Some(inner) = &self.0 {
232                inner.inc_by(v);
233            }
234        }
235
236        #[inline]
237        pub fn reset(&self) {
238            if let Some(inner) = &self.0 {
239                inner.reset();
240            }
241        }
242    }
243
244    impl_generic_metric_traits!(GenericCounter);
245
246    pub struct GenericGauge<P: Atomic>(Option<prometheus::core::GenericGauge<P>>);
247
248    impl<P: Atomic> GenericGauge<P> {
249        pub fn new_some(inner: prometheus::core::GenericGauge<P>) -> Self {
250            Self(Some(inner))
251        }
252
253        pub fn new_none() -> Self {
254            Self(None)
255        }
256
257        pub fn new(name: &str, help: &str) -> super::Result<Self> {
258            prometheus::core::GenericGauge::new(name, help).map(Self::new_some)
259        }
260
261        pub fn with_opts(opts: super::Opts) -> super::Result<Self> {
262            prometheus::core::GenericGauge::with_opts(opts).map(Self::new_some)
263        }
264
265        #[inline]
266        pub fn get(&self) -> P::T {
267            self.0
268                .as_ref()
269                .map(|inner| inner.get())
270                .unwrap_or(<P::T>::from_i64(0))
271        }
272
273        #[inline]
274        pub fn set(&self, v: P::T) {
275            if let Some(inner) = &self.0 {
276                inner.set(v);
277            }
278        }
279
280        #[inline]
281        pub fn inc(&self) {
282            if let Some(inner) = &self.0 {
283                inner.inc();
284            }
285        }
286
287        #[inline]
288        pub fn dec(&self) {
289            if let Some(inner) = &self.0 {
290                inner.dec();
291            }
292        }
293
294        #[inline]
295        pub fn add(&self, v: P::T) {
296            if let Some(inner) = &self.0 {
297                inner.add(v);
298            }
299        }
300
301        #[inline]
302        pub fn sub(&self, v: P::T) {
303            if let Some(inner) = &self.0 {
304                inner.sub(v);
305            }
306        }
307    }
308
309    impl_generic_metric_traits!(GenericGauge);
310
311    pub struct GenericCounterVec<P: Atomic>(Option<prometheus::core::GenericCounterVec<P>>);
312
313    impl_generic_metric_traits!(GenericCounterVec);
314    impl_generic_metric_vec!(GenericCounterVec, GenericCounter);
315
316    pub struct GenericGaugeVec<P: Atomic>(Option<prometheus::core::GenericGaugeVec<P>>);
317
318    impl_generic_metric_traits!(GenericGaugeVec);
319    impl_generic_metric_vec!(GenericGaugeVec, GenericGauge);
320
321    pub struct Histogram(Option<prometheus::Histogram>);
322
323    impl_metric_traits!(Histogram);
324
325    impl Histogram {
326        pub fn new_some(inner: prometheus::Histogram) -> Self {
327            Self(Some(inner))
328        }
329
330        pub fn new_none() -> Self {
331            Self(None)
332        }
333
334        pub fn with_opts(opts: prometheus::HistogramOpts) -> prometheus::Result<Self> {
335            prometheus::Histogram::with_opts(opts).map(|h| Self(Some(h)))
336        }
337
338        #[inline]
339        pub fn observe(&self, v: f64) {
340            if let Some(h) = &self.0 {
341                h.observe(v);
342            }
343        }
344
345        #[inline]
346        pub fn start_timer(&self) -> HistogramTimer {
347            HistogramTimer(self.0.as_ref().map(|h| h.start_timer()))
348        }
349
350        #[inline]
351        pub fn get_sample_count(&self) -> u64 {
352            self.0.as_ref().map_or(0, |h| h.get_sample_count())
353        }
354
355        #[inline]
356        pub fn get_sample_sum(&self) -> f64 {
357            self.0.as_ref().map_or(0.0, |h| h.get_sample_sum())
358        }
359    }
360
361    pub struct HistogramTimer(Option<prometheus::HistogramTimer>);
362
363    impl std::fmt::Debug for HistogramTimer {
364        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
365            write!(f, "HistogramTimer")?;
366            if self.0.is_none() {
367                write!(f, "(disabled)")?;
368            }
369            Ok(())
370        }
371    }
372
373    impl Drop for HistogramTimer {
374        fn drop(&mut self) {
375            // Dropping the inner prometheus::HistogramTimer records the observation.
376            drop(self.0.take());
377        }
378    }
379
380    impl HistogramTimer {
381        /// Records the elapsed time and returns it; prevents the `Drop` impl
382        /// from recording a second time.
383        #[inline]
384        pub fn stop_and_record(self) -> f64 {
385            // ManuallyDrop prevents our Drop impl from running, so the inner timer
386            // can be consumed by its own stop_and_record without double-recording.
387            let mut wrapper = ManuallyDrop::new(self);
388            wrapper
389                .0
390                .take()
391                .map(|t| t.stop_and_record())
392                .unwrap_or_default()
393        }
394
395        /// Records the duration; provided for compatibility with older
396        /// prometheus APIs.
397        #[inline]
398        pub fn observe_duration(self) {
399            let _ = self.stop_and_record();
400        }
401
402        /// Discards the timer without recording; returns the elapsed seconds.
403        #[inline]
404        pub fn stop_and_discard(self) -> f64 {
405            let mut wrapper = ManuallyDrop::new(self);
406            wrapper
407                .0
408                .take()
409                .map(|t| t.stop_and_discard())
410                .unwrap_or_default()
411        }
412    }
413
414    pub struct HistogramVec(Option<prometheus::HistogramVec>);
415
416    impl_metric_traits!(HistogramVec);
417
418    impl HistogramVec {
419        pub fn new_some(inner: prometheus::HistogramVec) -> Self {
420            Self(Some(inner))
421        }
422
423        pub fn new_none() -> Self {
424            Self(None)
425        }
426
427        #[inline]
428        pub fn with_label_values(&self, vals: &[&str]) -> Histogram {
429            Histogram(self.0.as_ref().map(|v| v.with_label_values(vals)))
430        }
431
432        #[inline]
433        pub fn remove_label_values(&self, vals: &[&str]) -> prometheus::Result<()> {
434            match &self.0 {
435                Some(v) => v.remove_label_values(vals),
436                None => Ok(()),
437            }
438        }
439    }
440}
441
442pub type Counter = core::GenericCounter<prometheus::core::AtomicF64>;
443pub type IntCounter = core::GenericCounter<prometheus::core::AtomicU64>;
444pub type Gauge = core::GenericGauge<prometheus::core::AtomicF64>;
445pub type IntGauge = core::GenericGauge<prometheus::core::AtomicI64>;
446
447pub type CounterVec = core::GenericCounterVec<prometheus::core::AtomicF64>;
448pub type IntCounterVec = core::GenericCounterVec<prometheus::core::AtomicU64>;
449pub type GaugeVec = core::GenericGaugeVec<prometheus::core::AtomicF64>;
450pub type IntGaugeVec = core::GenericGaugeVec<prometheus::core::AtomicI64>;
451
452pub use core::{Histogram, HistogramTimer, HistogramVec};
453
454// ---------------------------------------------------------------------------
455// Filter
456// ---------------------------------------------------------------------------
457
458struct FilterDirective {
459    /// Empty string means global catch-all.
460    pattern: String,
461    enabled: bool,
462}
463
464/// Parses and evaluates `METRICS_FILTER`-style directives.
465///
466/// Filter string: comma-separated `pattern=on|off`. Bare `on`/`off` is a
467/// global default. A pattern matches if it is a prefix of the metric name OR
468/// is a component/prefix of the module path (e.g. `traffic_controller` matches
469/// `iota_core::traffic_controller::metrics`).
470#[derive(Default)]
471pub struct Filter {
472    directives: Vec<FilterDirective>,
473}
474
475impl Filter {
476    fn parse(s: &str) -> Self {
477        let directives = s
478            .split(',')
479            .filter_map(|part| {
480                let part = part.trim();
481                if part.is_empty() {
482                    None
483                } else {
484                    let (pattern, enabled) = if let Some(eq) = part.rfind('=') {
485                        (part[..eq].trim().to_owned(), part[eq + 1..].trim())
486                    } else {
487                        (String::new(), part)
488                    };
489                    match enabled {
490                        "on" | "true" | "1" => Some(true),
491                        "off" | "false" | "0" => Some(false),
492                        other => {
493                            warn!("invalid prometheus filter value {other:?} in {part:?}");
494                            None
495                        }
496                    }
497                    .map(|enabled| FilterDirective { pattern, enabled })
498                }
499            })
500            .collect();
501        Self { directives }
502    }
503
504    fn from_env() -> Self {
505        std::env::var("METRICS_FILTER")
506            .ok()
507            .map(|s| Self::parse(&s))
508            .unwrap_or_default()
509    }
510
511    /// Returns `true` if the metric should be registered (default when no
512    /// directives match: `true`).
513    ///
514    /// Matching order (last wins):
515    /// 1. Empty pattern — global default.
516    /// 2. `name.starts_with(pattern)` — metric name prefix.
517    /// 3. `module.starts_with(pattern)` — module path prefix.
518    /// 4. `module` contains `"::{pattern}"` — exact module component.
519    #[inline]
520    pub fn is_enabled(&self, name: &str, module: &str) -> bool {
521        let mut result = true;
522        for dir in &self.directives {
523            if dir.pattern.is_empty()
524                || name.starts_with(dir.pattern.as_str())
525                || module.starts_with(dir.pattern.as_str())
526                || module.contains(&format!("::{}", dir.pattern))
527            {
528                result = dir.enabled;
529            }
530        }
531        result
532    }
533}
534
535// ---------------------------------------------------------------------------
536// Registry
537// ---------------------------------------------------------------------------
538
539/// Wraps `prometheus::Registry` with an embedded `Filter` so that
540/// `register_*_with_registry!` macros can decide at construction time whether
541/// a metric should be active.
542#[derive(Clone)]
543pub struct Registry {
544    inner: prometheus::Registry,
545    filter: Arc<Filter>,
546}
547
548impl Registry {
549    /// Creates a registry whose filter is read from `METRICS_FILTER` env var.
550    pub fn new() -> Self {
551        Self {
552            inner: prometheus::Registry::new(),
553            filter: Arc::new(Filter::from_env()),
554        }
555    }
556
557    /// Creates a custom-prefixed registry whose filter is read from the
558    /// `METRICS_FILTER` env var.
559    pub fn new_custom(
560        prefix: Option<String>,
561        labels: Option<std::collections::HashMap<String, String>>,
562    ) -> prometheus::Result<Self> {
563        Ok(Self {
564            inner: prometheus::Registry::new_custom(prefix, labels)?,
565            filter: Arc::new(Filter::from_env()),
566        })
567    }
568
569    /// Creates a registry using the supplied filter string.
570    pub fn with_filter(filter_str: &str) -> Self {
571        Self {
572            inner: prometheus::Registry::new(),
573            filter: Arc::new(Filter::parse(filter_str)),
574        }
575    }
576
577    /// Used by wrapper macros to decide whether to register a metric.
578    #[inline]
579    pub fn is_enabled(&self, name: &str, module: &str) -> bool {
580        self.filter.is_enabled(name, module)
581    }
582
583    /// Returns the underlying `prometheus::Registry` for use inside wrapper
584    /// macros.
585    #[inline]
586    pub fn inner(&self) -> &prometheus::Registry {
587        &self.inner
588    }
589
590    pub fn register(&self, c: Box<dyn prometheus::core::Collector>) -> prometheus::Result<()> {
591        self.inner.register(c)
592    }
593
594    pub fn unregister(&self, c: Box<dyn prometheus::core::Collector>) -> prometheus::Result<()> {
595        self.inner.unregister(c)
596    }
597
598    pub fn gather(&self) -> Vec<prometheus::proto::MetricFamily> {
599        self.inner.gather()
600    }
601}
602
603impl Default for Registry {
604    fn default() -> Self {
605        Self::new()
606    }
607}
608
609impl std::fmt::Debug for Registry {
610    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
611        f.debug_struct("Registry").finish_non_exhaustive()
612    }
613}
614
615/// Returns the process-wide `Filter` parsed once from `METRICS_FILTER`.
616///
617/// Shared by [`default_registry`] and the global `register_*!` macros (via
618/// [`default_registry`]) so that metrics on the default registry honour the
619/// same filtering as those on explicit registries.
620pub fn default_filter() -> &'static Arc<Filter> {
621    use std::sync::OnceLock;
622    static INSTANCE: OnceLock<Arc<Filter>> = OnceLock::new();
623    INSTANCE.get_or_init(|| Arc::new(Filter::from_env()))
624}
625
626/// Returns a reference to the global default `Registry`, wrapping the
627/// underlying `prometheus::default_registry()`. Metrics registered here
628/// appear in the standard prometheus default gather output.
629pub fn default_registry() -> &'static Registry {
630    use std::sync::OnceLock;
631    static INSTANCE: OnceLock<Registry> = OnceLock::new();
632    INSTANCE.get_or_init(|| Registry {
633        inner: prometheus::default_registry().clone(),
634        filter: default_filter().clone(),
635    })
636}
637
638// ---------------------------------------------------------------------------
639// Wrapper macros
640// ---------------------------------------------------------------------------
641//
642// Each macro captures `module_path!()` at the call site so the filter can
643// match by subsystem in addition to metric name.
644//
645// The `$registry` must be a `prometheus_filtered::Registry`. On success the
646// macro always returns `Ok(WrappedType(Some(...)))` or `Ok(WrappedType(None))`
647// — never `Err` from the filtering logic itself.
648//
649// `$crate::prometheus::` is used for inner prometheus macro calls so that
650// callers don't need a direct `prometheus` crate dependency.
651//
652// `let _n = $name; let name: &str = &*_n;` handles both `&str` literals and
653// `format!(...)` String expressions uniformly.
654
655/// register_int_counter_with_registry!(name, help, registry)
656#[macro_export]
657macro_rules! register_int_counter_with_registry {
658    ($name:expr, $help:expr, $registry:expr $(,)?) => {{
659        let _n = $name;
660        let name: &str = &*_n;
661        let module: &str = module_path!();
662        if ($registry).is_enabled(name, module) {
663            $crate::prometheus::register_int_counter_with_registry!(
664                name,
665                $help,
666                ($registry).inner()
667            )
668            .map($crate::core::GenericCounter::new_some)
669        } else {
670            ::std::result::Result::Ok($crate::core::GenericCounter::new_none())
671        }
672    }};
673}
674
675/// register_int_counter_vec_with_registry!(name, help, labels, registry)
676#[macro_export]
677macro_rules! register_int_counter_vec_with_registry {
678    ($name:expr, $help:expr, $labels:expr, $registry:expr $(,)?) => {{
679        let _n = $name;
680        let name: &str = &*_n;
681        let module: &str = module_path!();
682        if ($registry).is_enabled(name, module) {
683            $crate::prometheus::register_int_counter_vec_with_registry!(
684                name,
685                $help,
686                $labels,
687                ($registry).inner()
688            )
689            .map($crate::IntCounterVec::new_some)
690        } else {
691            ::std::result::Result::Ok($crate::IntCounterVec::new_none())
692        }
693    }};
694}
695
696/// register_int_gauge_with_registry!(name, help, registry)
697#[macro_export]
698macro_rules! register_int_gauge_with_registry {
699    ($name:expr, $help:expr, $registry:expr $(,)?) => {{
700        let _n = $name;
701        let name: &str = &*_n;
702        let module: &str = module_path!();
703        if ($registry).is_enabled(name, module) {
704            $crate::prometheus::register_int_gauge_with_registry!(name, $help, ($registry).inner())
705                .map($crate::core::GenericGauge::new_some)
706        } else {
707            ::std::result::Result::Ok($crate::core::GenericGauge::new_none())
708        }
709    }};
710}
711
712/// register_int_gauge_vec_with_registry!(name, help, labels, registry)
713#[macro_export]
714macro_rules! register_int_gauge_vec_with_registry {
715    ($name:expr, $help:expr, $labels:expr, $registry:expr $(,)?) => {{
716        let _n = $name;
717        let name: &str = &*_n;
718        let module: &str = module_path!();
719        if ($registry).is_enabled(name, module) {
720            $crate::prometheus::register_int_gauge_vec_with_registry!(
721                name,
722                $help,
723                $labels,
724                ($registry).inner()
725            )
726            .map($crate::IntGaugeVec::new_some)
727        } else {
728            ::std::result::Result::Ok($crate::IntGaugeVec::new_none())
729        }
730    }};
731}
732
733/// register_histogram_with_registry!(name, help, registry)
734/// register_histogram_with_registry!(name, help, buckets, registry)
735#[macro_export]
736macro_rules! register_histogram_with_registry {
737    ($name:expr, $help:expr, $registry:expr $(,)?) => {{
738        let _n = $name;
739        let name: &str = &*_n;
740        let module: &str = module_path!();
741        if ($registry).is_enabled(name, module) {
742            $crate::prometheus::register_histogram_with_registry!(name, $help, ($registry).inner())
743                .map($crate::Histogram::new_some)
744        } else {
745            ::std::result::Result::Ok($crate::Histogram::new_none())
746        }
747    }};
748    ($name:expr, $help:expr, $buckets:expr, $registry:expr $(,)?) => {{
749        let _n = $name;
750        let name: &str = &*_n;
751        let module: &str = module_path!();
752        if ($registry).is_enabled(name, module) {
753            $crate::prometheus::register_histogram_with_registry!(
754                name,
755                $help,
756                $buckets,
757                ($registry).inner()
758            )
759            .map($crate::Histogram::new_some)
760        } else {
761            ::std::result::Result::Ok($crate::Histogram::new_none())
762        }
763    }};
764}
765
766/// register_histogram_vec_with_registry!(name, help, labels, registry)
767/// register_histogram_vec_with_registry!(name, help, labels, buckets, registry)
768#[macro_export]
769macro_rules! register_histogram_vec_with_registry {
770    ($name:expr, $help:expr, $labels:expr, $registry:expr $(,)?) => {{
771        let _n = $name;
772        let name: &str = &*_n;
773        let module: &str = module_path!();
774        if ($registry).is_enabled(name, module) {
775            $crate::prometheus::register_histogram_vec_with_registry!(
776                name,
777                $help,
778                $labels,
779                ($registry).inner()
780            )
781            .map($crate::HistogramVec::new_some)
782        } else {
783            ::std::result::Result::Ok($crate::HistogramVec::new_none())
784        }
785    }};
786    ($name:expr, $help:expr, $labels:expr, $buckets:expr, $registry:expr $(,)?) => {{
787        let _n = $name;
788        let name: &str = &*_n;
789        let module: &str = module_path!();
790        if ($registry).is_enabled(name, module) {
791            $crate::prometheus::register_histogram_vec_with_registry!(
792                name,
793                $help,
794                $labels,
795                $buckets,
796                ($registry).inner()
797            )
798            .map($crate::HistogramVec::new_some)
799        } else {
800            ::std::result::Result::Ok($crate::HistogramVec::new_none())
801        }
802    }};
803}
804
805/// register_gauge_vec_with_registry!(name, help, labels, registry)
806#[macro_export]
807macro_rules! register_gauge_vec_with_registry {
808    ($name:expr, $help:expr, $labels:expr, $registry:expr $(,)?) => {{
809        let _n = $name;
810        let name: &str = &*_n;
811        let module: &str = module_path!();
812        if ($registry).is_enabled(name, module) {
813            $crate::prometheus::register_gauge_vec_with_registry!(
814                name,
815                $help,
816                $labels,
817                ($registry).inner()
818            )
819            .map($crate::core::GenericGaugeVec::new_some)
820        } else {
821            ::std::result::Result::Ok($crate::core::GenericGaugeVec::new_none())
822        }
823    }};
824}
825
826/// register_gauge_with_registry!(name, help, registry)
827#[macro_export]
828macro_rules! register_gauge_with_registry {
829    ($name:expr, $help:expr, $registry:expr $(,)?) => {{
830        let _n = $name;
831        let name: &str = &*_n;
832        let module: &str = module_path!();
833        if ($registry).is_enabled(name, module) {
834            $crate::prometheus::register_gauge_with_registry!(name, $help, ($registry).inner())
835                .map($crate::core::GenericGauge::new_some)
836        } else {
837            ::std::result::Result::Ok($crate::core::GenericGauge::new_none())
838        }
839    }};
840}
841
842/// register_counter!(name, help) - global prometheus registry, filtered.
843#[macro_export]
844macro_rules! register_counter {
845    ($name:expr, $help:expr $(,)?) => {{
846        let _n = $name;
847        let name: &str = &*_n;
848        let module: &str = module_path!();
849        if $crate::default_filter().is_enabled(name, module) {
850            $crate::prometheus::register_counter!(name, $help)
851                .map($crate::core::GenericCounter::new_some)
852        } else {
853            ::std::result::Result::Ok($crate::core::GenericCounter::new_none())
854        }
855    }};
856}
857
858/// register_counter_vec_with_registry!(name, help, labels, registry)
859#[macro_export]
860macro_rules! register_counter_vec_with_registry {
861    ($name:expr, $help:expr, $labels:expr, $registry:expr $(,)?) => {{
862        let _n = $name;
863        let name: &str = &*_n;
864        let module: &str = module_path!();
865        if ($registry).is_enabled(name, module) {
866            $crate::prometheus::register_counter_vec_with_registry!(
867                name,
868                $help,
869                $labels,
870                ($registry).inner()
871            )
872            .map($crate::core::GenericCounterVec::new_some)
873        } else {
874            ::std::result::Result::Ok($crate::core::GenericCounterVec::new_none())
875        }
876    }};
877}
878
879/// register_counter_vec!(name, help, labels) - global registry, filtered.
880#[macro_export]
881macro_rules! register_counter_vec {
882    ($name:expr, $help:expr, $labels:expr $(,)?) => {{
883        let _n = $name;
884        let name: &str = &*_n;
885        let module: &str = module_path!();
886        if $crate::default_filter().is_enabled(name, module) {
887            $crate::prometheus::register_counter_vec!(name, $help, $labels)
888                .map($crate::core::GenericCounterVec::new_some)
889        } else {
890            ::std::result::Result::Ok($crate::core::GenericCounterVec::new_none())
891        }
892    }};
893}
894
895/// register_histogram_vec!(opts, labels) or (name, help, labels) or (name,
896/// help, labels, buckets) — global prometheus registry, filtered.
897#[macro_export]
898macro_rules! register_histogram_vec {
899    ($opts:expr, $labels:expr $(,)?) => {{
900        let opts = $opts;
901        let name: &str = &opts.common_opts.name;
902        let module: &str = module_path!();
903        if $crate::default_filter().is_enabled(name, module) {
904            $crate::prometheus::register_histogram_vec!(opts, $labels)
905                .map($crate::HistogramVec::new_some)
906        } else {
907            ::std::result::Result::Ok($crate::HistogramVec::new_none())
908        }
909    }};
910    ($name:expr, $help:expr, $labels:expr $(,)?) => {{
911        let _n = $name;
912        let name: &str = &*_n;
913        let module: &str = module_path!();
914        if $crate::default_filter().is_enabled(name, module) {
915            $crate::prometheus::register_histogram_vec!(name, $help, $labels)
916                .map($crate::HistogramVec::new_some)
917        } else {
918            ::std::result::Result::Ok($crate::HistogramVec::new_none())
919        }
920    }};
921    ($name:expr, $help:expr, $labels:expr, $buckets:expr $(,)?) => {{
922        let _n = $name;
923        let name: &str = &*_n;
924        let module: &str = module_path!();
925        if $crate::default_filter().is_enabled(name, module) {
926            $crate::prometheus::register_histogram_vec!(name, $help, $labels, $buckets)
927                .map($crate::HistogramVec::new_some)
928        } else {
929            ::std::result::Result::Ok($crate::HistogramVec::new_none())
930        }
931    }};
932}
933
934#[cfg(test)]
935mod tests {
936    #[test]
937    fn filter_matches_metric_or_module_name_prefix() {
938        // filter matches all the metric names and module names having given prefix
939        let filter = super::Filter::parse("authority=off");
940        assert!(filter.is_enabled("some_authority", "iota_core::checkpoints"));
941        assert!(!filter.is_enabled("authority", "iota_core::checkpoints"));
942        assert!(!filter.is_enabled("authority_aggregator", "iota_core::checkpoints"));
943        assert!(filter.is_enabled("certs_total", "iota_core::some_authority"));
944        assert!(!filter.is_enabled("certs_total", "iota_core::authority"));
945        assert!(!filter.is_enabled("certs_total", "iota_core::authority_aggregator"));
946
947        // the last matching prefix shadows the previous ones
948        let filter = super::Filter::parse("authority=off,authority_aggregator=on");
949        assert!(!filter.is_enabled("authority", "iota_core::checkpoints"));
950        assert!(filter.is_enabled("authority_aggregator", "iota_core::checkpoints"));
951        assert!(!filter.is_enabled("certs_total", "iota_core::authority"));
952        assert!(filter.is_enabled("certs_total", "iota_core::authority_aggregator"));
953
954        // filter can be set off by default
955        let filter = super::Filter::parse("off,authority_aggregator=on");
956        assert!(!filter.is_enabled("some_authority", "iota_core::checkpoints"));
957        assert!(!filter.is_enabled("authority", "iota_core::checkpoints"));
958        assert!(filter.is_enabled("authority_aggregator", "iota_core::checkpoints"));
959        assert!(!filter.is_enabled("certs_total", "iota_core::some_authority"));
960        assert!(!filter.is_enabled("certs_total", "iota_core::authority"));
961        assert!(filter.is_enabled("certs_total", "iota_core::authority_aggregator"));
962
963        // the full prefix must be matched
964        let filter = super::Filter::parse("authority_aggregator=off");
965        assert!(filter.is_enabled("authority", "iota_core::checkpoints"));
966        assert!(!filter.is_enabled("authority_aggregator", "iota_core::checkpoints"));
967        assert!(filter.is_enabled("certs_total", "iota_core::authority"));
968        assert!(!filter.is_enabled("certs_total", "iota_core::authority_aggregator"));
969    }
970
971    #[test]
972    fn no_filter_enables_everything() {
973        // an unset/empty filter must leave every metric registered (backward
974        // compatibility: filtering is purely opt-in).
975        assert!(super::Filter::parse("").is_enabled("anything", "any::module"));
976        assert!(super::Filter::default().is_enabled("anything", "any::module"));
977        // empty segments are ignored rather than treated as directives.
978        assert!(super::Filter::parse(",,").is_enabled("anything", "any::module"));
979    }
980
981    #[test]
982    fn accepts_on_off_value_aliases() {
983        for off in ["off", "false", "0"] {
984            let filter = super::Filter::parse(&format!("authority={off}"));
985            assert!(!filter.is_enabled("authority", "m"), "{off} should disable");
986        }
987        // start from a global `off` so the `on` alias has an observable effect.
988        for on in ["on", "true", "1"] {
989            let filter = super::Filter::parse(&format!("off,authority={on}"));
990            assert!(filter.is_enabled("authority", "m"), "{on} should enable");
991            assert!(!filter.is_enabled("other", "m"));
992        }
993    }
994
995    #[test]
996    fn invalid_directives_are_dropped() {
997        // an unrecognised value leaves the directive out, falling back to the
998        // default (enabled).
999        assert!(super::Filter::parse("authority=maybe").is_enabled("authority", "m"));
1000        // a bare token without `=on|off` is parsed as a global value and, being
1001        // invalid, dropped — it does NOT enable/disable the `authority` subsystem.
1002        assert!(super::Filter::parse("authority").is_enabled("authority", "m"));
1003        // a valid directive alongside an invalid one still takes effect.
1004        let filter = super::Filter::parse("authority=off,bogus=nope");
1005        assert!(!filter.is_enabled("authority", "m"));
1006    }
1007
1008    #[test]
1009    fn matches_module_path_prefix() {
1010        // a pattern that is a prefix of the full module path (not only a `::`
1011        // component) matches.
1012        let filter = super::Filter::parse("iota_core=off");
1013        assert!(!filter.is_enabled("certs_total", "iota_core::authority"));
1014        assert!(filter.is_enabled("certs_total", "starfish::core"));
1015    }
1016
1017    #[test]
1018    fn global_on_default() {
1019        // last-match-wins applies to bare global directives too.
1020        assert!(super::Filter::parse("off,on").is_enabled("authority", "m"));
1021        // an explicit `on` default with a targeted `off` override.
1022        let filter = super::Filter::parse("on,authority=off");
1023        assert!(filter.is_enabled("certs_total", "m"));
1024        assert!(!filter.is_enabled("authority", "m"));
1025    }
1026
1027    #[test]
1028    fn whitespace_is_trimmed() {
1029        let filter = super::Filter::parse("  authority = off ,  authority_aggregator = on  ");
1030        assert!(!filter.is_enabled("authority", "m"));
1031        assert!(filter.is_enabled("authority_aggregator", "m"));
1032    }
1033}