Make couch_log configurable

This allows to set logging backend and level via config API making
logging configuration a bit closer as it was in CouchDB 1.x.
diff --git a/src/couch_log.app.src b/src/couch_log.app.src
new file mode 100644
index 0000000..efcebec
--- /dev/null
+++ b/src/couch_log.app.src
@@ -0,0 +1,26 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+{application, couch_log, [
+    {description, "CouchDB Log API"},
+    {vsn, git},
+    {modules, [
+        couch_log,
+        couch_log_app,
+        couch_log_config_listener,
+        couch_log_stderr,
+        couch_log_sup
+    ]},
+    {registered, [couch_log_sup]},
+    {applications, [kernel, stdlib, config]},
+    {mod, {couch_log_app, []}}
+]}.
diff --git a/src/couch_log.app.src.script b/src/couch_log.app.src.script
deleted file mode 100644
index aafd368..0000000
--- a/src/couch_log.app.src.script
+++ /dev/null
@@ -1,42 +0,0 @@
-% Licensed under the Apache License, Version 2.0 (the "License"); you may not
-% use this file except in compliance with the License. You may obtain a copy of
-% the License at
-%
-%   http://www.apache.org/licenses/LICENSE-2.0
-%
-% Unless required by applicable law or agreed to in writing, software
-% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-% License for the specific language governing permissions and limitations under
-% the License.
-
-CouchConfig = case filelib:is_file(os:getenv("COUCHDB_CONFIG")) of
-    true ->
-        {ok, Result} = file:consult(os:getenv("COUCHDB_CONFIG")),
-        Result;
-    false ->
-        []
-end.
-
-Backend = case lists:keyfind(couch_log_backend, 1, CouchConfig) of
-    {couch_log_backend, Backend0} ->
-        Backend0;
-    false ->
-        couch_log_stderr
-end.
-
-BackendApps = case lists:keyfind(couch_log_backend_apps, 1, CouchConfig) of
-    {couch_log_backend_apps, {list, Apps}} ->
-        Apps;
-    false ->
-        []
-end.
-
-{application, couch_log, [
-    {description, "CouchDB Log API"},
-    {vsn, git},
-    {modules, [couch_log]},
-    {registered, []},
-    {applications, [kernel, stdlib] ++ BackendApps},
-    {env, [{backend, Backend}]}
-]}.
diff --git a/src/couch_log.erl b/src/couch_log.erl
index b24e5e1..c07f9a4 100644
--- a/src/couch_log.erl
+++ b/src/couch_log.erl
@@ -30,63 +30,93 @@
 -callback emergency(Fmt::string(), Args::list()) -> ok.
 -callback set_level(Level::atom()) -> ok.
 
+-spec level_integer(atom()) -> integer().
+level_integer(debug)             -> 1;
+level_integer(info)              -> 2;
+level_integer(notice)            -> 3;
+level_integer(warning)           -> 4;
+level_integer(error)             -> 5;
+level_integer(critical)          -> 6;
+level_integer(alert)             -> 7;
+level_integer(emergency)         -> 8;
+level_integer(none)              -> 9.
+
+-spec level_to_atom(string() | integer()) -> atom().
+level_to_atom("1")                  -> debug;
+level_to_atom("debug")              -> debug;
+level_to_atom("2")                  -> info;
+level_to_atom("info")               -> info;
+level_to_atom("3")                  -> notice;
+level_to_atom("notice")             -> notice;
+level_to_atom("4")                  -> warning;
+level_to_atom("warning")            -> warning;
+level_to_atom("5")                  -> error;
+level_to_atom("error")              -> error;
+level_to_atom("6")                  -> critical;
+level_to_atom("critical")           -> critical;
+level_to_atom("7")                  -> alert;
+level_to_atom("alert")              -> alert;
+level_to_atom("8")                  -> emergency;
+level_to_atom("emergency")          -> emergency;
+level_to_atom("9")                  -> none;
+level_to_atom("none")               -> none;
+level_to_atom(V) when is_integer(V) -> level_to_atom(integer_to_list(V));
+level_to_atom(V) when is_list(V)    -> notice.
 
 -spec debug(string(), list()) -> ok.
-debug(Fmt, Args) ->
-    {ok, Backend} = get_backend(),
-    catch couch_stats:increment_counter([couch_log, level, debug]),
-    Backend:debug(Fmt, Args).
+debug(Fmt, Args) -> log(debug, Fmt, Args).
 
 -spec info(string(), list()) -> ok.
-info(Fmt, Args) ->
-    {ok, Backend} = get_backend(),
-    catch couch_stats:increment_counter([couch_log, level, info]),
-    Backend:info(Fmt, Args).
+info(Fmt, Args) -> log(info, Fmt, Args).
 
 -spec notice(string(), list()) -> ok.
-notice(Fmt, Args) ->
-    {ok, Backend} = get_backend(),
-    catch couch_stats:increment_counter([couch_log, level, notice]),
-    Backend:notice(Fmt, Args).
+notice(Fmt, Args) -> log(notice, Fmt, Args).
 
 -spec warning(string(), list()) -> ok.
-warning(Fmt, Args) ->
-    {ok, Backend} = get_backend(),
-    catch couch_stats:increment_counter([couch_log, level, warning]),
-    Backend:warning(Fmt, Args).
+warning(Fmt, Args) -> log(warning, Fmt, Args).
 
 -spec error(string(), list()) -> ok.
-error(Fmt, Args) ->
-    {ok, Backend} = get_backend(),
-    catch couch_stats:increment_counter([couch_log, level, 'error']),
-    Backend:error(Fmt, Args).
+error(Fmt, Args) -> log(error, Fmt, Args).
 
 -spec critical(string(), list()) -> ok.
-critical(Fmt, Args) ->
-    {ok, Backend} = get_backend(),
-    catch couch_stats:increment_counter([couch_log, level, critical]),
-    Backend:critical(Fmt, Args).
+critical(Fmt, Args) -> log(critical, Fmt, Args).
 
 -spec alert(string(), list()) -> ok.
-alert(Fmt, Args) ->
-    {ok, Backend} = get_backend(),
-    catch couch_stats:increment_counter([couch_log, level, alert]),
-    Backend:alert(Fmt, Args).
+alert(Fmt, Args) -> log(alert, Fmt, Args).
 
 -spec emergency(string(), list()) -> ok.
-emergency(Fmt, Args) ->
-    {ok, Backend} = get_backend(),
-    catch couch_stats:increment_counter([couch_log, level, emergency]),
-    Backend:emergency(Fmt, Args).
+emergency(Fmt, Args) -> log(emergency, Fmt, Args).
 
--spec set_level(atom()) -> ok.
-set_level(Level) ->
+-spec log(atom(), string(), list()) -> ok.
+log(Level, Fmt, Args) ->
+    case is_active_level(Level) of
+        false -> ok;
+        true ->
+            {ok, Backend} = get_backend(),
+            catch couch_stats:increment_counter([couch_log, level, Level]),
+            apply(Backend, Level, [Fmt, Args])
+    end.
+
+-spec is_active_level(atom()) -> boolean.
+is_active_level(Level) ->
+    CurrentLevel = level_to_atom(config:get("log", "level", "notice")),
+    level_integer(Level) >= level_integer(CurrentLevel).
+
+-spec set_level(atom() | string() | integer()) -> ok.
+set_level(Level) when is_atom(Level) ->
     {ok, Backend} = get_backend(),
-    Backend:set_level(Level).
+    Backend:set_level(Level);
+set_level(Level) ->
+    set_level(level_to_atom(Level)).
 
 -spec get_backend() -> {ok, atom()}.
 get_backend() ->
-    application:get_env(?MODULE, backend).
+    BackendName = "couch_log_" ++ config:get("log", "backend", "stderr"),
+    Backend = list_to_existing_atom(BackendName),  %% yes, we need crash here
+    case erlang:module_loaded(Backend) of
+        true -> {ok, Backend};
+        false -> {ok, couch_log_stderr}
+    end.
 
 -ifdef(TEST).
 
@@ -111,13 +141,16 @@
     }.
 
 setup() ->
+    ok = meck:new(config),
+    ok = meck:expect(config, get,
+        fun("log", "backend", _) -> "eunit";
+           ("log", "level", _)   -> "debug" end),
     meck:new([couch_stats, couch_log_eunit], [non_strict]),
     meck:expect(couch_stats, increment_counter, 1, ok),
-    setup_couch_log_eunit(),
-    application:load(?MODULE),
-    application:set_env(?MODULE, backend, couch_log_eunit).
+    setup_couch_log_eunit().
 
 cleanup(_) ->
+    meck:unload(config),
     meck:unload([couch_stats, couch_log_eunit]).
 
 setup_couch_log_eunit() ->
diff --git a/src/couch_log_app.erl b/src/couch_log_app.erl
new file mode 100644
index 0000000..91a8ecc
--- /dev/null
+++ b/src/couch_log_app.erl
@@ -0,0 +1,24 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_log_app).
+
+-behaviour(application).
+
+-export([start/2, stop/1]).
+
+
+start(_Type, _StartArgs) ->
+    couch_log_sup:start_link().
+
+stop(_State) ->
+    ok.
diff --git a/src/couch_log_config_listener.erl b/src/couch_log_config_listener.erl
new file mode 100644
index 0000000..6dc7ea6
--- /dev/null
+++ b/src/couch_log_config_listener.erl
@@ -0,0 +1,46 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_log_config_listener).
+-vsn(2).
+-behaviour(config_listener).
+
+% public interface
+-export([subscribe/0]).
+
+% config_listener callback
+-export([handle_config_change/5, handle_config_terminate/3]).
+
+subscribe() ->
+    Settings = [
+        {backend, config:get("log", "backend", "stderr")},
+        {level, config:get("log", "level", "notice")}
+    ],
+    ok = config:listen_for_changes(?MODULE, Settings),
+    ok.
+
+handle_config_change("log", "backend", Value, _, Settings) ->
+    {level, Level} = lists:keyfind(level, 1, Settings),
+    couch_log:set_level(Level),
+    {ok, lists:keyreplace(backend, 1, Settings, {backend, Value})};
+handle_config_change("log", "level", Value, _, Settings) ->
+    couch_log:set_level(Value),
+    {ok, lists:keyreplace(level, 1, Settings, {level, Value})};
+handle_config_change(_, _, _, _, Settings) ->
+    {ok, Settings}.
+
+handle_config_terminate(_, stop, _) -> ok;
+handle_config_terminate(_Server, _Reason, State) ->
+    spawn(fun() ->
+        timer:sleep(5000),
+        config:listen_for_changes(?MODULE, State)
+    end).
diff --git a/src/couch_log_sup.erl b/src/couch_log_sup.erl
new file mode 100644
index 0000000..9d69fd0
--- /dev/null
+++ b/src/couch_log_sup.erl
@@ -0,0 +1,27 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_log_sup).
+
+-behaviour(supervisor).
+
+-export([init/1]).
+-export([start_link/0]).
+
+
+start_link() ->
+    supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+
+init([]) ->
+    ok = couch_log_config_listener:subscribe(),
+    {ok, {{one_for_one, 1, 1}, []}}.