metrics/recorder/mod.rs
1use std::{cell::Cell, ptr::NonNull};
2
3mod cell;
4use self::cell::RecorderOnceCell;
5
6mod errors;
7pub use self::errors::SetRecorderError;
8
9mod noop;
10pub use self::noop::NoopRecorder;
11
12use crate::{Counter, Gauge, Histogram, Key, KeyName, Metadata, SharedString, Unit};
13
14static NOOP_RECORDER: NoopRecorder = NoopRecorder;
15static GLOBAL_RECORDER: RecorderOnceCell = RecorderOnceCell::new();
16
17thread_local! {
18 static LOCAL_RECORDER: Cell<Option<NonNull<dyn Recorder>>> = Cell::new(None);
19}
20
21/// A trait for registering and recording metrics.
22///
23/// This is the core trait that allows interoperability between exporter implementations and the
24/// macros provided by `metrics`.
25pub trait Recorder {
26 /// Describes a counter.
27 ///
28 /// Callers may provide the unit or a description of the counter being registered. Whether or
29 /// not a metric can be reregistered to provide a unit/description, if one was already passed
30 /// or not, as well as how units/descriptions are used by the underlying recorder, is an
31 /// implementation detail.
32 fn describe_counter(&self, key: KeyName, unit: Option<Unit>, description: SharedString);
33
34 /// Describes a gauge.
35 ///
36 /// Callers may provide the unit or a description of the gauge being registered. Whether or
37 /// not a metric can be reregistered to provide a unit/description, if one was already passed
38 /// or not, as well as how units/descriptions are used by the underlying recorder, is an
39 /// implementation detail.
40 fn describe_gauge(&self, key: KeyName, unit: Option<Unit>, description: SharedString);
41
42 /// Describes a histogram.
43 ///
44 /// Callers may provide the unit or a description of the histogram being registered. Whether or
45 /// not a metric can be reregistered to provide a unit/description, if one was already passed
46 /// or not, as well as how units/descriptions are used by the underlying recorder, is an
47 /// implementation detail.
48 fn describe_histogram(&self, key: KeyName, unit: Option<Unit>, description: SharedString);
49
50 /// Registers a counter.
51 fn register_counter(&self, key: &Key, metadata: &Metadata<'_>) -> Counter;
52
53 /// Registers a gauge.
54 fn register_gauge(&self, key: &Key, metadata: &Metadata<'_>) -> Gauge;
55
56 /// Registers a histogram.
57 fn register_histogram(&self, key: &Key, metadata: &Metadata<'_>) -> Histogram;
58}
59
60/// Guard for setting a local recorder.
61///
62/// When using a local recorder, we take a reference to the recorder and only hold it for as long as
63/// the duration of the closure. However, we must store this reference in a static variable
64/// (thread-local storage) so that it can be accessed by the macros. This guard ensures that the
65/// pointer we store to the reference is cleared when the guard is dropped, so that it can't be used
66/// after the closure has finished, even if the closure panics and unwinds the stack.
67struct LocalRecorderGuard;
68
69impl LocalRecorderGuard {
70 /// Creates a new `LocalRecorderGuard` and sets the thread-local recorder.
71 fn new(recorder: &dyn Recorder) -> Self {
72 // SAFETY: While we take a lifetime-less pointer to the given reference, the reference we
73 // derive _from_ the pointer is never given a lifetime that exceeds the lifetime of the
74 // input reference.
75 let recorder_ptr = unsafe { NonNull::new_unchecked(recorder as *const _ as *mut _) };
76
77 LOCAL_RECORDER.with(|local_recorder| {
78 local_recorder.set(Some(recorder_ptr));
79 });
80
81 Self
82 }
83}
84
85impl Drop for LocalRecorderGuard {
86 fn drop(&mut self) {
87 // Clear the thread-local recorder.
88 LOCAL_RECORDER.with(|local_recorder| {
89 local_recorder.set(None);
90 });
91 }
92}
93
94/// Sets the global recorder.
95///
96/// This function may only be called once in the lifetime of a program. Any metrics recorded
97/// before this method is called will be completely ignored.
98///
99/// This function does not typically need to be called manually. Metrics implementations should
100/// provide an initialization method that installs the recorder internally.
101///
102/// # Errors
103///
104/// An error is returned if a recorder has already been set.
105pub fn set_global_recorder<R>(recorder: R) -> Result<(), SetRecorderError<R>>
106where
107 R: Recorder + 'static,
108{
109 GLOBAL_RECORDER.set(recorder)
110}
111
112/// Runs the closure with the given recorder set as the global recorder for the duration.
113pub fn with_local_recorder<T>(recorder: &dyn Recorder, f: impl FnOnce() -> T) -> T {
114 let _local = LocalRecorderGuard::new(recorder);
115 f()
116}
117
118/// Runs the closure with a reference to the current recorder for this scope.
119///
120/// If a local recorder has been set, it will be used. Otherwise, the global recorder will be used.
121/// If neither a local recorder or global recorder have been set, a no-op recorder will be used.
122///
123/// This is used primarily by the generated code from the convenience macros used to record metrics.
124/// It should typically not be necessary to call this function directly.
125#[doc(hidden)]
126pub fn with_recorder<T>(f: impl FnOnce(&dyn Recorder) -> T) -> T {
127 LOCAL_RECORDER.with(|local_recorder| {
128 if let Some(recorder) = local_recorder.get() {
129 // SAFETY: If we have a local recorder, we know that it is valid because it can only be
130 // set during the duration of a closure that is passed to `with_local_recorder`, which
131 // is the only time this method can be called and have a local recorder set. This
132 // ensures that the lifetime of the recorder is valid for the duration of this method
133 // call.
134 unsafe { f(recorder.as_ref()) }
135 } else if let Some(global_recorder) = GLOBAL_RECORDER.try_load() {
136 f(global_recorder)
137 } else {
138 f(&NOOP_RECORDER)
139 }
140 })
141}
142
143#[cfg(test)]
144mod tests {
145 use std::sync::{
146 atomic::{AtomicBool, Ordering},
147 Arc,
148 };
149
150 use super::{Recorder, RecorderOnceCell};
151
152 #[test]
153 fn boxed_recorder_dropped_on_existing_set() {
154 // This test simply ensures that if a boxed recorder is handed to us to install, and another
155 // recorder has already been installed, that we drop th new boxed recorder instead of
156 // leaking it.
157 struct TrackOnDropRecorder(Arc<AtomicBool>);
158
159 impl TrackOnDropRecorder {
160 pub fn new() -> (Self, Arc<AtomicBool>) {
161 let arc = Arc::new(AtomicBool::new(false));
162 (Self(arc.clone()), arc)
163 }
164 }
165
166 impl Recorder for TrackOnDropRecorder {
167 fn describe_counter(
168 &self,
169 _: crate::KeyName,
170 _: Option<crate::Unit>,
171 _: crate::SharedString,
172 ) {
173 }
174 fn describe_gauge(
175 &self,
176 _: crate::KeyName,
177 _: Option<crate::Unit>,
178 _: crate::SharedString,
179 ) {
180 }
181 fn describe_histogram(
182 &self,
183 _: crate::KeyName,
184 _: Option<crate::Unit>,
185 _: crate::SharedString,
186 ) {
187 }
188
189 fn register_counter(&self, _: &crate::Key, _: &crate::Metadata<'_>) -> crate::Counter {
190 crate::Counter::noop()
191 }
192
193 fn register_gauge(&self, _: &crate::Key, _: &crate::Metadata<'_>) -> crate::Gauge {
194 crate::Gauge::noop()
195 }
196
197 fn register_histogram(
198 &self,
199 _: &crate::Key,
200 _: &crate::Metadata<'_>,
201 ) -> crate::Histogram {
202 crate::Histogram::noop()
203 }
204 }
205
206 impl Drop for TrackOnDropRecorder {
207 fn drop(&mut self) {
208 self.0.store(true, Ordering::SeqCst);
209 }
210 }
211
212 let recorder_cell = RecorderOnceCell::new();
213
214 // This is the first set of the cell, so it should always succeed.
215 let (first_recorder, _) = TrackOnDropRecorder::new();
216 let first_set_result = recorder_cell.set(first_recorder);
217 assert!(first_set_result.is_ok());
218
219 // Since the cell is already set, this second set should fail. We'll also then assert that
220 // our atomic boolean is set to `true`, indicating the drop logic ran for it.
221 let (second_recorder, was_dropped) = TrackOnDropRecorder::new();
222 assert!(!was_dropped.load(Ordering::SeqCst));
223
224 let second_set_result = recorder_cell.set(second_recorder);
225 assert!(second_set_result.is_err());
226 assert!(!was_dropped.load(Ordering::SeqCst));
227 drop(second_set_result);
228 assert!(was_dropped.load(Ordering::SeqCst));
229 }
230}