Encryptor.Telemetry (Encryptor v0.4.0)

Copy Markdown View Source

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-tenant dimension unless the vault opted in with telemetry_tenant_ref: true, and then it is the keyed tenant_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.

EventKindFires
[:encryptor, :vault, :started]pointa vault's supervisor came up with its configuration frozen
[:encryptor, :vault, :stopped]pointEncryptor.Vault.Lifecycle erased the frozen configuration
[:encryptor, :vault, :start_refused]pointconfiguration resolution refused, before any process existed
[:encryptor, :cache, :recycled]pointthe recycler dropped and restarted the cache child
[:encryptor, :encrypt, :start] / [..., :stop]spanencrypt/2
[:encryptor, :decrypt, :start] / [..., :stop]spandecrypt/2
[:encryptor, :rekey, :start] / [..., :stop]spanrekey/2
[:encryptor, :provider, :start] / [..., :stop]spanone 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 twelve names: the four point events above and those eight halves.

Measurements

MeasurementUnitOn
duration:nativeevery span stop, and :recycled
system_time:nativeevery span start and every point event
sizebytes[:encryptor, :encrypt, :start] and [:encryptor, :decrypt, :stop]
candidatescount[:encryptor, :provider, :stop] on a successful decryption_keys/2

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.

KeyTypeOn
vaultmodule()every event
operationEncryptor.Error.operation/0spans, :start_refused
span_refreference()span halves
outcome:ok | :errorspan stops, :recycled
reason_tagreason_tag/0when outcome is :error
providermodule()provider spans
callback:encryption_key | :decryption_keysprovider spans
cacheboolean()[:encryptor, :vault, :started]
profile:single | :tenant[:encryptor, :vault, :started]
reference_check:verified | :unpinned[:encryptor, :vault, :started]
tenant_refString.t()the four span names' halves, only when :telemetry_tenant_ref is on

The opt-in tenant dimension

With telemetry_tenant_ref: true, every encrypt, decrypt, rekey and provider event carries tenant_ref - ADR-0003 decision 5's keyed reference for the tenant the call routed to. It is a pseudonym and not an identifier: it does not contain the tenant 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 tenant and comparing, and so can anyone who can enumerate or guess your tenant 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 tenant 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

metadata()

@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(:tenant_ref) => String.t()
}

Every metadata key any event may carry, and nothing else.

reason_tag()

@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

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.

span_name()

@type span_name() :: :encrypt | :decrypt | :rekey | :provider

The operations that open a span pair.

Functions

events()

@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())
12

reason_tag(arg1)

@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_failed

It 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.