diff --git a/metrics/RELEASES.md b/metrics/RELEASES.md index cdf7bc12..80e4d88b 100644 --- a/metrics/RELEASES.md +++ b/metrics/RELEASES.md @@ -9,13 +9,13 @@ long-form description and would be too verbose for the changelog alone. ### Metric metadata -Metrics now support a limited set of metadata field, which can be added to provide for context about -the metric in terms of where it originates from as well as its verbosity. +Metrics now support collecting a limited set of metadata field, which can be provided to add context +on where a metric originates from as well as its verbosity. -In the grand vision of the `metrics` crate, where everyone uses the crate to emit metrics and then -users get those metrics in their application for free... the biggest problem is that users had no -way to actually filter out metrics they didn't want. Metric metadata aims to provide a solution to -this problem. +In the grand vision of the `metrics` crate, where library authors use it to emit metrics from their +libraries, and then downstream users get those metrics in their application for free... the biggest +problem is that users had no way to actually filter out metrics they didn't want. Metric metadata +aims to provide a solution to this problem. A new type `Metadata<'a>` has been added to track all of this information, which includes **module path**, a **target**, and a **level**. These fields map almost directly to @@ -73,8 +73,8 @@ provided automatically, as well. ### Macros overhaul -In this release, we've reworked the macros to both simplify their implementation and to hopefully -provide a more ergonomic experience for users. +We've reworked the macros to both simplify their implementation and to hopefully provide a more +ergonomic experience for users. At a high level, we've: @@ -128,6 +128,82 @@ help with build times, even if only slightly. [filter_layer_docs]: https://docs.rs/metrics-util/latest/metrics_util/layers/struct.FilterLayer.html +### Scoped recorders + +We've added support for scoped recorders, which should allow both library authors writing exporters, +as well as downstream users of `metrics`, test their implementations far more easily. + +#### Global recorder + +Prior to this release, the only way to test either exporters or metrics emission was to install a +special debug/test recorder as the global recorder. This conceptually works, but quickly runs into a +few issues: + +- it's not thread-safe (the global recorder is, well, global) +- it requires the recorder be _implemented_ in a thread-safe way + +This meant that in order to safely do this type of testing, users would have to use something like +[`DebuggingRecorder`](https://docs.rs/metrics-util/latest/metrics_util/debugging/struct.DebuggingRecorder.html) +(which is thread-safe and _could_ be used in a per-thread way, mind you) but that they would have to +install it for every single test... which could still run into issues with destroying the collected +metrics of another concurrently running test that took the same approach. + +All in all, this was a pretty poor experience that required many compromises and _still_ didn't +fully allow testing metrics in a deterministic and repeatable way. + +#### Scoped recorders + +Scoped recorders solve this problem by allowing the temporary overriding of what is considered the +"global" recorder on the current thread. We've added a new method, +[`metrics::with_local_recorder`](https://docs.rs/metrics/latest/metrics/fn.set_boxed_recorder.html), +that allows users to pass a reference to a recorder that is used as the "global" recorder for the +duration of a closure that the user also passes. + +Here's a quick example of what the prior approach looked like, and how it's been simplified by +adding support for scoped recorders: + +```rust +// This is the old approach, which mind you, still isn't thread-safe if you have concurrent tests +// doing the same thing: +let global = DebuggingRecorder::per_thread(); +let snapshotter = global.snapshotter(); + +unsafe { metrics::clear_recorder(); } +global.install().expect("global recorder should not be installed"); + +increment_counter!("my_counter"); + +let snapshot = snapshotter.snapshot(); +assert_eq!(snapshot.into_vec().len(), 1); + +// Now let's use a scoped recorder instead: +let scoped = DebuggingRecorder::new(); +let snapshotter = scoped.snappshotter(); + +metrics::with_local_recorder(&scoped, || { + increment_counter!("my_counter") +}); + +let snapshot = snapppshotter.snapshot(); +assert_eq!(snapshot.into_vec().len(), 1); +``` + +There's a lot of boilerplate here, but let's look specifically at what we _don't_ have to do +anymore: + +- unsafely clear the global recorder +- install as the global recorder +- transfer ownership of the recorder itself + +This means that recorders can now themselves hold references to other resources, without the need to +do the common `Arc>` dance that was previously required. Given the interface of +`Recorder`, interior mutability still has to be provided somehow, although now it only requires the +use of something as straightforward as `RefCell`. + +Beyond testing, this also opens up additional functionality that wasn't previously available. For +example, this approach could be used to avoid the need for using thread-safe primitives when users +don't want to pay that cost (perhaps on an embedded system). + ## [0.21.1] - 2023-07-02 - No notable changes.