I am looking for some extra info and examples on when to use the Logger
with functions instead of binaries. I’ve read that the function version is lazily evaluated and useful when the log statement could be ignored.
For example, Dave Thomas writes in Programming Elixir 1.2 (chapter 13):
The basic logging functions are Logger.debug, .info, .warn, and .error. Each function takes either a string or a zero-arity function:
Logger.debug "Order total #{total(order)}" Logger.debug fn -> "Order total #{total(order)}" end
Why have the function version? Perhaps the calculation of the order total is expensive. In the first version, we’ll always call it to interpolate the value into our string, even if the runtime log level is set to ignore debug-level messages. In the function variant, though, the total function will be invoked only if the log message is needed.
This seems to contradict other things I’ve learned as I’m getting more familiar with the language.
For example I’m aware that Logger.info/2
and friends are implemented as macros and the fact that, even though originally it was to get the caller metadata, this allows to remove logging calls that would not be used with the current log level (depending on the value compile_time_purge_level
).
Now, since the interface is already made of macros, the binary messages should be lazily evaluated. That is, if Logger.info/2
was a function, the arguments would be evaluated first. Since it’s a macro that returns a quoted expression, the argument will be evaluated later. More precisely, when we hand it over to the Logger.bare_log/3
function.
What’s the purpose of using a function, then? Is it to keep the evaluation lazy even when we change the log level at runtime? How often does that happen though? I’d expect that the expensive log calls will have been already purged through compile_time_purge_level
.