I am currently learning Elixir and setting up a toy REST service example using Plug. Here is the code that I wrote initially. It has a bug on the row that generates a random number. I was able to figure out the issue. However, when the error occurs there's no helpful info to figure out what the problem is.
defmodule HelloWorld.Web do
use Plug.Router
plug :match
plug :dispatch
get "/random" do
conn = Plug.Conn.fetch_query_params(conn)
max = Integer.parse(Map.fetch!(conn.params, "max"))
r = Enum.random(0..max) # This throws an error
conn
|> Plug.Conn.put_resp_content_type("text/plain")
|> Plug.Conn.send_resp(200, Integer.to_string(r))
end
def child_spec(_) do
Plug.Adapters.Cowboy.child_spec(
scheme: :http,
options: [port: 8080],
plug: __MODULE__)
end
end
To figure out the problem, I had to modify the original and add a try/rescue block and print out the error info and stacktrace, which gave me a meaningful error message. So I was able to figure out that Integer.parse
also returns the remainder.
defmodule HelloWorld.Web do
require Logger
use Plug.Router
plug :match
plug :dispatch
get "/random" do
try do
conn = Plug.Conn.fetch_query_params(conn)
max = Integer.parse(Map.fetch!(conn.params, "max"))
r = Enum.random(0..max)
conn
|> Plug.Conn.put_resp_content_type("text/plain")
|> Plug.Conn.send_resp(200, Integer.to_string(r))
rescue
err ->
Logger.error(Exception.format(:error, err, __STACKTRACE__))
reraise err, __STACKTRACE__
end
end
def child_spec(_) do
Plug.Adapters.Cowboy.child_spec(
scheme: :http,
options: [port: 8080],
plug: __MODULE__)
end
end
Is there any way for Elixir to do this automatically when it encounters an uncaught error just before it terminates the process?
Update: I ran the app as the book suggested by running iex -S mix
. When I curl the URL, I see the following error in the console:
17:35:12.218 [error] #PID<0.386.0> running HelloWorld.Web (cowboy_protocol) terminated
Server: localhost:8080 (http)
Request: GET /random?max=100
** (exit) {%Plug.Conn.WrapperError{conn: %Plug.Conn{adapter: {Plug.Adapters.Cowboy.Conn, :...}, assigns: %{}, body_params: %Plug.Conn.Unfetched{aspect: :body_params}, cookies: %Plug.Conn.Unfetched{aspect: :cookies}, halted: false, host: "localhost", method: "GET", owner: #PID<0.386.0>, params: %{}, path_info: ["random"], path_params: %{}, port: 8080, private: %{plug_route: {"/random", #Function<1.75067752/2 in HelloWorld.Web.do_match/4>}}, query_params: %Plug.Conn.Unfetched{aspect: :query_params}, query_string: "max=100", remote_ip: {127, 0, 0, 1}, req_cookies: %Plug.Conn.Unfetched{aspect: :cookies}, req_headers: [{"mime-version", "1.0"}, {"connection", "keep-alive"}, {"extension", "Security/Digest Security/SSL"}, {"host", "localhost:8080"}, {"accept-encoding", "gzip"}, {"accept", "*/*"}, {"content-length", "0"}], request_path: "/random", resp_body: nil, resp_cookies: %{}, resp_headers: [{"cache-control", "max-age=0, private, must-revalidate"}], scheme: :http, script_name: [], secret_key_base: nil, state: :unset, status: nil}, kind: :error, reason: %ArgumentError{message: "ranges (first..last) expect both sides to be integers, got: 0..{100, \"\"}"}, stack: [{Range, :new, 2, [file: 'lib/range.ex', line: 193]}, {HelloWorld.Web, :"-do_match/4-fun-1-", 2, [file: 'lib/hello_world/web.ex', line: 10]}, {HelloWorld.Web, :"-dispatch/2-fun-0-", 4, [file: 'lib/plug/router.ex', line: 246]}, {:telemetry, :span, 3, [file: '/projects/hello_world/deps/telemetry/src/telemetry.erl', line: 321]}, {HelloWorld.Web, :dispatch, 2, [file: 'lib/plug/router.ex', line: 242]}, {HelloWorld.Web, :plug_builder_call, 2, [file: 'lib/hello_world/web.ex', line: 1]}, {Plug.Adapters.Cowboy.Handler, :upgrade, 4, [file: 'lib/plug/cowboy/handler.ex', line: 18]}, {:cowboy_protocol, :execute, 4, [file: '/projects/hello_world/deps/cowboy/src/cowboy_protocol.erl', line: 442]}]}, []}
It heavily depends on how do you test it. Generally speaking, errors when the process crashes are dumped out to the default
stderr
.This is the excerpt from my
iex
session testing it.You also can test the router as shown in the doc
Plug.Router
, which would also dump the error message.That said, it’s unclear what suppressed it in your environment, but it should have been printed by default.