Optional ENV vars? Or supplying default values?

I’m struggling with what I thought would be simple to set up. In a nutshell, I want to define a couple ENV variables that are optional – i.e. they may or may not exist. The one in question is for controlling the logging level. In my config/config.ex I have something like this:

config :logger, :console,
   format: "$time $metadata[$level] $message\n",
   metadata: [:request_id],
   level: System.get_env("APP_LOG_LEVEL")

This appears to work so long as I have set the “APP_LOG_LEVEL” ENV. However, if I don’t set that variable, things seem to fall apart.

I would prefer to have a default logging level set inside the app and then let environments (or other developers) modify it if desired. I thought that the Application.get_env() might help, but its values don’t seem to update when the ENV is updated.

Hope that makes sense. Can someone point me in a better direction? Thanks!

level = System.get_env("APP_LOG_LEVEL") || "info"

Also, the config file will grab the ENV variable at compile time, so it won’t update while it’s running.

You could have a module that would update application vars during runtime based on ENV vars if you wanted to.

2 Likes

Hmm… ok, I must have screwed up something else, because I tried that syntax in my config/config.exs:

config :logger, :console,
       format: "$message\n",
       level: System.get_env("APP_LOG_LEVEL") || "info",
       metadata: [:request_id]

It works for a request (maybe 2), but pretty quickly it bombs out with an error like the following:

cannot use Logger, the :logger application is not running

In my mix.exs, the :logger is referenced by the extra_applications (default setup there):
extra_applications: [:logger, :runtime_tools],

I’m working with this JSON logger: https://github.com/bleacherreport/plug_logger_json and based on your response, I think that’s the real problem. Any thoughts on what I might have overlooked? Thanks again.

I may be mistaken, but I think the :level needs to be an atom, and you’re setting it to a string. Try String.to_atom(System.get_env("APP_LOG_LEVEL") || "info")

2 Likes

Nice! That appears to have fixed it. Wow… I would never have guessed that from the errors I was seeing. Oh boy, now it’s obvious I don’t understand the difference between an atom and a string.

1 Like

Think of it this way:

  • String: A binary array.
  • Atom: A key/value hashmap that uses a string as a key at module load time (or some call like String.to_atom/1) to look up an integer and that integer is used ‘as’ the atom (very fast comparisons and so forth). Think of it like a Flyweight String if you come from a C/C++/Java/Whatever world. I even made an atom library in C++ YEARS ago that handles it all at compile-time modeled after what I learned in Erlang nearing 20 years ago (though my pure-compile time version is reversable, and limited in character set, unlike Erlang’s, if I need unbounded sizes I just use a flyweight string again). ^.^
1 Like

Mind. Blown. That’s genius.

So, is compilation smart enough to see which atoms were used and do the necessary conversions during compile time, or do you have to define which atoms (i.e. keys) are available within a given hashmap?

Basically when you load a BEAM file it loads it into the VM, and in that process it parses the literals, when it runs across an atom literal it checks in the atom table for if it already exists, if it does it replaces that atom with that tagged integer value, if not then it creates a new entry with a new incremented id and uses that instead, then repeats. Something like String.to_atom/1 does the same thing. Something like String.to_existing_atom/1 will check and return if it exists, or returns nil if it does not already exist (thus avoiding its creation, which is important as once an atom is added it is never removed, that is why you never ever atomize external input that is not well sanitized first).

If you are curious between communicating BEAM nodes on different systems then they negotiate a table of known atoms between them to do the proper conversions as they send to each other. :slight_smile:

(TL;DR, it’s not during compile-time that this happens, but rather the time it is loaded in to the BEAM VM)