Skip to main content

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    pub fn with_registry<'r>(
560        mut self,
561        registry: impl IntoCow<'r, prometheus::Registry>,
562    ) -> Self {
563        self.storage.prometheus = registry.into_cow().into_owned();
564        self
565    }
566
567    /// Sets the provided [`failure::Strategy`] to be used by the built
568    /// [`Recorder`].
569    ///
570    /// [`prometheus::Registry`] has far more stricter semantics than the ones
571    /// implied by a [`metrics::Recorder`]. That's why incorrect usage of
572    /// [`prometheus`] metrics via [`metrics`] crate will inevitably lead to a
573    /// [`prometheus::Registry`] returning a [`prometheus::Error`] instead of a
574    /// registering the metric. The returned [`prometheus::Error`] can be either
575    /// turned into a panic, or just silently ignored, making the [`Recorder`]
576    /// to return a no-op metric instead (see [`metrics::Counter::noop()`] for
577    /// example).
578    ///
579    /// The default [`failure::Strategy`] is [`PanicInDebugNoOpInRelease`]. See
580    /// [`failure::strategy`] module for other available [`failure::Strategy`]s,
581    /// or provide your own one by implementing the [`failure::Strategy`] trait.
582    ///
583    /// # Example
584    ///
585    /// ```rust
586    /// use metrics_prometheus::failure::strategy;
587    ///
588    /// metrics_prometheus::Recorder::builder()
589    ///     .with_failure_strategy(strategy::NoOp)
590    ///     .build_and_install();
591    ///
592    /// metrics::counter!("invalid.name").increment(1);
593    ///
594    /// let stats = prometheus::default_registry().gather();
595    /// assert_eq!(stats.len(), 0);
596    /// ```
597    pub fn with_failure_strategy<F>(self, strategy: F) -> Builder<F, L>
598    where
599        F: failure::Strategy,
600    {
601        Builder {
602            storage: self.storage,
603            failure_strategy: strategy,
604            layers: self.layers,
605        }
606    }
607
608    /// Tries to register the provided [`prometheus`] `metric` in the underlying
609    /// [`prometheus::Registry`] in the way making it usable via the created
610    /// [`Recorder`] (and, so, [`metrics`] crate interfaces).
611    ///
612    /// Accepts only the following [`prometheus`] metrics:
613    /// - [`prometheus::IntCounter`], [`prometheus::IntCounterVec`]
614    /// - [`prometheus::Gauge`], [`prometheus::GaugeVec`]
615    /// - [`prometheus::Histogram`], [`prometheus::HistogramVec`]
616    ///
617    /// # Errors
618    ///
619    /// If the underlying [`prometheus::Registry`] fails to register the
620    /// provided `metric`.
621    ///
622    /// # Example
623    ///
624    /// ```rust
625    /// let gauge = prometheus::Gauge::new("value", "help")?;
626    ///
627    /// metrics_prometheus::Recorder::builder()
628    ///     .try_with_metric(gauge.clone())?
629    ///     .build_and_install();
630    ///
631    /// gauge.inc();
632    ///
633    /// let report = prometheus::TextEncoder::new()
634    ///     .encode_to_string(&prometheus::default_registry().gather())?;
635    /// assert_eq!(
636    ///     report.trim(),
637    ///     r#"
638    /// ## HELP value help
639    /// ## TYPE value gauge
640    /// value 1
641    ///     "#
642    ///     .trim(),
643    /// );
644    ///
645    /// metrics::gauge!("value").increment(1.0);
646    ///
647    /// let report = prometheus::TextEncoder::new()
648    ///     .encode_to_string(&prometheus::default_registry().gather())?;
649    /// assert_eq!(
650    ///     report.trim(),
651    ///     r#"
652    /// ## HELP value help
653    /// ## TYPE value gauge
654    /// value 2
655    ///     "#
656    ///     .trim(),
657    /// );
658    /// # Ok::<_, prometheus::Error>(())
659    /// ```
660    pub fn try_with_metric<M>(self, metric: M) -> prometheus::Result<Self>
661    where
662        M: metric::Bundled + prometheus::core::Collector,
663        <M as metric::Bundled>::Bundle:
664            prometheus::core::Collector + Clone + 'static,
665        storage::Mutable: storage::Get<
666                storage::mutable::Collection<<M as metric::Bundled>::Bundle>,
667            >,
668    {
669        self.storage.register_external(metric)?;
670        Ok(self)
671    }
672
673    /// Registers the provided [`prometheus`] `metric` in the underlying
674    /// [`prometheus::Registry`] in the way making it usable via the created
675    /// [`Recorder`] (and, so, [`metrics`] crate interfaces).
676    ///
677    /// Accepts only the following [`prometheus`] metrics:
678    /// - [`prometheus::IntCounter`], [`prometheus::IntCounterVec`]
679    /// - [`prometheus::Gauge`], [`prometheus::GaugeVec`]
680    /// - [`prometheus::Histogram`], [`prometheus::HistogramVec`]
681    ///
682    /// # Panics
683    ///
684    /// If the underlying [`prometheus::Registry`] fails to register the
685    /// provided `metric`.
686    ///
687    /// # Example
688    ///
689    /// ```rust
690    /// let counter = prometheus::IntCounter::new("value", "help")?;
691    ///
692    /// metrics_prometheus::Recorder::builder()
693    ///     .with_metric(counter.clone())
694    ///     .build_and_install();
695    ///
696    /// counter.inc();
697    ///
698    /// let report = prometheus::TextEncoder::new()
699    ///     .encode_to_string(&prometheus::default_registry().gather())?;
700    /// assert_eq!(
701    ///     report.trim(),
702    ///     r#"
703    /// ## HELP value help
704    /// ## TYPE value counter
705    /// value 1
706    ///     "#
707    ///     .trim(),
708    /// );
709    ///
710    /// metrics::counter!("value").increment(1);
711    ///
712    /// let report = prometheus::TextEncoder::new()
713    ///     .encode_to_string(&prometheus::default_registry().gather())?;
714    /// assert_eq!(
715    ///     report.trim(),
716    ///     r#"
717    /// ## HELP value help
718    /// ## TYPE value counter
719    /// value 2
720    ///     "#
721    ///     .trim(),
722    /// );
723    /// # Ok::<_, prometheus::Error>(())
724    /// ```
725    pub fn with_metric<M>(self, metric: M) -> Self
726    where
727        M: metric::Bundled + prometheus::core::Collector,
728        <M as metric::Bundled>::Bundle:
729            prometheus::core::Collector + Clone + 'static,
730        storage::Mutable: storage::Get<
731                storage::mutable::Collection<<M as metric::Bundled>::Bundle>,
732            >,
733    {
734        self.try_with_metric(metric).unwrap_or_else(|e| {
735            panic!("failed to register `prometheus` metric: {e}")
736        })
737    }
738
739    /// Builds a [`Recorder`] out of this [`Builder`] and returns it being
740    /// wrapped into all the provided [`metrics::Layer`]s.
741    ///
742    /// # Usage
743    ///
744    /// Use this method if you want to:
745    /// - either install the built [`Recorder`] with the
746    ///   [`metrics::set_global_recorder()`] manually;
747    /// - or to compose the built [`Recorder`] with some other
748    ///   [`metrics::Recorder`]s (like being able to write into multiple
749    ///   [`prometheus::Registry`]s via [`metrics::layer::Fanout`], for
750    ///   example).
751    ///
752    /// Otherwise, consider using the [`build_and_install()`] method instead.
753    ///
754    /// [`build_and_install()`]: Builder::build_and_install
755    /// [`metrics::layer::Fanout`]: metrics_util::layers::Fanout
756    /// [`metrics::Layer`]: Layer
757    pub fn build(self) -> <L as Layer<Recorder<S>>>::Output
758    where
759        S: failure::Strategy,
760        L: Layer<Recorder<S>>,
761    {
762        let Self { storage, failure_strategy, layers } = self;
763        let rec = Recorder {
764            metrics: Arc::new(metrics_util::registry::Registry::new(
765                storage.clone(),
766            )),
767            storage,
768            failure_strategy,
769        };
770        layers.layer(rec)
771    }
772
773    /// Builds a [`FreezableRecorder`] out of this [`Builder`] and returns it
774    /// being wrapped into all the provided [`metrics::Layer`]s.
775    ///
776    /// # Usage
777    ///
778    /// Use this method if you want to:
779    /// - either install the built [`FreezableRecorder`] with the
780    ///   [`metrics::set_global_recorder()`] manually;
781    /// - or to compose the built [`FreezableRecorder`] with some other
782    ///   [`metrics::Recorder`]s (like being able to write into multiple
783    ///   [`prometheus::Registry`]s via [`metrics::layer::Fanout`], for
784    ///   example).
785    ///
786    /// Otherwise, consider using the [`build_freezable_and_install()`] method
787    /// instead.
788    ///
789    /// [`build_freezable_and_install()`]: Builder::build_freezable_and_install
790    /// [`metrics::layer::Fanout`]: metrics_util::layers::Fanout
791    /// [`metrics::Layer`]: Layer
792    /// [`FreezableRecorder`]: Freezable
793    pub fn build_freezable(self) -> <L as Layer<freezable::Recorder<S>>>::Output
794    where
795        S: failure::Strategy,
796        L: Layer<freezable::Recorder<S>>,
797    {
798        let Self { storage, failure_strategy, layers } = self;
799        let rec = freezable::Recorder::wrap(Recorder {
800            metrics: Arc::new(metrics_util::registry::Registry::new(
801                storage.clone(),
802            )),
803            storage,
804            failure_strategy,
805        });
806        layers.layer(rec)
807    }
808
809    /// Builds a [`FrozenRecorder`] out of this [`Builder`] and returns it being
810    /// wrapped into all the provided [`metrics::Layer`]s.
811    ///
812    /// # Usage
813    ///
814    /// Use this method if you want to:
815    /// - either install the built [`FrozenRecorder`] with the
816    ///   [`metrics::set_global_recorder()`] manually;
817    /// - or to compose the built [`FrozenRecorder`] with some other
818    ///   [`metrics::Recorder`]s (like being able to write into multiple
819    ///   [`prometheus::Registry`]s via [`metrics::layer::Fanout`], for
820    ///   example).
821    ///
822    /// Otherwise, consider using the [`build_frozen_and_install()`] method
823    /// instead.
824    ///
825    /// [`build_frozen_and_install()`]: Builder::build_frozen_and_install
826    /// [`metrics::layer::Fanout`]: metrics_util::layers::Fanout
827    /// [`metrics::Layer`]: Layer
828    /// [`FrozenRecorder`]: Frozen
829    pub fn build_frozen(self) -> <L as Layer<frozen::Recorder<S>>>::Output
830    where
831        S: failure::Strategy,
832        L: Layer<frozen::Recorder<S>>,
833    {
834        let Self { storage, failure_strategy, layers } = self;
835        let rec =
836            frozen::Recorder { storage: (&storage).into(), failure_strategy };
837        layers.layer(rec)
838    }
839
840    /// Builds a [`Recorder`] out of this [`Builder`] and tries to install it
841    /// with the [`metrics::set_global_recorder()`].
842    ///
843    /// # Errors
844    ///
845    /// If the built [`Recorder`] fails to be installed with the
846    /// [`metrics::set_global_recorder()`].
847    ///
848    /// # Example
849    ///
850    /// ```rust
851    /// use metrics_prometheus::{failure::strategy, recorder};
852    /// use metrics_util::layers::FilterLayer;
853    ///
854    /// let custom = prometheus::Registry::new_custom(Some("my".into()), None)?;
855    ///
856    /// let res = metrics_prometheus::Recorder::builder()
857    ///     .with_registry(&custom)
858    ///     .with_metric(prometheus::IntCounter::new("count", "help")?)
859    ///     .with_metric(prometheus::Gauge::new("value", "help")?)
860    ///     .with_failure_strategy(strategy::Panic)
861    ///     .with_layer(FilterLayer::from_patterns(["ignored"]))
862    ///     .try_build_and_install();
863    /// assert!(res.is_ok(), "cannot install `Recorder`: {}", res.unwrap_err());
864    ///
865    /// metrics::counter!("count").increment(1);
866    /// metrics::gauge!("value").increment(3.0);
867    /// metrics::histogram!("histo").record(38.0);
868    /// metrics::histogram!("ignored_histo").record(1.0);
869    ///
870    /// let report =
871    ///     prometheus::TextEncoder::new().encode_to_string(&custom.gather())?;
872    /// assert_eq!(
873    ///     report.trim(),
874    ///     r#"
875    /// ## HELP my_count help
876    /// ## TYPE my_count counter
877    /// my_count 1
878    /// ## HELP my_histo histo
879    /// ## TYPE my_histo histogram
880    /// my_histo_bucket{le="0.005"} 0
881    /// my_histo_bucket{le="0.01"} 0
882    /// my_histo_bucket{le="0.025"} 0
883    /// my_histo_bucket{le="0.05"} 0
884    /// my_histo_bucket{le="0.1"} 0
885    /// my_histo_bucket{le="0.25"} 0
886    /// my_histo_bucket{le="0.5"} 0
887    /// my_histo_bucket{le="1"} 0
888    /// my_histo_bucket{le="2.5"} 0
889    /// my_histo_bucket{le="5"} 0
890    /// my_histo_bucket{le="10"} 0
891    /// my_histo_bucket{le="+Inf"} 1
892    /// my_histo_sum 38
893    /// my_histo_count 1
894    /// ## HELP my_value help
895    /// ## TYPE my_value gauge
896    /// my_value 3
897    ///     "#
898    ///     .trim(),
899    /// );
900    /// # Ok::<_, prometheus::Error>(())
901    /// ```
902    pub fn try_build_and_install(
903        self,
904    ) -> Result<Recorder<S>, metrics::SetRecorderError<L::Output>>
905    where
906        S: failure::Strategy + Clone,
907        L: Layer<Recorder<S>>,
908        <L as Layer<Recorder<S>>>::Output: metrics::Recorder + Sync + 'static,
909    {
910        let Self { storage, failure_strategy, layers } = self;
911        let rec = Recorder {
912            metrics: Arc::new(metrics_util::registry::Registry::new(
913                storage.clone(),
914            )),
915            storage,
916            failure_strategy,
917        };
918        metrics::set_global_recorder(layers.layer(rec.clone()))?;
919        Ok(rec)
920    }
921
922    /// Builds a [`FreezableRecorder`] out of this [`Builder`] and tries to
923    /// install it with the [`metrics::set_global_recorder()`].
924    ///
925    /// # Errors
926    ///
927    /// If the built [`FreezableRecorder`] fails to be installed with the
928    /// [`metrics::set_global_recorder()`].
929    ///
930    /// # Example
931    ///
932    /// ```rust
933    /// use metrics_prometheus::{failure::strategy, recorder};
934    /// use metrics_util::layers::FilterLayer;
935    ///
936    /// let custom = prometheus::Registry::new_custom(Some("my".into()), None)?;
937    ///
938    /// let res = metrics_prometheus::Recorder::builder()
939    ///     .with_registry(&custom)
940    ///     .with_metric(prometheus::IntCounter::new("count", "help")?)
941    ///     .with_failure_strategy(strategy::Panic)
942    ///     .with_layer(FilterLayer::from_patterns(["ignored"]))
943    ///     .try_build_freezable_and_install();
944    /// assert!(
945    ///     res.is_ok(),
946    ///     "cannot install `FreezableRecorder`: {}",
947    ///     res.unwrap_err(),
948    /// );
949    ///
950    /// metrics::gauge!("value").increment(3.0);
951    /// metrics::gauge!("ignored_value").increment(1.0);
952    ///
953    /// res.unwrap().freeze();
954    ///
955    /// metrics::counter!("count").increment(1);
956    /// metrics::gauge!("value").increment(4.0);
957    ///
958    /// let report =
959    ///     prometheus::TextEncoder::new().encode_to_string(&custom.gather())?;
960    /// assert_eq!(
961    ///     report.trim(),
962    ///     r#"
963    /// ## HELP my_count help
964    /// ## TYPE my_count counter
965    /// my_count 1
966    /// ## HELP my_value value
967    /// ## TYPE my_value gauge
968    /// my_value 7
969    ///     "#
970    ///     .trim(),
971    /// );
972    /// # Ok::<_, prometheus::Error>(())
973    /// ```
974    ///
975    /// [`FreezableRecorder`]: Freezable
976    pub fn try_build_freezable_and_install(
977        self,
978    ) -> Result<freezable::Recorder<S>, metrics::SetRecorderError<L::Output>>
979    where
980        S: failure::Strategy + Clone,
981        L: Layer<freezable::Recorder<S>>,
982        <L as Layer<freezable::Recorder<S>>>::Output:
983            metrics::Recorder + Sync + 'static,
984    {
985        let Self { storage, failure_strategy, layers } = self;
986        let rec = freezable::Recorder::wrap(Recorder {
987            metrics: Arc::new(metrics_util::registry::Registry::new(
988                storage.clone(),
989            )),
990            storage,
991            failure_strategy,
992        });
993        metrics::set_global_recorder(layers.layer(rec.clone()))?;
994        Ok(rec)
995    }
996
997    /// Builds a [`FrozenRecorder`] out of this [`Builder`] and tries to install
998    /// it with the [`metrics::set_global_recorder()`].
999    ///
1000    /// Returns the [`prometheus::Registry`] backing the installed
1001    /// [`FrozenRecorder`], as there is nothing you can configure with the
1002    /// installed [`FrozenRecorder`] itself.
1003    ///
1004    /// # Errors
1005    ///
1006    /// If the built [`FrozenRecorder`] fails to be installed with the
1007    /// [`metrics::set_global_recorder()`].
1008    ///
1009    /// # Example
1010    ///
1011    /// ```rust
1012    /// use metrics_prometheus::{failure::strategy, recorder};
1013    /// use metrics_util::layers::FilterLayer;
1014    ///
1015    /// let custom = prometheus::Registry::new_custom(Some("my".into()), None)?;
1016    ///
1017    /// let res = metrics_prometheus::Recorder::builder()
1018    ///     .with_registry(&custom)
1019    ///     .with_metric(prometheus::IntCounter::new("count", "help")?)
1020    ///     .with_metric(prometheus::Gauge::new("value", "help")?)
1021    ///     .with_metric(prometheus::Gauge::new("ignored_value", "help")?)
1022    ///     .with_failure_strategy(strategy::Panic)
1023    ///     .with_layer(FilterLayer::from_patterns(["ignored"]))
1024    ///     .try_build_frozen_and_install();
1025    /// assert!(
1026    ///     res.is_ok(),
1027    ///     "cannot install `FrozenRecorder`: {}",
1028    ///     res.unwrap_err(),
1029    /// );
1030    ///
1031    /// metrics::counter!("count").increment(1);
1032    /// metrics::gauge!("value").increment(3.0);
1033    /// metrics::gauge!("ignored_value").increment(1.0);
1034    ///
1035    /// let report =
1036    ///     prometheus::TextEncoder::new().encode_to_string(&custom.gather())?;
1037    /// assert_eq!(
1038    ///     report.trim(),
1039    ///     r#"
1040    /// ## HELP my_count help
1041    /// ## TYPE my_count counter
1042    /// my_count 1
1043    /// ## HELP my_ignored_value help
1044    /// ## TYPE my_ignored_value gauge
1045    /// my_ignored_value 0
1046    /// ## HELP my_value help
1047    /// ## TYPE my_value gauge
1048    /// my_value 3
1049    ///     "#
1050    ///     .trim(),
1051    /// );
1052    /// # Ok::<_, prometheus::Error>(())
1053    /// ```
1054    ///
1055    /// [`FrozenRecorder`]: Frozen
1056    pub fn try_build_frozen_and_install(
1057        self,
1058    ) -> Result<prometheus::Registry, metrics::SetRecorderError<L::Output>>
1059    where
1060        S: failure::Strategy + Clone,
1061        L: Layer<frozen::Recorder<S>>,
1062        <L as Layer<frozen::Recorder<S>>>::Output:
1063            metrics::Recorder + Sync + 'static,
1064    {
1065        let Self { storage, failure_strategy, layers } = self;
1066        let rec =
1067            frozen::Recorder { storage: (&storage).into(), failure_strategy };
1068        metrics::set_global_recorder(layers.layer(rec))?;
1069        Ok(storage.prometheus)
1070    }
1071
1072    /// Builds a [`Recorder`] out of this [`Builder`] and installs it with the
1073    /// [`metrics::set_global_recorder()`].
1074    ///
1075    /// # Panics
1076    ///
1077    /// If the built [`Recorder`] fails to be installed with the
1078    /// [`metrics::set_global_recorder()`].
1079    ///
1080    /// # Example
1081    ///
1082    /// ```rust
1083    /// use metrics_prometheus::{failure::strategy, recorder};
1084    /// use metrics_util::layers::FilterLayer;
1085    ///
1086    /// let custom = prometheus::Registry::new_custom(Some("my".into()), None)?;
1087    ///
1088    /// let recorder = metrics_prometheus::Recorder::builder()
1089    ///     .with_registry(custom)
1090    ///     .with_metric(prometheus::IntCounter::new("count", "help")?)
1091    ///     .with_metric(prometheus::Gauge::new("value", "help")?)
1092    ///     .with_failure_strategy(strategy::Panic)
1093    ///     .with_layer(FilterLayer::from_patterns(["ignored"]))
1094    ///     .build_and_install();
1095    ///
1096    /// metrics::counter!("count").increment(1);
1097    /// metrics::gauge!("value").increment(3.0);
1098    /// metrics::histogram!("histo").record(38.0);
1099    /// metrics::histogram!("ignored_histo").record(1.0);
1100    ///
1101    /// let report = prometheus::TextEncoder::new()
1102    ///     .encode_to_string(&recorder.registry().gather())?;
1103    /// assert_eq!(
1104    ///     report.trim(),
1105    ///     r#"
1106    /// ## HELP my_count help
1107    /// ## TYPE my_count counter
1108    /// my_count 1
1109    /// ## HELP my_histo histo
1110    /// ## TYPE my_histo histogram
1111    /// my_histo_bucket{le="0.005"} 0
1112    /// my_histo_bucket{le="0.01"} 0
1113    /// my_histo_bucket{le="0.025"} 0
1114    /// my_histo_bucket{le="0.05"} 0
1115    /// my_histo_bucket{le="0.1"} 0
1116    /// my_histo_bucket{le="0.25"} 0
1117    /// my_histo_bucket{le="0.5"} 0
1118    /// my_histo_bucket{le="1"} 0
1119    /// my_histo_bucket{le="2.5"} 0
1120    /// my_histo_bucket{le="5"} 0
1121    /// my_histo_bucket{le="10"} 0
1122    /// my_histo_bucket{le="+Inf"} 1
1123    /// my_histo_sum 38
1124    /// my_histo_count 1
1125    /// ## HELP my_value help
1126    /// ## TYPE my_value gauge
1127    /// my_value 3
1128    ///     "#
1129    ///     .trim(),
1130    /// );
1131    /// # Ok::<_, prometheus::Error>(())
1132    /// ```
1133    pub fn build_and_install(self) -> Recorder<S>
1134    where
1135        S: failure::Strategy + Clone,
1136        L: Layer<Recorder<S>>,
1137        <L as Layer<Recorder<S>>>::Output: metrics::Recorder + Sync + 'static,
1138    {
1139        self.try_build_and_install().unwrap_or_else(|e| {
1140            panic!(
1141                "failed to install `metrics_prometheus::Recorder` with \
1142                 `metrics::set_global_recorder()`: {e}",
1143            )
1144        })
1145    }
1146
1147    /// Builds a [`FreezableRecorder`] out of this [`Builder`] and installs it
1148    /// with the [`metrics::set_global_recorder()`].
1149    ///
1150    /// # Panics
1151    ///
1152    /// If the built [`FreezableRecorder`] fails to be installed with the
1153    /// [`metrics::set_global_recorder()`].
1154    ///
1155    /// # Example
1156    ///
1157    /// ```rust
1158    /// use metrics_prometheus::{failure::strategy, recorder};
1159    /// use metrics_util::layers::FilterLayer;
1160    ///
1161    /// let custom = prometheus::Registry::new_custom(Some("my".into()), None)?;
1162    ///
1163    /// let recorder = metrics_prometheus::Recorder::builder()
1164    ///     .with_registry(&custom)
1165    ///     .with_metric(prometheus::IntCounter::new("count", "help")?)
1166    ///     .with_failure_strategy(strategy::Panic)
1167    ///     .with_layer(FilterLayer::from_patterns(["ignored"]))
1168    ///     .build_freezable_and_install();
1169    ///
1170    /// metrics::gauge!("value").increment(3.0);
1171    /// metrics::gauge!("ignored_value").increment(1.0);
1172    ///
1173    /// recorder.freeze();
1174    ///
1175    /// metrics::counter!("count").increment(1);
1176    /// metrics::gauge!("value").increment(4.0);
1177    ///
1178    /// let report =
1179    ///     prometheus::TextEncoder::new().encode_to_string(&custom.gather())?;
1180    /// assert_eq!(
1181    ///     report.trim(),
1182    ///     r#"
1183    /// ## HELP my_count help
1184    /// ## TYPE my_count counter
1185    /// my_count 1
1186    /// ## HELP my_value value
1187    /// ## TYPE my_value gauge
1188    /// my_value 7
1189    ///     "#
1190    ///     .trim(),
1191    /// );
1192    /// # Ok::<_, prometheus::Error>(())
1193    /// ```
1194    ///
1195    /// [`FreezableRecorder`]: Freezable
1196    pub fn build_freezable_and_install(self) -> freezable::Recorder<S>
1197    where
1198        S: failure::Strategy + Clone,
1199        L: Layer<freezable::Recorder<S>>,
1200        <L as Layer<freezable::Recorder<S>>>::Output:
1201            metrics::Recorder + Sync + 'static,
1202    {
1203        self.try_build_freezable_and_install().unwrap_or_else(|e| {
1204            panic!(
1205                "failed to install `metrics_prometheus::FreezableRecorder` \
1206                 with `metrics::set_global_recorder()`: {e}",
1207            )
1208        })
1209    }
1210
1211    /// Builds a [`FrozenRecorder`] out of this [`Builder`] and installs it with
1212    /// the [`metrics::set_global_recorder()`].
1213    ///
1214    /// Returns the [`prometheus::Registry`] backing the installed
1215    /// [`FrozenRecorder`], as there is nothing you can configure with the
1216    /// installed [`FrozenRecorder`] itself.
1217    ///
1218    /// # Panics
1219    ///
1220    /// If the built [`FrozenRecorder`] fails to be installed with the
1221    /// [`metrics::set_global_recorder()`].
1222    ///
1223    /// # Example
1224    ///
1225    /// ```rust
1226    /// use metrics_prometheus::{failure::strategy, recorder};
1227    /// use metrics_util::layers::FilterLayer;
1228    ///
1229    /// let custom = prometheus::Registry::new_custom(Some("my".into()), None)?;
1230    ///
1231    /// metrics_prometheus::Recorder::builder()
1232    ///     .with_registry(&custom)
1233    ///     .with_metric(prometheus::IntCounter::new("count", "help")?)
1234    ///     .with_metric(prometheus::Gauge::new("value", "help")?)
1235    ///     .with_metric(prometheus::Gauge::new("ignored_value", "help")?)
1236    ///     .with_failure_strategy(strategy::Panic)
1237    ///     .with_layer(FilterLayer::from_patterns(["ignored"]))
1238    ///     .build_frozen_and_install();
1239    ///
1240    /// metrics::counter!("count").increment(1);
1241    /// metrics::gauge!("value").increment(3.0);
1242    /// metrics::gauge!("ignored_value").increment(1.0);
1243    ///
1244    /// let report =
1245    ///     prometheus::TextEncoder::new().encode_to_string(&custom.gather())?;
1246    /// assert_eq!(
1247    ///     report.trim(),
1248    ///     r#"
1249    /// ## HELP my_count help
1250    /// ## TYPE my_count counter
1251    /// my_count 1
1252    /// ## HELP my_ignored_value help
1253    /// ## TYPE my_ignored_value gauge
1254    /// my_ignored_value 0
1255    /// ## HELP my_value help
1256    /// ## TYPE my_value gauge
1257    /// my_value 3
1258    ///     "#
1259    ///     .trim(),
1260    /// );
1261    /// # Ok::<_, prometheus::Error>(())
1262    /// ```
1263    ///
1264    /// [`FrozenRecorder`]: Frozen
1265    pub fn build_frozen_and_install(self) -> prometheus::Registry
1266    where
1267        S: failure::Strategy + Clone,
1268        L: Layer<frozen::Recorder<S>>,
1269        <L as Layer<frozen::Recorder<S>>>::Output:
1270            metrics::Recorder + Sync + 'static,
1271    {
1272        self.try_build_frozen_and_install().unwrap_or_else(|e| {
1273            panic!(
1274                "failed to install `metrics_prometheus::FrozenRecorder` with \
1275                 `metrics::set_global_recorder()`: {e}",
1276            )
1277        })
1278    }
1279}
1280
1281impl<S, H, T> Builder<S, layer::Stack<H, T>> {
1282    /// Adds the provided [`metrics::Layer`] to wrap the built [`Recorder`] upon
1283    /// its installation with the [`metrics::set_global_recorder()`].
1284    ///
1285    /// # Example
1286    ///
1287    /// ```rust
1288    /// use metrics_util::layers::FilterLayer;
1289    ///
1290    /// metrics_prometheus::Recorder::builder()
1291    ///     .with_layer(FilterLayer::from_patterns(["ignored"]))
1292    ///     .with_layer(FilterLayer::from_patterns(["skipped"]))
1293    ///     .build_and_install();
1294    ///
1295    /// metrics::counter!("ignored_counter").increment(1);
1296    /// metrics::counter!("reported_counter").increment(1);
1297    /// metrics::counter!("skipped_counter").increment(1);
1298    ///
1299    /// let report = prometheus::TextEncoder::new()
1300    ///     .encode_to_string(&prometheus::default_registry().gather())?;
1301    /// assert_eq!(
1302    ///     report.trim(),
1303    ///     r#"
1304    /// ## HELP reported_counter reported_counter
1305    /// ## TYPE reported_counter counter
1306    /// reported_counter 1
1307    ///     "#
1308    ///     .trim(),
1309    /// );
1310    /// # Ok::<_, prometheus::Error>(())
1311    /// ```
1312    ///
1313    /// [`metrics::Layer`]: Layer
1314    pub fn with_layer<L>(
1315        self,
1316        layer: L,
1317    ) -> Builder<S, layer::Stack<L, layer::Stack<H, T>>>
1318    where
1319        L: Layer<<layer::Stack<H, T> as Layer<Recorder<S>>>::Output>,
1320        layer::Stack<H, T>: Layer<Recorder<S>>,
1321    {
1322        Builder {
1323            storage: self.storage,
1324            failure_strategy: self.failure_strategy,
1325            layers: self.layers.push(layer),
1326        }
1327    }
1328}
1329
1330/// Ad hoc polymorphism for accepting either a reference or an owned function
1331/// argument.
1332pub trait IntoCow<'a, T: ToOwned + ?Sized + 'a> {
1333    /// Wraps this reference (or owned value) into a [`Cow`].
1334    #[must_use]
1335    fn into_cow(self) -> Cow<'a, T>;
1336}
1337
1338impl<'a> IntoCow<'a, Self> for prometheus::Registry {
1339    fn into_cow(self) -> Cow<'a, Self> {
1340        Cow::Owned(self)
1341    }
1342}
1343
1344impl<'a> IntoCow<'a, prometheus::Registry> for &'a prometheus::Registry {
1345    fn into_cow(self) -> Cow<'a, prometheus::Registry> {
1346        Cow::Borrowed(self)
1347    }
1348}