Add config_env/3 and config_env/4

I’d like to propose adding config_env/3 and config_env/4 for conditionally configuring applications based on the current build environment. This would be useful if you wanted to move all runtime config to runtime.exs but still keep a similar structure of various config/*.exs files.

config_env/0 already exists, but I’m personally fine with overloading the name.

Example usage:

config :logger,
  level: :debug,
  format: "$time $metadata[$level] $message\n",
  metadata: [:request_id],
  utc_log: true

config_env :prod, :logger,
  # Do not print debug messages in production.
  level: :info

config_env :test, :logger,
  # Print only warnings and errors during test.
  level: :warn

with a dead simple implementation:

def config_env(env, root_key, opts) do
  if env == Config.config_env() do
    Config.config(root_key, opts)
  end
end

def config_env(env, root_key, key, opts) do
  if env == Config.config_env() do
    Config.config(root_key, key, opts)
  end
end

I think this is much cleaner and more declarative than the alternatives. I also think it also has a nice pattern matching vibe to it.

The alternatives:

  • Nesting per root key:
config_env :logger,
  dev: ...
  test:
  # What's the right name for this or do we fallback to the defaults?
  default: ...
  • Nesting per key:
config :logger,
  level: per_env(
    dev: :info,
    test: :warn
  )
  • Collecting all per-env config togheter:
case Config.config_env() do
  :dev ->
    config ...

  :test ->
    config ...
end

I have all my config settings in the various config files, I didn’t read about a runtime settings file.
What would be the difference and why would I move the logger settings from the config files into the runtime file?

By the way, usually proposals are made on the Google group I think: https://groups.google.com/g/elixir-lang-core

The app has two concepts: build configuration (Mix.env() - :dev, :test and :prod by default) and runtime environment (e.g. local, CI, staging, production). config/config.exs is meant to configure the app based on the build configuration - it’s evaluated at compile time which means that if, for example, you read an environmental variable in config/config.exs, the value will be “baked” into the app. Which on it’s own can be perfectly fine for your use case or setup.

But if you’d like to run the same code (e.g. Docker image) on staging and production but would like to have different logging levels, this means that the logger level has to be configured at runtime.

This section of the forum is meant to serve as a “filter”: How to use the Suggestions & Proposals section

Our original design had something similar but we decided to simply rely on conditionals because it is clearer for everybody. You can define and import additional helpers in your config/runtime.exs though, if you really don’t like the current approach.

2 Likes

Interesting. The more imperative the config becomes, the harder I find it to read and work with. That’s why I like the helpers so much.

:+1:

1 Like

Thank you for the explanations:) that’s interesting.

Good to know! :+1:

A thing that using if allows is configuring multiple envs with similar config.

if config_env() in [:dev, :test] do
  config ...
end

This can be easily handled with config_env too:

config_env [:dev, :test], :logger, ...

But I can see how if (and other constructs) can handle more cases. Still, my point about imperative vs declarative holds.

Also, don’t forget about config_target. If you want to handle a logic clause where env and target are combined, then case/if/cond statements would be much powerful.

1 Like