DEV Community

Marcio Lopes de Faria
Marcio Lopes de Faria

Posted on • Edited on

Inspector, a better way to debug Elixir Code

EDIT: Since Elixir 1.14, there is a new macro dbg/2 that substitute the Inspector package.

I have been tinkering with better ways to improve my Elixir workflow and during this journey I realized that I am, somehow, trying to bring things which I'm used with from other environments, the same way a foreign citizen does when living in a different country.

Elixir prizes highly the Developer Experience, so that we have the astounding tools to generate documentation, first quality package management, and an excellent and mature build tool, and a REPL, iex that allows fast experiments, but I think that may I have pushed a lot the expected usage of it, in a not intended way as it was planned by the creators.

We can feel it during a IEx.pry/0 usage, it's a pretty limited tool, if you are accustomed to use Ruby pry for example, but given that Elixir doesn't hold mutable state, maybe it isn't necessary at all, so I started to think about the root cause of my dissatisfaction and I found it.

Debugging

Elixir is blaze fast, and we can notice it clearly when running a test suite with a lot of tests, they run really fast and the maintainers are working hard to make it even faster, so the so called Puts debugging is more than enough.

But what really hurts is to type always and everyplace the same think, IO.inspect/2, passing ad-hoc variables. It's not uncommon to make several calls to IO.inspect/2 during a debugging session, so if we improve it, enhancing the IO.inspect/2?

This is how Inspector was born, a library that makes debugging less annoying. You need only to place it in the calling site, like this:

# If this code is on lib/my_module.ex
defmodule MyModule do
  def my_function(args) do
    awesome_var = :foo
    amazing_var = :bar
    require Inspector; Inspector.i
  end
end

MyModule.my_function(:hey)
Enter fullscreen mode Exit fullscreen mode

And you will see in the stdout:

inspector: %{
  bindings: [
    args: :hey,
    awesome_var: :foo,
    amazing_var: :bar
  ],
  file: "lib/my_module.ex:5",
  location: "MyModule.my_function/1"
}
Enter fullscreen mode Exit fullscreen mode

If you uses Vim or Neovim, like me, you can even make it better with a key-map:

nnoremap <Leader>i orequire Inspector; Inspector.i<esc>
Enter fullscreen mode Exit fullscreen mode

I hight recommend use it running this simple command:

Or with mix test.watch found here

That is it folks. Happy debugging!

Top comments (5)

Collapse
 
cheerfulstoic profile image
Brian Underwood

This is awesome! I wonder if this could be used to do something I've been vaguely thinking about for a long time. I very, very, very often do this to trace a specific variable:

IO.inspect(variable, label: :variable)
Enter fullscreen mode Exit fullscreen mode

I thought it would be nice if you could just do something like inspect_v(variable) and have it do that for me. I didn't realize that binding() gives you a keyword list of the currently scoped variables, so maybe this is something that you could add to inspector?

Collapse
 
marciol profile image
Marcio Lopes de Faria

Yes sure, I'll include it next week when I'll get some free time. Stay tunned

Collapse
 
bartotten profile image
Bart Otten

Always appreciate new projects and ideas! Am I right that IO.inspect(binding()) gives you almost the same without an extra dependency?

Collapse
 
marciol profile image
Marcio Lopes de Faria • Edited

To me, knowing the location and file during the scanning of the inspect output on stdout/logs is a crucial feature, and in addition to how easy it's to just type i(). But people can still use the IO.inspect/2 and their variants without bother to bring another dependency (note, a development time dependency) at their own will.

Collapse
 
mgh profile image
Masoud Ghorbani

This approach is really unique and useful 🦄
Thanks for sharing, I learned something new