如何让Elixir自动打印未捕获错误的堆栈跟踪信息
Elixir Plug服务未捕获错误自动日志问题
我正在学习Elixir,用Plug搭建一个示例REST服务。最初写的代码里生成随机数的部分有bug,但错误发生时没有有用的排查信息。
初始存在bug的代码
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) # 此行抛出错误 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
后来我手动添加了try/rescue块打印错误信息和堆栈跟踪,才发现Integer.parse会返回包含剩余字符串的元组,这才是问题根源。
添加手动错误捕获后的代码
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
我的问题是:有没有办法让Elixir在遇到未捕获错误、进程终止前自动执行这种错误日志记录操作?
更新:用iex -S mix运行应用,请求/random?max=100时控制台输出以下错误:
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]}]}, []}
解决方案
1. 使用Plug内置的错误处理插件
不需要在每个路由里手动编写try/rescue,Plug提供了Plug.ErrorHandler来统一处理所有未捕获的错误。只需在模块中引入该插件并实现handle_errors/2函数:
defmodule HelloWorld.Web do require Logger use Plug.Router use Plug.ErrorHandler # 引入全局错误处理 plug :match plug :dispatch get "/random" do conn = Plug.Conn.fetch_query_params(conn) # 修正Integer.parse的问题:用模式匹配确保没有剩余字符串,或用Integer.parse!严格验证 {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)) end # 全局错误处理函数,自动捕获所有未处理的错误 def handle_errors(conn, %{reason: reason, stack: stack}) do # 打印错误详情到日志 Logger.error(Exception.format(:error, reason, stack)) # 给客户端返回标准错误响应 conn |> Plug.Conn.put_resp_content_type("text/plain") |> Plug.Conn.send_resp(500, "服务器内部错误") end def child_spec(_) do Plug.Adapters.Cowboy.child_spec( scheme: :http, options: [port: 8080], plug: __MODULE__) end end
2. 开发环境专用调试工具
在开发阶段,可以使用Plug.Debugger插件,它会自动在浏览器中展示详细的错误堆栈、变量信息,无需查看控制台:
defmodule HelloWorld.Web do require Logger use Plug.Router use Plug.ErrorHandler # 仅在开发环境启用调试器 if Mix.env() == :dev do plug Plug.Debugger end plug :match plug :dispatch # 路由和其他函数保持不变... end
3. 应用级全局错误监控
如果需要覆盖整个应用的进程错误日志,可以通过配置Elixir的日志系统,或者使用:logger模块的默认行为。Erlang的:error_logger也能捕获进程终止信息,但对于Web服务场景,Plug的错误处理已经足够高效。
内容的提问来源于stack exchange,提问作者oneself
相关产品推荐
相关产品推荐

