1use std::{
42 result::Result as StdResult,
43 sync::{Arc, OnceLock, RwLock},
44};
45
46#[doc(hidden)]
50pub use prometheus;
51pub use prometheus::{
59 Counter, CounterVec, Gauge, GaugeVec, Histogram, HistogramTimer, HistogramVec, IntCounter,
60 IntCounterVec, IntGauge, IntGaugeVec, core,
61};
62pub use prometheus::{
64 DEFAULT_BUCKETS, Encoder, Error, HistogramOpts, Opts, PROTOBUF_FORMAT, ProtobufEncoder, Result,
65 TextEncoder, exponential_buckets, gather, histogram_opts, linear_buckets, opts, proto,
66};
67use tracing::warn;
68
69#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
75#[serde(rename_all = "lowercase")]
76pub enum MetricLevel {
77 Off,
80 Warn,
81 Info,
82 #[default]
84 Debug,
85 Trace,
86}
87
88impl MetricLevel {
89 pub(crate) const fn verbosity(self) -> u8 {
90 match self {
91 Self::Off => 0,
92 Self::Warn => 1,
93 Self::Info => 2,
94 Self::Debug => 3,
95 Self::Trace => 4,
96 }
97 }
98
99 pub const fn as_str(self) -> &'static str {
100 match self {
101 Self::Off => "off",
102 Self::Warn => "warn",
103 Self::Info => "info",
104 Self::Debug => "debug",
105 Self::Trace => "trace",
106 }
107 }
108}
109
110pub const METRICS_FILTER_ENV: &str = "METRICS_FILTER";
113
114const DEFAULT_THRESHOLD: u8 = MetricLevel::Trace.verbosity();
117
118#[derive(Clone)]
119struct FilterDirective {
120 pattern: String,
124 component_pattern: String,
127 level: MetricLevel,
130}
131
132#[derive(Default)]
137pub struct Filter {
138 startup: Arc<DirectiveSet>,
140 runtime: RwLock<Arc<DirectiveSet>>,
142}
143
144#[derive(Clone, Copy)]
150pub struct FilterSource<'a> {
151 pub directives: &'a str,
152 pub display: &'a str,
153}
154
155impl<'a> FilterSource<'a> {
156 pub fn new(s: &'a str) -> Self {
157 Self {
158 directives: s,
159 display: s,
160 }
161 }
162
163 pub fn with_display(directives: &'a str, display: &'a str) -> Self {
166 Self {
167 directives,
168 display,
169 }
170 }
171}
172
173#[derive(Default, Clone)]
176struct DirectiveSet {
177 directives: Vec<FilterDirective>,
178 display: Vec<FilterDirective>,
179}
180
181impl DirectiveSet {
182 fn from_source(source: FilterSource<'_>) -> Self {
183 Self {
184 directives: parse_valid_directives(source.directives),
185 display: parse_valid_directives(source.display),
186 }
187 }
188
189 fn replaces(&self) -> bool {
191 self.directives.iter().any(|dir| dir.pattern.is_empty())
192 }
193
194 fn merged(&self, over: &Self) -> Self {
202 if over.replaces() {
203 return over.clone();
204 }
205 Self {
206 directives: merge_directives(&self.directives, &over.directives),
207 display: merge_directives(&self.display, &over.display),
208 }
209 }
210}
211
212fn merge_directives(base: &[FilterDirective], over: &[FilterDirective]) -> Vec<FilterDirective> {
215 base.iter()
216 .filter(|dir| !over.iter().any(|o| o.pattern == dir.pattern))
217 .chain(over.iter())
218 .cloned()
219 .collect()
220}
221
222fn parse_directives(s: &str) -> (Vec<FilterDirective>, Vec<String>) {
226 let mut directives = Vec::new();
227 let mut errors = Vec::new();
228 for part in directive_parts(s) {
229 match split_directive(part) {
230 Ok((pattern, level)) => directives.push(FilterDirective {
231 component_pattern: format!("::{pattern}"),
232 pattern: pattern.to_owned(),
233 level,
234 }),
235 Err(err) => errors.push(err),
236 }
237 }
238 (directives, errors)
239}
240
241fn parse_valid_directives(s: &str) -> Vec<FilterDirective> {
243 let (directives, errors) = parse_directives(s);
244 for err in errors {
245 warn!("dropping prometheus filter directive: {err}");
246 }
247 directives
248}
249
250pub fn directive_parts(s: &str) -> impl Iterator<Item = &str> + '_ {
253 s.split(',').map(str::trim).filter(|part| !part.is_empty())
254}
255
256pub fn split_directive(part: &str) -> StdResult<(&str, MetricLevel), String> {
259 let (pattern, value) = match part.rfind('=') {
260 Some(eq) => (part[..eq].trim(), part[eq + 1..].trim()),
261 None => ("", part.trim()),
262 };
263 let level = match value {
264 "off" => MetricLevel::Off,
265 "warn" => MetricLevel::Warn,
266 "info" => MetricLevel::Info,
267 "debug" => MetricLevel::Debug,
268 "trace" => MetricLevel::Trace,
269 other => {
270 return Err(format!(
271 "invalid level {other:?} in directive {part:?}: expected one of \
272 off/warn/info/debug/trace"
273 ));
274 }
275 };
276 Ok((pattern, level))
277}
278
279fn render_directives(directives: &[FilterDirective]) -> String {
281 directives
282 .iter()
283 .map(|dir| {
284 if dir.pattern.is_empty() {
285 dir.level.as_str().to_owned()
286 } else {
287 format!("{}={}", dir.pattern, dir.level.as_str())
288 }
289 })
290 .collect::<Vec<_>>()
291 .join(",")
292}
293
294fn threshold(directives: &[FilterDirective], name: &str, module: &str) -> u8 {
312 let mut best: Option<((bool, usize), u8)> = None;
316 for dir in directives {
317 let specificity = if dir.pattern.is_empty() || dir.pattern == "default" {
318 Some((false, 0))
319 } else if name.starts_with(dir.pattern.as_str()) {
320 Some((true, dir.pattern.len()))
321 } else if module.starts_with(dir.pattern.as_str())
322 || module.contains(dir.component_pattern.as_str())
323 {
324 Some((false, dir.pattern.len()))
325 } else {
326 None
327 };
328 match specificity {
329 Some(specificity) if best.is_none_or(|(prev, _)| specificity >= prev) => {
330 best = Some((specificity, dir.level.verbosity()));
331 }
332 _ => {}
333 }
334 }
335 best.map_or(DEFAULT_THRESHOLD, |(_, threshold)| threshold)
336}
337
338impl Filter {
339 pub fn parse(s: &str) -> Self {
343 Self::from_sources(FilterSource::new(s), None)
344 }
345
346 pub fn from_env() -> Self {
349 let env = std::env::var(METRICS_FILTER_ENV).ok();
350 Self::from_sources(FilterSource::new(""), env.as_deref().map(FilterSource::new))
351 }
352
353 pub fn from_sources(config: FilterSource<'_>, env: Option<FilterSource<'_>>) -> Self {
356 let startup = Arc::new(DirectiveSet::from_source(config).merged(
357 &DirectiveSet::from_source(env.unwrap_or(FilterSource::new(""))),
358 ));
359 Self {
360 runtime: RwLock::new(startup.clone()),
361 startup,
362 }
363 }
364
365 #[inline]
369 pub fn is_exposed(&self, name: &str, module: &str, level: MetricLevel) -> bool {
370 let runtime = self.runtime.read().unwrap();
371 threshold(&runtime.directives, name, module) >= level.verbosity()
372 }
373
374 pub fn filter_string(&self) -> String {
376 render_directives(&self.runtime.read().unwrap().display)
377 }
378
379 pub fn startup_filter_string(&self) -> String {
381 render_directives(&self.startup.display)
382 }
383
384 pub fn set_runtime_filter(&self, source: FilterSource<'_>) -> StdResult<(), String> {
391 let (directives, errors) = parse_directives(source.directives);
392 if !errors.is_empty() {
393 return Err(errors.join("; "));
394 }
395 let over = DirectiveSet {
396 directives,
397 display: parse_valid_directives(source.display),
398 };
399 *self.runtime.write().unwrap() = Arc::new(self.startup.merged(&over));
400 Ok(())
401 }
402
403 pub fn reset_runtime_filter(&self) {
405 *self.runtime.write().unwrap() = self.startup.clone();
406 }
407}
408
409struct FilteredCollector<C> {
417 name: String,
418 module: String,
419 level: MetricLevel,
420 filter: Arc<Filter>,
421 inner: C,
422}
423
424impl<C: prometheus::core::Collector> prometheus::core::Collector for FilteredCollector<C> {
425 fn desc(&self) -> Vec<&prometheus::core::Desc> {
426 self.inner.desc()
427 }
428
429 fn collect(&self) -> Vec<prometheus::proto::MetricFamily> {
430 if self.filter.is_exposed(&self.name, &self.module, self.level) {
431 self.inner.collect()
432 } else {
433 Vec::new()
434 }
435 }
436}
437
438#[derive(Clone)]
447pub struct Registry {
448 inner: prometheus::Registry,
449 filter: Arc<Filter>,
450 prefix: Option<String>,
453}
454
455impl Registry {
456 pub fn new() -> Self {
459 Self {
460 inner: prometheus::Registry::new(),
461 filter: Arc::new(Filter::from_env()),
462 prefix: None,
463 }
464 }
465
466 pub fn new_custom(
468 prefix: Option<String>,
469 labels: Option<std::collections::HashMap<String, String>>,
470 filter: Option<Arc<Filter>>,
471 ) -> prometheus::Result<Self> {
472 Ok(Self {
473 inner: prometheus::Registry::new_custom(prefix.clone(), labels)?,
474 filter: filter.unwrap_or_else(|| Arc::new(Filter::from_env())),
475 prefix,
476 })
477 }
478
479 #[inline]
482 pub fn filter(&self) -> Arc<Filter> {
483 self.filter.clone()
484 }
485
486 fn exposed_name(&self, name: &str) -> String {
487 match &self.prefix {
488 Some(prefix) => format!("{prefix}_{name}"),
489 None => name.to_owned(),
490 }
491 }
492
493 #[inline]
498 pub fn register_filtered<C>(
499 &self,
500 name: &str,
501 module: &str,
502 level: MetricLevel,
503 collector: C,
504 ) -> prometheus::Result<C>
505 where
506 C: prometheus::core::Collector + Clone + 'static,
507 {
508 self.inner.register(Box::new(FilteredCollector {
509 name: self.exposed_name(name),
510 module: module.to_owned(),
511 level,
512 filter: self.filter.clone(),
513 inner: collector.clone(),
514 }))?;
515 Ok(collector)
516 }
517
518 pub fn register(&self, c: Box<dyn prometheus::core::Collector>) -> prometheus::Result<()> {
519 self.inner.register(c)
520 }
521
522 pub fn unregister(&self, c: Box<dyn prometheus::core::Collector>) -> prometheus::Result<()> {
523 self.inner.unregister(c)
524 }
525
526 pub fn gather(&self) -> Vec<prometheus::proto::MetricFamily> {
528 self.inner.gather()
529 }
530}
531
532impl Default for Registry {
533 fn default() -> Self {
534 Self::new()
535 }
536}
537
538impl std::fmt::Debug for Registry {
539 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
540 f.debug_struct("Registry").finish_non_exhaustive()
541 }
542}
543
544fn default_filter() -> &'static Arc<Filter> {
547 static INSTANCE: OnceLock<Arc<Filter>> = OnceLock::new();
548 INSTANCE.get_or_init(|| Arc::new(Filter::from_env()))
549}
550
551pub fn default_registry() -> &'static Registry {
555 use std::sync::OnceLock;
556 static INSTANCE: OnceLock<Registry> = OnceLock::new();
557 INSTANCE.get_or_init(|| Registry {
558 inner: prometheus::default_registry().clone(),
559 filter: default_filter().clone(),
560 prefix: None,
561 })
562}
563
564#[macro_export]
584macro_rules! register_int_counter_with_registry {
585 ($name:expr, $help:expr, $registry:expr $(,)?) => {
586 $crate::register_int_counter_with_registry!(
587 $name, $help, $registry; $crate::MetricLevel::Debug
588 )
589 };
590 ($name:expr, $help:expr, $registry:expr ; $level:expr $(,)?) => {{
591 let _n = $name;
592 let name: &str = &*_n;
593 let module: &str = module_path!();
594 $crate::prometheus::IntCounter::new(name, $help)
595 .and_then(|metric| ($registry).register_filtered(name, module, $level, metric))
596 }};
597}
598
599#[macro_export]
601macro_rules! register_int_counter_vec_with_registry {
602 ($name:expr, $help:expr, $labels:expr, $registry:expr $(,)?) => {
603 $crate::register_int_counter_vec_with_registry!(
604 $name, $help, $labels, $registry; $crate::MetricLevel::Debug
605 )
606 };
607 ($name:expr, $help:expr, $labels:expr, $registry:expr ; $level:expr $(,)?) => {{
608 let _n = $name;
609 let name: &str = &*_n;
610 let module: &str = module_path!();
611 $crate::prometheus::IntCounterVec::new($crate::prometheus::Opts::new(name, $help), $labels)
612 .and_then(|metric| ($registry).register_filtered(name, module, $level, metric))
613 }};
614}
615
616#[macro_export]
618macro_rules! register_int_gauge_with_registry {
619 ($name:expr, $help:expr, $registry:expr $(,)?) => {
620 $crate::register_int_gauge_with_registry!(
621 $name, $help, $registry; $crate::MetricLevel::Debug
622 )
623 };
624 ($name:expr, $help:expr, $registry:expr ; $level:expr $(,)?) => {{
625 let _n = $name;
626 let name: &str = &*_n;
627 let module: &str = module_path!();
628 $crate::prometheus::IntGauge::new(name, $help)
629 .and_then(|metric| ($registry).register_filtered(name, module, $level, metric))
630 }};
631}
632
633#[macro_export]
635macro_rules! register_int_gauge_vec_with_registry {
636 ($name:expr, $help:expr, $labels:expr, $registry:expr $(,)?) => {
637 $crate::register_int_gauge_vec_with_registry!(
638 $name, $help, $labels, $registry; $crate::MetricLevel::Debug
639 )
640 };
641 ($name:expr, $help:expr, $labels:expr, $registry:expr ; $level:expr $(,)?) => {{
642 let _n = $name;
643 let name: &str = &*_n;
644 let module: &str = module_path!();
645 $crate::prometheus::IntGaugeVec::new($crate::prometheus::Opts::new(name, $help), $labels)
646 .and_then(|metric| ($registry).register_filtered(name, module, $level, metric))
647 }};
648}
649
650#[macro_export]
653macro_rules! register_histogram_with_registry {
654 ($name:expr, $help:expr, $registry:expr $(,)?) => {
655 $crate::register_histogram_with_registry!($name, $help, $registry; $crate::MetricLevel::Debug)
656 };
657 ($name:expr, $help:expr, $buckets:expr, $registry:expr $(,)?) => {
658 $crate::register_histogram_with_registry!(
659 $name, $help, $buckets, $registry; $crate::MetricLevel::Debug
660 )
661 };
662 ($name:expr, $help:expr, $registry:expr ; $level:expr $(,)?) => {{
663 let _n = $name;
664 let name: &str = &*_n;
665 let module: &str = module_path!();
666 $crate::prometheus::Histogram::with_opts($crate::prometheus::HistogramOpts::new(name, $help))
667 .and_then(|metric| ($registry).register_filtered(name, module, $level, metric))
668 }};
669 ($name:expr, $help:expr, $buckets:expr, $registry:expr ; $level:expr $(,)?) => {{
670 let _n = $name;
671 let name: &str = &*_n;
672 let module: &str = module_path!();
673 $crate::prometheus::Histogram::with_opts(
674 $crate::prometheus::HistogramOpts::new(name, $help).buckets($buckets),
675 )
676 .and_then(|metric| ($registry).register_filtered(name, module, $level, metric))
677 }};
678}
679
680#[macro_export]
683macro_rules! register_histogram_vec_with_registry {
684 ($name:expr, $help:expr, $labels:expr, $registry:expr $(,)?) => {
685 $crate::register_histogram_vec_with_registry!(
686 $name, $help, $labels, $registry; $crate::MetricLevel::Debug
687 )
688 };
689 ($name:expr, $help:expr, $labels:expr, $buckets:expr, $registry:expr $(,)?) => {
690 $crate::register_histogram_vec_with_registry!(
691 $name, $help, $labels, $buckets, $registry; $crate::MetricLevel::Debug
692 )
693 };
694 ($name:expr, $help:expr, $labels:expr, $registry:expr ; $level:expr $(,)?) => {{
695 let _n = $name;
696 let name: &str = &*_n;
697 let module: &str = module_path!();
698 $crate::prometheus::HistogramVec::new(
699 $crate::prometheus::HistogramOpts::new(name, $help),
700 $labels,
701 )
702 .and_then(|metric| ($registry).register_filtered(name, module, $level, metric))
703 }};
704 ($name:expr, $help:expr, $labels:expr, $buckets:expr, $registry:expr ; $level:expr $(,)?) => {{
705 let _n = $name;
706 let name: &str = &*_n;
707 let module: &str = module_path!();
708 $crate::prometheus::HistogramVec::new(
709 $crate::prometheus::HistogramOpts::new(name, $help).buckets($buckets),
710 $labels,
711 )
712 .and_then(|metric| ($registry).register_filtered(name, module, $level, metric))
713 }};
714}
715
716#[macro_export]
718macro_rules! register_gauge_vec_with_registry {
719 ($name:expr, $help:expr, $labels:expr, $registry:expr $(,)?) => {
720 $crate::register_gauge_vec_with_registry!(
721 $name, $help, $labels, $registry; $crate::MetricLevel::Debug
722 )
723 };
724 ($name:expr, $help:expr, $labels:expr, $registry:expr ; $level:expr $(,)?) => {{
725 let _n = $name;
726 let name: &str = &*_n;
727 let module: &str = module_path!();
728 $crate::prometheus::GaugeVec::new($crate::prometheus::Opts::new(name, $help), $labels)
729 .and_then(|metric| ($registry).register_filtered(name, module, $level, metric))
730 }};
731}
732
733#[macro_export]
735macro_rules! register_gauge_with_registry {
736 ($name:expr, $help:expr, $registry:expr $(,)?) => {
737 $crate::register_gauge_with_registry!($name, $help, $registry; $crate::MetricLevel::Debug)
738 };
739 ($name:expr, $help:expr, $registry:expr ; $level:expr $(,)?) => {{
740 let _n = $name;
741 let name: &str = &*_n;
742 let module: &str = module_path!();
743 $crate::prometheus::Gauge::new(name, $help)
744 .and_then(|metric| ($registry).register_filtered(name, module, $level, metric))
745 }};
746}
747
748#[macro_export]
750macro_rules! register_counter {
751 ($name:expr, $help:expr $(,)?) => {
752 $crate::register_counter!($name, $help; $crate::MetricLevel::Debug)
753 };
754 ($name:expr, $help:expr ; $level:expr $(,)?) => {{
755 let _n = $name;
756 let name: &str = &*_n;
757 let module: &str = module_path!();
758 $crate::prometheus::Counter::new(name, $help).and_then(|metric| {
759 $crate::default_registry().register_filtered(name, module, $level, metric)
760 })
761 }};
762}
763
764#[macro_export]
766macro_rules! register_counter_with_registry {
767 ($name:expr, $help:expr, $registry:expr $(,)?) => {
768 $crate::register_counter_with_registry!(
769 $name, $help, $registry; $crate::MetricLevel::Debug
770 )
771 };
772 ($name:expr, $help:expr, $registry:expr ; $level:expr $(,)?) => {{
773 let _n = $name;
774 let name: &str = &*_n;
775 let module: &str = module_path!();
776 $crate::prometheus::Counter::new(name, $help)
777 .and_then(|metric| ($registry).register_filtered(name, module, $level, metric))
778 }};
779}
780
781#[macro_export]
783macro_rules! register_counter_vec_with_registry {
784 ($name:expr, $help:expr, $labels:expr, $registry:expr $(,)?) => {
785 $crate::register_counter_vec_with_registry!(
786 $name, $help, $labels, $registry; $crate::MetricLevel::Debug
787 )
788 };
789 ($name:expr, $help:expr, $labels:expr, $registry:expr ; $level:expr $(,)?) => {{
790 let _n = $name;
791 let name: &str = &*_n;
792 let module: &str = module_path!();
793 $crate::prometheus::CounterVec::new($crate::prometheus::Opts::new(name, $help), $labels)
794 .and_then(|metric| {
795 ($registry).register_filtered(name, module, $level, metric)
796 })
797 }};
798}
799
800#[macro_export]
802macro_rules! register_counter_vec {
803 ($name:expr, $help:expr, $labels:expr $(,)?) => {
804 $crate::register_counter_vec!($name, $help, $labels; $crate::MetricLevel::Debug)
805 };
806 ($name:expr, $help:expr, $labels:expr ; $level:expr $(,)?) => {{
807 let _n = $name;
808 let name: &str = &*_n;
809 let module: &str = module_path!();
810 $crate::prometheus::CounterVec::new($crate::prometheus::Opts::new(name, $help), $labels)
811 .and_then(|metric| {
812 $crate::default_registry().register_filtered(name, module, $level, metric)
813 })
814 }};
815}
816
817#[macro_export]
820macro_rules! register_histogram_vec {
821 ($opts:expr, $labels:expr $(,)?) => {
822 $crate::register_histogram_vec!($opts, $labels; $crate::MetricLevel::Debug)
823 };
824 ($name:expr, $help:expr, $labels:expr $(,)?) => {
825 $crate::register_histogram_vec!($name, $help, $labels; $crate::MetricLevel::Debug)
826 };
827 ($name:expr, $help:expr, $labels:expr, $buckets:expr $(,)?) => {
828 $crate::register_histogram_vec!(
829 $name, $help, $labels, $buckets; $crate::MetricLevel::Debug
830 )
831 };
832 ($opts:expr, $labels:expr ; $level:expr $(,)?) => {{
833 let opts = $opts;
834 let name = opts.common_opts.name.clone();
835 let name: &str = &name;
836 let module: &str = module_path!();
837 $crate::prometheus::HistogramVec::new(opts, $labels).and_then(|metric| {
838 $crate::default_registry().register_filtered(name, module, $level, metric)
839 })
840 }};
841 ($name:expr, $help:expr, $labels:expr ; $level:expr $(,)?) => {{
842 let _n = $name;
843 let name: &str = &*_n;
844 let module: &str = module_path!();
845 $crate::prometheus::HistogramVec::new(
846 $crate::prometheus::HistogramOpts::new(name, $help),
847 $labels,
848 )
849 .and_then(|metric| {
850 $crate::default_registry().register_filtered(name, module, $level, metric)
851 })
852 }};
853 ($name:expr, $help:expr, $labels:expr, $buckets:expr ; $level:expr $(,)?) => {{
854 let _n = $name;
855 let name: &str = &*_n;
856 let module: &str = module_path!();
857 $crate::prometheus::HistogramVec::new(
858 $crate::prometheus::HistogramOpts::new(name, $help).buckets($buckets),
859 $labels,
860 )
861 .and_then(|metric| {
862 $crate::default_registry().register_filtered(name, module, $level, metric)
863 })
864 }};
865}
866
867#[cfg(test)]
868mod tests {
869 use super::MetricLevel::Debug;
870
871 #[test]
872 fn filter_matches_metric_or_module_name_prefix() {
873 let filter = super::Filter::parse("authority=off");
876 assert!(filter.is_exposed("some_authority", "iota_core::checkpoints", Debug));
877 assert!(!filter.is_exposed("authority", "iota_core::checkpoints", Debug));
878 assert!(!filter.is_exposed("authority_aggregator", "iota_core::checkpoints", Debug));
879 assert!(filter.is_exposed("certs_total", "iota_core::some_authority", Debug));
880 assert!(!filter.is_exposed("certs_total", "iota_core::authority", Debug));
881 assert!(!filter.is_exposed("certs_total", "iota_core::authority_aggregator", Debug));
882
883 let filter = super::Filter::parse("authority=off,authority_aggregator=trace");
885 assert!(!filter.is_exposed("authority", "iota_core::checkpoints", Debug));
886 assert!(filter.is_exposed("authority_aggregator", "iota_core::checkpoints", Debug));
887 assert!(!filter.is_exposed("certs_total", "iota_core::authority", Debug));
888 assert!(filter.is_exposed("certs_total", "iota_core::authority_aggregator", Debug));
889
890 let filter = super::Filter::parse("off,authority_aggregator=trace");
892 assert!(!filter.is_exposed("some_authority", "iota_core::checkpoints", Debug));
893 assert!(!filter.is_exposed("authority", "iota_core::checkpoints", Debug));
894 assert!(filter.is_exposed("authority_aggregator", "iota_core::checkpoints", Debug));
895 assert!(!filter.is_exposed("certs_total", "iota_core::some_authority", Debug));
896 assert!(!filter.is_exposed("certs_total", "iota_core::authority", Debug));
897 assert!(filter.is_exposed("certs_total", "iota_core::authority_aggregator", Debug));
898
899 let filter = super::Filter::parse("authority_aggregator=off");
901 assert!(filter.is_exposed("authority", "iota_core::checkpoints", Debug));
902 assert!(!filter.is_exposed("authority_aggregator", "iota_core::checkpoints", Debug));
903 assert!(filter.is_exposed("certs_total", "iota_core::authority", Debug));
904 assert!(!filter.is_exposed("certs_total", "iota_core::authority_aggregator", Debug));
905
906 let filter = super::Filter::parse("iota_core=off");
909 assert!(!filter.is_exposed("certs_total", "iota_core::authority", Debug));
910 assert!(filter.is_exposed("certs_total", "starfish::core", Debug));
911 }
912
913 #[test]
914 fn more_specific_pattern_wins_regardless_of_order() {
915 use super::MetricLevel::{Info, Trace, Warn};
916 for input in [
919 "iota_core::authority=warn,iota_core=off",
920 "iota_core=off,iota_core::authority=warn",
921 ] {
922 let filter = super::Filter::parse(input);
923 assert!(
924 filter.is_exposed("x", "iota_core::authority", Warn),
925 "{input}"
926 );
927 assert!(
928 !filter.is_exposed("x", "iota_core::checkpoints", Warn),
929 "{input}"
930 );
931 }
932 let filter = super::Filter::parse("authority=off,info");
935 assert!(!filter.is_exposed("authority", "m", Warn));
936 assert!(filter.is_exposed("certs_total", "m", Info));
937 assert!(!filter.is_exposed("certs_total", "m", Debug));
938 assert!(super::Filter::parse("off,trace").is_exposed("authority", "m", Debug));
941 assert!(
942 super::Filter::parse("authority=off,authority=trace").is_exposed(
943 "authority",
944 "m",
945 Debug
946 )
947 );
948 for input in [
952 "certs_total=trace,iota_core::execution_cache=warn",
953 "iota_core::execution_cache=warn,certs_total=trace",
954 ] {
955 let filter = super::Filter::parse(input);
956 assert!(
957 filter.is_exposed("certs_total", "iota_core::execution_cache", Trace),
958 "{input}"
959 );
960 assert!(
962 !filter.is_exposed("other_metric", "iota_core::execution_cache", Debug),
963 "{input}"
964 );
965 }
966 let filter = super::Filter::parse("iota_core::execution_cache=trace,certs_total=off");
968 assert!(!filter.is_exposed("certs_total", "iota_core::execution_cache", Warn));
969 }
970
971 #[test]
972 fn env_directives_merge_over_config_into_one_startup_filter() {
973 use super::MetricLevel::{Info, Trace, Warn};
974 let filter = super::Filter::from_sources(
978 super::FilterSource::new("iota_core::authority=off,starfish=warn"),
979 Some(super::FilterSource::new("iota_core=info,starfish=info")),
980 );
981 assert!(!filter.is_exposed("x", "iota_core::authority", Warn));
982 assert!(filter.is_exposed("x", "iota_core::checkpoints", Info));
983 assert!(!filter.is_exposed("x", "iota_core::checkpoints", Debug));
984 assert!(filter.is_exposed("x", "starfish::core", Info));
986 assert!(!filter.is_exposed("x", "starfish::core", Debug));
987 assert_eq!(
989 filter.startup_filter_string(),
990 "iota_core::authority=off,iota_core=info,starfish=info"
991 );
992
993 let filter = super::Filter::from_sources(
997 super::FilterSource::new("default=info,iota_core=warn"),
998 Some(super::FilterSource::new("default=trace")),
999 );
1000 assert!(filter.is_exposed("x", "iota_core::authority", Warn));
1001 assert!(!filter.is_exposed("x", "iota_core::authority", Info));
1002 assert!(filter.is_exposed("x", "m", Trace));
1003 assert_eq!(
1004 filter.startup_filter_string(),
1005 "iota_core=warn,default=trace"
1006 );
1007
1008 let filter = super::Filter::from_sources(
1011 super::FilterSource::new("default=info,iota_core=warn"),
1012 Some(super::FilterSource::new("trace")),
1013 );
1014 assert!(filter.is_exposed("x", "iota_core::authority", Trace));
1015 assert!(filter.is_exposed("x", "m", Trace));
1016 assert_eq!(filter.startup_filter_string(), "trace");
1019
1020 let filter = super::Filter::from_sources(
1023 super::FilterSource::new("off"),
1024 Some(super::FilterSource::new(" ")),
1025 );
1026 assert!(!filter.is_exposed("x", "m", Warn));
1027 }
1028
1029 #[test]
1030 fn unmatched_metrics_are_exposed() {
1031 use super::MetricLevel::{Info, Trace, Warn};
1032 let mut filters = vec![
1035 super::Filter::parse(""),
1036 super::Filter::default(),
1037 super::Filter::parse(",,"),
1039 ];
1040 if std::env::var_os(super::METRICS_FILTER_ENV).is_none() {
1044 filters.push(super::Filter::from_env());
1045 }
1046 for filter in filters {
1047 assert!(filter.is_exposed("anything", "any::module", Warn));
1048 assert!(filter.is_exposed("anything", "any::module", Info));
1049 assert!(filter.is_exposed("anything", "any::module", Debug));
1050 assert!(filter.is_exposed("anything", "any::module", Trace));
1051 }
1052 }
1053
1054 #[test]
1055 fn invalid_directives_are_dropped() {
1056 use super::MetricLevel::Trace;
1057 for level in ["maybe", "on", "true", "1", "false", "0"] {
1062 let filter = super::Filter::parse(&format!("authority={level}"));
1063 assert!(
1064 filter.is_exposed("authority", "m", Trace),
1065 "{level} should be dropped as invalid, leaving the default"
1066 );
1067 }
1068 assert!(super::Filter::parse("authority").is_exposed("authority", "m", Trace));
1071 let filter = super::Filter::parse("authority=off,bogus=nope");
1073 assert!(!filter.is_exposed("authority", "m", Debug));
1074 }
1075
1076 #[test]
1077 fn whitespace_is_trimmed() {
1078 let filter = super::Filter::parse(" authority = off , authority_aggregator = trace ");
1079 assert!(!filter.is_exposed("authority", "m", Debug));
1080 assert!(filter.is_exposed("authority_aggregator", "m", Debug));
1081 }
1082
1083 #[test]
1084 fn registries_built_to_share_a_filter_see_the_same_decisions() {
1085 use super::{Arc, Filter, MetricLevel, Registry};
1086
1087 let filter = Arc::new(Filter::parse("off,authority=trace"));
1088 assert!(filter.is_exposed("authority", "m", MetricLevel::Debug));
1089 assert!(!filter.is_exposed("consensus", "m", MetricLevel::Debug));
1090 let registry = Registry::new_custom(None, None, Some(filter.clone())).unwrap();
1091 let shared = Registry::new_custom(None, None, Some(filter)).unwrap();
1092 assert!(std::sync::Arc::ptr_eq(®istry.filter(), &shared.filter()));
1093 }
1094
1095 #[test]
1096 fn level_thresholds() {
1097 use super::MetricLevel::{Debug, Info, Trace, Warn};
1098 let f = super::Filter::parse("authority=warn");
1100 assert!(f.is_exposed("x", "iota_core::authority", Warn));
1101 assert!(!f.is_exposed("x", "iota_core::authority", Info));
1102 assert!(!f.is_exposed("x", "iota_core::authority", Debug));
1103 let f = super::Filter::parse("authority=info");
1105 assert!(f.is_exposed("x", "iota_core::authority", Warn));
1106 assert!(f.is_exposed("x", "iota_core::authority", Info));
1107 assert!(!f.is_exposed("x", "iota_core::authority", Debug));
1108 let f = super::Filter::parse("authority=debug");
1110 assert!(f.is_exposed("x", "iota_core::authority", Debug));
1111 assert!(!f.is_exposed("x", "iota_core::authority", Trace));
1112 let f = super::Filter::parse("authority=trace");
1114 assert!(f.is_exposed("x", "iota_core::authority", Trace));
1115 assert!(!super::Filter::parse("authority=off").is_exposed(
1117 "x",
1118 "iota_core::authority",
1119 Warn
1120 ));
1121 assert!(super::Filter::parse("").is_exposed("x", "m", Info));
1123 assert!(super::Filter::parse("").is_exposed("x", "m", Trace));
1124 }
1125}
1126
1127#[cfg(test)]
1128mod test_helpers {
1129 use super::{Filter, Registry};
1130
1131 pub fn registry(filter: &str) -> Registry {
1132 Registry::new_custom(None, None, Some(std::sync::Arc::new(Filter::parse(filter)))).unwrap()
1133 }
1134
1135 pub fn gathered_names(registry: &Registry) -> Vec<String> {
1136 let mut names: Vec<_> = registry
1137 .gather()
1138 .iter()
1139 .map(|f| f.name().to_owned())
1140 .collect();
1141 names.sort();
1142 names
1143 }
1144}
1145
1146#[cfg(test)]
1147mod gather_filter_tests {
1148 use super::{
1149 Filter, MetricLevel, Registry,
1150 test_helpers::{gathered_names, registry},
1151 };
1152
1153 #[test]
1154 fn gather_applies_level_thresholds_by_module() {
1155 let reg = registry("gather_filter_tests=warn");
1157 crate::register_int_gauge_with_registry!("g_warn", "h", ® MetricLevel::Warn).unwrap();
1158 let g_debug = crate::register_int_gauge_with_registry!("g_debug", "h", ®).unwrap();
1159 g_debug.set(7);
1160
1161 assert_eq!(gathered_names(®), ["g_warn"]);
1164 assert_eq!(g_debug.get(), 7);
1165 }
1166
1167 #[test]
1168 fn duplicate_name_is_rejected_even_when_filtered_out() {
1169 let reg = registry("g_dup=off");
1173 crate::register_int_gauge_with_registry!("g_dup", "h", ® MetricLevel::Warn).unwrap();
1174 let err = crate::register_int_gauge_with_registry!("g_dup", "h", ® MetricLevel::Warn)
1175 .unwrap_err();
1176 assert!(
1177 matches!(err, prometheus::Error::AlreadyReg),
1178 "unexpected error: {err:?}"
1179 );
1180 }
1181
1182 #[test]
1183 fn prefixed_registry_records_exposed_family_names() {
1184 let exposed = Registry::new_custom(
1185 Some("consensus".to_owned()),
1186 None,
1187 Some(std::sync::Arc::new(Filter::parse(""))),
1188 )
1189 .unwrap();
1190 crate::register_int_gauge_with_registry!("g", "h", &exposed; MetricLevel::Warn).unwrap();
1191 assert_eq!(gathered_names(&exposed), ["consensus_g"]);
1192
1193 let hidden = Registry::new_custom(
1196 Some("consensus".to_owned()),
1197 None,
1198 Some(std::sync::Arc::new(Filter::parse(
1199 "gather_filter_tests=off",
1200 ))),
1201 )
1202 .unwrap();
1203 crate::register_int_gauge_with_registry!("g", "h", &hidden; MetricLevel::Warn).unwrap();
1204 assert_eq!(gathered_names(&hidden), Vec::<String>::new());
1205 }
1206
1207 #[test]
1208 fn directly_registered_collectors_bypass_filter() {
1209 let reg = registry("off");
1210 crate::register_int_gauge_with_registry!("g_macro", "h", ®).unwrap();
1211 let gauge = prometheus::IntGauge::new("g_direct", "h").unwrap();
1212 reg.register(Box::new(gauge)).unwrap();
1213
1214 assert_eq!(gathered_names(®), ["g_direct"]);
1217 }
1218}
1219
1220#[cfg(test)]
1221mod runtime_filter_tests {
1222 use super::{
1223 Filter, FilterSource, MetricLevel, Registry,
1224 test_helpers::{gathered_names, registry},
1225 };
1226
1227 fn set_runtime(registry: &Registry, s: &str) {
1230 registry
1231 .filter()
1232 .set_runtime_filter(FilterSource::new(s))
1233 .unwrap();
1234 }
1235
1236 fn reset_runtime(registry: &Registry) {
1237 registry.filter().reset_runtime_filter();
1238 }
1239
1240 #[test]
1241 fn raising_runtime_level_exposes_collected_metrics() {
1242 let reg = registry("runtime_filter_tests=warn");
1244 crate::register_int_gauge_with_registry!("g_warn", "h", ® MetricLevel::Warn).unwrap();
1245 let g_debug = crate::register_int_gauge_with_registry!("g_debug", "h", ®).unwrap();
1246 g_debug.set(7);
1247 assert_eq!(gathered_names(®), ["g_warn"]);
1248
1249 set_runtime(®, "runtime_filter_tests=debug");
1252 assert_eq!(gathered_names(®), ["g_debug", "g_warn"]);
1253 let family = reg
1254 .gather()
1255 .into_iter()
1256 .find(|f| f.name() == "g_debug")
1257 .unwrap();
1258 assert_eq!(family.get_metric()[0].get_gauge().value() as i64, 7);
1259
1260 let reg = registry("g_hidden=off");
1263 let g = crate::register_int_gauge_with_registry!("g_hidden", "h", ® MetricLevel::Warn)
1264 .unwrap();
1265 g.set(9);
1266 assert_eq!(gathered_names(®), Vec::<String>::new());
1267 set_runtime(®, "g_hidden=warn");
1268 assert_eq!(gathered_names(®), ["g_hidden"]);
1269 let family = ®.gather()[0];
1270 assert_eq!(family.get_metric()[0].get_gauge().value() as i64, 9);
1271 }
1272
1273 #[test]
1274 fn runtime_override_keeps_startup_directives_for_other_patterns() {
1275 let reg = registry("g_a=off");
1276 crate::register_int_gauge_with_registry!("g_a", "h", ® MetricLevel::Warn).unwrap();
1277 crate::register_int_gauge_with_registry!("g_b", "h", ® MetricLevel::Warn).unwrap();
1278 assert_eq!(gathered_names(®), ["g_b"]);
1279
1280 set_runtime(®, "g_b=off");
1283 assert_eq!(gathered_names(®), Vec::<String>::new());
1284
1285 set_runtime(®, "");
1289 assert_eq!(gathered_names(®), ["g_b"]);
1290
1291 reset_runtime(®);
1292 assert_eq!(gathered_names(®), ["g_b"]);
1293 }
1294
1295 #[test]
1296 fn runtime_directives_replace_same_pattern_startup_directives() {
1297 use MetricLevel::{Debug, Trace, Warn};
1298
1299 let filter = Filter::parse("g_a=off,g_b=warn");
1301 filter
1302 .set_runtime_filter(FilterSource::new("g_b=trace"))
1303 .unwrap();
1304 assert!(!filter.is_exposed("g_a", "m", Warn));
1305 assert!(filter.is_exposed("g_b", "m", Trace));
1306 assert_eq!(filter.filter_string(), "g_a=off,g_b=trace");
1307 assert_eq!(filter.startup_filter_string(), "g_a=off,g_b=warn");
1309
1310 let filter = Filter::parse("iota_core=warn,iota_core::authority::sub=off");
1314 filter
1315 .set_runtime_filter(FilterSource::new("iota_core::authority=trace"))
1316 .unwrap();
1317 assert!(!filter.is_exposed("x", "iota_core::authority::sub", Warn));
1318 assert!(filter.is_exposed("x", "iota_core::authority::other", Trace));
1319 assert!(!filter.is_exposed("x", "iota_core::checkpoints", Debug));
1320 assert_eq!(
1321 filter.filter_string(),
1322 "iota_core=warn,iota_core::authority::sub=off,iota_core::authority=trace"
1323 );
1324
1325 let filter = Filter::parse("g_a=off,g_b=warn");
1329 filter
1330 .set_runtime_filter(FilterSource::new("default=trace,g_c=off"))
1331 .unwrap();
1332 assert!(!filter.is_exposed("g_a", "m", Warn));
1333 assert!(filter.is_exposed("g_b", "m", Warn));
1334 assert!(!filter.is_exposed("g_b", "m", Debug));
1335 assert!(!filter.is_exposed("g_c", "m", Warn));
1336 assert!(filter.is_exposed("other", "m", Trace));
1337 assert_eq!(
1338 filter.filter_string(),
1339 "g_a=off,g_b=warn,default=trace,g_c=off"
1340 );
1341
1342 let filter = Filter::parse("g_a=off,g_b=warn");
1345 filter
1346 .set_runtime_filter(FilterSource::new("trace,g_c=off"))
1347 .unwrap();
1348 assert!(filter.is_exposed("g_a", "m", Trace));
1349 assert!(filter.is_exposed("g_b", "m", Trace));
1350 assert!(!filter.is_exposed("g_c", "m", Warn));
1351 assert_eq!(filter.filter_string(), "trace,g_c=off");
1352
1353 filter.reset_runtime_filter();
1355 assert_eq!(filter.filter_string(), filter.startup_filter_string());
1356 assert!(!filter.is_exposed("g_a", "m", Warn));
1357 }
1358
1359 #[test]
1360 fn default_pattern_does_not_match_a_module_named_default() {
1361 use MetricLevel::Warn;
1362
1363 let filter = Filter::parse("default=off,p2p=warn");
1366 assert!(filter.is_exposed("x", "p2p::discovery", Warn));
1367 assert!(!filter.is_exposed("x", "other_module", Warn));
1368 }
1369
1370 #[test]
1371 fn filter_reports_startup_and_current_strings() {
1372 let filter = Filter::from_sources(
1375 FilterSource::with_display("iota_core::authority=off", "authority=off"),
1376 Some(FilterSource::with_display(
1377 "iota_core::checkpoints=warn",
1378 "checkpoints=warn",
1379 )),
1380 );
1381 assert_eq!(
1382 filter.startup_filter_string(),
1383 "authority=off,checkpoints=warn"
1384 );
1385 assert_eq!(filter.filter_string(), filter.startup_filter_string());
1386 assert!(!filter.is_exposed("x", "iota_core::authority", MetricLevel::Warn));
1387
1388 filter
1391 .set_runtime_filter(FilterSource::with_display(
1392 "iota_core::authority=warn",
1393 "authority=warn",
1394 ))
1395 .unwrap();
1396 assert_eq!(filter.filter_string(), "checkpoints=warn,authority=warn");
1397 assert!(filter.is_exposed("x", "iota_core::authority", MetricLevel::Warn));
1398 assert!(!filter.is_exposed("x", "iota_core::authority", MetricLevel::Debug));
1399
1400 filter.reset_runtime_filter();
1401 assert_eq!(filter.filter_string(), filter.startup_filter_string());
1402 assert!(!filter.is_exposed("x", "iota_core::authority", MetricLevel::Warn));
1403 }
1404
1405 #[test]
1406 fn filter_string_is_canonical_and_round_trips() {
1407 let filter = Filter::parse("foo=bogus, typed_store=warn ,default=info");
1411 let startup = filter.startup_filter_string();
1412 assert_eq!(startup, "typed_store=warn,default=info");
1413 filter
1414 .set_runtime_filter(FilterSource::new(&startup))
1415 .unwrap();
1416 assert_eq!(filter.filter_string(), "typed_store=warn,default=info");
1417
1418 let filter = Filter::parse("g_a=off");
1422 filter
1423 .set_runtime_filter(FilterSource::new("trace,g_c=off"))
1424 .unwrap();
1425 let current = filter.filter_string();
1426 assert_eq!(current, "trace,g_c=off");
1427 filter
1428 .set_runtime_filter(FilterSource::new(¤t))
1429 .unwrap();
1430 assert_eq!(filter.filter_string(), "trace,g_c=off");
1431 assert!(filter.is_exposed("g_a", "m", MetricLevel::Trace));
1432 }
1433
1434 #[test]
1435 fn set_runtime_filter_rejects_invalid_directives() {
1436 let filter = Filter::parse("authority=off");
1437 let err = filter
1438 .set_runtime_filter(FilterSource::new("authority=warn,bogus=nope"))
1439 .unwrap_err();
1440 assert!(err.contains("bogus=nope"), "unexpected error: {err}");
1441 assert_eq!(filter.filter_string(), filter.startup_filter_string());
1443 assert!(!filter.is_exposed("x", "iota_core::authority", MetricLevel::Warn));
1444 }
1445}