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 requireing 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