use std::collections::BTreeMap;
use std::{borrow::Cow, time::SystemTime};
use sentry_types::protocol::v7::{
LogAttribute, Metric as ProtocolMetric, MetricType, SpanId, TraceId, Unit,
};
#[cfg(any(doc, feature = "client"))]
use crate::Hub;
pub fn counter<N, V>(name: N, value: V) -> CounterMetric
where
N: Into<Cow<'static, str>>,
V: Into<f64>,
{
MetricInner::new(name, value).into()
}
pub fn gauge<N, V>(name: N, value: V) -> GaugeMetric
where
N: Into<Cow<'static, str>>,
V: Into<f64>,
{
MetricInner::new(name, value).into()
}
pub fn distribution<N, V>(name: N, value: V) -> DistributionMetric
where
N: Into<Cow<'static, str>>,
V: Into<f64>,
{
MetricInner::new(name, value).into()
}
#[must_use = "metrics must be captured via `.capture()` to be sent to Sentry"]
pub struct CounterMetric {
inner: MetricInner,
}
#[must_use = "metrics must be captured via `.capture()` to be sent to Sentry"]
pub struct GaugeMetric {
inner: UnitMetricInner,
}
#[must_use = "metrics must be captured via `.capture()` to be sent to Sentry"]
pub struct DistributionMetric {
inner: UnitMetricInner,
}
pub trait IntoProtocolMetric: sealed::IntoProtocolMetricImpl {}
macro_rules! implement_metric_common_methods {
($struct:ident, $metric_type:expr) => {
impl $struct {
pub fn attribute<K, V>(self, key: K, value: V) -> Self
where
K: Into<Cow<'static, str>>,
V: Into<LogAttribute>,
{
let inner = self.inner.attribute(key, value);
Self { inner }
}
#[inline]
pub fn capture(self) {
with_client_impl! {{
Hub::current().capture_metric(self)
}}
}
}
impl IntoProtocolMetric for $struct {}
impl sealed::IntoProtocolMetricImpl for $struct {
#[expect(private_interfaces)] fn into_protocol_metric(self, trace: MetricTraceInfo) -> ProtocolMetric {
self.inner.into_protocol_metric($metric_type, trace)
}
}
};
}
macro_rules! implement_unit {
($struct:ident) => {
impl $struct {
pub fn unit<U>(self, unit: U) -> Self
where
U: Into<Unit>,
{
let inner = self.inner.unit(unit);
Self { inner }
}
}
};
}
implement_metric_common_methods!(CounterMetric, MetricType::Counter);
implement_metric_common_methods!(GaugeMetric, MetricType::Gauge);
implement_metric_common_methods!(DistributionMetric, MetricType::Distribution);
implement_unit!(GaugeMetric);
implement_unit!(DistributionMetric);
pub(crate) struct MetricTraceInfo {
pub(crate) trace_id: TraceId,
pub(crate) span_id: Option<SpanId>,
}
struct MetricInner {
name: Cow<'static, str>,
value: f64,
attributes: BTreeMap<Cow<'static, str>, LogAttribute>,
}
struct UnitMetricInner {
metric_inner: MetricInner,
unit: Option<Unit>,
}
impl MetricInner {
fn new<N, V>(name: N, value: V) -> Self
where
N: Into<Cow<'static, str>>,
V: Into<f64>,
{
let name = name.into();
let value = value.into();
Self {
name,
value,
attributes: Default::default(),
}
}
fn attribute<K, V>(mut self, key: K, value: V) -> Self
where
K: Into<Cow<'static, str>>,
V: Into<LogAttribute>,
{
self.attributes.insert(key.into(), value.into());
self
}
fn into_protocol_metric(self, r#type: MetricType, trace: MetricTraceInfo) -> ProtocolMetric {
let Self {
name,
value,
attributes,
} = self;
let MetricTraceInfo { trace_id, span_id } = trace;
ProtocolMetric {
r#type,
trace_id,
name,
value,
attributes,
span_id,
timestamp: SystemTime::now(),
unit: None,
}
}
}
impl UnitMetricInner {
fn attribute<K, V>(self, key: K, value: V) -> Self
where
K: Into<Cow<'static, str>>,
V: Into<LogAttribute>,
{
Self {
metric_inner: self.metric_inner.attribute(key, value),
..self
}
}
fn unit<U>(self, unit: U) -> Self
where
U: Into<Unit>,
{
let unit = Some(unit.into());
Self { unit, ..self }
}
fn into_protocol_metric(self, r#type: MetricType, trace: MetricTraceInfo) -> ProtocolMetric {
ProtocolMetric {
unit: self.unit,
..self.metric_inner.into_protocol_metric(r#type, trace)
}
}
}
impl From<MetricInner> for CounterMetric {
#[inline]
fn from(inner: MetricInner) -> Self {
Self { inner }
}
}
impl From<MetricInner> for GaugeMetric {
#[inline]
fn from(inner: MetricInner) -> Self {
let inner = inner.into();
Self { inner }
}
}
impl From<MetricInner> for DistributionMetric {
#[inline]
fn from(inner: MetricInner) -> Self {
let inner = inner.into();
Self { inner }
}
}
impl From<MetricInner> for UnitMetricInner {
#[inline]
fn from(metric_inner: MetricInner) -> Self {
Self {
metric_inner,
unit: None,
}
}
}
mod sealed {
use sentry_types::protocol::v7::Metric as ProtocolMetric;
use crate::metrics::MetricTraceInfo;
#[cfg(doc)]
use super::IntoProtocolMetric;
pub trait IntoProtocolMetricImpl {
#[expect(private_interfaces)] fn into_protocol_metric(self, trace: MetricTraceInfo) -> ProtocolMetric;
}
}