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}