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