1use 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
13pub 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
26pub 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
38pub 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#[must_use = "metrics must be captured via `.capture()` to be sent to Sentry"]
52pub struct CounterMetric {
53 inner: MetricInner,
54}
55
56#[must_use = "metrics must be captured via `.capture()` to be sent to Sentry"]
58pub struct GaugeMetric {
59 inner: UnitMetricInner,
60}
61
62#[must_use = "metrics must be captured via `.capture()` to be sent to Sentry"]
64pub struct DistributionMetric {
65 inner: UnitMetricInner,
66}
67
68pub trait IntoProtocolMetric: sealed::IntoProtocolMetricImpl {}
73
74macro_rules! implement_metric_common_methods {
78 ($struct:ident, $metric_type:expr) => {
79 impl $struct {
80 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 #[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)] fn into_protocol_metric(self, trace: MetricTraceInfo) -> ProtocolMetric {
107 self.inner.into_protocol_metric($metric_type, trace)
108 }
109 }
110 };
111}
112
113macro_rules! implement_unit {
115 ($struct:ident) => {
116 impl $struct {
117 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
136pub(crate) struct MetricTraceInfo {
138 pub(crate) trace_id: TraceId,
139 pub(crate) span_id: Option<SpanId>,
140}
141
142struct MetricInner {
146 name: Cow<'static, str>,
147 value: f64,
148 attributes: BTreeMap<Cow<'static, str>, LogAttribute>,
149}
150
151struct 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
266mod 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 pub trait IntoProtocolMetricImpl {
278 #[expect(private_interfaces)] fn into_protocol_metric(self, trace: MetricTraceInfo) -> ProtocolMetric;
281 }
282}