pub trait Metrics:
Clone
+ Send
+ Sync
+ 'static {
// Required methods
fn label(&self) -> String;
fn with_label(&self, label: &str) -> Self;
fn with_attribute(&self, key: &str, value: impl Display) -> Self;
fn register<N: Into<String>, H: Into<String>>(
&self,
name: N,
help: H,
metric: impl Metric,
);
fn encode(&self) -> String;
fn with_scope(&self) -> Self;
}Expand description
Interface to register and encode metrics.
Required Methods§
Sourcefn with_label(&self, label: &str) -> Self
fn with_label(&self, label: &str) -> Self
Create a new instance of Metrics with the given label appended to the end
of the current Metrics label.
This is commonly used to create a nested context for register.
Labels must start with [a-zA-Z] and contain only [a-zA-Z0-9_]. It is not permitted for
any implementation to use METRICS_PREFIX as the start of a label (reserved for metrics for the runtime).
Sourcefn with_attribute(&self, key: &str, value: impl Display) -> Self
fn with_attribute(&self, key: &str, value: impl Display) -> Self
Create a new instance of Metrics with an additional attribute (key-value pair) applied
to all metrics registered in this context and any child contexts.
Unlike Metrics::with_label which affects the metric name prefix, with_attribute adds
a key-value pair that appears as a separate dimension in the metric output. This is
useful for instrumenting n-ary data structures in a way that is easy to manage downstream.
Keys must start with [a-zA-Z] and contain only [a-zA-Z0-9_]. Values can be any string.
§Labeling Children
Attributes apply to the entire subtree of contexts. When you call with_attribute, the
label is automatically added to all metrics registered in that context and any child
contexts created via with_label:
context
|-- with_label("orchestrator")
|-- with_attribute("epoch", "5")
|-- counter: votes -> orchestrator_votes{epoch="5"}
|-- counter: proposals -> orchestrator_proposals{epoch="5"}
|-- with_label("engine")
|-- gauge: height -> orchestrator_engine_height{epoch="5"}This pattern avoids wrapping every metric in a Family and avoids polluting metric
names with dynamic values like orchestrator_epoch_5_votes.
Using attributes does not reduce cardinality (N epochs still means N time series). Attributes just make metrics easier to query, filter, and aggregate.
§Family Label Conflicts
When using Family metrics, avoid using attribute keys that match the Family’s label field names.
If a conflict occurs, the encoded output will contain duplicate labels (e.g., {env="prod",env="staging"}),
which is invalid Prometheus format and may cause scraping issues.
#[derive(EncodeLabelSet)]
struct Labels { env: String }
// BAD: attribute "env" conflicts with Family field "env"
let ctx = context.with_attribute("env", "prod");
let family: Family<Labels, Counter> = Family::default();
ctx.register("requests", "help", family);
// Produces invalid: requests_total{env="prod",env="staging"}
// GOOD: use distinct names
let ctx = context.with_attribute("region", "us_east");
// Produces valid: requests_total{region="us_east",env="staging"}§Example
// Instead of creating epoch-specific metric names:
let ctx = context.with_label(&format!("consensus_engine_{}", epoch));
// Produces: consensus_engine_5_votes_total, consensus_engine_6_votes_total, ...
// Use attributes to add epoch as a label dimension:
let ctx = context.with_label("consensus_engine").with_attribute("epoch", epoch);
// Produces: consensus_engine_votes_total{epoch="5"}, consensus_engine_votes_total{epoch="6"}, ...Multiple attributes can be chained:
let ctx = context
.with_label("engine")
.with_attribute("region", "us_east")
.with_attribute("instance", "i1");
// Produces: engine_requests_total{region="us_east",instance="i1"} 42§Querying The Latest Attribute
To query the latest attribute value dynamically, create a gauge to track the current value:
// Create a gauge to track the current epoch
let latest_epoch = Gauge::<i64>::default();
context.with_label("orchestrator").register("latest_epoch", "current epoch", latest_epoch.clone());
latest_epoch.set(current_epoch);
// Produces: orchestrator_latest_epoch 5Then create a dashboard variable $latest_epoch with query max(orchestrator_latest_epoch)
and use it in panel queries: consensus_engine_votes_total{epoch="$latest_epoch"}
Sourcefn register<N: Into<String>, H: Into<String>>(
&self,
name: N,
help: H,
metric: impl Metric,
)
fn register<N: Into<String>, H: Into<String>>( &self, name: N, help: H, metric: impl Metric, )
Register a metric with the runtime.
Any registered metric will include (as a prefix) the label of the current context.
Names must start with [a-zA-Z] and contain only [a-zA-Z0-9_].
Sourcefn encode(&self) -> String
fn encode(&self) -> String
Encode all metrics into a buffer.
To ensure downstream analytics tools work correctly, users must never duplicate metrics
(via the concatenation of nested with_label and register calls). This can be
avoided by using with_label and with_attribute to create new context instances
(ensures all context instances are namespaced).
Sourcefn with_scope(&self) -> Self
fn with_scope(&self) -> Self
Create a scoped context for metrics with a bounded lifetime (e.g., per-epoch
consensus engines). All metrics registered through the returned context (and
child contexts via Metrics::with_label/Metrics::with_attribute) go into
a separate registry that is automatically removed when all clones of the scoped
context are dropped.
If the context is already scoped, returns a clone with the same scope (scopes nest by inheritance, not by creating new independent scopes).
§Uniqueness
Scoped metrics share the same global uniqueness constraint as
Metrics::register: each (prefixed_name, attributes) pair must be unique.
Callers should use Metrics::with_attribute to distinguish metrics across
scopes (e.g., an “epoch” attribute) rather than re-registering identical keys.
§Example
let scoped = context
.with_label("engine")
.with_attribute("epoch", epoch)
.with_scope();
// Register metrics into the scoped registry
let counter = Counter::default();
scoped.register("votes", "vote count", counter.clone());
// Metrics are removed when all clones of `scoped` are dropped.Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.