metrics_prometheus/recorder/
mod.rs

1//! [`metrics::Recorder`] implementations.
2
3pub mod freezable;
4pub mod frozen;
5pub mod layer;
6
7use std::{borrow::Cow, fmt, sync::Arc};
8
9pub use metrics_util::layers::Layer;
10
11pub use self::{freezable::Recorder as Freezable, frozen::Recorder as Frozen};
12use crate::{
13    failure::{self, strategy::PanicInDebugNoOpInRelease},
14    metric, storage,
15};
16
17/// [`metrics::Recorder`] registering metrics in a [`prometheus::Registry`] and
18/// powered by a [`metrics::Registry`] built on top of a [`storage::Mutable`].
19///
20/// This [`Recorder`] is capable of registering metrics in its
21/// [`prometheus::Registry`] on the fly. By default, the
22/// [`prometheus::default_registry()`] is used.
23///
24/// # Example
25///
26/// ```rust
27/// let recorder = metrics_prometheus::install();
28///
29/// // Either use `metrics` crate interfaces.
30/// metrics::counter!(
31///     "count", "whose" => "mine", "kind" => "owned",
32/// ).increment(1);
33/// metrics::counter!(
34///     "count", "whose" => "mine", "kind" => "ref",
35/// ).increment(1);
36/// metrics::counter!(
37///     "count", "kind" => "owned", "whose" => "dummy",
38/// ).increment(1);
39///
40/// // Or construct and provide `prometheus` metrics directly.
41/// recorder.register_metric(prometheus::Gauge::new("value", "help")?);
42///
43/// let report = prometheus::TextEncoder::new()
44///     .encode_to_string(&prometheus::default_registry().gather())?;
45/// assert_eq!(
46///     report.trim(),
47///     r#"
48/// ## HELP count count
49/// ## TYPE count counter
50/// count{kind="owned",whose="dummy"} 1
51/// count{kind="owned",whose="mine"} 1
52/// count{kind="ref",whose="mine"} 1
53/// ## HELP value help
54/// ## TYPE value gauge
55/// value 0
56///     "#
57///     .trim(),
58/// );
59///
60/// // Metrics can be described anytime after being registered in
61/// // `prometheus::Registry`.
62/// metrics::describe_counter!("count", "Example of counter.");
63/// metrics::describe_gauge!("value", "Example of gauge.");
64///
65/// let report = prometheus::TextEncoder::new()
66///     .encode_to_string(&recorder.registry().gather())?;
67/// assert_eq!(
68///     report.trim(),
69///     r#"
70/// ## HELP count Example of counter.
71/// ## TYPE count counter
72/// count{kind="owned",whose="dummy"} 1
73/// count{kind="owned",whose="mine"} 1
74/// count{kind="ref",whose="mine"} 1
75/// ## HELP value Example of gauge.
76/// ## TYPE value gauge
77/// value 0
78///     "#
79///     .trim(),
80/// );
81///
82/// // Description can be changed multiple times and anytime:
83/// metrics::describe_counter!("count", "Another description.");
84///
85/// // Even before a metric is registered in `prometheus::Registry`.
86/// metrics::describe_counter!("another", "Yet another counter.");
87/// metrics::counter!("another").increment(1);
88///
89/// let report = prometheus::TextEncoder::new()
90///     .encode_to_string(&recorder.registry().gather())?;
91/// assert_eq!(
92///     report.trim(),
93///     r#"
94/// ## HELP another Yet another counter.
95/// ## TYPE another counter
96/// another 1
97/// ## HELP count Another description.
98/// ## TYPE count counter
99/// count{kind="owned",whose="dummy"} 1
100/// count{kind="owned",whose="mine"} 1
101/// count{kind="ref",whose="mine"} 1
102/// ## HELP value Example of gauge.
103/// ## TYPE value gauge
104/// value 0
105///     "#
106///     .trim(),
107/// );
108/// # Ok::<_, prometheus::Error>(())
109/// ```
110///
111/// # Performance
112///
113/// This [`Recorder`] provides the same overhead of accessing an already
114/// registered metric as a [`metrics::Registry`] does: [`read`-lock] on a
115/// sharded [`HashMap`] plus [`Arc`] cloning.
116///
117/// # Errors
118///
119/// [`prometheus::Registry`] has far more stricter semantics than the ones
120/// implied by a [`metrics::Recorder`]. That's why incorrect usage of
121/// [`prometheus`] metrics via [`metrics`] crate will inevitably lead to a
122/// [`prometheus::Registry`] returning a [`prometheus::Error`] instead of
123/// registering the metric. The returned [`prometheus::Error`] can be either
124/// turned into a panic, or just silently ignored, making this [`Recorder`] to
125/// return a no-op metric instead (see [`metrics::Counter::noop()`] for
126/// example).
127///
128/// The desired behavior can be specified with a [`failure::Strategy`]
129/// implementation of this [`Recorder`]. By default a
130/// [`PanicInDebugNoOpInRelease`] [`failure::Strategy`] is used. See
131/// [`failure::strategy`] module for other available [`failure::Strategy`]s, or
132/// provide your own one by implementing the [`failure::Strategy`] trait.
133///
134/// ```rust,should_panic
135/// use metrics_prometheus::failure::strategy;
136///
137/// metrics_prometheus::Recorder::builder()
138///     .with_failure_strategy(strategy::Panic)
139///     .build_and_install();
140///
141/// metrics::counter!("count", "kind" => "owned").increment(1);
142/// // This panics, as such labeling is not allowed by `prometheus` crate.
143/// metrics::counter!("count", "whose" => "mine").increment(1);
144/// ```
145///
146/// [`HashMap`]: std::collections::HashMap
147/// [`metrics::Registry`]: metrics_util::registry::Registry
148/// [`read`-lock]: std::sync::RwLock::read()
149#[derive(Clone)]
150pub struct Recorder<FailureStrategy = PanicInDebugNoOpInRelease> {
151    /// [`metrics::Registry`] providing performant access to the stored metrics.
152    ///
153    /// [`metrics::Registry`]: metrics_util::registry::Registry
154    metrics:
155        Arc<metrics_util::registry::Registry<metrics::Key, storage::Mutable>>,
156
157    /// [`storage::Mutable`] backing the [`metrics::Registry`] and registering
158    /// metrics in its [`prometheus::Registry`].
159    ///
160    /// [`metrics::Registry`]: metrics_util::registry::Registry
161    storage: storage::Mutable,
162
163    /// [`failure::Strategy`] to apply when a [`prometheus::Error`] is
164    /// encountered inside [`metrics::Recorder`] methods.
165    failure_strategy: FailureStrategy,
166}
167
168// TODO: Make a PR with `Debug` impl for `metrics_util::registry::Registry`.
169impl<S: fmt::Debug> fmt::Debug for Recorder<S> {
170    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171        f.debug_struct("Recorder")
172            .field("storage", &self.storage)
173            .field("failure_strategy", &self.failure_strategy)
174            .finish_non_exhaustive()
175    }
176}
177
178impl Recorder {
179    /// Starts building a new [`Recorder`] on top of the
180    /// [`prometheus::default_registry()`].
181    pub fn builder() -> Builder {
182        Builder {
183            storage: storage::Mutable::default(),
184            failure_strategy: PanicInDebugNoOpInRelease,
185            layers: layer::Stack::identity(),
186        }
187    }
188}
189
190impl<S> Recorder<S> {
191    /// Returns the underlying [`prometheus::Registry`] backing this
192    /// [`Recorder`].
193    ///
194    /// # Warning
195    ///
196    /// Any [`prometheus`] metrics, registered directly in the returned
197    /// [`prometheus::Registry`], cannot be used via this [`metrics::Recorder`]
198    /// (and, so, [`metrics`] crate interfaces), and trying to use them will
199    /// inevitably cause a [`prometheus::Error`] being emitted.
200    ///
201    /// ```rust,should_panic
202    /// use metrics_prometheus::failure::strategy;
203    ///
204    /// let recorder = metrics_prometheus::Recorder::builder()
205    ///     .with_failure_strategy(strategy::Panic)
206    ///     .build_and_install();
207    ///
208    /// let counter = prometheus::IntCounter::new("value", "help")?;
209    /// recorder.registry().register(Box::new(counter))?;
210    ///
211    /// // panics: Duplicate metrics collector registration attempted
212    /// metrics::counter!("value").increment(1);
213    /// # Ok::<_, prometheus::Error>(())
214    /// ```
215    #[must_use]
216    pub const fn registry(&self) -> &prometheus::Registry {
217        &self.storage.prometheus
218    }
219
220    /// Tries to register the provided [`prometheus`] `metric` in the underlying
221    /// [`prometheus::Registry`] in the way making it usable via this
222    /// [`Recorder`] (and, so, [`metrics`] crate interfaces).
223    ///
224    /// Accepts only the following [`prometheus`] metrics:
225    /// - [`prometheus::IntCounter`], [`prometheus::IntCounterVec`]
226    /// - [`prometheus::Gauge`], [`prometheus::GaugeVec`]
227    /// - [`prometheus::Histogram`], [`prometheus::HistogramVec`]
228    ///
229    /// # Errors
230    ///
231    /// If the underlying [`prometheus::Registry`] fails to register the
232    /// provided `metric`.
233    ///
234    /// # Example
235    ///
236    /// ```rust
237    /// let recorder = metrics_prometheus::install();
238    ///
239    /// let counter = prometheus::IntCounterVec::new(
240    ///     prometheus::opts!("value", "help"),
241    ///     &["whose", "kind"],
242    /// )?;
243    ///
244    /// recorder.try_register_metric(counter.clone())?;
245    ///
246    /// counter.with_label_values(&["mine", "owned"]).inc();
247    /// counter.with_label_values(&["foreign", "ref"]).inc_by(2);
248    /// counter.with_label_values(&["foreign", "owned"]).inc_by(3);
249    ///
250    /// let report = prometheus::TextEncoder::new()
251    ///     .encode_to_string(&prometheus::default_registry().gather())?;
252    /// assert_eq!(
253    ///     report.trim(),
254    ///     r#"
255    /// ## HELP value help
256    /// ## TYPE value counter
257    /// value{kind="owned",whose="foreign"} 3
258    /// value{kind="owned",whose="mine"} 1
259    /// value{kind="ref",whose="foreign"} 2
260    ///     "#
261    ///     .trim(),
262    /// );
263    ///
264    /// metrics::counter!(
265    ///     "value", "whose" => "mine", "kind" => "owned",
266    /// ).increment(1);
267    /// metrics::counter!(
268    ///     "value", "whose" => "mine", "kind" => "ref",
269    /// ).increment(1);
270    /// metrics::counter!(
271    ///     "value", "kind" => "owned", "whose" => "foreign",
272    /// ).increment(1);
273    ///
274    /// let report = prometheus::TextEncoder::new()
275    ///     .encode_to_string(&recorder.registry().gather())?;
276    /// assert_eq!(
277    ///     report.trim(),
278    ///     r#"
279    /// ## HELP value help
280    /// ## TYPE value counter
281    /// value{kind="owned",whose="foreign"} 4
282    /// value{kind="owned",whose="mine"} 2
283    /// value{kind="ref",whose="foreign"} 2
284    /// value{kind="ref",whose="mine"} 1
285    ///     "#
286    ///     .trim(),
287    /// );
288    /// # Ok::<_, prometheus::Error>(())
289    /// ```
290    pub fn try_register_metric<M>(&self, metric: M) -> prometheus::Result<()>
291    where
292        M: metric::Bundled + prometheus::core::Collector,
293        <M as metric::Bundled>::Bundle:
294            prometheus::core::Collector + Clone + 'static,
295        storage::Mutable: storage::Get<
296                storage::mutable::Collection<<M as metric::Bundled>::Bundle>,
297            >,
298    {
299        self.storage.register_external(metric)
300    }
301
302    /// Registers the provided [`prometheus`] `metric` in the underlying
303    /// [`prometheus::Registry`] in the way making it usable via this
304    /// [`Recorder`] (and, so, [`metrics`] crate interfaces).
305    ///
306    /// Accepts only the following [`prometheus`] metrics:
307    /// - [`prometheus::IntCounter`], [`prometheus::IntCounterVec`]
308    /// - [`prometheus::Gauge`], [`prometheus::GaugeVec`]
309    /// - [`prometheus::Histogram`], [`prometheus::HistogramVec`]
310    ///
311    /// # Panics
312    ///
313    /// If the underlying [`prometheus::Registry`] fails to register the
314    /// provided `metric`.
315    ///
316    /// # Example
317    ///
318    /// ```rust
319    /// let recorder = metrics_prometheus::install();
320    ///
321    /// let gauge = prometheus::GaugeVec::new(
322    ///     prometheus::opts!("value", "help"),
323    ///     &["whose", "kind"],
324    /// )?;
325    ///
326    /// recorder.register_metric(gauge.clone());
327    ///
328    /// gauge.with_label_values(&["mine", "owned"]).inc();
329    /// gauge.with_label_values(&["foreign", "ref"]).set(2.0);
330    /// gauge.with_label_values(&["foreign", "owned"]).set(3.0);
331    ///
332    /// let report = prometheus::TextEncoder::new()
333    ///     .encode_to_string(&prometheus::default_registry().gather())?;
334    /// assert_eq!(
335    ///     report.trim(),
336    ///     r#"
337    /// ## HELP value help
338    /// ## TYPE value gauge
339    /// value{kind="owned",whose="foreign"} 3
340    /// value{kind="owned",whose="mine"} 1
341    /// value{kind="ref",whose="foreign"} 2
342    ///     "#
343    ///     .trim(),
344    /// );
345    ///
346    /// metrics::gauge!(
347    ///     "value", "whose" => "mine", "kind" => "owned",
348    /// ).increment(2.0);
349    /// metrics::gauge!(
350    ///     "value", "whose" => "mine", "kind" => "ref",
351    /// ).decrement(2.0);
352    /// metrics::gauge!(
353    ///     "value", "kind" => "owned", "whose" => "foreign",
354    /// ).increment(2.0);
355    ///
356    /// let report = prometheus::TextEncoder::new()
357    ///     .encode_to_string(&prometheus::default_registry().gather())?;
358    /// assert_eq!(
359    ///     report.trim(),
360    ///     r#"
361    /// ## HELP value help
362    /// ## TYPE value gauge
363    /// value{kind="owned",whose="foreign"} 5
364    /// value{kind="owned",whose="mine"} 3
365    /// value{kind="ref",whose="foreign"} 2
366    /// value{kind="ref",whose="mine"} -2
367    ///     "#
368    ///     .trim(),
369    /// );
370    /// # Ok::<_, prometheus::Error>(())
371    /// ```
372    pub fn register_metric<M>(&self, metric: M)
373    where
374        M: metric::Bundled + prometheus::core::Collector,
375        <M as metric::Bundled>::Bundle:
376            prometheus::core::Collector + Clone + 'static,
377        storage::Mutable: storage::Get<
378                storage::mutable::Collection<<M as metric::Bundled>::Bundle>,
379            >,
380    {
381        self.try_register_metric(metric).unwrap_or_else(|e| {
382            panic!("failed to register `prometheus` metric: {e}")
383        });
384    }
385}
386
387#[warn(clippy::missing_trait_methods)]
388impl<S> metrics::Recorder for Recorder<S>
389where
390    S: failure::Strategy,
391{
392    fn describe_counter(
393        &self,
394        key: metrics::KeyName,
395        _: Option<metrics::Unit>,
396        description: metrics::SharedString,
397    ) {
398        self.storage.describe::<prometheus::IntCounter>(
399            key.as_str(),
400            description.into_owned(),
401        );
402    }
403
404    fn describe_gauge(
405        &self,
406        key: metrics::KeyName,
407        _: Option<metrics::Unit>,
408        description: metrics::SharedString,
409    ) {
410        self.storage.describe::<prometheus::Gauge>(
411            key.as_str(),
412            description.into_owned(),
413        );
414    }
415
416    fn describe_histogram(
417        &self,
418        key: metrics::KeyName,
419        _: Option<metrics::Unit>,
420        description: metrics::SharedString,
421    ) {
422        self.storage.describe::<prometheus::Histogram>(
423            key.as_str(),
424            description.into_owned(),
425        );
426    }
427
428    fn register_counter(
429        &self,
430        key: &metrics::Key,
431        _: &metrics::Metadata<'_>,
432    ) -> metrics::Counter {
433        self.metrics
434            .get_or_create_counter(key, |counter| {
435                counter.as_ref().map(|c| Arc::clone(c).into()).or_else(|e| {
436                    match self.failure_strategy.decide(e) {
437                        failure::Action::NoOp => Ok(metrics::Counter::noop()),
438                        // PANIC: We cannot panic inside this closure, because
439                        //        this may lead to poisoning `RwLock`s inside
440                        //        `metrics_util::registry::Registry`.
441                        failure::Action::Panic => Err(e.to_string()),
442                    }
443                })
444            })
445            .unwrap_or_else(|e| {
446                panic!(
447                    "failed to register `prometheus::IntCounter` metric: {e}"
448                )
449            })
450    }
451
452    fn register_gauge(
453        &self,
454        key: &metrics::Key,
455        _: &metrics::Metadata<'_>,
456    ) -> metrics::Gauge {
457        self.metrics
458            .get_or_create_gauge(key, |gauge| {
459                gauge.as_ref().map(|c| Arc::clone(c).into()).or_else(|e| {
460                    match self.failure_strategy.decide(e) {
461                        failure::Action::NoOp => Ok(metrics::Gauge::noop()),
462                        // PANIC: We cannot panic inside this closure, because
463                        //        this may lead to poisoning `RwLock`s inside
464                        //        `metrics_util::registry::Registry`.
465                        failure::Action::Panic => Err(e.to_string()),
466                    }
467                })
468            })
469            .unwrap_or_else(|e| {
470                panic!("failed to register `prometheus::Gauge` metric: {e}")
471            })
472    }
473
474    fn register_histogram(
475        &self,
476        key: &metrics::Key,
477        _: &metrics::Metadata<'_>,
478    ) -> metrics::Histogram {
479        self.metrics
480            .get_or_create_histogram(key, |histogram| {
481                histogram.as_ref().map(|c| Arc::clone(c).into()).or_else(|e| {
482                    match self.failure_strategy.decide(e) {
483                        failure::Action::NoOp => Ok(metrics::Histogram::noop()),
484                        // PANIC: We cannot panic inside this closure, because
485                        //        this may lead to poisoning `RwLock`s inside
486                        //        `metrics_util::registry::Registry`.
487                        failure::Action::Panic => Err(e.to_string()),
488                    }
489                })
490            })
491            .unwrap_or_else(|e| {
492                panic!("failed to register `prometheus::Histogram` metric: {e}")
493            })
494    }
495}
496
497/// Builder for building a [`Recorder`].
498#[derive(Debug)]
499#[must_use]
500pub struct Builder<
501    FailureStrategy = PanicInDebugNoOpInRelease,
502    Layers = layer::Stack,
503> {
504    /// [`storage::Mutable`] registering metrics in its
505    /// [`prometheus::Registry`].
506    storage: storage::Mutable,
507
508    /// [`failure::Strategy`] of the built [`Recorder`] to apply when a
509    /// [`prometheus::Error`] is encountered inside its [`metrics::Recorder`]
510    /// methods.
511    failure_strategy: FailureStrategy,
512
513    /// [`metrics::Layer`]s to wrap the built [`Recorder`] with upon its
514    /// installation with the [`metrics::set_global_recorder()`].
515    ///
516    /// [`metrics::Layer`]: Layer
517    layers: Layers,
518}
519
520impl<S, L> Builder<S, L> {
521    /// Sets the provided [`prometheus::Registry`] to be used by the built
522    /// [`Recorder`].
523    ///
524    /// When not specified, the [`prometheus::default_registry()`] is used by
525    /// default.
526    ///
527    /// # Warning
528    ///
529    /// Any [`prometheus`] metrics, already registered in the provided
530    /// [`prometheus::Registry`], cannot be used via the built
531    /// [`metrics::Recorder`] (and, so, [`metrics`] crate interfaces), and
532    /// trying to use them will inevitably cause a [`prometheus::Error`] being
533    /// emitted.
534    ///
535    /// # Example
536    ///
537    /// ```rust
538    /// let custom = prometheus::Registry::new_custom(Some("my".into()), None)?;
539    ///
540    /// metrics_prometheus::Recorder::builder()
541    ///     .with_registry(&custom)
542    ///     .build_and_install();
543    ///
544    /// metrics::counter!("count").increment(1);
545    ///
546    /// let report =
547    ///     prometheus::TextEncoder::new().encode_to_string(&custom.gather())?;
548    /// assert_eq!(
549    ///     report.trim(),
550    ///     r#"
551    /// ## HELP my_count count
552    /// ## TYPE my_count counter
553    /// my_count 1
554    ///     "#
555    ///     .trim(),
556    /// );
557    /// # Ok::<_, prometheus::Error>(())
558    /// ```
559    #[expect( // anonymous lifetimes in `impl Trait` are unstable
560        single_use_lifetimes,
561        reason = "anonymous lifetimes in `impl Trait` are unstable"
562    )]
563    pub fn with_registry<'r>(
564        mut self,
565        registry: impl IntoCow<'r, prometheus::Registry>,
566    ) -> Self {
567        self.storage.prometheus = registry.into_cow().into_owned();
568        self
569    }
570
571    /// Sets the provided [`failure::Strategy`] to be used by the built
572    /// [`Recorder`].
573    ///
574    /// [`prometheus::Registry`] has far more stricter semantics than the ones
575    /// implied by a [`metrics::Recorder`]. That's why incorrect usage of
576    /// [`prometheus`] metrics via [`metrics`] crate will inevitably lead to a
577    /// [`prometheus::Registry`] returning a [`prometheus::Error`] instead of a
578    /// registering the metric. The returned [`prometheus::Error`] can be either
579    /// turned into a panic, or just silently ignored, making the [`Recorder`]
580    /// to return a no-op metric instead (see [`metrics::Counter::noop()`] for
581    /// example).
582    ///
583    /// The default [`failure::Strategy`] is [`PanicInDebugNoOpInRelease`]. See
584    /// [`failure::strategy`] module for other available [`failure::Strategy`]s,
585    /// or provide your own one by implementing the [`failure::Strategy`] trait.
586    ///
587    /// # Example
588    ///
589    /// ```rust
590    /// use metrics_prometheus::failure::strategy;
591    ///
592    /// metrics_prometheus::Recorder::builder()
593    ///     .with_failure_strategy(strategy::NoOp)
594    ///     .build_and_install();
595    ///
596    /// metrics::counter!("invalid.name").increment(1);
597    ///
598    /// let stats = prometheus::default_registry().gather();
599    /// assert_eq!(stats.len(), 0);
600    /// ```
601    pub fn with_failure_strategy<F>(self, strategy: F) -> Builder<F, L>
602    where
603        F: failure::Strategy,
604    {
605        Builder {
606            storage: self.storage,
607            failure_strategy: strategy,
608            layers: self.layers,
609        }
610    }
611
612    /// Tries to register the provided [`prometheus`] `metric` in the underlying
613    /// [`prometheus::Registry`] in the way making it usable via the created
614    /// [`Recorder`] (and, so, [`metrics`] crate interfaces).
615    ///
616    /// Accepts only the following [`prometheus`] metrics:
617    /// - [`prometheus::IntCounter`], [`prometheus::IntCounterVec`]
618    /// - [`prometheus::Gauge`], [`prometheus::GaugeVec`]
619    /// - [`prometheus::Histogram`], [`prometheus::HistogramVec`]
620    ///
621    /// # Errors
622    ///
623    /// If the underlying [`prometheus::Registry`] fails to register the
624    /// provided `metric`.
625    ///
626    /// # Example
627    ///
628    /// ```rust
629    /// let gauge = prometheus::Gauge::new("value", "help")?;
630    ///
631    /// metrics_prometheus::Recorder::builder()
632    ///     .try_with_metric(gauge.clone())?
633    ///     .build_and_install();
634    ///
635    /// gauge.inc();
636    ///
637    /// let report = prometheus::TextEncoder::new()
638    ///     .encode_to_string(&prometheus::default_registry().gather())?;
639    /// assert_eq!(
640    ///     report.trim(),
641    ///     r#"
642    /// ## HELP value help
643    /// ## TYPE value gauge
644    /// value 1
645    ///     "#
646    ///     .trim(),
647    /// );
648    ///
649    /// metrics::gauge!("value").increment(1.0);
650    ///
651    /// let report = prometheus::TextEncoder::new()
652    ///     .encode_to_string(&prometheus::default_registry().gather())?;
653    /// assert_eq!(
654    ///     report.trim(),
655    ///     r#"
656    /// ## HELP value help
657    /// ## TYPE value gauge
658    /// value 2
659    ///     "#
660    ///     .trim(),
661    /// );
662    /// # Ok::<_, prometheus::Error>(())
663    /// ```
664    pub fn try_with_metric<M>(self, metric: M) -> prometheus::Result<Self>
665    where
666        M: metric::Bundled + prometheus::core::Collector,
667        <M as metric::Bundled>::Bundle:
668            prometheus::core::Collector + Clone + 'static,
669        storage::Mutable: storage::Get<
670                storage::mutable::Collection<<M as metric::Bundled>::Bundle>,
671            >,
672    {
673        self.storage.register_external(metric)?;
674        Ok(self)
675    }
676
677    /// Registers the provided [`prometheus`] `metric` in the underlying
678    /// [`prometheus::Registry`] in the way making it usable via the created
679    /// [`Recorder`] (and, so, [`metrics`] crate interfaces).
680    ///
681    /// Accepts only the following [`prometheus`] metrics:
682    /// - [`prometheus::IntCounter`], [`prometheus::IntCounterVec`]
683    /// - [`prometheus::Gauge`], [`prometheus::GaugeVec`]
684    /// - [`prometheus::Histogram`], [`prometheus::HistogramVec`]
685    ///
686    /// # Panics
687    ///
688    /// If the underlying [`prometheus::Registry`] fails to register the
689    /// provided `metric`.
690    ///
691    /// # Example
692    ///
693    /// ```rust
694    /// let counter = prometheus::IntCounter::new("value", "help")?;
695    ///
696    /// metrics_prometheus::Recorder::builder()
697    ///     .with_metric(counter.clone())
698    ///     .build_and_install();
699    ///
700    /// counter.inc();
701    ///
702    /// let report = prometheus::TextEncoder::new()
703    ///     .encode_to_string(&prometheus::default_registry().gather())?;
704    /// assert_eq!(
705    ///     report.trim(),
706    ///     r#"
707    /// ## HELP value help
708    /// ## TYPE value counter
709    /// value 1
710    ///     "#
711    ///     .trim(),
712    /// );
713    ///
714    /// metrics::counter!("value").increment(1);
715    ///
716    /// let report = prometheus::TextEncoder::new()
717    ///     .encode_to_string(&prometheus::default_registry().gather())?;
718    /// assert_eq!(
719    ///     report.trim(),
720    ///     r#"
721    /// ## HELP value help
722    /// ## TYPE value counter
723    /// value 2
724    ///     "#
725    ///     .trim(),
726    /// );
727    /// # Ok::<_, prometheus::Error>(())
728    /// ```
729    pub fn with_metric<M>(self, metric: M) -> Self
730    where
731        M: metric::Bundled + prometheus::core::Collector,
732        <M as metric::Bundled>::Bundle:
733            prometheus::core::Collector + Clone + 'static,
734        storage::Mutable: storage::Get<
735                storage::mutable::Collection<<M as metric::Bundled>::Bundle>,
736            >,
737    {
738        self.try_with_metric(metric).unwrap_or_else(|e| {
739            panic!("failed to register `prometheus` metric: {e}")
740        })
741    }
742
743    /// Builds a [`Recorder`] out of this [`Builder`] and returns it being
744    /// wrapped into all the provided [`metrics::Layer`]s.
745    ///
746    /// # Usage
747    ///
748    /// Use this method if you want to:
749    /// - either install the built [`Recorder`] with the
750    ///   [`metrics::set_global_recorder()`] manually;
751    /// - or to compose the built [`Recorder`] with some other
752    ///   [`metrics::Recorder`]s (like being able to write into multiple
753    ///   [`prometheus::Registry`]s via [`metrics::layer::Fanout`], for
754    ///   example).
755    ///
756    /// Otherwise, consider using the [`build_and_install()`] method instead.
757    ///
758    /// [`build_and_install()`]: Builder::build_and_install
759    /// [`metrics::layer::Fanout`]: metrics_util::layers::Fanout
760    /// [`metrics::Layer`]: Layer
761    pub fn build(self) -> <L as Layer<Recorder<S>>>::Output
762    where
763        S: failure::Strategy,
764        L: Layer<Recorder<S>>,
765    {
766        let Self { storage, failure_strategy, layers } = self;
767        let rec = Recorder {
768            metrics: Arc::new(metrics_util::registry::Registry::new(
769                storage.clone(),
770            )),
771            storage,
772            failure_strategy,
773        };
774        layers.layer(rec)
775    }
776
777    /// Builds a [`FreezableRecorder`] out of this [`Builder`] and returns it
778    /// being wrapped into all the provided [`metrics::Layer`]s.
779    ///
780    /// # Usage
781    ///
782    /// Use this method if you want to:
783    /// - either install the built [`FreezableRecorder`] with the
784    ///   [`metrics::set_global_recorder()`] manually;
785    /// - or to compose the built [`FreezableRecorder`] with some other
786    ///   [`metrics::Recorder`]s (like being able to write into multiple
787    ///   [`prometheus::Registry`]s via [`metrics::layer::Fanout`], for
788    ///   example).
789    ///
790    /// Otherwise, consider using the [`build_freezable_and_install()`] method
791    /// instead.
792    ///
793    /// [`build_freezable_and_install()`]: Builder::build_freezable_and_install
794    /// [`metrics::layer::Fanout`]: metrics_util::layers::Fanout
795    /// [`metrics::Layer`]: Layer
796    /// [`FreezableRecorder`]: Freezable
797    pub fn build_freezable(self) -> <L as Layer<freezable::Recorder<S>>>::Output
798    where
799        S: failure::Strategy,
800        L: Layer<freezable::Recorder<S>>,
801    {
802        let Self { storage, failure_strategy, layers } = self;
803        let rec = freezable::Recorder::wrap(Recorder {
804            metrics: Arc::new(metrics_util::registry::Registry::new(
805                storage.clone(),
806            )),
807            storage,
808            failure_strategy,
809        });
810        layers.layer(rec)
811    }
812
813    /// Builds a [`FrozenRecorder`] out of this [`Builder`] and returns it being
814    /// wrapped into all the provided [`metrics::Layer`]s.
815    ///
816    /// # Usage
817    ///
818    /// Use this method if you want to:
819    /// - either install the built [`FrozenRecorder`] with the
820    ///   [`metrics::set_global_recorder()`] manually;
821    /// - or to compose the built [`FrozenRecorder`] with some other
822    ///   [`metrics::Recorder`]s (like being able to write into multiple
823    ///   [`prometheus::Registry`]s via [`metrics::layer::Fanout`], for
824    ///   example).
825    ///
826    /// Otherwise, consider using the [`build_frozen_and_install()`] method
827    /// instead.
828    ///
829    /// [`build_frozen_and_install()`]: Builder::build_frozen_and_install
830    /// [`metrics::layer::Fanout`]: metrics_util::layers::Fanout
831    /// [`metrics::Layer`]: Layer
832    /// [`FrozenRecorder`]: Frozen
833    pub fn build_frozen(self) -> <L as Layer<frozen::Recorder<S>>>::Output
834    where
835        S: failure::Strategy,
836        L: Layer<frozen::Recorder<S>>,
837    {
838        let Self { storage, failure_strategy, layers } = self;
839        let rec =
840            frozen::Recorder { storage: (&storage).into(), failure_strategy };
841        layers.layer(rec)
842    }
843
844    /// Builds a [`Recorder`] out of this [`Builder`] and tries to install it
845    /// with the [`metrics::set_global_recorder()`].
846    ///
847    /// # Errors
848    ///
849    /// If the built [`Recorder`] fails to be installed with the
850    /// [`metrics::set_global_recorder()`].
851    ///
852    /// # Example
853    ///
854    /// ```rust
855    /// use metrics_prometheus::{failure::strategy, recorder};
856    /// use metrics_util::layers::FilterLayer;
857    ///
858    /// let custom = prometheus::Registry::new_custom(Some("my".into()), None)?;
859    ///
860    /// let res = metrics_prometheus::Recorder::builder()
861    ///     .with_registry(&custom)
862    ///     .with_metric(prometheus::IntCounter::new("count", "help")?)
863    ///     .with_metric(prometheus::Gauge::new("value", "help")?)
864    ///     .with_failure_strategy(strategy::Panic)
865    ///     .with_layer(FilterLayer::from_patterns(["ignored"]))
866    ///     .try_build_and_install();
867    /// assert!(res.is_ok(), "cannot install `Recorder`: {}", res.unwrap_err());
868    ///
869    /// metrics::counter!("count").increment(1);
870    /// metrics::gauge!("value").increment(3.0);
871    /// metrics::histogram!("histo").record(38.0);
872    /// metrics::histogram!("ignored_histo").record(1.0);
873    ///
874    /// let report =
875    ///     prometheus::TextEncoder::new().encode_to_string(&custom.gather())?;
876    /// assert_eq!(
877    ///     report.trim(),
878    ///     r#"
879    /// ## HELP my_count help
880    /// ## TYPE my_count counter
881    /// my_count 1
882    /// ## HELP my_histo histo
883    /// ## TYPE my_histo histogram
884    /// my_histo_bucket{le="0.005"} 0
885    /// my_histo_bucket{le="0.01"} 0
886    /// my_histo_bucket{le="0.025"} 0
887    /// my_histo_bucket{le="0.05"} 0
888    /// my_histo_bucket{le="0.1"} 0
889    /// my_histo_bucket{le="0.25"} 0
890    /// my_histo_bucket{le="0.5"} 0
891    /// my_histo_bucket{le="1"} 0
892    /// my_histo_bucket{le="2.5"} 0
893    /// my_histo_bucket{le="5"} 0
894    /// my_histo_bucket{le="10"} 0
895    /// my_histo_bucket{le="+Inf"} 1
896    /// my_histo_sum 38
897    /// my_histo_count 1
898    /// ## HELP my_value help
899    /// ## TYPE my_value gauge
900    /// my_value 3
901    ///     "#
902    ///     .trim(),
903    /// );
904    /// # Ok::<_, prometheus::Error>(())
905    /// ```
906    pub fn try_build_and_install(
907        self,
908    ) -> Result<Recorder<S>, metrics::SetRecorderError<L::Output>>
909    where
910        S: failure::Strategy + Clone,
911        L: Layer<Recorder<S>>,
912        <L as Layer<Recorder<S>>>::Output: metrics::Recorder + Sync + 'static,
913    {
914        let Self { storage, failure_strategy, layers } = self;
915        let rec = Recorder {
916            metrics: Arc::new(metrics_util::registry::Registry::new(
917                storage.clone(),
918            )),
919            storage,
920            failure_strategy,
921        };
922        metrics::set_global_recorder(layers.layer(rec.clone()))?;
923        Ok(rec)
924    }
925
926    /// Builds a [`FreezableRecorder`] out of this [`Builder`] and tries to
927    /// install it with the [`metrics::set_global_recorder()`].
928    ///
929    /// # Errors
930    ///
931    /// If the built [`FreezableRecorder`] fails to be installed with the
932    /// [`metrics::set_global_recorder()`].
933    ///
934    /// # Example
935    ///
936    /// ```rust
937    /// use metrics_prometheus::{failure::strategy, recorder};
938    /// use metrics_util::layers::FilterLayer;
939    ///
940    /// let custom = prometheus::Registry::new_custom(Some("my".into()), None)?;
941    ///
942    /// let res = metrics_prometheus::Recorder::builder()
943    ///     .with_registry(&custom)
944    ///     .with_metric(prometheus::IntCounter::new("count", "help")?)
945    ///     .with_failure_strategy(strategy::Panic)
946    ///     .with_layer(FilterLayer::from_patterns(["ignored"]))
947    ///     .try_build_freezable_and_install();
948    /// assert!(
949    ///     res.is_ok(),
950    ///     "cannot install `FreezableRecorder`: {}",
951    ///     res.unwrap_err(),
952    /// );
953    ///
954    /// metrics::gauge!("value").increment(3.0);
955    /// metrics::gauge!("ignored_value").increment(1.0);
956    ///
957    /// res.unwrap().freeze();
958    ///
959    /// metrics::counter!("count").increment(1);
960    /// metrics::gauge!("value").increment(4.0);
961    ///
962    /// let report =
963    ///     prometheus::TextEncoder::new().encode_to_string(&custom.gather())?;
964    /// assert_eq!(
965    ///     report.trim(),
966    ///     r#"
967    /// ## HELP my_count help
968    /// ## TYPE my_count counter
969    /// my_count 1
970    /// ## HELP my_value value
971    /// ## TYPE my_value gauge
972    /// my_value 7
973    ///     "#
974    ///     .trim(),
975    /// );
976    /// # Ok::<_, prometheus::Error>(())
977    /// ```
978    ///
979    /// [`FreezableRecorder`]: Freezable
980    pub fn try_build_freezable_and_install(
981        self,
982    ) -> Result<freezable::Recorder<S>, metrics::SetRecorderError<L::Output>>
983    where
984        S: failure::Strategy + Clone,
985        L: Layer<freezable::Recorder<S>>,
986        <L as Layer<freezable::Recorder<S>>>::Output:
987            metrics::Recorder + Sync + 'static,
988    {
989        let Self { storage, failure_strategy, layers } = self;
990        let rec = freezable::Recorder::wrap(Recorder {
991            metrics: Arc::new(metrics_util::registry::Registry::new(
992                storage.clone(),
993            )),
994            storage,
995            failure_strategy,
996        });
997        metrics::set_global_recorder(layers.layer(rec.clone()))?;
998        Ok(rec)
999    }
1000
1001    /// Builds a [`FrozenRecorder`] out of this [`Builder`] and tries to install
1002    /// it with the [`metrics::set_global_recorder()`].
1003    ///
1004    /// Returns the [`prometheus::Registry`] backing the installed
1005    /// [`FrozenRecorder`], as there is nothing you can configure with the
1006    /// installed [`FrozenRecorder`] itself.
1007    ///
1008    /// # Errors
1009    ///
1010    /// If the built [`FrozenRecorder`] fails to be installed with the
1011    /// [`metrics::set_global_recorder()`].
1012    ///
1013    /// # Example
1014    ///
1015    /// ```rust
1016    /// use metrics_prometheus::{failure::strategy, recorder};
1017    /// use metrics_util::layers::FilterLayer;
1018    ///
1019    /// let custom = prometheus::Registry::new_custom(Some("my".into()), None)?;
1020    ///
1021    /// let res = metrics_prometheus::Recorder::builder()
1022    ///     .with_registry(&custom)
1023    ///     .with_metric(prometheus::IntCounter::new("count", "help")?)
1024    ///     .with_metric(prometheus::Gauge::new("value", "help")?)
1025    ///     .with_metric(prometheus::Gauge::new("ignored_value", "help")?)
1026    ///     .with_failure_strategy(strategy::Panic)
1027    ///     .with_layer(FilterLayer::from_patterns(["ignored"]))
1028    ///     .try_build_frozen_and_install();
1029    /// assert!(
1030    ///     res.is_ok(),
1031    ///     "cannot install `FrozenRecorder`: {}",
1032    ///     res.unwrap_err(),
1033    /// );
1034    ///
1035    /// metrics::counter!("count").increment(1);
1036    /// metrics::gauge!("value").increment(3.0);
1037    /// metrics::gauge!("ignored_value").increment(1.0);
1038    ///
1039    /// let report =
1040    ///     prometheus::TextEncoder::new().encode_to_string(&custom.gather())?;
1041    /// assert_eq!(
1042    ///     report.trim(),
1043    ///     r#"
1044    /// ## HELP my_count help
1045    /// ## TYPE my_count counter
1046    /// my_count 1
1047    /// ## HELP my_ignored_value help
1048    /// ## TYPE my_ignored_value gauge
1049    /// my_ignored_value 0
1050    /// ## HELP my_value help
1051    /// ## TYPE my_value gauge
1052    /// my_value 3
1053    ///     "#
1054    ///     .trim(),
1055    /// );
1056    /// # Ok::<_, prometheus::Error>(())
1057    /// ```
1058    ///
1059    /// [`FrozenRecorder`]: Frozen
1060    pub fn try_build_frozen_and_install(
1061        self,
1062    ) -> Result<prometheus::Registry, metrics::SetRecorderError<L::Output>>
1063    where
1064        S: failure::Strategy + Clone,
1065        L: Layer<frozen::Recorder<S>>,
1066        <L as Layer<frozen::Recorder<S>>>::Output:
1067            metrics::Recorder + Sync + 'static,
1068    {
1069        let Self { storage, failure_strategy, layers } = self;
1070        let rec =
1071            frozen::Recorder { storage: (&storage).into(), failure_strategy };
1072        metrics::set_global_recorder(layers.layer(rec))?;
1073        Ok(storage.prometheus)
1074    }
1075
1076    /// Builds a [`Recorder`] out of this [`Builder`] and installs it with the
1077    /// [`metrics::set_global_recorder()`].
1078    ///
1079    /// # Panics
1080    ///
1081    /// If the built [`Recorder`] fails to be installed with the
1082    /// [`metrics::set_global_recorder()`].
1083    ///
1084    /// # Example
1085    ///
1086    /// ```rust
1087    /// use metrics_prometheus::{failure::strategy, recorder};
1088    /// use metrics_util::layers::FilterLayer;
1089    ///
1090    /// let custom = prometheus::Registry::new_custom(Some("my".into()), None)?;
1091    ///
1092    /// let recorder = metrics_prometheus::Recorder::builder()
1093    ///     .with_registry(custom)
1094    ///     .with_metric(prometheus::IntCounter::new("count", "help")?)
1095    ///     .with_metric(prometheus::Gauge::new("value", "help")?)
1096    ///     .with_failure_strategy(strategy::Panic)
1097    ///     .with_layer(FilterLayer::from_patterns(["ignored"]))
1098    ///     .build_and_install();
1099    ///
1100    /// metrics::counter!("count").increment(1);
1101    /// metrics::gauge!("value").increment(3.0);
1102    /// metrics::histogram!("histo").record(38.0);
1103    /// metrics::histogram!("ignored_histo").record(1.0);
1104    ///
1105    /// let report = prometheus::TextEncoder::new()
1106    ///     .encode_to_string(&recorder.registry().gather())?;
1107    /// assert_eq!(
1108    ///     report.trim(),
1109    ///     r#"
1110    /// ## HELP my_count help
1111    /// ## TYPE my_count counter
1112    /// my_count 1
1113    /// ## HELP my_histo histo
1114    /// ## TYPE my_histo histogram
1115    /// my_histo_bucket{le="0.005"} 0
1116    /// my_histo_bucket{le="0.01"} 0
1117    /// my_histo_bucket{le="0.025"} 0
1118    /// my_histo_bucket{le="0.05"} 0
1119    /// my_histo_bucket{le="0.1"} 0
1120    /// my_histo_bucket{le="0.25"} 0
1121    /// my_histo_bucket{le="0.5"} 0
1122    /// my_histo_bucket{le="1"} 0
1123    /// my_histo_bucket{le="2.5"} 0
1124    /// my_histo_bucket{le="5"} 0
1125    /// my_histo_bucket{le="10"} 0
1126    /// my_histo_bucket{le="+Inf"} 1
1127    /// my_histo_sum 38
1128    /// my_histo_count 1
1129    /// ## HELP my_value help
1130    /// ## TYPE my_value gauge
1131    /// my_value 3
1132    ///     "#
1133    ///     .trim(),
1134    /// );
1135    /// # Ok::<_, prometheus::Error>(())
1136    /// ```
1137    pub fn build_and_install(self) -> Recorder<S>
1138    where
1139        S: failure::Strategy + Clone,
1140        L: Layer<Recorder<S>>,
1141        <L as Layer<Recorder<S>>>::Output: metrics::Recorder + Sync + 'static,
1142    {
1143        self.try_build_and_install().unwrap_or_else(|e| {
1144            panic!(
1145                "failed to install `metrics_prometheus::Recorder` with \
1146                 `metrics::set_global_recorder()`: {e}",
1147            )
1148        })
1149    }
1150
1151    /// Builds a [`FreezableRecorder`] out of this [`Builder`] and installs it
1152    /// with the [`metrics::set_global_recorder()`].
1153    ///
1154    /// # Panics
1155    ///
1156    /// If the built [`FreezableRecorder`] fails to be installed with the
1157    /// [`metrics::set_global_recorder()`].
1158    ///
1159    /// # Example
1160    ///
1161    /// ```rust
1162    /// use metrics_prometheus::{failure::strategy, recorder};
1163    /// use metrics_util::layers::FilterLayer;
1164    ///
1165    /// let custom = prometheus::Registry::new_custom(Some("my".into()), None)?;
1166    ///
1167    /// let recorder = metrics_prometheus::Recorder::builder()
1168    ///     .with_registry(&custom)
1169    ///     .with_metric(prometheus::IntCounter::new("count", "help")?)
1170    ///     .with_failure_strategy(strategy::Panic)
1171    ///     .with_layer(FilterLayer::from_patterns(["ignored"]))
1172    ///     .build_freezable_and_install();
1173    ///
1174    /// metrics::gauge!("value").increment(3.0);
1175    /// metrics::gauge!("ignored_value").increment(1.0);
1176    ///
1177    /// recorder.freeze();
1178    ///
1179    /// metrics::counter!("count").increment(1);
1180    /// metrics::gauge!("value").increment(4.0);
1181    ///
1182    /// let report =
1183    ///     prometheus::TextEncoder::new().encode_to_string(&custom.gather())?;
1184    /// assert_eq!(
1185    ///     report.trim(),
1186    ///     r#"
1187    /// ## HELP my_count help
1188    /// ## TYPE my_count counter
1189    /// my_count 1
1190    /// ## HELP my_value value
1191    /// ## TYPE my_value gauge
1192    /// my_value 7
1193    ///     "#
1194    ///     .trim(),
1195    /// );
1196    /// # Ok::<_, prometheus::Error>(())
1197    /// ```
1198    ///
1199    /// [`FreezableRecorder`]: Freezable
1200    pub fn build_freezable_and_install(self) -> freezable::Recorder<S>
1201    where
1202        S: failure::Strategy + Clone,
1203        L: Layer<freezable::Recorder<S>>,
1204        <L as Layer<freezable::Recorder<S>>>::Output:
1205            metrics::Recorder + Sync + 'static,
1206    {
1207        self.try_build_freezable_and_install().unwrap_or_else(|e| {
1208            panic!(
1209                "failed to install `metrics_prometheus::FreezableRecorder` \
1210                 with `metrics::set_global_recorder()`: {e}",
1211            )
1212        })
1213    }
1214
1215    /// Builds a [`FrozenRecorder`] out of this [`Builder`] and installs it with
1216    /// the [`metrics::set_global_recorder()`].
1217    ///
1218    /// Returns the [`prometheus::Registry`] backing the installed
1219    /// [`FrozenRecorder`], as there is nothing you can configure with the
1220    /// installed [`FrozenRecorder`] itself.
1221    ///
1222    /// # Panics
1223    ///
1224    /// If the built [`FrozenRecorder`] fails to be installed with the
1225    /// [`metrics::set_global_recorder()`].
1226    ///
1227    /// # Example
1228    ///
1229    /// ```rust
1230    /// use metrics_prometheus::{failure::strategy, recorder};
1231    /// use metrics_util::layers::FilterLayer;
1232    ///
1233    /// let custom = prometheus::Registry::new_custom(Some("my".into()), None)?;
1234    ///
1235    /// metrics_prometheus::Recorder::builder()
1236    ///     .with_registry(&custom)
1237    ///     .with_metric(prometheus::IntCounter::new("count", "help")?)
1238    ///     .with_metric(prometheus::Gauge::new("value", "help")?)
1239    ///     .with_metric(prometheus::Gauge::new("ignored_value", "help")?)
1240    ///     .with_failure_strategy(strategy::Panic)
1241    ///     .with_layer(FilterLayer::from_patterns(["ignored"]))
1242    ///     .build_frozen_and_install();
1243    ///
1244    /// metrics::counter!("count").increment(1);
1245    /// metrics::gauge!("value").increment(3.0);
1246    /// metrics::gauge!("ignored_value").increment(1.0);
1247    ///
1248    /// let report =
1249    ///     prometheus::TextEncoder::new().encode_to_string(&custom.gather())?;
1250    /// assert_eq!(
1251    ///     report.trim(),
1252    ///     r#"
1253    /// ## HELP my_count help
1254    /// ## TYPE my_count counter
1255    /// my_count 1
1256    /// ## HELP my_ignored_value help
1257    /// ## TYPE my_ignored_value gauge
1258    /// my_ignored_value 0
1259    /// ## HELP my_value help
1260    /// ## TYPE my_value gauge
1261    /// my_value 3
1262    ///     "#
1263    ///     .trim(),
1264    /// );
1265    /// # Ok::<_, prometheus::Error>(())
1266    /// ```
1267    ///
1268    /// [`FrozenRecorder`]: Frozen
1269    pub fn build_frozen_and_install(self) -> prometheus::Registry
1270    where
1271        S: failure::Strategy + Clone,
1272        L: Layer<frozen::Recorder<S>>,
1273        <L as Layer<frozen::Recorder<S>>>::Output:
1274            metrics::Recorder + Sync + 'static,
1275    {
1276        self.try_build_frozen_and_install().unwrap_or_else(|e| {
1277            panic!(
1278                "failed to install `metrics_prometheus::FrozenRecorder` with \
1279                 `metrics::set_global_recorder()`: {e}",
1280            )
1281        })
1282    }
1283}
1284
1285impl<S, H, T> Builder<S, layer::Stack<H, T>> {
1286    /// Adds the provided [`metrics::Layer`] to wrap the built [`Recorder`] upon
1287    /// its installation with the [`metrics::set_global_recorder()`].
1288    ///
1289    /// # Example
1290    ///
1291    /// ```rust
1292    /// use metrics_util::layers::FilterLayer;
1293    ///
1294    /// metrics_prometheus::Recorder::builder()
1295    ///     .with_layer(FilterLayer::from_patterns(["ignored"]))
1296    ///     .with_layer(FilterLayer::from_patterns(["skipped"]))
1297    ///     .build_and_install();
1298    ///
1299    /// metrics::counter!("ignored_counter").increment(1);
1300    /// metrics::counter!("reported_counter").increment(1);
1301    /// metrics::counter!("skipped_counter").increment(1);
1302    ///
1303    /// let report = prometheus::TextEncoder::new()
1304    ///     .encode_to_string(&prometheus::default_registry().gather())?;
1305    /// assert_eq!(
1306    ///     report.trim(),
1307    ///     r#"
1308    /// ## HELP reported_counter reported_counter
1309    /// ## TYPE reported_counter counter
1310    /// reported_counter 1
1311    ///     "#
1312    ///     .trim(),
1313    /// );
1314    /// # Ok::<_, prometheus::Error>(())
1315    /// ```
1316    ///
1317    /// [`metrics::Layer`]: Layer
1318    pub fn with_layer<L>(
1319        self,
1320        layer: L,
1321    ) -> Builder<S, layer::Stack<L, layer::Stack<H, T>>>
1322    where
1323        L: Layer<<layer::Stack<H, T> as Layer<Recorder<S>>>::Output>,
1324        layer::Stack<H, T>: Layer<Recorder<S>>,
1325    {
1326        Builder {
1327            storage: self.storage,
1328            failure_strategy: self.failure_strategy,
1329            layers: self.layers.push(layer),
1330        }
1331    }
1332}
1333
1334/// Ad hoc polymorphism for accepting either a reference or an owned function
1335/// argument.
1336pub trait IntoCow<'a, T: ToOwned + ?Sized + 'a> {
1337    /// Wraps this reference (or owned value) into a [`Cow`].
1338    #[must_use]
1339    fn into_cow(self) -> Cow<'a, T>;
1340}
1341
1342impl<'a> IntoCow<'a, Self> for prometheus::Registry {
1343    fn into_cow(self) -> Cow<'a, Self> {
1344        Cow::Owned(self)
1345    }
1346}
1347
1348impl<'a> IntoCow<'a, prometheus::Registry> for &'a prometheus::Registry {
1349    fn into_cow(self) -> Cow<'a, prometheus::Registry> {
1350        Cow::Borrowed(self)
1351    }
1352}