We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
Debugging GenServer state with `:sys.get_state`
almirsarajcic
Ever wondered what’s actually inside your GenServer’s state when things go wrong? Instead of adding debug prints or crashing the process, you can peek inside any running GenServer with Elixir’s built-in :sys.get_state/1
.
# Peek inside any GenServer without stopping it
:sys.get_state(MyApp.UserCache)
# => %{users: %{"123" => %User{name: "Alice"}}, last_updated: ~U[2024...]}
# Works with PIDs too
pid = GenServer.whereis(MyApp.GameServer)
:sys.get_state(pid)
# => %{players: ["alice", "bob"], status: :waiting, round: 1}
This is incredibly useful for debugging production issues, understanding why your GenServer isn’t behaving as expected, or just exploring how your application’s state evolves over time.
Beyond basic inspection
You can also replace the state entirely for testing scenarios:
# Temporarily modify state for debugging
new_state = %{users: %{}, cache_hits: 0}
:sys.replace_state(MyApp.UserCache, fn _old_state -> new_state end)
# Or just update part of it
:sys.replace_state(MyApp.GameServer, fn state ->
%{state | status: :game_over}
end)
The :sys
module is part of OTP and works with any process that implements the gen_*
behaviors. This gives you direct access to process internals without stopping or modifying the running code - essential for debugging live systems.
Testing LiveView socket assigns
This becomes incredibly powerful in tests where you need to inspect LiveView socket assigns:
test "user data loads correctly on mount", %{conn: conn} do
{:ok, view, _html} = live(conn, "/profile/123")
# Peek inside the LiveView process to see socket assigns
view_state = :sys.get_state(view.pid)
assigns = view_state.socket.assigns
assert assigns.current_user.id == "123"
refute assigns.loading
end
test "form assigns update after validation", %{conn: conn} do
{:ok, view, _html} = live(conn, "/users/new")
# Submit invalid form
view
|> form("#user-form", user: %{email: "invalid"})
|> render_submit()
# Check the assigns contain validation errors
view_state = :sys.get_state(view.pid)
changeset = view_state.socket.assigns.changeset
refute changeset.valid?
assert changeset.errors[:email]
end
Copy link
copied to clipboard