How do i get the name of variables and its value passed to macro?

I am trying to write custom logger , as I am tired of writing Logger.xyz() and then interpolate and inspect some variable.

Trying to achieve

  • pass any number of variable to the macro
  • macro prints name of variable and its value

why -

  • tired of writing and passing tuples
  • tired of inspect var
  • cant find var name
  • i am aware of binding but dont know how to use it here
    ex:
  def some_func(x, y) do
    z = func(x, y)
    m = funx2(y, z)
    if(m, do: :ok, else: log_error("error happend by", x, y, m, z))
  end

so it prints
error happend by, x: value of x, y: value of y, m: value of m, z: value of z

my custom logger WIP

defmacro log_error(data) do
    quote do
     require Logger
      caller_module = unquote(__CALLER__.module)
      caller_function = unquote(__CALLER__.function)

      Logger.error(
        "[#{caller_module}][#{inspect(caller_function)}]: Error: [[#{inspect(unquote(data))}]]"
      )
    end
  end

That’s not supported in Elixir at all. The only exceptions here are special forms, but they are not written in Elixir. However you can pass a list of variables instead.

Here is an example script:

defmodule Example do
  defmacro log_error(data) do
    # maps list elements returning list of quoted expressions
    ast = Enum.map(data, &quoted_kv/1)

    prefix =
      case __CALLER__ do
        %{function: nil, module: nil} ->
          ""

        %{function: nil, module: module} ->
          "[#{inspect(module)}]: "

        %{function: {function, arity}, module: module} ->
          "[#{inspect(module)}][#{function}/#{arity}]: "
      end

    quote do
      data = Enum.join([unquote_splicing(ast)], ", ")
      Logger.error(unquote(prefix) <> "Error: " <> "[[#{data}]]")
    end
  end

  # each list element is 3-element tuple
  defp quoted_kv({name, _meta, _args} = var) do
    quote do
      "#{unquote(name)}: #{unquote(var)}"
    end
  end
end

prints:

14:28:01.418 [error] [SomeModule][function_name/0]: Error: [[a: 5, b: 10, c: 15]]

Please keep in mind you have to pass all variables directly.

# won't work:
a = 5
b = 10
c = 15
list = [a, b, c]
Example.log_error(list)
# raises error
2 Likes

How can we create a generalized, all scenario one?
should I just pattern match? or use guards?

you can look at how stream_data does it:

For this specific problem I’d recommend solving it with an editor snippet instead of an in-project macro. I have a snippet that does a Logger.info and inspects the variable that I’m interested in.

You can use binding/1 to get the variables from a context and then inspect them. That’s how I solved interpolation in GitHub - elixir-dbvisor/sql: Brings an extensible SQL parser and sigil to Elixir, confidently write SQL with automatic parameterized queries.

2 Likes