AuroraMeter. Telemetry
(Aurora Meter v1.0.0)
View Source
The telemetry contract: the event catalogue as data, the tag rules, the redaction helper and the on-demand gauges.
Aurora Meter emits :telemetry events and nothing else. It starts no
exporter, opens no socket and sends nothing anywhere: what a host does with
these events is entirely the host's decision. AuroraMeter.Telemetry.Metrics
offers Telemetry.Metrics presets for hosts that want a reporter, and it is
compiled only when that optional dependency is present.
Why the catalogue is data
events/0 returns one entry per event, with its real measurement and
metadata keys and the closed set of metadata keys that are safe to use as a
metric tag. Documentation drifts from code when the only thing joining
them is a person reading both; AuroraMeter.TelemetryContractTest compares
this catalogue against the emit sites in lib/, against docs/api.md and
against docs/telemetry.md, in both directions, so a new event that is not
documented and a documented event that is not emitted both fail the suite.
Cardinality is the reason for the tag rules
A metric tagged on tenant_key creates one time series per tenant, and one
tagged on any _id creates one per object. Both are unbounded, and the cost
lands on the host's monitoring bill and on its reporter's memory rather than
here, which is exactly why the library must not teach it by example.
tag_allow_list/0 is closed. Every tag a preset uses is one of those names,
and the value set behind each is bounded by something nameable in the source:
| Tag | Bounded by |
|---|---|
:result | the documented outcome set of the operation that emits it |
:kind | AuroraMeter.Schema.CreditTransaction.kinds/0, the retention table allow list, the cluster batch kinds, the span exception kinds |
:state | the state machine that owns the event |
:exporter | the exporter modules a host configures |
:worker | the AuroraMeter.Oban.* module list |
:feature | the host's compiled plan definitions, which is why it is opt-in |
forbidden_tags/0 and forbidden_tag_suffixes/0 are the other half: names
that must never become a tag, whatever a preset believes about them.
The library polices its own presets and can do nothing about a handler a host
writes. That is a real limit and it is stated plainly rather than implied
away: attach whatever you like, but redact/2 is here so you do not have to
invent the rule yourself.
Gauges
Three measurements have no natural event because nothing happens when they
change: how much is buffered, how old the oldest buffered thing is, and how
far behind the cluster is. They are sampled on a timer inside processes that
already exist (AuroraMeter.Store and AuroraMeter.Cluster), every
:metrics_interval milliseconds. Set metrics_interval: 0 to switch the
timers off and drive emit_gauges/0 from telemetry_poller or any other
scheduler.
Handlers attached to a gauge run inside the emitting process. A slow
handler on [:aurora_meter, :store, :gauge] delays flush batch snapshots; do
no I/O in one.
Summary
Types
One entry in the catalogue.
Functions
Emits every gauge once, synchronously, from the caller's process.
Every concrete event name in the catalogue, flattened.
The event catalogue.
The opt-in tag, :feature.
Name suffixes that must never be a metric tag, whatever the rest of the name.
Metadata keys that must never be a metric tag, named one by one.
The most recent gauge sample of each gauge, with how long ago it was taken.
A copy of metadata safe to put in a log line or a span attribute.
The closed set of metadata keys a preset may use as a metric tag.
Whether tag may be used as a metric tag.
Types
@type event_doc() :: %{ event: [atom()], form: :execute | :span | :family, names: [[atom()]], measurements: [atom()], metadata: [atom()], tags: [atom()], description: String.t(), since: String.t(), emitter: module() }
One entry in the catalogue.
:event is the literal name at the emit site and :form is how it is
emitted. A segment computed at the emit site appears as :"<kind>" with
form: :family, and :names then lists the concrete names a handler
attaches to. A form: :span entry emits the :start, :stop and
:exception names under :event, and :names lists all three.
Functions
@spec emit_gauges() :: :ok
Emits every gauge once, synchronously, from the caller's process.
For a host that has set metrics_interval: 0 and drives the gauges from
telemetry_poller or its own scheduler. Each gauge is emitted by the process
that owns the state behind it, so this is a message to AuroraMeter.Store and
another to AuroraMeter.Cluster, not a read of their internals.
A process that is not running contributes no sample, rather than a zero: a gauge that reports zero when nothing is watching is worse than a gauge that reports nothing, because zero looks healthy.
Examples
iex> AuroraMeter.Telemetry.emit_gauges()
:ok
@spec event_names() :: [[atom()]]
Every concrete event name in the catalogue, flattened.
Examples
iex> [:aurora_meter, :credits, :grant] in AuroraMeter.Telemetry.event_names()
true
@spec events() :: [event_doc()]
The event catalogue.
Every entry names measurement and metadata keys that the emitter really
writes. :tags is the subset of :metadata a preset may tag on, before the
opt-in :feature tag is added.
Examples
iex> AuroraMeter.Telemetry.events() |> Enum.find(&(&1.event == [:aurora_meter, :flush])) |> Map.get(:measurements)
[:count, :delta_sum]
@spec feature_tag() :: atom()
The opt-in tag, :feature.
Examples
iex> AuroraMeter.Telemetry.feature_tag()
:feature
@spec forbidden_tag_suffixes() :: [String.t()]
Name suffixes that must never be a metric tag, whatever the rest of the name.
Examples
iex> AuroraMeter.Telemetry.forbidden_tag_suffixes()
["_key", "_id", "_secret", "_token", "_ref"]
@spec forbidden_tags() :: [atom()]
Metadata keys that must never be a metric tag, named one by one.
Examples
iex> :tenant_key in AuroraMeter.Telemetry.forbidden_tags()
true
@spec gauges() :: [%{event: [atom()], measurements: map(), age_ms: non_neg_integer()}]
The most recent gauge sample of each gauge, with how long ago it was taken.
For a reader that wants the figure without emitting one: a dashboard
refreshing every second would otherwise fire a gauge event per refresh per
connected operator, and every handler a host has attached would run inside
AuroraMeter.Store each time.
Each entry is %{event: name, measurements: map, age_ms: integer}. A gauge
whose owning process is not running, or that has never sampled, contributes
no entry rather than a zero-filled one: a gauge that reports zero when
nothing is watching is worse than a gauge that reports nothing, because zero
looks healthy. The same rule as emit_gauges/0.
age_ms is a monotonic span inside this node (AuroraMeter.Clock.monotonic_ms/0).
It is never persisted and never compared across nodes, which is the one
reading that cannot step backwards.
Examples
iex> is_list(AuroraMeter.Telemetry.gauges())
true
A copy of metadata safe to put in a log line or a span attribute.
Everything that identifies a customer, a payment or an object is dropped, and
so is anything whose name ends in one of forbidden_tag_suffixes/0. error
becomes error_class: the exception module, or the outcome tag, and never the
message, which is where a query, a key or a customer's data ends up.
:tenant chooses what happens to tenant_key:
:drop(default) removes it.:digestreplaces it withtenant_digest, the first eight bytes of its SHA-256 in lower-case hex. That is pseudonymous, not anonymous: it is stable, so it still links a person's activity across records, and several regimes treat it as personal data.:rawkeeps it. It is spelled out so that carrying an identifier into a monitoring pipeline is a decision somebody wrote down.
Examples
iex> AuroraMeter.Telemetry.redact(%{tenant_key: "acme", feature: :api_calls})
%{feature: :api_calls}
iex> AuroraMeter.Telemetry.redact(%{tenant_key: "acme"}, tenant: :raw)
%{tenant_key: "acme"}
iex> AuroraMeter.Telemetry.redact(%{error: %RuntimeError{message: "boom"}})
%{error_class: "RuntimeError"}
@spec tag_allow_list() :: [atom()]
The closed set of metadata keys a preset may use as a metric tag.
:feature is not on it. It is offered separately, behind
metrics_feature_label: true, because its bound is the host's plan
definitions and only the host knows how many that is.
Examples
iex> AuroraMeter.Telemetry.tag_allow_list()
[:result, :kind, :exporter, :state, :worker]
Whether tag may be used as a metric tag.
feature_label? says whether the host has turned the opt-in :feature tag
on; with it false, :feature is refused like any other name.
Examples
iex> AuroraMeter.Telemetry.tag_allowed?(:result, false)
true
iex> AuroraMeter.Telemetry.tag_allowed?(:tenant_key, true)
false
iex> AuroraMeter.Telemetry.tag_allowed?(:feature, false)
false