LiveView `debug_errors` analogue for the connected state

Hi everyone,

It would be nice to have an equivalent to the debug_errors configuration option from Phoenix.Endpoint in Phoenix.LiveView for raised errors in the connected state.

The configuration setting’s effect could be as simple as being more vocal in logs about any errors encountered in the connected state.

The following section contains an example of why it can be helpful.


I cornered myself in an infinite reloading loop while working with LiveView.

In short, my code behaves differently in an HTTP mount than on a connected mount. It loads data only on connected mounts.

Alas, the connected mount’s implementation was buggy and raised an Ecto error. The error implements Plug.Exception and results in the 404 plug_status.

LiveView sends a reload command to its clients if connected mounts encounter errors in 400…499 range:

rescue
  exception ->
    status = Plug.Exception.status(exception)

    if status >= 400 and status < 500 do
      GenServer.reply(from, {:error, %{reason: "reload", status: status}})
      {:stop, :shutdown, :no_state}
    else
      reraise(exception, __STACKTRACE__)
    end
end

You can see where this is going:

  • My HTTP mount renders without issues
  • My connected mount raised an exception with a 400…499 plug_status
  • LiveView sends reload to clients
  • LiveView clients happily reload the page and we are back and the step one.

While it’s an expected behavior, it’s nevertheless a frustrating one. One of the reasons is that there are no errors in the logs. They look fine, showing the HTTP mount followed by the connected mount over and over again:

[info] GET /

[debug] Processing with PhoenixPlayground.Router.DelegateLive.index/2
  Parameters: %{}
  Pipelines: [:browser]

[info] Sent 200 in 252µs

[info] CONNECTED TO Phoenix.LiveView.Socket in 26µs
  Transport: :websocket
  Serializer: Phoenix.Socket.V2.JSONSerializer
  Parameters: %{"vsn" => "2.0.0"}

[debug] MOUNT PhoenixPlayground.Router.DelegateLive
  Parameters: %{}
  Session: %{}

Mind that the behavior manifests only with errors that have 400…499 plug_status.

The gist of the behavior looks like this:

Mix.install([
  {:phoenix_playground, "~> 0.1.0"}
])

defmodule MyError do
  defexception plug_status: 400, message: nil
end

defmodule DemoLive do
  use Phoenix.LiveView

  def mount(_params, _session, socket) do
    if connected?(socket), do: raise MyError, message: "oops"

    {:ok, socket}
  end

  def render(assigns) do
    ~H"""
    <p>Page</p>
    """
  end
end

PhoenixPlayground.start(live: DemoLive)

What do you all think?

1 Like

A quick update regarding the issue, or rather a way to avoid it by using LiveView.assign_async.

Firstly, the callback is called only in the connected state. As LiveView.assign_async executes the callback in a separate process, it can’t do much unless it has a stateful connection.

My issue chased this behavior, and LiveView.assign_async gives it for free!

Secondly, the callback’s execution happens in a separate process. Meaning that errors from these processes are treated as regular errors. In other words, they will be reported to Logger.

Thirdly, LiveView.assign_async reports back the results with AsyncResult structure, but you can get even more control leveraging start_async like described here: Arbitrary Async Operations.

Sometimes I happen to inflict upon my live views infinite reload loop as well :slight_smile:

But at the moment I feel logs tend to be so noisy that adding more verbosity may not aid in spotting the problem.

For the specific case you had in mind, your solution using assign_async seems quite appropriate, as you noted it both isolates errors and handles the connected/disconnected cases.

I think my “best” live views tend to converge to a pattern in which mount and handle_params are simple and obvious. mount will unconditionally set all assigns I need to render the template, and handle_params will set all assigns that need to change in response to live patches.

1 Like

Thank you for sharing your thoughts. And you are totally on point regarding log noisiness.

It’s worth bringing some long-term perspective, like you did with deriving a pattern, while logging is just a low-hanging fruit, so to say.