Bounds payloads before they are stored (ADR 03): plugins use these helpers
in PhoenixDevToolbar.Plugin.collect/3, which runs inside host processes.
Every function is pure, cheap on small inputs and bounded on large ones.
redact/2 hides the values of sensitive keys (config :phoenix_dev_toolbar, redact: [...], by default keys containing "password", "token" or "secret").
Summary
Functions
Formats an exception, throw or exit as a bounded one line banner.
Exception.format_banner/2 inspects throw and exit reasons without limits.
Inspects a value with the toolbar limits.
Redacted, bounded inspect of any value, at most bytes long.
Replaces the value of sensitive keys with "[REDACTED]".
Caps a SQL string at 8192 bytes.
Formats the first 10 stacktrace frames that do not belong to the
toolbar, to :telemetry or to the extra skip modules and namespaces (an
Elixir module skips itself and every module nested under it).
Caps any text (exception messages, banners) at limit bytes, 2048 by default.
Types
@type changes() :: %{ keys: [term()], values: %{required(term()) => String.t()}, omitted: non_neg_integer() }
Functions
Formats an exception, throw or exit as a bounded one line banner.
Exception.format_banner/2 inspects throw and exit reasons without limits.
iex> PhoenixDevToolbar.Truncate.banner(:error, %RuntimeError{message: "bad"})
"** (RuntimeError) bad"
iex> PhoenixDevToolbar.Truncate.banner(:throw, :oops)
"** (throw) :oops"
Inspects a value with the toolbar limits.
iex> PhoenixDevToolbar.Truncate.inspect_value(Enum.to_list(1..60)) |> String.ends_with?("...]")
true
@spec preview(term(), pos_integer(), [String.t()]) :: String.t()
Redacted, bounded inspect of any value, at most bytes long.
Unlike redact/2, the cost does not depend on the value: at most
200 nodes and 20 items per container are walked,
and whatever is not walked is replaced by "…", so a sensitive key
deep in a large value is cut, never shown.
iex> PhoenixDevToolbar.Truncate.preview(%{user: %{password: "hunter2"}})
~s(%{user: %{password: "[REDACTED]"}})
iex> PhoenixDevToolbar.Truncate.preview(Enum.to_list(1..30))
"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...]"
Replaces the value of sensitive keys with "[REDACTED]".
A key is sensitive when its name (atom or string, case insensitive, the first
256 bytes of it) contains one of keys. Walks maps, structs,
keyword lists, lists and tuples, at most 50 entries per container
and 1000 nodes in total, so the cost does not depend on the value.
Contract: a sensitive key's value is "[REDACTED]"; anything the walk does
not reach is replaced by "…", never returned as is. The rest of a list
or tuple becomes a final "…" element (so does the tail of an improper
list), the rest of a map becomes a "…" => "…" entry, and a struct
with more than 50 fields becomes a plain map with that entry.
Scalars, including large binaries, are returned as they are (they are cheap;
inspect_value/2 bounds their size). Key matching only sees map, struct and
keyword keys: a bare {"password", "x"} tuple outside a list, a charlist key
and a secret inside a string value are not recognised.
iex> PhoenixDevToolbar.Truncate.redact(%{"user" => %{"password" => "hunter2", "name" => "Ada"}})
%{"user" => %{"password" => "[REDACTED]", "name" => "Ada"}}
iex> PhoenixDevToolbar.Truncate.redact([api_token: "abc", page: 1])
[api_token: "[REDACTED]", page: 1]
iex> PhoenixDevToolbar.Truncate.redact({:login, %{password: "hunter2"}})
{:login, %{password: "[REDACTED]"}}
iex> PhoenixDevToolbar.Truncate.redact([%{password: "hunter2"} | :tail])
[%{password: "[REDACTED]"}, "…"]
Caps a SQL string at 8192 bytes.
iex> PhoenixDevToolbar.Truncate.sql("SELECT 1")
"SELECT 1"
iex> PhoenixDevToolbar.Truncate.sql(nil)
nil
@spec stacktrace(Exception.stacktrace() | nil, [module()]) :: [String.t()]
Formats the first 10 stacktrace frames that do not belong to the
toolbar, to :telemetry or to the extra skip modules and namespaces (an
Elixir module skips itself and every module nested under it).
iex> PhoenixDevToolbar.Truncate.stacktrace([{MyApp.Users, :list, 0, [file: ~c"lib/users.ex", line: 3]}])
["lib/users.ex:3: MyApp.Users.list/0"]
iex> PhoenixDevToolbar.Truncate.stacktrace([{Ecto.Repo.Queryable, :all, 3, []}], [Ecto])
[]
iex> PhoenixDevToolbar.Truncate.stacktrace(nil)
[]
@spec string(String.t(), pos_integer()) :: String.t()
Caps any text (exception messages, banners) at limit bytes, 2048 by default.
iex> PhoenixDevToolbar.Truncate.string("boom")
"boom"
iex> "x" |> String.duplicate(3000) |> PhoenixDevToolbar.Truncate.string() |> byte_size()
2060