In most of my modules, I
require Logger
and then do some logging. In modules where I don't, I often make a change that includes adding some logging, and then I have to mark that I'm using the logger.
What's the cost of require
ing the Logger in every single module? Is there a clean way to just always require
the Logger without mentioning it in every module?
The main purpose of require
is to provide an access to macros declared in the required module:
Public functions in modules are globally available, but in order to use macros, you need to opt-in by requiring the module they are defined in.
— from docs forKernel.SpecialForms.require/2
Also, from Elixir guide:
Macros are lexical: it is impossible to inject code or macros globally. In order to use a macro, you need to explicitly require or import the module that defines the macro.
Emphasis is mine. That is done on purpose of your own safety: unlike functions, macros might drastically modify your existing AST. Logger
is a perfect example of this behaviour: it does not leave any trail in the AST when the level is less than the respective compile_time_purge_level
specified in config. No single noop. None. Nihil.
That is basically why Logger
is implemented as a macro.
There are two approaches to overcome this behaviour.
1. Simple. Not recommended.
Make your own wrapper of Logger
as a function:
defmodule Log do
require Logger
def log(whatever), do: Logger.log(whatever)
end
Log.log
is now available everywhere (because it’s a function.)
Drawbacks:
noop
calls to Log.log
left in the code on debug
level. That might impact the performance.Logger
has to be a macro) will be rendered useless — you can't fetch the line number, module, etc of the log-line because they'll always give the site of the macro invocation (credits @NathanielWaisbrot)2. Correct. Recommended.
Make your own MyApp.Utils
module, which nevertheless exists in every single project slightly more complicated than todo-or-tictactoe-exercise. In this module, save for all your utility functions etc, implement the macro callback __using__
:
defmodule MyApp.Utils do
defmacro __using__(opts \\ []) do
quote do
require Logger
require MyApp.Utils # if this module has macros itself
def helper, do: Logger.log("Hey, I am always included")
...
end
...
end
end
Yes, now in all your modules using Logger
you have to use MyApp.Utils
explicitly, but since it nevertheless contains handy helpers that are used application-wide, the Logger
itself is required for free.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With