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}