Metrics
Business metrics, declared rather than instrumented. Nobody writes code to fill these in — which is the point, because the person who wants the number is rarely the person who can deploy a change to get it.
# modules/sales/metrics.yaml
metrics:
- name: sales.orders.written
description: Orders created or changed, by the status they ended up in.
entity: sales.Order
on: [create, write]
labels: [status]
- name: sales.orders.cancelled
entity: sales.Order
on: [create, write]
when: status = 'Cancelled'
Three orders later, in whatever the platform team runs:
sales.orders.written_total{status="Placed"} 2
sales.orders.written_total{status="Cancelled"} 1
sales.orders.cancelled_total 1
The fields
| Field | What |
|---|---|
name | Lower case, dot-separated. Namespace it yourself |
entity | Whose commits it counts |
on | create, write, delete. Empty counts nothing |
when | Optional condition on the committed row |
labels | Attributes to break down by. Enums and booleans only |
when uses the same expression language as a row constraint, but evaluated
against the row that just committed rather than compiled into a query. So no
traversal (Customer/tier) and no $currentUser: there is no query to traverse
in, and no request to ask about. Validation refuses both rather than letting
them silently never match.
Counters are emitted after the transaction commits, so a rolled-back write never appears.
Why labels are so restricted
A label value is part of a time series’ identity. Ten statuses is ten series; ten thousand customer names is ten thousand, multiplied by every other label, and again by every replica. The failure is slow, expensive, and lands on the platform team rather than on you.
So a label must be an enum or a boolean — the only types whose value set your
model declares, and therefore the only ones whose cardinality can be checked by
reading the file:
error[M210]: `companyName` is a `string`, which has no bounded set of values
--> modules/sales/metrics.yaml (metrics[0].labels)
help: a label value becomes part of a time series' identity, so only `enum`
and `boolean` attributes may be labels…
The model does not load. That is deliberate: the alternative is discovering it in production, on the day of the traffic that caused it.
When the restriction is in your way
You want a breakdown by an enum on another entity — revenue by customer tier,
counted on Order. Not available: a label cannot traverse. Denormalise the enum
onto the row being written. That is a real cost and it is visible in your model,
which is better than a metrics bill that is not.
You want a breakdown by something high-cardinality — by customer, by order number. Then the answer is not a metric at all. Every write is already in the audit stream with its entity and row identifier, and that goes to a log backend, which is built for exactly this. Ask your platform team for it.
What is not here yet
Counters only. No gauges, no histograms, and no value: naming an attribute to
sum — so “orders placed” works and “revenue booked” does not. A metric on a
delete can count but cannot label, because there is no row afterwards.