Current section

Files

Jump to
ash_age lib telemetry.ex
Raw

lib/telemetry.ex

defmodule AshAge.Telemetry do
@moduledoc false
# Value-free span wrapper for ash_age data-layer operations. Owns the metadata
# allowlist — the single enforcement point for the R7 invariant "no row-level
# or tenant-derived value in telemetry". A call site that tries to emit an
# off-allowlist key (e.g. a tenant-derived `graph`) fails loudly here rather
# than silently shipping tenant identity into span metadata. Level 0: imports
# nothing project-internal.
@allowed_meta_keys ~w(resource multitenancy tenant? stale? properties?
direction row_count batch_size group_count
destination_count depth result rls?)a
@doc "The permitted `:start`/`:stop` metadata keys (the R7 allowlist guard)."
@spec allowed_meta_keys() :: [atom()]
def allowed_meta_keys, do: @allowed_meta_keys
@doc """
Runs `fun` inside `:telemetry.span([:ash_age, op], start_meta, ...)`. `fun`
returns `{result, stop_meta}`; this returns `result`. Every `start_meta` and
`stop_meta` key MUST be in `allowed_meta_keys/0` or an ArgumentError is raised.
"""
@spec span(atom(), map(), (-> {term(), map()})) :: term()
def span(op, start_meta, fun) when is_atom(op) and is_map(start_meta) and is_function(fun, 0) do
:telemetry.span([:ash_age, op], validate!(start_meta), fn ->
{result, stop_meta} = fun.()
{result, validate!(Map.merge(start_meta, stop_meta))}
end)
end
@doc "Maps a callback return to a value-free `:ok | :error` tag."
@spec result_tag(term()) :: :ok | :error
def result_tag({:error, _}), do: :error
def result_tag(_), do: :ok
@doc false
def validate!(meta) when is_map(meta) do
case Map.keys(meta) -- @allowed_meta_keys do
[] ->
meta
bad ->
raise ArgumentError,
"telemetry metadata keys #{inspect(bad)} are not in the value-free allowlist " <>
"#{inspect(@allowed_meta_keys)} (R7: no row-level or tenant-derived value)"
end
end
end