NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
crates.io · #823 most downloaded on crates.io
A lightweight metrics facade.
Last release 4 months ago
13 May 2026
Ships fairly regularly
a new release about every 3 months
Some releases are documented
notes for 16 of 43 stable releases
4 versions withdrawn
withdrawn after publishing
11 years old
59 releases · first in 2015
chore: Release metrics version 0.24.6
chore: Release metrics version 0.24.6
chore: Release metrics version 0.24.5
One column per quarter.
chore: Release metrics version 0.24.4
chore: Release metrics version 0.24.4
- No notable changes.
- No notable changes.
- No notable changes.
- No notable changes.
Nothing published for this version
- No notable changes.
Nothing published for this version
- No notable changes.
- No notable changes.
- No notable changes.
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
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 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
tracing -- the inspiration for adding this metadata support -- and
provide the ability to:
Metadata<'a> is passed into the Recorder API when registering a metric so that exporters can
capture it and utilize it.
As an example, users may wish to filter out metrics defined by a particular crate because they don't care about them at all. While they might have previously been able to get lucky and simply filter the metrics by a common prefix, this still allows for changes to the metric names to break the filter configuration. If we instead filtered by module path, where we can simply use the crate name itself, then we'd catch all metrics for that crate regardless of their name and regardless of the crate version.
Similarly, as another example, users may wish to only emit common metrics related to operation of their application/service in order to consume less resources, pay less money for the ingest/storage of the metrics, and so on. During an outage, or when debugging an issue, though, they may wish to increase the verbosity of metrics they emit in order to capture more granular detail. Being able to filter by level now provides a mechanism to do so.
First, it needs to be said that nothing in the core metrics crates actually utilizes this
metadata yet. We'll add support in the future to existing layers, such as the
filter layer, in order to take advantage of this support.
With that said, actually setting this metadata is very easy! As a refresher, you'd normally emit metrics with something like this:
metrics::increment_counter!("my_counter");
Now, you can specify the additional metadata attributes as fields at the beginning of the macro call. This applies to all of the "emission" macros for counters, gauges, and histograms:
metrics::increment_counter!(target: "myapp", "my_counter");
metrics::increment_gauge!(level: metrics::Level::DEBUG, "my_gauge", 42.2);
metrics::histogram!(target: "myapp", level: metrics::Level::DEBUG, "my_histogram", 180.1);
These metrics will have the relevant metadata field set, and all of them will get the module path provided automatically, as well.
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:
increment_counter!
for incrementing a counter) and replaced them with one macro per metric typeregister_counter!)Users no longer need to remember the specific macro to use for a given metric operation, such as
increment_gauge! or decrement_gauge!. Instead, if the user knows they're working with a gauge,
they simply call gauge!(...) to get the handle, and chain on a method call to perform the
operation, such as gauge!(...).increment(42.2).
Additionally, because we've condensed the registration macros into the new, simplified macros, the same macro is used whether registering the metric to get a handle, or simply performing an operation on the metric all at once.
Let's look at a few examples:
// This is the old style of registering a metric and then performing an operation on it.
//
// We're working with a counter here.
let counter_handle = metrics::register_counter!("my_counter");
counter_handle.increment(1);
metrics::increment_counter!("my_counter");
// Now let's use the new, simplified macros instead:
let counter_handle = metrics::counter!("my_counter");
counter_handle.increment(1);
metrics::counter!("my_counter").increment(1);
As you can see, users no longer need to know about as many macros, and their usage is consistent whether working with a metric handle that is held long-term, or chaining the method call inline with the macro call. As a benefit, this also means that IDE completion will be better in some situations, as support for autocompletion of method calls is generally well supported, while macro autocompletion is effectively nonexistent.
As part of this rework, the macros have also been rewritten declaratively. While the macro code
itself is slightly more complicated/verbose, it has a major benefit that the metrics-macros crate
was able to be removed. This is one less dependency that has to be compiled, which should hopefully
help with build times, even if only slightly.
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.
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:
This meant that in order to safely do this type of testing, users would have to use something like
DebuggingRecorder
(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 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,
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:
// 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:
This means that recorders can now themselves hold references to other resources, without the need to
do the common Arc<Mutex<...>> 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).
- No notable changes.
- No notable changes.
- No notable changes.
- No notable changes.
- No notable changes.
- No notable changes.
metrics has now switched to "metric handles." This requires a small amount of backstory.
metrics has now switched to "metric handles." This requires a small amount of backstory.
metricsOriginally, metrics started its life as metrics-runtime, which represented a batteries-included
implementation of metrics storage, with both a user-accessible API that allowed directly interacting
with the metrics as well as a mode for being installed as the global recorder for [metrics].
The user-accessible API was most commonly accessed via a helper type called Sink, which could both
create metric type-specific handles -- such as Counter or Gauge -- and could also perform
operations directly, such as increment_counter.
However, users still needed to both create the machinery supplied by metrics-runtime to handle
metrics storage, as well as the actual exporters to access those metrics, or send them to the
downstream system of choice. We ultimately made the decision to bundle the metrics storage
capabilities into exporters directly by moving much of metrics-runtime to
metrics-util as a set of reusable components.
In doing so, though, we lost the ability to access metrics directly, as the metrics facade became
the only way to register or emit metrics, and this could only happen through the opaque macros,
going through the opaque Recorder trait, and so on. This made it easy for all users to simply
interact with the macros, but made it harder to optimize the performance of recording metrics.
With this update, we've switched back to a handle-based model that also works seamlessly with the macros.
The metrics crate now provides concrete handle types for all metrics. The Recorder API has also
been updated to return these handles when registering a metric. Thus, for macros, instead of simply
calling the operation directly on the global recorder, they now register the metric first and call
the operation on the handle. If the metric has already been registered, similar to as before, a
copy of the handle is simply returned and can be operated as normally.
On the flipside, though, users can use directly acquire these handles using the register_* macros.
The macros for adding descriptive information have been renamed to describe_*. Thus, when there
is an opportunity to register metrics and store their handles somewhere for later reuse, users can
use the register_* macros to do so.
In order to provide an mechanism that allows enough flexibility for exporter authors, the design of the handle types was considered in depth.
Simply put, handles represent an incredibly thin layer over compatible implementations of each
metric type. There are now three traits, one for each metric type, that must be implemented to
satisfy being wrapped in the corresponding handle type. Internally, this is wrapped in an Arc<T> to make it usable in multi-threaded scenarios.
Implementations of Counter and Gauge based on atomic integers can be found in
metrics, and an example of Histogram can be found in metrics-util.
In most cases, the new handle-based design will be effectively invisible, with no visible changes to behavior. There is one scenario where changes could be encountered, though: use of layers.
Layers work on a per-metric basis, which means that when a handle is acquired and used directly, any layers that were used no longer have a way to intercept the calls made to handles. When the macros are used, however, a call is always made to "register" the metric, which provides a chance for the layers to intercept and modify things as necessary.
As a concrete example, the metrics-tracing-context layer exists to annotate a metric's set of
labels with fields from the tracing::Span that is currently entered when the metric is updated.
Naturally, a given metric could be emitted while in a various number of different spans, but if a
metric is first registered in a particular span, and then the handle is used from that point on...
only the original span fields will be attached to the metric.
This is an inherent trade-off between providing a way to reduce the overhead of updating a metric to the bare minimum, and providing extensibility via layers.
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →