Skip to main content

sentry_core/
metrics.rs

1//! APIs for creating and capturing metrics.
2
3use std::collections::BTreeMap;
4use std::{borrow::Cow, time::SystemTime};
5
6use sentry_types::protocol::v7::{
7    LogAttribute, Metric as ProtocolMetric, MetricType, SpanId, TraceId, Unit,
8};
9
10#[cfg(any(doc, feature = "client"))]
11use crate::Hub;
12
13/// Creates a counter metric, with the given name and value.
14///
15/// You may set attributes via [`CounterMetric::attribute`].
16///
17/// Note that unlike [`gauge`] and [`distribution`] metrics, counters are always unitless.
18pub fn counter<N, V>(name: N, value: V) -> CounterMetric
19where
20    N: Into<Cow<'static, str>>,
21    V: Into<f64>,
22{
23    MetricInner::new(name, value).into()
24}
25
26/// Creates a gauge metric, with the given name and value.
27///
28/// It is recommended to set the unit on the metric via [`GaugeMetric::unit`]. You may also set
29/// attributes with [`GaugeMetric::attribute`].
30pub fn gauge<N, V>(name: N, value: V) -> GaugeMetric
31where
32    N: Into<Cow<'static, str>>,
33    V: Into<f64>,
34{
35    MetricInner::new(name, value).into()
36}
37
38/// Creates a distribution metric, with the given name and value.
39///
40/// It is recommended to set the unit on the metric via [`DistributionMetric::unit`]. You may also set
41/// attributes with [`DistributionMetric::attribute`].
42pub fn distribution<N, V>(name: N, value: V) -> DistributionMetric
43where
44    N: Into<Cow<'static, str>>,
45    V: Into<f64>,
46{
47    MetricInner::new(name, value).into()
48}
49
50/// A counter metric, created with [`counter`].
51#[must_use = "metrics must be captured via `.capture()` to be sent to Sentry"]
52pub struct CounterMetric {
53    inner: MetricInner,
54}
55
56/// A gauge metric, created with [`gauge`].
57#[must_use = "metrics must be captured via `.capture()` to be sent to Sentry"]
58pub struct GaugeMetric {
59    inner: UnitMetricInner,
60}
61
62/// A distribution metric, created with [`distribution`].
63#[must_use = "metrics must be captured via `.capture()` to be sent to Sentry"]
64pub struct DistributionMetric {
65    inner: UnitMetricInner,
66}
67
68/// Marker trait for types which can be converted to a [protocol metric](ProtocolMetric),
69/// allowing [`Hub::capture_metric`] to capture and send them to Sentry.
70///
71/// This trait is sealed and cannot be implemented outside this crate.
72pub trait IntoProtocolMetric: sealed::IntoProtocolMetricImpl {}
73
74/// Implement metric methods common to all metric types.
75///
76/// This includes the `attribute` and the `capture` functions.
77macro_rules! implement_metric_common_methods {
78    ($struct:ident, $metric_type:expr) => {
79        impl $struct {
80            /// Adds an attribute to the metric.
81            pub fn attribute<K, V>(self, key: K, value: V) -> Self
82            where
83                K: Into<Cow<'static, str>>,
84                V: Into<LogAttribute>,
85            {
86                let inner = self.inner.attribute(key, value);
87                Self { inner }
88            }
89
90            /// Captures the metric on the current [`Hub`], sending it to Sentry.
91            ///
92            /// If the current hub has no client, the metric is dropped. To capture on a different
93            /// hub, use [`Hub::capture_metric`].
94            #[inline]
95            pub fn capture(self) {
96                with_client_impl! {{
97                    Hub::current().capture_metric(self)
98                }}
99            }
100        }
101
102        impl IntoProtocolMetric for $struct {}
103
104        impl sealed::IntoProtocolMetricImpl for $struct {
105            #[expect(private_interfaces)] // Not actually a public API
106            fn into_protocol_metric(self, trace: MetricTraceInfo) -> ProtocolMetric {
107                self.inner.into_protocol_metric($metric_type, trace)
108            }
109        }
110    };
111}
112
113/// Implements the `unit` method for a given metric type.
114macro_rules! implement_unit {
115    ($struct:ident) => {
116        impl $struct {
117            /// Sets the unit on the metric.
118            pub fn unit<U>(self, unit: U) -> Self
119            where
120                U: Into<Unit>,
121            {
122                let inner = self.inner.unit(unit);
123                Self { inner }
124            }
125        }
126    };
127}
128
129implement_metric_common_methods!(CounterMetric, MetricType::Counter);
130implement_metric_common_methods!(GaugeMetric, MetricType::Gauge);
131implement_metric_common_methods!(DistributionMetric, MetricType::Distribution);
132
133implement_unit!(GaugeMetric);
134implement_unit!(DistributionMetric);
135
136/// Information that links a metric to a trace.
137pub(crate) struct MetricTraceInfo {
138    pub(crate) trace_id: TraceId,
139    pub(crate) span_id: Option<SpanId>,
140}
141
142/// Common data that all metrics share.
143///
144/// Includes the metric type, name, value, and attributes.
145struct MetricInner {
146    name: Cow<'static, str>,
147    value: f64,
148    attributes: BTreeMap<Cow<'static, str>, LogAttribute>,
149}
150
151/// Common data that metrics, which support units, share.
152///
153/// Includes everything from [`MetricInner`] plus an optional [`Unit`].
154struct UnitMetricInner {
155    metric_inner: MetricInner,
156    unit: Option<Unit>,
157}
158
159impl MetricInner {
160    fn new<N, V>(name: N, value: V) -> Self
161    where
162        N: Into<Cow<'static, str>>,
163        V: Into<f64>,
164    {
165        let name = name.into();
166        let value = value.into();
167
168        Self {
169            name,
170            value,
171            attributes: Default::default(),
172        }
173    }
174
175    fn attribute<K, V>(mut self, key: K, value: V) -> Self
176    where
177        K: Into<Cow<'static, str>>,
178        V: Into<LogAttribute>,
179    {
180        self.attributes.insert(key.into(), value.into());
181        self
182    }
183
184    fn into_protocol_metric(self, r#type: MetricType, trace: MetricTraceInfo) -> ProtocolMetric {
185        let Self {
186            name,
187            value,
188            attributes,
189        } = self;
190        let MetricTraceInfo { trace_id, span_id } = trace;
191
192        ProtocolMetric {
193            r#type,
194            trace_id,
195            name,
196            value,
197            attributes,
198            span_id,
199            timestamp: SystemTime::now(),
200            unit: None,
201        }
202    }
203}
204
205impl UnitMetricInner {
206    fn attribute<K, V>(self, key: K, value: V) -> Self
207    where
208        K: Into<Cow<'static, str>>,
209        V: Into<LogAttribute>,
210    {
211        Self {
212            metric_inner: self.metric_inner.attribute(key, value),
213            ..self
214        }
215    }
216
217    fn unit<U>(self, unit: U) -> Self
218    where
219        U: Into<Unit>,
220    {
221        let unit = Some(unit.into());
222        Self { unit, ..self }
223    }
224
225    fn into_protocol_metric(self, r#type: MetricType, trace: MetricTraceInfo) -> ProtocolMetric {
226        ProtocolMetric {
227            unit: self.unit,
228            ..self.metric_inner.into_protocol_metric(r#type, trace)
229        }
230    }
231}
232
233impl From<MetricInner> for CounterMetric {
234    #[inline]
235    fn from(inner: MetricInner) -> Self {
236        Self { inner }
237    }
238}
239
240impl From<MetricInner> for GaugeMetric {
241    #[inline]
242    fn from(inner: MetricInner) -> Self {
243        let inner = inner.into();
244        Self { inner }
245    }
246}
247
248impl From<MetricInner> for DistributionMetric {
249    #[inline]
250    fn from(inner: MetricInner) -> Self {
251        let inner = inner.into();
252        Self { inner }
253    }
254}
255
256impl From<MetricInner> for UnitMetricInner {
257    #[inline]
258    fn from(metric_inner: MetricInner) -> Self {
259        Self {
260            metric_inner,
261            unit: None,
262        }
263    }
264}
265
266/// Private module for used to prevent [`IntoProtocolMetric`] from being implemented outside this
267/// crate, and for keeping its implementation details private.
268mod sealed {
269    use sentry_types::protocol::v7::Metric as ProtocolMetric;
270
271    use crate::metrics::MetricTraceInfo;
272
273    #[cfg(doc)]
274    use super::IntoProtocolMetric;
275
276    /// Actual implementation of [`IntoProtocolMetric`]
277    pub trait IntoProtocolMetricImpl {
278        /// Converts this item into a [`ProtocolMetric`], with the given [`MetricTraceInfo`].
279        #[expect(private_interfaces)] // This trait is not actually publicly accessible.
280        fn into_protocol_metric(self, trace: MetricTraceInfo) -> ProtocolMetric;
281    }
282}