defmodule Hibana.Plugins.DevErrorPage do @moduledoc """ Rich error pages for development with stack traces, request info, and code context. ## Usage # Only in dev! plug Hibana.Plugins.DevErrorPage ## Options - `:enabled` - Whether the dev error page rendering is active (default: `true`) """ use Hibana.Plugin import Plug.Conn @impl true def init(opts), do: %{enabled: Keyword.get(opts, :enabled, true)} @impl true def call(conn, %{enabled: true}) do register_before_send(conn, fn conn -> if conn.status >= 500 and not conn.halted do render_error_page(conn) else conn end end) end def call(conn, _), do: conn @doc "Render an exception as a rich HTML error page" def render_exception(conn, kind, reason, stacktrace) do html = build_error_html(kind, reason, stacktrace, conn) conn |> put_resp_content_type("text/html") |> send_resp(500, html) |> halt() end defp render_error_page(conn) do %{conn | resp_body: build_simple_error_html(conn)} end defp build_error_html(kind, reason, stacktrace, conn) do title = exception_title(kind, reason) message = Exception.message(reason) |> html_escape() stack_html = format_stacktrace(stacktrace) request_html = format_request(conn) """
#{conn.method} #{conn.request_path}\nStatus: #{conn.status}
"""
end
defp exception_title(:error, %{__struct__: mod}), do: inspect(mod)
defp exception_title(kind, _), do: to_string(kind)
defp format_stacktrace(stacktrace) do
stacktrace
|> Enum.map(fn
{mod, fun, arity, location} ->
file = Keyword.get(location, :file, ~c"?") |> to_string()
line = Keyword.get(location, :line, 0)
arity_str = if is_list(arity), do: length(arity), else: arity
is_app = not String.contains?(file, "/deps/") and not String.starts_with?(file, "(")
class = if is_app, do: "app", else: "lib"
"""
| Method | #{conn.method} |
| Path | #{conn.request_path} |
| Query | #{conn.query_string} |
| Remote IP | #{conn.remote_ip |> Tuple.to_list() |> Enum.join(".")} |