The package's :telemetry events.
No event carries a plaintext, a key of any kind, an encryption context
value, a :key selector, a partition id, or an Encryptor.Error's
:engine term. No event carries a per-scope dimension unless the vault
opted in with telemetry_scope_ref: true, and then it is the keyed
scope_ref and never the partition id.
Handlers run on the calling process, so a slow handler is a slow encrypt.
This module is the single definition site for the vocabulary (ADR-0006
decision 3) and the only module in lib/ that calls
:telemetry.execute/3. The event set is closed: adding a name, a
measurement, or a metadata key is additive, and removing or renaming one is
a breaking change to a consumer's case and to their dashboards, so it
takes an amendment to that record.
Attaching
:telemetry.attach_many(
"my-app-encryptor",
Encryptor.Telemetry.events(),
&MyApp.Handler.handle/4,
nil
):telemetry detaches a handler that raises, permanently and silently, for
the lifetime of the VM. attach_many/4's detach is total, so one malformed
branch takes every event with it; attaching one handler id per name - which
is what events/0 is for - bounds that to the event that raised.
The events
:start and :stop name a span pair and nothing else. Every other event is
a point event, named in the past tense, so an attach list can be read
without reading the record.
| Event | Kind | Fires |
|---|---|---|
[:encryptor, :vault, :started] | point | a vault's supervisor came up with its configuration frozen |
[:encryptor, :vault, :stopped] | point | Encryptor.Vault.Lifecycle erased the frozen configuration |
[:encryptor, :vault, :start_refused] | point | configuration resolution refused, before any process existed |
[:encryptor, :cache, :recycled] | point | the recycler dropped and restarted the cache child |
[:encryptor, :suspension, :changed] | point | a vault's suspension state changed: see below |
[:encryptor, :encrypt, :start] / [..., :stop] | span | encrypt/2 |
[:encryptor, :decrypt, :start] / [..., :stop] | span | decrypt/2 |
[:encryptor, :rekey, :start] / [..., :stop] | span | rekey/2 |
[:encryptor, :provider, :start] / [..., :stop] | span | one encryption_key/2 or decryption_keys/2 round trip |
The eight span halves - four pairs - are specified by ADR-0006 and emitted
by the paths they instrument (that record's decision 10), all of which are
written. events/0 returns thirteen names: the five point events above and
those eight halves.
Measurements
| Measurement | Unit | On |
|---|---|---|
duration | :native | every span stop, and :recycled |
system_time | :native | every span start and every point event |
size | bytes | [:encryptor, :encrypt, :start] and [:encryptor, :decrypt, :stop] |
candidates | count | [:encryptor, :provider, :stop] on a successful decryption_keys/2 |
count | count | [:encryptor, :suspension, :changed] when outcome is :ok: the scopes in the view after the action |
Metadata
Metadata is an allow-list. No struct rides verbatim: not a
Encryptor.Vault.Config, not a key descriptor, not a keyring, a CMM, a
client, a context map, a ciphertext, a plaintext, an Encryptor.Error or
its :engine term.
| Key | Type | On |
|---|---|---|
vault | module() | every event |
operation | Encryptor.Error.operation/0 | spans, :start_refused |
span_ref | reference() | span halves |
| outcome | :ok | :error | span stops, :recycled, :changed |
| reason_tag | reason_tag/0 | when outcome is :error |
| provider | module() | provider spans |
| callback | :encryption_key \| :decryption_keys | provider spans |
| cache | boolean() | [:encryptor, :vault, :started] |
| profile | :single \| :scoped | [:encryptor, :vault, :started] |
| reference_check | :verified \| :unpinned | [:encryptor, :vault, :started] |
| scope_ref | String.t() | the four span names' halves, only when :telemetry_scope_ref is on |
| action | :suspend | :reinstate | :refresh | [:encryptor, :suspension, :changed] |
| store | module() | [:encryptor, :suspension, :changed]: the store module, never its state or options |
Suspension changes
[:encryptor, :suspension, :changed] fires on the process that changed a
vault's suspension state (ADR-0010 decision 8):
- after every
Encryptor.Vault.suspend/2orEncryptor.Vault.reinstate/2the vault performed, successful or not; - under a shared
:suspension_store, after the refresh that loads the view, after a refresh that changed the view's membership, after a refresh that failed, and after the first refresh that succeeds following a failure.
Until a shared store's view is loaded, the vault denies every scope, so
the refresh that loads it is a change even when the store's set is empty:
it emits action: :refresh, outcome: :ok and the loaded count. That is
the first successful refresh after the vault starts, and again the first
one after a restarted Encryptor.Vault.Lifecycle has recreated the view.
Any other refresh that changed nothing emits nothing, so a healthy vault is
silent between operator actions. The selector is never in the event,
whatever :telemetry_scope_ref says: an operator who needs to know which
scope was suspended reads the store.
The opt-in scope dimension
With telemetry_scope_ref: true, every encrypt, decrypt, rekey and
provider event carries scope_ref - ADR-0003 decision 5's keyed reference
for the scope the call routed to. It is a pseudonym and not an identifier:
it does not contain the scope identifier and cannot be reversed into it.
Anyone holding the vault's reference subkey can re-identify it, by deriving
the reference for a candidate scope and comparing, and so can anyone who
can enumerate or guess your scope identifiers. Telemetry metadata is
forwarded verbatim by handlers you did not write to vendors whose retention
you did not choose. Turning this on is a decision about that, and it is off
by default.
It is vault configuration, refused as true on a :single vault, and the
reference is derived once per operation and threaded through both halves of
the operation span and through the nested provider span's halves. When the
option is off the key is absent from the metadata map rather than
present as nil: a handler tells "this host did not opt in" from any value
by Map.has_key?/2, and a nil in a :telemetry_metrics tag is a
dimension value. Cardinality is your scope count, which is what you asked
for and is your vendor's bill (ADR-0006 amendment A).
Summary
Types
Every metadata key any event may carry, and nothing else.
The metadata tag for a failure.
The operations that open a span pair.
Functions
Every event name this package emits. The single definition site.
The metadata tag for a reason term. Never the term itself.
Types
@type metadata() :: %{ optional(:vault) => module(), optional(:operation) => Encryptor.Error.operation(), optional(:span_ref) => reference(), optional(:outcome) => :ok | :error, optional(:reason_tag) => reason_tag(), optional(:provider) => module(), optional(:callback) => :encryption_key | :decryption_keys, optional(:cache) => boolean(), optional(:profile) => Encryptor.Vault.Config.profile(), optional(:reference_check) => :verified | :unpinned, optional(:scope_ref) => String.t(), optional(:action) => :suspend | :reinstate | :refresh, optional(:store) => module() }
Every metadata key any event may carry, and nothing else.
@type reason_tag() ::
:decrypt_failed
| :vault_not_started
| :missing_config
| :invalid_config
| :unknown_key
| :encryption_context_conflict
| :reserved_context_key
| :key_unavailable
| :invalid_key_descriptor
| :provider_not_started
| :missing_optional_dependency
| :missing_required_context_keys
| :invalid_context_value
| :invalid_selector
| :not_provisionable
| :suspension_store_unavailable
The metadata tag for a failure.
The head of an Encryptor.Error.reason/0, never the term: every member
but :decrypt_failed is a tagged tuple whose second element is caller data
- a selector, a context key, a config path, a module - and that data does not leave the process in a metric. The set is closed, and extends only when the error vocabulary does, which is itself an ADR-gated act (ADR-0001 decision 10). That closure is what makes it safe as a metric dimension: a backend keying on it has a bounded label set no matter what a caller passes.
@type span_name() :: :encrypt | :decrypt | :rekey | :provider
The operations that open a span pair.
Functions
@spec events() :: [[atom(), ...], ...]
Every event name this package emits. The single definition site.
A host attaches against this rather than hand-copying names the record may later extend.
iex> [:encryptor, :cache, :recycled] in Encryptor.Telemetry.events()
true
iex> length(Encryptor.Telemetry.events())
13
@spec reason_tag(Encryptor.Error.reason()) :: reason_tag()
The metadata tag for a reason term. Never the term itself.
iex> Encryptor.Telemetry.reason_tag({:key_unavailable, "acct_9f21"})
:key_unavailable
iex> Encryptor.Telemetry.reason_tag(:decrypt_failed)
:decrypt_failedIt is deliberately not elem(reason, 0): :decrypt_failed is a bare atom,
and a fallthrough that reached elem/2 on a term the record did not
anticipate would either raise inside an emit or leak whatever the term was.