Getting command-line options into erlang

 

So you have your killer erlang application that possibly could make you millions, but it was written in a test environment. Shoot, how do you change that "on the fly" at the application runtime? There are a many different ways this can be accomplished. This post will go over the basics of this typical issue.

more

Application variables

Application variables must be declared in the .app file for your application. For instance:

{application, killer_app,
 [{description, "The most killer application ever"},
  {modules, []}, {registered,[]},{applications, [kernel,stdlib,sasl]},
  {env, [
    {key, 'value'} % proplist
        ]
...

From here, the key is settable from the command-line simply by passing it (with a little erlang idiom):

erl -pa ./ebin -killer_app key 'new_value'

From within the application, this can be fetched by looking it up:

Value = case application:get_env(killer_app, key) of
  undefined   -> 'default';
  {ok, V}     -> V
end.

Environment variables

Sometimes it's just easier and the application runtime environment requires that variables need to be fetched from an environment variable. These are also super easy to lookup, arguably even easier:

EnvParam = string:to_upper(erlang:atom_to_list('key')),
Value = case os:getenv(EnvParam) of
  false -> Default;
  E -> E
end.

This can obviously be set the standard way an environment variable is set:

KEY='awesome_value' erl -pa ./ebin

Configuration file

Other times I just want to set my configuration in a file and be done with it, so that deployment is only dependent upon a change of the configuration file. An application configuration file is a newline separated set of proplists. For instance, it might look like:

{port, 8080}.
{log_path, "logs/killer_app.log"}.

These are pretty easy to look up as well, but it's important to note that the variables set here must be in the application configuration file as shown above. Fetching these variables might look something like:

Proplists = case file:consult("config/config.cfg") of
  {ok, C} -> C;
  O -> O
end,
Value = proplists:get_value(key, Proplists).

I tend to like more niceties than this, don't you? When fetching from a configuration file, I tend to use a helper:

-module (config).
-include ("killer_app.hrl").
-compile (export_all).

%%--------------------------------------------------------------------
%% Function: Read the config file () -> {ok, Config} | 
%%                                      {error, Reason}
%% Description: Read the configuration data
%%--------------------------------------------------------------------
read() ->
  case read_1(?CONFIG_FILE) of
    {ok, C} -> {ok, C};
    {error, enoent} -> {error, no_file};
    Err -> Err
  end.

read_1(Location) ->
  case file:consult(Location) of
    {ok, C} -> C;
    O -> O
  end.
%%--------------------------------------------------------------------
%% Function: get (Key, Config) -> {error, not_found} |
%%                                {ok, Value}
%% Description: Get the value of a config element
%%--------------------------------------------------------------------
get(Key) -> get(Key, read()).
get(_Key, []) ->
  {error, not_found};
get(Key, [{Key, Value} | _Config]) ->
  {ok, Value};
get(Key, [{_Other, _Value} | Config]) ->
  get(Key, Config).

By using that, I can simply call:

config:get(key).

Finally, I hate to clutter my code with all the funkiness of fetching an application variable, so I tend to use a utility that cleans it up pretty nicely.

-module (apps).

-export ([search_for_application_value/3]).

% Find the application config value
search_for_application_value(Param, Default, App) ->
  case application:get_env(App, Param) of
    undefined         -> search_for_application_value_from_config(Param, Default);
    {ok, undefined}   -> search_for_application_value_from_config(Param, Default);
    {ok, V}    -> V
  end.

search_for_application_value_from_config(Param, Default) ->
    case config:get(Param) of
        {error, _} -> search_for_application_value_from_environment(Param, Default);
        V -> V
    end.

search_for_application_value_from_environment(Param, Default) ->
  EnvParam = string:to_upper(erlang:atom_to_list(Param)),
  case os:getenv(EnvParam) of
    false -> Default;
    E -> E
  end.

Using this, I can simply call and I get built-in defaults for free:

AppDir = apps:search_for_application_value(port, 8080, killer_app),