查看源代码 调试
在 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/2
与 binding/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/2
和 Inspect.Opts
。
dbg/2
Elixir v1.14 引入了 dbg/2
。 dbg
类似于 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 访问所有变量,以及代码中的导入和别名。这称为“窥视”。在窥视会话运行期间,代码执行会停止,直到调用 continue
或 next
。请记住,您始终可以使用 iex -S mix TASK
在项目的上下文中运行 iex
。
dbg
调用要求我们更改要调试的代码,并且步进功能有限。幸运的是,IEx 还提供了一个 IEx.break!/2
函数,它允许您在任何 Elixir 代码上设置和管理断点,而无需修改其源代码。
与 dbg
类似,一旦达到断点,代码执行就会停止,直到调用 continue
或 next
。但是,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 上查找名为
erlang
与erlang-nox
的包)。在其他管理器中,您可能需要安装单独的erlang-wx
(或类似命名的)包。正在讨论在未来版本中改进此体验。
以上操作将打开另一个图形用户界面,该界面提供许多窗格,以便完全了解和浏览运行时和您的项目。
我们在 Mix & OTP 指南的动态主管章节 中探讨了观察器在实际项目中的应用。这是 Phoenix 框架用于在一台机器上实现 200 万连接 的调试技术之一。
如果您使用 Phoenix Web 框架,它与 Phoenix LiveDashboard 一起提供,这是一个用于生产节点的 Web 仪表板,提供与观察器类似的功能。
最后,请记住,您也可以通过直接在 IEx 中调用 runtime_info/0
来获得运行时信息的简要概述。
其他工具和社区
我们只是触及了 Erlang VM 提供的功能,例如
除了观察器应用程序外,Erlang 还包括一个
:crashdump_viewer
用于查看崩溃转储与操作系统级跟踪器集成,例如 Linux 跟踪工具包、DTRACE 和 SystemTap
微状态记账 测量运行时在短时间间隔内在几个底层任务中花费的时间
Mix 在
profile
命名空间下附带了许多任务,例如cprof
和fprof
对于更高级的用例,我们推荐优秀的 Erlang in Anger,它可以免费作为电子书获取
祝您调试愉快!