You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 06:50:26