metrics
A lightweight metrics facade.
0.24.6
101M downloads/mo
#802 most downloaded on crates.io
metrics-rs/metrics
What this package is like to depend on
Last release 3 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
4 releases in the last 12 months
see the full history below
Release timeline
59 releases · Sep 2015 to May 2026Releases
latest 59-
0.24.613 May 2026 -
0.24.529 Apr 2026 withdrawn -
0.24.428 Apr 2026 withdrawn -
0.24.328 Nov 2025 -
0.24.220 Apr 2025 -
0.24.126 Nov 2024 -
0.24.012 Oct 2024 -
0.23.116 Apr 2025Nothing published for this version
-
0.23.027 May 2024 -
0.22.416 Apr 2025Nothing published for this version
-
0.22.316 Mar 2024 -
0.22.216 Mar 2024 -
0.22.111 Feb 2024 -
0.22.024 Dec 2023Release notes
Open source →Metric metadata
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
metricscrate, 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 totracing-- the inspiration for adding this metadata support -- and provide the ability to:- group/filter metrics by where they're defined (module path)
- group/filter metrics by where they're emitted from (target)
- group/filter metrics by their verbosity (level)
Metadata<'a>is passed into theRecorderAPI when registering a metric so that exporters can capture it and utilize it.Examples
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.
Usage
First, it needs to be said that nothing in the core
metricscrates 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.
Macros overhaul
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:
- removed all the various macros that were tied to specific operations (e.g.
increment_counter!for incrementing a counter) and replaced them with one macro per metric type - removed the standalone registration macros (e.g.
register_counter!) - exposed the operations as methods on the metric handles themselves
- switched from using procedural macros to declarative macros
Fewer macros, more ergonomic usage
Users no longer need to remember the specific macro to use for a given metric operation, such as
increment_gauge!ordecrement_gauge!. Instead, if the user knows they're working with a gauge, they simply callgauge!(...)to get the handle, and chain on a method call to perform the operation, such asgauge!(...).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.
Declarative macros
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-macroscrate 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.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(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, 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:
- 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<Mutex<...>>dance that was previously required. Given the interface ofRecorder, interior mutability still has to be provided somehow, although now it only requires the use of something as straightforward asRefCell.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.102 Jul 2023 -
0.21.016 Apr 2023 -
0.20.122 Jul 2022 -
0.20.021 Jul 2022 withdrawn -
0.19.030 May 2022 -
0.18.111 Mar 2022 -
0.18.015 Jan 2022Release notes
Open source →Switch to metric handles
metricshas now switched to "metric handles." This requires a small amount of backstory.Evolution of data storage in
metricsOriginally,
metricsstarted its life asmetrics-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 asCounterorGauge-- and could also perform operations directly, such asincrement_counter.However, users still needed to both create the machinery supplied by
metrics-runtimeto 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 ofmetrics-runtimetometrics-utilas a set of reusable components.In doing so, though, we lost the ability to access metrics directly, as the
metricsfacade became the only way to register or emit metrics, and this could only happen through the opaque macros, going through the opaqueRecordertrait, 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.
Handle-based design
The
metricscrate now provides concrete handle types for all metrics. TheRecorderAPI 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 todescribe_*. Thus, when there is an opportunity to register metrics and store their handles somewhere for later reuse, users can use theregister_*macros to do so.Implementation of handles
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
CounterandGaugebased on atomic integers can be found inmetrics, and an example ofHistogramcan be found inmetrics-util.Limitations of handles
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-contextlayer exists to annotate a metric's set of labels with fields from thetracing::Spanthat 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.
-
0.17.116 Dec 2021Nothing published for this version
-
0.17.013 Jul 2021Nothing published for this version
-
0.16.018 May 2021Nothing published for this version
-
0.15.103 May 2021Nothing published for this version
-
0.15.003 May 2021 withdrawnNothing published for this version
-
0.14.213 Feb 2021Nothing published for this version
-
0.14.103 Feb 2021Nothing published for this version
-
0.14.003 Feb 2021Nothing published for this version
-
0.13.124 Jan 2021Nothing published for this version
-
0.13.022 Jan 2021Nothing published for this version
-
0.13.0-alpha.1318 Dec 2020 pre-releaseNothing published for this version
-
0.13.0-alpha.1125 Nov 2020 pre-releaseNothing published for this version
-
0.13.0-alpha.1025 Nov 2020 pre-releaseNothing published for this version
-
0.13.0-alpha.925 Nov 2020 pre-releaseNothing published for this version
-
0.13.0-alpha.824 Oct 2020 pre-releaseNothing published for this version
-
0.13.0-alpha.724 Oct 2020 pre-releaseNothing published for this version
-
0.13.0-alpha.624 Oct 2020 pre-releaseNothing published for this version
-
0.13.0-alpha.504 Oct 2020 pre-releaseNothing published for this version
-
0.13.0-alpha.416 Sep 2020 pre-releaseNothing published for this version
-
0.13.0-alpha.327 Jul 2020 pre-releaseNothing published for this version
-
0.13.0-alpha.222 Jul 2020 pre-releaseNothing published for this version
-
0.13.0-alpha.117 Jun 2020 pre-releaseNothing published for this version
-
0.12.121 Nov 2019Nothing published for this version
-
0.12.018 Oct 2019Nothing published for this version
-
0.11.109 Aug 2019Nothing published for this version
-
0.11.030 Jul 2019Nothing published for this version
-
0.10.312 Jun 2019Nothing published for this version
-
0.10.212 Jun 2019Nothing published for this version
-
0.10.112 Jun 2019Nothing published for this version
-
0.10.011 Jun 2019Nothing published for this version
-
0.9.101 May 2019Nothing published for this version
-
0.9.024 Apr 2019Nothing published for this version
-
0.2.325 May 2017Nothing published for this version
-
0.2.209 Dec 2016Nothing published for this version
-
0.2.003 Sep 2016Nothing published for this version
-
0.1.127 May 2016Nothing published for this version
-
0.1.027 May 2016Nothing published for this version
-
0.0.203 Sep 2015Nothing published for this version