pub mod freezable;
pub mod frozen;
pub mod layer;
use std::{borrow::Cow, fmt, sync::Arc};
pub use metrics_util::layers::Layer;
pub use self::{freezable::Recorder as Freezable, frozen::Recorder as Frozen};
use crate::{
failure::{self, strategy::PanicInDebugNoOpInRelease},
metric, storage,
};
#[derive(Clone)]
pub struct Recorder<FailureStrategy = PanicInDebugNoOpInRelease> {
metrics:
Arc<metrics_util::registry::Registry<metrics::Key, storage::Mutable>>,
storage: storage::Mutable,
failure_strategy: FailureStrategy,
}
impl<S: fmt::Debug> fmt::Debug for Recorder<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Recorder")
.field("storage", &self.storage)
.field("failure_strategy", &self.failure_strategy)
.finish_non_exhaustive()
}
}
impl Recorder {
pub fn builder() -> Builder {
Builder {
storage: storage::Mutable::default(),
failure_strategy: PanicInDebugNoOpInRelease,
layers: layer::Stack::identity(),
}
}
}
impl<S> Recorder<S> {
#[must_use]
pub const fn registry(&self) -> &prometheus::Registry {
&self.storage.prometheus
}
pub fn try_register_metric<M>(&self, metric: M) -> prometheus::Result<()>
where
M: metric::Bundled + prometheus::core::Collector,
<M as metric::Bundled>::Bundle:
prometheus::core::Collector + Clone + 'static,
storage::Mutable: storage::Get<
storage::mutable::Collection<<M as metric::Bundled>::Bundle>,
>,
{
self.storage.register_external(metric)
}
pub fn register_metric<M>(&self, metric: M)
where
M: metric::Bundled + prometheus::core::Collector,
<M as metric::Bundled>::Bundle:
prometheus::core::Collector + Clone + 'static,
storage::Mutable: storage::Get<
storage::mutable::Collection<<M as metric::Bundled>::Bundle>,
>,
{
self.try_register_metric(metric).unwrap_or_else(|e| {
panic!("failed to register `prometheus` metric: {e}")
});
}
}
#[warn(clippy::missing_trait_methods)]
impl<S> metrics::Recorder for Recorder<S>
where
S: failure::Strategy,
{
fn describe_counter(
&self,
key: metrics::KeyName,
_: Option<metrics::Unit>,
description: metrics::SharedString,
) {
self.storage.describe::<prometheus::IntCounter>(
key.as_str(),
description.into_owned(),
);
}
fn describe_gauge(
&self,
key: metrics::KeyName,
_: Option<metrics::Unit>,
description: metrics::SharedString,
) {
self.storage.describe::<prometheus::Gauge>(
key.as_str(),
description.into_owned(),
);
}
fn describe_histogram(
&self,
key: metrics::KeyName,
_: Option<metrics::Unit>,
description: metrics::SharedString,
) {
self.storage.describe::<prometheus::Histogram>(
key.as_str(),
description.into_owned(),
);
}
fn register_counter(
&self,
key: &metrics::Key,
_: &metrics::Metadata<'_>,
) -> metrics::Counter {
self.metrics
.get_or_create_counter(key, |counter| {
counter.as_ref().map(|c| Arc::clone(c).into()).or_else(|e| {
match self.failure_strategy.decide(e) {
failure::Action::NoOp => Ok(metrics::Counter::noop()),
failure::Action::Panic => Err(e.to_string()),
}
})
})
.unwrap_or_else(|e| {
panic!(
"failed to register `prometheus::IntCounter` metric: {e}"
)
})
}
fn register_gauge(
&self,
key: &metrics::Key,
_: &metrics::Metadata<'_>,
) -> metrics::Gauge {
self.metrics
.get_or_create_gauge(key, |gauge| {
gauge.as_ref().map(|c| Arc::clone(c).into()).or_else(|e| {
match self.failure_strategy.decide(e) {
failure::Action::NoOp => Ok(metrics::Gauge::noop()),
failure::Action::Panic => Err(e.to_string()),
}
})
})
.unwrap_or_else(|e| {
panic!("failed to register `prometheus::Gauge` metric: {e}")
})
}
fn register_histogram(
&self,
key: &metrics::Key,
_: &metrics::Metadata<'_>,
) -> metrics::Histogram {
self.metrics
.get_or_create_histogram(key, |histogram| {
histogram.as_ref().map(|c| Arc::clone(c).into()).or_else(|e| {
match self.failure_strategy.decide(e) {
failure::Action::NoOp => Ok(metrics::Histogram::noop()),
failure::Action::Panic => Err(e.to_string()),
}
})
})
.unwrap_or_else(|e| {
panic!("failed to register `prometheus::Histogram` metric: {e}")
})
}
}
#[derive(Debug)]
#[must_use]
pub struct Builder<
FailureStrategy = PanicInDebugNoOpInRelease,
Layers = layer::Stack,
> {
storage: storage::Mutable,
failure_strategy: FailureStrategy,
layers: Layers,
}
impl<S, L> Builder<S, L> {
#[expect( // anonymous lifetimes in `impl Trait` are unstable
single_use_lifetimes,
reason = "anonymous lifetimes in `impl Trait` are unstable"
)]
pub fn with_registry<'r>(
mut self,
registry: impl IntoCow<'r, prometheus::Registry>,
) -> Self {
self.storage.prometheus = registry.into_cow().into_owned();
self
}
pub fn with_failure_strategy<F>(self, strategy: F) -> Builder<F, L>
where
F: failure::Strategy,
{
Builder {
storage: self.storage,
failure_strategy: strategy,
layers: self.layers,
}
}
pub fn try_with_metric<M>(self, metric: M) -> prometheus::Result<Self>
where
M: metric::Bundled + prometheus::core::Collector,
<M as metric::Bundled>::Bundle:
prometheus::core::Collector + Clone + 'static,
storage::Mutable: storage::Get<
storage::mutable::Collection<<M as metric::Bundled>::Bundle>,
>,
{
self.storage.register_external(metric)?;
Ok(self)
}
pub fn with_metric<M>(self, metric: M) -> Self
where
M: metric::Bundled + prometheus::core::Collector,
<M as metric::Bundled>::Bundle:
prometheus::core::Collector + Clone + 'static,
storage::Mutable: storage::Get<
storage::mutable::Collection<<M as metric::Bundled>::Bundle>,
>,
{
self.try_with_metric(metric).unwrap_or_else(|e| {
panic!("failed to register `prometheus` metric: {e}")
})
}
pub fn build(self) -> <L as Layer<Recorder<S>>>::Output
where
S: failure::Strategy,
L: Layer<Recorder<S>>,
{
let Self { storage, failure_strategy, layers } = self;
let rec = Recorder {
metrics: Arc::new(metrics_util::registry::Registry::new(
storage.clone(),
)),
storage,
failure_strategy,
};
layers.layer(rec)
}
pub fn build_freezable(self) -> <L as Layer<freezable::Recorder<S>>>::Output
where
S: failure::Strategy,
L: Layer<freezable::Recorder<S>>,
{
let Self { storage, failure_strategy, layers } = self;
let rec = freezable::Recorder::wrap(Recorder {
metrics: Arc::new(metrics_util::registry::Registry::new(
storage.clone(),
)),
storage,
failure_strategy,
});
layers.layer(rec)
}
pub fn build_frozen(self) -> <L as Layer<frozen::Recorder<S>>>::Output
where
S: failure::Strategy,
L: Layer<frozen::Recorder<S>>,
{
let Self { storage, failure_strategy, layers } = self;
let rec =
frozen::Recorder { storage: (&storage).into(), failure_strategy };
layers.layer(rec)
}
pub fn try_build_and_install(
self,
) -> Result<Recorder<S>, metrics::SetRecorderError<L::Output>>
where
S: failure::Strategy + Clone,
L: Layer<Recorder<S>>,
<L as Layer<Recorder<S>>>::Output: metrics::Recorder + Sync + 'static,
{
let Self { storage, failure_strategy, layers } = self;
let rec = Recorder {
metrics: Arc::new(metrics_util::registry::Registry::new(
storage.clone(),
)),
storage,
failure_strategy,
};
metrics::set_global_recorder(layers.layer(rec.clone()))?;
Ok(rec)
}
pub fn try_build_freezable_and_install(
self,
) -> Result<freezable::Recorder<S>, metrics::SetRecorderError<L::Output>>
where
S: failure::Strategy + Clone,
L: Layer<freezable::Recorder<S>>,
<L as Layer<freezable::Recorder<S>>>::Output:
metrics::Recorder + Sync + 'static,
{
let Self { storage, failure_strategy, layers } = self;
let rec = freezable::Recorder::wrap(Recorder {
metrics: Arc::new(metrics_util::registry::Registry::new(
storage.clone(),
)),
storage,
failure_strategy,
});
metrics::set_global_recorder(layers.layer(rec.clone()))?;
Ok(rec)
}
pub fn try_build_frozen_and_install(
self,
) -> Result<prometheus::Registry, metrics::SetRecorderError<L::Output>>
where
S: failure::Strategy + Clone,
L: Layer<frozen::Recorder<S>>,
<L as Layer<frozen::Recorder<S>>>::Output:
metrics::Recorder + Sync + 'static,
{
let Self { storage, failure_strategy, layers } = self;
let rec =
frozen::Recorder { storage: (&storage).into(), failure_strategy };
metrics::set_global_recorder(layers.layer(rec))?;
Ok(storage.prometheus)
}
pub fn build_and_install(self) -> Recorder<S>
where
S: failure::Strategy + Clone,
L: Layer<Recorder<S>>,
<L as Layer<Recorder<S>>>::Output: metrics::Recorder + Sync + 'static,
{
self.try_build_and_install().unwrap_or_else(|e| {
panic!(
"failed to install `metrics_prometheus::Recorder` with \
`metrics::set_global_recorder()`: {e}",
)
})
}
pub fn build_freezable_and_install(self) -> freezable::Recorder<S>
where
S: failure::Strategy + Clone,
L: Layer<freezable::Recorder<S>>,
<L as Layer<freezable::Recorder<S>>>::Output:
metrics::Recorder + Sync + 'static,
{
self.try_build_freezable_and_install().unwrap_or_else(|e| {
panic!(
"failed to install `metrics_prometheus::FreezableRecorder` \
with `metrics::set_global_recorder()`: {e}",
)
})
}
pub fn build_frozen_and_install(self) -> prometheus::Registry
where
S: failure::Strategy + Clone,
L: Layer<frozen::Recorder<S>>,
<L as Layer<frozen::Recorder<S>>>::Output:
metrics::Recorder + Sync + 'static,
{
self.try_build_frozen_and_install().unwrap_or_else(|e| {
panic!(
"failed to install `metrics_prometheus::FrozenRecorder` with \
`metrics::set_global_recorder()`: {e}",
)
})
}
}
impl<S, H, T> Builder<S, layer::Stack<H, T>> {
pub fn with_layer<L>(
self,
layer: L,
) -> Builder<S, layer::Stack<L, layer::Stack<H, T>>>
where
L: Layer<<layer::Stack<H, T> as Layer<Recorder<S>>>::Output>,
layer::Stack<H, T>: Layer<Recorder<S>>,
{
Builder {
storage: self.storage,
failure_strategy: self.failure_strategy,
layers: self.layers.push(layer),
}
}
}
pub trait IntoCow<'a, T: ToOwned + ?Sized + 'a> {
#[must_use]
fn into_cow(self) -> Cow<'a, T>;
}
impl<'a> IntoCow<'a, Self> for prometheus::Registry {
fn into_cow(self) -> Cow<'a, Self> {
Cow::Owned(self)
}
}
impl<'a> IntoCow<'a, prometheus::Registry> for &'a prometheus::Registry {
fn into_cow(self) -> Cow<'a, prometheus::Registry> {
Cow::Borrowed(self)
}
}