Ark.Error (ark v0.13.2)

Copy Markdown View Source

Turns error reasons into human-readable text.

Elixir code commonly returns errors as the reason in an {:error, reason} tuple, where reason can be a string, an exception, a changeset, or any term. Ark.Error renders any of these into a message you can log or show, without having to match on the shape first.

iex> Ark.Error.to_string({:error, "database is down"})
"database is down"

to_iodata/2 and to_string/2 accept, among others:

  • a binary message, returned as-is
  • an exception struct, rendered with Exception.message/1
  • an {exception, stacktrace} pair, rendered as a banner
  • a nested {:error, reason} or {:shutdown, reason} tuple
  • an Ecto.Changeset or Ecto.InvalidChangesetError, when Ecto is loaded
  • any other term, rendered with inspect/1

Audiences

Both functions take an audience, either :private (the default) or :public. The private rendering is meant for logs and developers, and falls back to inspect/1 for any term. The public rendering is meant for messages shown to end users:

  • an {exception, stacktrace} pair is rendered with Exception.message/1 instead of the banner

  • a {tag, reason} tuple with an atom tag is rendered as "(tag) " followed by the rendering of reason

  • atoms and numbers are rendered with inspect/1

  • any other term produces "unknown error" while the full term is logged at the :error level

    iex> Ark.Error.to_string({:error, %{secret: "shh"}}, :private) "%{secret: \"shh\"}"

    iex> Ark.Error.to_string({:error, :timeout}, :public) ":timeout"

    iex> Ark.Error.to_string({:error, %{secret: "shh"}}, :public) "unknown error"

    iex> Ark.Error.to_string({:error, {:enoent, "/etc/hosts"}}, :public) "(enoent) /etc/hosts"

Custom error formatting

An error can also be a {module, tag, data} triple, which lets a module render its own errors. When module exports format_reason/3, it is called with tag, data and the audience to produce the message. A format_reason/2 callback without the audience is also supported:

defmodule MyApp.Upload do
  @spec format_reason(term, term, Ark.Error.audience()) :: iodata
  def format_reason(:too_large, size, _audience) do
    "file is too large: #{size} bytes"
  end

  def format_reason(other, data, audience) do
    Ark.Error.format_fallback(__MODULE__, other, data, audience)
  end
end

Ark.Error.to_string({MyApp.Upload, :too_large, 5_000_000})
# => "file is too large: 5000000 bytes"

format_fallback/4 renders any tag the module does not handle, so a single catch-all clause covers every remaining case.

Logging helpers

log_error/2 and debug_error/2 format a reason for the :private audience and send it to Logger at the :error and :debug levels:

require Ark.Error
Ark.Error.log_error({:error, :timeout}, request_id: request_id)

Summary

Functions

Formats error with to_string/2 for the :private audience and logs it at the :debug level.

Renders a {module, tag, data} error that the module does not handle itself.

Formats error with to_string/2 for the :private audience and logs it at the :error level.

Renders an error reason as a binary.

Types

audience()

@type audience() :: :private | :public

Functions

debug_error(error, metadata \\ [])

(macro)

Formats error with to_string/2 for the :private audience and logs it at the :debug level.

Behaves like log_error/2 but logs through Logger.debug/2.

format_fallback(module, tag, data, audience \\ :private)

@spec format_fallback(module(), atom(), term(), audience()) :: binary()

Renders a {module, tag, data} error that the module does not handle itself.

Use this as the catch-all clause of a module's format_reason/3, as shown in Ark.Error. The triple is rendered like any other term for the given audience.

log_error(error, metadata \\ [])

(macro)

Formats error with to_string/2 for the :private audience and logs it at the :error level.

metadata is passed through to Logger.error/2. Require the module first, since this is a macro.

require Ark.Error
Ark.Error.log_error({:error, :timeout}, request_id: request_id)

to_iodata(reason, audience \\ :private)

@spec to_iodata(any(), audience()) :: iodata()

Renders an error reason as iodata/0.

Accepts the shapes listed in Ark.Error. Returning iodata avoids building intermediate strings, which is convenient when the result goes straight to Logger or IO.

iex> IO.iodata_to_binary(Ark.Error.to_iodata({:shutdown, "node left"}))
"(shutdown) node left"

to_string(reason, audience \\ :private)

@spec to_string(any(), audience()) :: binary()

Renders an error reason as a binary.

Same as to_iodata/2, with the result collapsed into a single string.

iex> Ark.Error.to_string({:error, :enoent})
":enoent"