查看源代码 调试

在 Elixir 中有许多调试代码的方法。在本节中,我们将介绍一些最常用的方法。

IO.inspect/2

在调试中,IO.inspect(item, opts \\ []) 非常有用,因为它会返回传递给它的 item 参数,而不会影响原始代码的行为。让我们看一个例子。

(1..10)
|> IO.inspect()
|> Enum.map(fn x -> x * 2 end)
|> IO.inspect()
|> Enum.sum()
|> IO.inspect()

输出

1..10
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
110

如您所见,IO.inspect/2 使您能够在代码中的几乎任何地方“窥视”值,而不会改变结果,这使得它在像上面这样的管道中非常有用。

IO.inspect/2 还提供使用 label 选项装饰输出的功能。该标签将打印在被检查的 item 之前。

[1, 2, 3]
|> IO.inspect(label: "before")
|> Enum.map(&(&1 * 2))
|> IO.inspect(label: "after")
|> Enum.sum

输出

before: [1, 2, 3]
after: [2, 4, 6]

IO.inspect/2binding/0 一起使用也很常见,后者返回所有变量名称及其值。

def some_fun(a, b, c) do
  IO.inspect binding()
  ...
end

some_fun/3 使用 :foo"bar":baz 调用时,它会输出

[a: :foo, b: "bar", c: :baz]

有关函数的更多信息,以及所有支持选项,请分别参阅 IO.inspect/2Inspect.Opts

dbg/2

Elixir v1.14 引入了 dbg/2dbg 类似于 IO.inspect/2,但专门针对调试。它会打印传递给它的值并返回它(就像 IO.inspect/2 一样),但它还会打印代码和位置。

# In my_file.exs
feature = %{name: :dbg, inspiration: "Rust"}
dbg(feature)
dbg(Map.put(feature, :in_version, "1.14.0"))

上面的代码会输出以下内容

[my_file.exs:2: (file)]
feature #=> %{inspiration: "Rust", name: :dbg}
[my_file.exs:3: (file)]
Map.put(feature, :in_version, "1.14.0") #=> %{in_version: "1.14.0", inspiration: "Rust", name: :dbg}

说到 IO.inspect/2,我们提到了它在 |> 管道步骤之间放置时的有用性。 dbg 做得更好:它理解 Elixir 代码,因此它将在管道的每一步打印值。

# In dbg_pipes.exs
__ENV__.file
|> String.split("/", trim: true)
|> List.last()
|> File.exists?()
|> dbg()

这段代码会输出

[dbg_pipes.exs:5: (file)]
__ENV__.file #=> "/home/myuser/dbg_pipes.exs"
|> String.split("/", trim: true) #=> ["home", "myuser", "dbg_pipes.exs"]
|> List.last() #=> "dbg_pipes.exs"
|> File.exists?() #=> true

虽然 dbg 提供了围绕 Elixir 结构的便利,但如果您想执行代码并在调试时设置断点,您将需要 IEx

断点

使用 IEx 时,您可以将 --dbg pry 作为选项传递,以在 dbg 调用所在的位置“停止”代码执行。

$ iex --dbg pry

现在,调用 dbg 会询问您是否要“窥视”现有代码。如果您接受,您将能够直接从 IEx 访问所有变量,以及代码中的导入和别名。这称为“窥视”。在窥视会话运行期间,代码执行会停止,直到调用 continuenext。请记住,您始终可以使用 iex -S mix TASK 在项目的上下文中运行 iex

dbg 调用要求我们更改要调试的代码,并且步进功能有限。幸运的是,IEx 还提供了一个 IEx.break!/2 函数,它允许您在任何 Elixir 代码上设置和管理断点,而无需修改其源代码。

dbg 类似,一旦达到断点,代码执行就会停止,直到调用 continuenext。但是,break!/2 无法访问调试代码中的别名和导入,因为它是在编译后的工件上而不是在源代码上运行。

观察器

对于调试复杂的系统,仅仅跳到代码是不够的。需要了解整个虚拟机、进程、应用程序,以及设置跟踪机制。幸运的是,这可以通过 Erlang 中的 :observer 来实现。在您的应用程序中

$ iex
iex> :observer.start()

缺少依赖项

当使用 iex -S mix 在项目中运行 iex 时,observer 将不可用作为依赖项。要做到这一点,您需要在之前调用以下函数

iex> Mix.ensure_application!(:wx)
iex> Mix.ensure_application!(:runtime_tools)
iex> Mix.ensure_application!(:observer)
iex> :observer.start()

如果上述任何调用失败,以下是可能发生的情况:一些包管理器默认安装一个简化的 Erlang,没有 WX 绑定用于 GUI 支持。在一些包管理器中,您可以用更完整的包替换无头 Erlang(在 Debian/Ubuntu/Arch 上查找名为 erlangerlang-nox 的包)。在其他管理器中,您可能需要安装单独的 erlang-wx(或类似命名的)包。

正在讨论在未来版本中改进此体验。

以上操作将打开另一个图形用户界面,该界面提供许多窗格,以便完全了解和浏览运行时和您的项目。

我们在 Mix & OTP 指南的动态主管章节 中探讨了观察器在实际项目中的应用。这是 Phoenix 框架用于在一台机器上实现 200 万连接 的调试技术之一。

如果您使用 Phoenix Web 框架,它与 Phoenix LiveDashboard 一起提供,这是一个用于生产节点的 Web 仪表板,提供与观察器类似的功能。

最后,请记住,您也可以通过直接在 IEx 中调用 runtime_info/0 来获得运行时信息的简要概述。

其他工具和社区

我们只是触及了 Erlang VM 提供的功能,例如

  • 除了观察器应用程序外,Erlang 还包括一个 :crashdump_viewer 用于查看崩溃转储

  • 与操作系统级跟踪器集成,例如 Linux 跟踪工具包DTRACESystemTap

  • 微状态记账 测量运行时在短时间间隔内在几个底层任务中花费的时间

  • Mix 在 profile 命名空间下附带了许多任务,例如 cproffprof

  • 对于更高级的用例,我们推荐优秀的 Erlang in Anger,它可以免费作为电子书获取

祝您调试愉快!