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}