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.
iex|💧|1 ▶ Mix.install([:plug_cowboy])
iex|💧|2 ▶ … # COPY-PASTE OF YOUR CODE
iex|💧|3 ▶ {:ok, pid} = Supervisor.start_link([{Plug.Cowboy, scheme: :http, plug: HelloWorld.Web, options: [port: 4040]}], strategy: :one_for_one)
{:ok, #PID<0.297.0>}
# Go access http://localhost:4040/random/?max=1000
iex|💧|4 ▶
13:37:21.346 [error] #PID<0.403.0> running HelloWorld.Web (connection #PID<0.402.0>, stream id 1) terminated
Server: localhost:4040 (http)
Request: GET /random/?max=1000
** (exit) an exception was raised:
** (ArgumentError) ranges (first..last) expect both sides to be integers, got: 0..{1000, ""}
(elixir 1.14.3) lib/range.ex:193: Range.new/2
iex:11: anonymous fn/2 in HelloWorld.Web.do_match/4
lib/plug/router.ex:246: anonymous fn/4 in HelloWorld.Web.dispatch/2
lib/plug/router.ex:242: HelloWorld.Web.dispatch/2
iex:2: HelloWorld.Web.plug_builder_call/2
(plug_cowboy 2.6.0) lib/plug/cowboy/handler.ex:11: Plug.Cowboy.Handler.init/2
(cowboy 2.9.0)
…
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.
I don't know why you are not seeing the errors in the console, I get similar results to Aleksei. Here's a couple of other pointers.
You can prevent some logic errors at write-time by using elixir-ls and Dialyzer . Here is how your code looks when I open it in my editor:
The warning is a bit cryptic, but it's telling you that max
should be an integer, but it's analysed that max
will either be an {integer, binary}
tuple or :error
.
Is there any way for Elixir to do this automatically when it encounters an uncaught error just before it terminates the process?
If I add use Plug.Debugger
to your router, loading the page produces this:
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.