metrics/recorder/
mod.rs

1use std::{cell::Cell, marker::PhantomData, 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.
67///
68/// ## Note
69///
70/// The guard has a lifetime parameter `'a` that is bounded using a
71/// `PhantomData` type. This upholds the guard's contravariance, it must live
72/// _at most as long_ as the recorder it takes a reference to. The bounded
73/// lifetime prevents accidental use-after-free errors when using a guard
74/// directly through [`crate::set_default_local_recorder`].
75pub struct LocalRecorderGuard<'a> {
76    prev_recorder: Option<NonNull<dyn Recorder>>,
77    phantom: PhantomData<&'a dyn Recorder>,
78}
79
80impl<'a> LocalRecorderGuard<'a> {
81    /// Creates a new `LocalRecorderGuard` and sets the thread-local recorder.
82    fn new(recorder: &'a (dyn Recorder + 'a)) -> Self {
83        // SAFETY: We extend `'a` to `'static` to satisfy the signature of `LOCAL_RECORDER`, which
84        // has an implied `'static` bound on `dyn Recorder`. We enforce that all usages of `LOCAL_RECORDER`
85        // are limited to `'a` as we mediate its access entirely through `LocalRecorderGuard<'a>`.
86        let recorder_ptr = unsafe {
87            std::mem::transmute::<*const (dyn Recorder + 'a), *mut (dyn Recorder + 'static)>(
88                recorder as &'a (dyn Recorder + 'a),
89            )
90        };
91        // SAFETY: While we take a lifetime-less pointer to the given reference, the reference we derive _from_ the
92        // pointer is given the same lifetime of the reference used to construct the guard -- captured in the guard type
93        // itself -- and so derived references never outlive the source reference.
94        let recorder_ptr = unsafe { NonNull::new_unchecked(recorder_ptr) };
95
96        let prev_recorder =
97            LOCAL_RECORDER.with(|local_recorder| local_recorder.replace(Some(recorder_ptr)));
98
99        Self { prev_recorder, phantom: PhantomData }
100    }
101}
102
103impl<'a> Drop for LocalRecorderGuard<'a> {
104    fn drop(&mut self) {
105        // Clear the thread-local recorder.
106        LOCAL_RECORDER.with(|local_recorder| local_recorder.replace(self.prev_recorder.take()));
107    }
108}
109
110/// Sets the global recorder.
111///
112/// This function may only be called once in the lifetime of a program. Any metrics recorded
113/// before this method is called will be completely ignored.
114///
115/// This function does not typically need to be called manually.  Metrics implementations should
116/// provide an initialization method that installs the recorder internally.
117///
118/// # Errors
119///
120/// An error is returned if a recorder has already been set.
121pub fn set_global_recorder<R>(recorder: R) -> Result<(), SetRecorderError<R>>
122where
123    R: Recorder + 'static,
124{
125    GLOBAL_RECORDER.set(recorder)
126}
127
128/// Sets the recorder as the default for the current thread for the duration of
129/// the lifetime of the returned [`LocalRecorderGuard`].
130///
131/// This function is suitable for capturing metrics in asynchronous code, in particular
132/// when using a single-threaded runtime. Any metrics registered prior to the returned
133/// guard will remain attached to the recorder that was present at the time of registration,
134/// and so this cannot be used to intercept existing metrics.
135///
136/// Additionally, local recorders can be used in a nested fashion. When setting a new
137/// default local recorder, the previous default local recorder will be captured if one
138/// was set, and will be restored when the returned guard drops.
139/// the lifetime of the returned [`LocalRecorderGuard`].
140///
141/// Any metrics recorded before a guard is returned will be completely ignored.
142/// Metrics implementations should provide an initialization method that
143/// installs the recorder internally.
144///
145/// The function is suitable for capturing metrics in asynchronous code that
146/// uses a single threaded runtime.
147///
148/// If a global recorder is set, it will be restored once the guard is dropped.
149#[must_use]
150pub fn set_default_local_recorder(recorder: &dyn Recorder) -> LocalRecorderGuard {
151    LocalRecorderGuard::new(recorder)
152}
153
154/// Runs the closure with the given recorder set as the global recorder for the duration.
155pub fn with_local_recorder<T>(recorder: &dyn Recorder, f: impl FnOnce() -> T) -> T {
156    let _local = LocalRecorderGuard::new(recorder);
157    f()
158}
159
160/// Runs the closure with a reference to the current recorder for this scope.
161///
162/// If a local recorder has been set, it will be used. Otherwise, the global recorder will be used.
163/// If neither a local recorder or global recorder have been set, a no-op recorder will be used.
164///
165/// This is used primarily by the generated code from the convenience macros used to record metrics.
166/// It should typically not be necessary to call this function directly.
167#[doc(hidden)]
168pub fn with_recorder<T>(f: impl FnOnce(&dyn Recorder) -> T) -> T {
169    LOCAL_RECORDER.with(|local_recorder| {
170        if let Some(recorder) = local_recorder.get() {
171            // SAFETY: If we have a local recorder, we know that it is valid because it can only be
172            // set during the duration of a closure that is passed to `with_local_recorder`, which
173            // is the only time this method can be called and have a local recorder set. This
174            // ensures that the lifetime of the recorder is valid for the duration of this method
175            // call.
176            unsafe { f(recorder.as_ref()) }
177        } else if let Some(global_recorder) = GLOBAL_RECORDER.try_load() {
178            f(global_recorder)
179        } else {
180            f(&NOOP_RECORDER)
181        }
182    })
183}
184
185#[cfg(test)]
186mod tests {
187    use std::sync::atomic::Ordering;
188
189    use crate::with_local_recorder;
190
191    use super::RecorderOnceCell;
192
193    #[test]
194    fn boxed_recorder_dropped_on_existing_set() {
195        // This test simply ensures that if a boxed recorder is handed to us to install, and another
196        // recorder has already been installed, that we drop the new boxed recorder instead of
197        // leaking it.
198        let recorder_cell = RecorderOnceCell::new();
199
200        // This is the first set of the cell, so it should always succeed.
201        let (first_recorder, _) = test_recorders::TrackOnDropRecorder::new();
202        let first_set_result = recorder_cell.set(first_recorder);
203        assert!(first_set_result.is_ok());
204
205        // Since the cell is already set, this second set should fail. We'll also then assert that
206        // our atomic boolean is set to `true`, indicating the drop logic ran for it.
207        let (second_recorder, was_dropped) = test_recorders::TrackOnDropRecorder::new();
208        assert!(!was_dropped.load(Ordering::SeqCst));
209
210        let second_set_result = recorder_cell.set(second_recorder);
211        assert!(second_set_result.is_err());
212        assert!(!was_dropped.load(Ordering::SeqCst));
213        drop(second_set_result);
214        assert!(was_dropped.load(Ordering::SeqCst));
215    }
216
217    #[test]
218    fn thread_scoped_recorder_guards() {
219        // This test ensures that when a recorder is installed through
220        // `crate::set_default_local_recorder` it will only be valid in the scope of the
221        // thread.
222        //
223        // The goal of the test is to give confidence that no invalid memory
224        // access errors are present when operating with locally scoped
225        // recorders.
226        let t1_recorder = test_recorders::SimpleCounterRecorder::new();
227        let t2_recorder = test_recorders::SimpleCounterRecorder::new();
228        let t3_recorder = test_recorders::SimpleCounterRecorder::new();
229        // Start a new thread scope to take references to each recorder in the
230        // closures passed to the thread.
231        std::thread::scope(|s| {
232            s.spawn(|| {
233                let _guard = crate::set_default_local_recorder(&t1_recorder);
234                crate::counter!("t1_counter").increment(1);
235            });
236
237            s.spawn(|| {
238                with_local_recorder(&t2_recorder, || {
239                    crate::counter!("t2_counter").increment(2);
240                })
241            });
242
243            s.spawn(|| {
244                let _guard = crate::set_default_local_recorder(&t3_recorder);
245                crate::counter!("t3_counter").increment(3);
246            });
247        });
248
249        assert!(t1_recorder.get_value() == 1);
250        assert!(t2_recorder.get_value() == 2);
251        assert!(t3_recorder.get_value() == 3);
252    }
253
254    #[test]
255    fn local_recorder_restored_when_dropped() {
256        // This test ensures that any previously installed local recorders are
257        // restored when the subsequently installed recorder's guard is dropped.
258        let root_recorder = test_recorders::SimpleCounterRecorder::new();
259        // Install the root recorder and increment the counter once.
260        let _guard = crate::set_default_local_recorder(&root_recorder);
261        crate::counter!("test_counter").increment(1);
262
263        // Install a second recorder and increment its counter once.
264        let next_recorder = test_recorders::SimpleCounterRecorder::new();
265        let next_guard = crate::set_default_local_recorder(&next_recorder);
266        crate::counter!("test_counter").increment(1);
267        let final_recorder = test_recorders::SimpleCounterRecorder::new();
268        crate::with_local_recorder(&final_recorder, || {
269            // Final recorder increments the counter by 10. At the end of the
270            // closure, the guard should be dropped, and `next_recorder`
271            // restored.
272            crate::counter!("test_counter").increment(10);
273        });
274        // Since `next_recorder` is restored, we can increment it once and check
275        // that the value is 2 (+1 before and after the closure).
276        crate::counter!("test_counter").increment(1);
277        assert!(next_recorder.get_value() == 2);
278        drop(next_guard);
279
280        // At the end, increment the counter again by an arbitrary value. Since
281        // `next_guard` is dropped, the root recorder is restored.
282        crate::counter!("test_counter").increment(20);
283        assert!(root_recorder.get_value() == 21);
284    }
285
286    mod test_recorders {
287        use std::sync::{
288            atomic::{AtomicBool, AtomicU64, Ordering},
289            Arc,
290        };
291
292        use crate::Recorder;
293
294        #[derive(Debug)]
295        // Tracks how many times the recorder was dropped
296        pub struct TrackOnDropRecorder(Arc<AtomicBool>);
297
298        impl TrackOnDropRecorder {
299            pub fn new() -> (Self, Arc<AtomicBool>) {
300                let arc = Arc::new(AtomicBool::new(false));
301                (Self(arc.clone()), arc)
302            }
303        }
304
305        // === impl TrackOnDropRecorder ===
306
307        impl Recorder for TrackOnDropRecorder {
308            fn describe_counter(
309                &self,
310                _: crate::KeyName,
311                _: Option<crate::Unit>,
312                _: crate::SharedString,
313            ) {
314            }
315            fn describe_gauge(
316                &self,
317                _: crate::KeyName,
318                _: Option<crate::Unit>,
319                _: crate::SharedString,
320            ) {
321            }
322            fn describe_histogram(
323                &self,
324                _: crate::KeyName,
325                _: Option<crate::Unit>,
326                _: crate::SharedString,
327            ) {
328            }
329
330            fn register_counter(&self, _: &crate::Key, _: &crate::Metadata<'_>) -> crate::Counter {
331                crate::Counter::noop()
332            }
333
334            fn register_gauge(&self, _: &crate::Key, _: &crate::Metadata<'_>) -> crate::Gauge {
335                crate::Gauge::noop()
336            }
337
338            fn register_histogram(
339                &self,
340                _: &crate::Key,
341                _: &crate::Metadata<'_>,
342            ) -> crate::Histogram {
343                crate::Histogram::noop()
344            }
345        }
346
347        impl Drop for TrackOnDropRecorder {
348            fn drop(&mut self) {
349                self.0.store(true, Ordering::SeqCst);
350            }
351        }
352
353        // A simple recorder that only implements `register_counter`.
354        #[derive(Debug)]
355        pub struct SimpleCounterRecorder {
356            state: Arc<AtomicU64>,
357        }
358
359        impl SimpleCounterRecorder {
360            pub fn new() -> Self {
361                Self { state: Arc::new(AtomicU64::default()) }
362            }
363
364            pub fn get_value(&self) -> u64 {
365                self.state.load(Ordering::Acquire)
366            }
367        }
368
369        struct SimpleCounterHandle {
370            state: Arc<AtomicU64>,
371        }
372
373        impl crate::CounterFn for SimpleCounterHandle {
374            fn increment(&self, value: u64) {
375                self.state.fetch_add(value, Ordering::Acquire);
376            }
377
378            fn absolute(&self, _value: u64) {
379                unimplemented!()
380            }
381        }
382
383        // === impl SimpleCounterRecorder ===
384
385        impl Recorder for SimpleCounterRecorder {
386            fn describe_counter(
387                &self,
388                _: crate::KeyName,
389                _: Option<crate::Unit>,
390                _: crate::SharedString,
391            ) {
392            }
393            fn describe_gauge(
394                &self,
395                _: crate::KeyName,
396                _: Option<crate::Unit>,
397                _: crate::SharedString,
398            ) {
399            }
400            fn describe_histogram(
401                &self,
402                _: crate::KeyName,
403                _: Option<crate::Unit>,
404                _: crate::SharedString,
405            ) {
406            }
407
408            fn register_counter(&self, _: &crate::Key, _: &crate::Metadata<'_>) -> crate::Counter {
409                crate::Counter::from_arc(Arc::new(SimpleCounterHandle {
410                    state: self.state.clone(),
411                }))
412            }
413
414            fn register_gauge(&self, _: &crate::Key, _: &crate::Metadata<'_>) -> crate::Gauge {
415                crate::Gauge::noop()
416            }
417
418            fn register_histogram(
419                &self,
420                _: &crate::Key,
421                _: &crate::Metadata<'_>,
422            ) -> crate::Histogram {
423                crate::Histogram::noop()
424            }
425        }
426    }
427}