Skip to main content

sentry_core/
metrics.rs

1//! APIs for creating and capturing metrics.
2//!
3//! With [Sentry's Application Metrics], you can record [counters], [gauges], and
4//! [distributions] from application code. This module is available when the `metrics` feature is
5//! enabled. Captured metrics are sent through the current [`Hub`] and are associated with the
6//! current trace and active span when available. The SDK also attaches [default metric attributes].
7//!
8//! Counters are unitless. Gauges and distributions support units via `unit`.
9//!
10//! For more information, see the [Rust SDK metrics guide].
11//!
12//! [Sentry's Application Metrics]: https://docs.sentry.io/product/explore/metrics/
13//! [Application Metrics]: https://docs.sentry.io/product/explore/metrics/
14//! [counters]: https://docs.sentry.io/product/explore/metrics/#counters
15//! [gauges]: https://docs.sentry.io/product/explore/metrics/#gauges
16//! [distributions]: https://docs.sentry.io/product/explore/metrics/#distributions
17//! [default metric attributes]: https://docs.sentry.io/platforms/rust/metrics/#default-attributes
18//! [Rust SDK metrics guide]: https://docs.sentry.io/platforms/rust/metrics/
19//!
20//! # Examples
21//!
22//! Capture counters, gauges, and distributions:
23//!
24//! ```rust
25//! use sentry::metrics;
26//! use sentry::protocol::Unit;
27//!
28//! metrics::counter("http.requests", 1).capture();
29//! metrics::gauge("queue.depth", 42).capture();
30//! metrics::distribution("http.response_time", 187.5)
31//!     .unit(Unit::Millisecond)
32//!     .capture();
33//! ```
34//!
35//! Add attributes that can be used to filter and group metrics in Sentry:
36//!
37//! ```rust
38//! use sentry::metrics;
39//!
40//! metrics::counter("http.requests", 1)
41//!     .attribute("http.route", "/health")
42//!     .attribute("http.response.status_code", 200)
43//!     .capture();
44//! ```
45
46use std::collections::BTreeMap;
47use std::{borrow::Cow, time::SystemTime};
48
49use sentry_types::protocol::v7::{
50    LogAttribute, Metric as ProtocolMetric, MetricType, SpanId, TraceId, Unit,
51};
52
53#[cfg(any(doc, feature = "client"))]
54use crate::Hub;
55
56/// Creates a counter metric, with the given name and value.
57///
58/// Use counters for occurrences, such as handled requests or processed jobs. You may set
59/// attributes via [`CounterMetric::attribute`].
60///
61/// Unlike [`gauge`] and [`distribution`] metrics, counters are always unitless.
62///
63/// # Example
64///
65/// ```rust
66/// use sentry::metrics;
67///
68/// metrics::counter("http.requests", 1).capture();
69/// ```
70pub fn counter<N, V>(name: N, value: V) -> CounterMetric
71where
72    N: Into<Cow<'static, str>>,
73    V: Into<f64>,
74{
75    MetricInner::new(name, value).into()
76}
77
78/// Creates a gauge metric, with the given name and value.
79///
80/// Use gauges for current state, such as queue depth or active connections. Set the unit on the
81/// metric via [`GaugeMetric::unit`] where applicable. You may also set attributes with
82/// [`GaugeMetric::attribute`].
83///
84/// # Example
85///
86/// ```rust
87/// use sentry::metrics;
88///
89/// metrics::gauge("queue.depth", 42).capture();
90/// ```
91pub fn gauge<N, V>(name: N, value: V) -> GaugeMetric
92where
93    N: Into<Cow<'static, str>>,
94    V: Into<f64>,
95{
96    MetricInner::new(name, value).into()
97}
98
99/// Creates a distribution metric, with the given name and value.
100///
101/// Use distributions for values that need statistical analysis, such as response time or payload
102/// size. Set the unit on the metric via [`DistributionMetric::unit`] where applicable. You may also
103/// set attributes with [`DistributionMetric::attribute`].
104///
105/// # Example
106///
107/// ```rust
108/// use sentry::metrics;
109/// use sentry::protocol::Unit;
110///
111/// metrics::distribution("http.response_time", 187.5)
112///     .unit(Unit::Millisecond)
113///     .capture();
114/// ```
115pub fn distribution<N, V>(name: N, value: V) -> DistributionMetric
116where
117    N: Into<Cow<'static, str>>,
118    V: Into<f64>,
119{
120    MetricInner::new(name, value).into()
121}
122
123/// A counter metric, created with [`counter`].
124#[must_use = "metrics must be captured via `.capture()` to be sent to Sentry"]
125pub struct CounterMetric {
126    inner: MetricInner,
127}
128
129/// A gauge metric, created with [`gauge`].
130#[must_use = "metrics must be captured via `.capture()` to be sent to Sentry"]
131pub struct GaugeMetric {
132    inner: UnitMetricInner,
133}
134
135/// A distribution metric, created with [`distribution`].
136#[must_use = "metrics must be captured via `.capture()` to be sent to Sentry"]
137pub struct DistributionMetric {
138    inner: UnitMetricInner,
139}
140
141/// Marker trait for types which can be converted to a [protocol metric](ProtocolMetric),
142/// allowing [`Hub::capture_metric`] to capture and send them to Sentry.
143///
144/// This trait is sealed and cannot be implemented outside this crate.
145pub trait IntoProtocolMetric: sealed::IntoProtocolMetricImpl {}
146
147/// Implement metric methods common to all metric types.
148///
149/// This includes the `attribute` and the `capture` functions.
150macro_rules! implement_metric_common_methods {
151    ($struct:ident, $metric_type:expr) => {
152        impl $struct {
153            /// Adds an attribute to the metric.
154            ///
155            /// Attributes are keys that can be used to filter and group metrics in Sentry. Multiple
156            /// attributes can be chained. We recommend using [Sentry semantic conventions] for key
157            /// values, where applicable.
158            ///
159            /// [Sentry semantic conventions]: https://getsentry.github.io/sentry-conventions/
160            pub fn attribute<K, V>(self, key: K, value: V) -> Self
161            where
162                K: Into<Cow<'static, str>>,
163                V: Into<LogAttribute>,
164            {
165                let inner = self.inner.attribute(key, value);
166                Self { inner }
167            }
168
169            /// Captures the metric on the current [`Hub`], sending it to Sentry.
170            ///
171            /// If the current hub has no client bound, the metric is dropped. To capture on a
172            /// different hub, use [`Hub::capture_metric`].
173            #[inline]
174            pub fn capture(self) {
175                with_client_impl! {{
176                    Hub::current().capture_metric(self)
177                }}
178            }
179        }
180
181        impl IntoProtocolMetric for $struct {}
182
183        impl sealed::IntoProtocolMetricImpl for $struct {
184            #[expect(private_interfaces)] // Not actually a public API
185            fn into_protocol_metric(self, trace: MetricTraceInfo) -> ProtocolMetric {
186                self.inner.into_protocol_metric($metric_type, trace)
187            }
188        }
189    };
190}
191
192/// Implements the `unit` method for a given metric type.
193macro_rules! implement_unit {
194    ($struct:ident) => {
195        impl $struct {
196            /// Sets the unit on the metric.
197            pub fn unit<U>(self, unit: U) -> Self
198            where
199                U: Into<Unit>,
200            {
201                let inner = self.inner.unit(unit);
202                Self { inner }
203            }
204        }
205    };
206}
207
208implement_metric_common_methods!(CounterMetric, MetricType::Counter);
209implement_metric_common_methods!(GaugeMetric, MetricType::Gauge);
210implement_metric_common_methods!(DistributionMetric, MetricType::Distribution);
211
212implement_unit!(GaugeMetric);
213implement_unit!(DistributionMetric);
214
215/// Information that links a metric to a trace.
216pub(crate) struct MetricTraceInfo {
217    pub(crate) trace_id: TraceId,
218    pub(crate) span_id: Option<SpanId>,
219}
220
221/// Common data that all metrics share.
222///
223/// Includes the metric type, name, value, and attributes.
224struct MetricInner {
225    name: Cow<'static, str>,
226    value: f64,
227    attributes: BTreeMap<Cow<'static, str>, LogAttribute>,
228}
229
230/// Common data that metrics, which support units, share.
231///
232/// Includes everything from [`MetricInner`] plus an optional [`Unit`].
233struct UnitMetricInner {
234    metric_inner: MetricInner,
235    unit: Option<Unit>,
236}
237
238impl MetricInner {
239    fn new<N, V>(name: N, value: V) -> Self
240    where
241        N: Into<Cow<'static, str>>,
242        V: Into<f64>,
243    {
244        let name = name.into();
245        let value = value.into();
246
247        Self {
248            name,
249            value,
250            attributes: Default::default(),
251        }
252    }
253
254    fn attribute<K, V>(mut self, key: K, value: V) -> Self
255    where
256        K: Into<Cow<'static, str>>,
257        V: Into<LogAttribute>,
258    {
259        self.attributes.insert(key.into(), value.into());
260        self
261    }
262
263    fn into_protocol_metric(self, r#type: MetricType, trace: MetricTraceInfo) -> ProtocolMetric {
264        let Self {
265            name,
266            value,
267            attributes,
268        } = self;
269        let MetricTraceInfo { trace_id, span_id } = trace;
270
271        ProtocolMetric {
272            r#type,
273            trace_id,
274            name,
275            value,
276            attributes,
277            span_id,
278            timestamp: SystemTime::now(),
279            unit: None,
280        }
281    }
282}
283
284impl UnitMetricInner {
285    fn attribute<K, V>(self, key: K, value: V) -> Self
286    where
287        K: Into<Cow<'static, str>>,
288        V: Into<LogAttribute>,
289    {
290        Self {
291            metric_inner: self.metric_inner.attribute(key, value),
292            ..self
293        }
294    }
295
296    fn unit<U>(self, unit: U) -> Self
297    where
298        U: Into<Unit>,
299    {
300        let unit = Some(unit.into());
301        Self { unit, ..self }
302    }
303
304    fn into_protocol_metric(self, r#type: MetricType, trace: MetricTraceInfo) -> ProtocolMetric {
305        ProtocolMetric {
306            unit: self.unit,
307            ..self.metric_inner.into_protocol_metric(r#type, trace)
308        }
309    }
310}
311
312impl From<MetricInner> for CounterMetric {
313    #[inline]
314    fn from(inner: MetricInner) -> Self {
315        Self { inner }
316    }
317}
318
319impl From<MetricInner> for GaugeMetric {
320    #[inline]
321    fn from(inner: MetricInner) -> Self {
322        let inner = inner.into();
323        Self { inner }
324    }
325}
326
327impl From<MetricInner> for DistributionMetric {
328    #[inline]
329    fn from(inner: MetricInner) -> Self {
330        let inner = inner.into();
331        Self { inner }
332    }
333}
334
335impl From<MetricInner> for UnitMetricInner {
336    #[inline]
337    fn from(metric_inner: MetricInner) -> Self {
338        Self {
339            metric_inner,
340            unit: None,
341        }
342    }
343}
344
345/// Private module for used to prevent [`IntoProtocolMetric`] from being implemented outside this
346/// crate, and for keeping its implementation details private.
347mod sealed {
348    use sentry_types::protocol::v7::Metric as ProtocolMetric;
349
350    use crate::metrics::MetricTraceInfo;
351
352    #[cfg(doc)]
353    use super::IntoProtocolMetric;
354
355    /// Actual implementation of [`IntoProtocolMetric`]
356    pub trait IntoProtocolMetricImpl {
357        /// Converts this item into a [`ProtocolMetric`], with the given [`MetricTraceInfo`].
358        #[expect(private_interfaces)] // This trait is not actually publicly accessible.
359        fn into_protocol_metric(self, trace: MetricTraceInfo) -> ProtocolMetric;
360    }
361}