Passing through the correct mfa to logger

I have a utility module, DebugUtils, which defines the function

def log_inspect(value, message, log_level \\ :debug) do
  Logger.log(level, "#{message}: #{inspect value}"
  value
end

I like to be able to add this utility function in the pipeline to log intermediate computation values, e.g.

  input
  |> some_processing()
  |> DebugUtils.log_inspect("Intermediate result => ", :debug)
  |> some_more_processing()
  |> DebugUtils.log_inspect("Final result => ", :warn)

The logger backend I’m using allows to set log level per module. The issue is that Logger’s should_log logic checks the direct caller’s module name. How to make sure the utility function passes through the caller of log_inspect? At the moment I need to add DebugUtils to the config with :debug level but all I want is to pass through the calling module to the logger for should_log logic and for mfa metadata.

I can’t do it as macro (I think) because inlining will break the piping.

I was thinking maybe log_inspect can use :stacktrace from Process info and the pass [mfa: actual_caller] but not sure it’s the right or most efficient way.

How to make sure the utility function passes through the caller of log_inspect

Make it a macro!

defmacro log_inspect(value, message, log_level \\ :debug) do
  quote do
    require Logger
    Logger.log(unquote(level), "#{unquote message}: #{inspect unquote value}")
    unquote value
  end
end

As I mentioned, it may not work as a macro for piping because it’s multiple statements.

Alright, so let’s modify the macro

defmacro log_inspect(value, message, log_level \\ :debug) do
  quote do
    require Logger
    value = unquote value
    Logger.log(unquote(level), "#{unquote message}: #{inspect value}")
    value
  end
end
1 Like

This works. The only thing extra was to go through every module where DebugUtils is referenced and add require to be able to use the macro.

1 Like

Afaik, if you import module, you shouldn’t require it. And please, mark an answer as solution