Defining metrics
A definition is a value with no effect and no registry. It is checked when it is written:
| Constructor | Name must | Records | Exposed as |
|---|---|---|---|
Counter |
not end with _total, _seconds, _bytes |
Long |
<name>_total |
Counter.seconds, Counter.bytes |
end with the unit | FiniteDuration, Long |
<name>_total |
Gauge |
not end with _seconds, _bytes |
Double |
<name> |
Gauge.seconds, Gauge.bytes |
end with the unit | FiniteDuration, Long |
<name> |
Histogram.seconds, Histogram.bytes |
end with the unit | FiniteDuration, Long |
_bucket, _sum, _count |
Histogram.sizes |
not end with _seconds, _bytes |
Long |
_bucket, _sum, _count |
Info |
not end with _seconds, _bytes |
its labels | <name>_info, always 1 |
On top of that, every name is snake case with no __, help is never empty, and none of the suffixes the exposition
adds (_bucket, _count, _sum, _created, _info, _total) can be written by hand. The compiler says why:
Invalid metric name "processor_items_total": it must not end with _total: every counter is exposed with _total addedBuckets are checked the same way (Buckets(0.1, 0.5, 1): positive, strictly increasing). Computed ones go through
Buckets.from(seq): Either[MetricError, Buckets]. Buckets.Fast, Buckets.Normal and Buckets.Sizes are the sets the
services already use. Every histogram is registered classicOnly.
Labels
Labels are added with .labelled[L]. They always come from a closed set:
enum Source derives LabelValue:
case Kafka, Rabbit
final case class ByOutcome(outcome: Outcome, source: Source) derives Labels
val bySource = Counter("processor_source_items", "Items by outcome and source").labelled[ByOutcome]
val skipped = Counter("processor_skipped", "Skipped items by reason").labelled[(reason: SkipReason)]derives LabelValueworks for an enum whose cases take no parameters. The value is the case name in snake case:NotFoundbecomesnot_found. An enum that carries its own string says so instead:given LabelValue[Level] = LabelValue.enumerated(Level.values)(_.value).derives Labelsworks for case classes, and named tuples need nothing at all. Label names are the field names in snake case.- A single label needs its name:
given Labels[Outcome] = Labels.single("outcome"). A label name is never guessed from a type name. - A value bounded by its type but not enumerable, such as a provider configured at startup, is
LabelValue.bounded(render). Its series appear as they are used. - There is no
LabelValue[String]. An id, a path or an error message would be a new series forever.
When every label is enumerated, registration creates every combination at zero. A product larger than
MetricsConfig.preCreateLimit (512 by default) fails registration with MetricError.DomainTooLarge, unless the
definition says .withoutPreCreation. Registration also refuses reserved label names (le, quantile, __*), a
label declared twice and two values that render the same.