summaryrefslogtreecommitdiff
path: root/deps
diff options
context:
space:
mode:
authorUlf Wiger <ulf@feuerlabs.com>2015-10-28 21:23:05 +0100
committerUlf Wiger <ulf@feuerlabs.com>2015-11-20 13:46:13 -0800
commit1b44c2448344a10ae63904a796b6211c40a3f212 (patch)
treece8f7dda870a5c454ffbe9e2c0bc7035b34f0f4b /deps
parent34aa86b5a2e97650fe6299ccf794d5eb5d052d91 (diff)
downloadrvi_core-1b44c2448344a10ae63904a796b6211c40a3f212.tar.gz
Lots of changes to make dlink_bt (simulated) and dlink_tls runtime tests pass
* Introduced high-level logging (rvi_log) * Upgraded to new lager version, customized debug output * Thread rvi_log IDs between nodes and components * Introduce simplified protocol for dlink_tls * Use msgpack encoding for dlink_tls * dlink_bt can use TCP instead of Bluetooth for testing purposes * Bug fixes and additions to the test suite
Diffstat (limited to 'deps')
-rw-r--r--deps/ale/.gitignore3
-rw-r--r--deps/ale/README.md153
-rw-r--r--deps/ale/rebar.config7
-rw-r--r--deps/ale/rebar.config.script7
-rw-r--r--deps/ale/src/.gitignore2
-rw-r--r--deps/ale/src/ale.app.src18
-rw-r--r--deps/ale/src/ale.erl437
-rw-r--r--deps/ale/src/ale_srv.erl590
-rw-r--r--deps/ale/src/ale_sup.erl95
-rw-r--r--deps/ale/sys.config39
-rw-r--r--deps/ale/test/.gitignore4
-rw-r--r--deps/ale/test/Makefile34
-rw-r--r--deps/ale/test/ale_SUITE.erl361
-rw-r--r--deps/goldrush/.gitignore10
-rw-r--r--deps/goldrush/LICENSE13
-rw-r--r--deps/goldrush/Makefile37
-rw-r--r--deps/goldrush/README.org237
-rw-r--r--deps/goldrush/priv/edoc.css130
-rw-r--r--deps/goldrush/rebar.config8
-rw-r--r--deps/goldrush/src/glc.erl793
-rw-r--r--deps/goldrush/src/glc_code.erl528
-rw-r--r--deps/goldrush/src/glc_lib.erl409
-rw-r--r--deps/goldrush/src/glc_ops.erl145
-rw-r--r--deps/goldrush/src/goldrush.app.src8
-rw-r--r--deps/goldrush/src/gr_app.erl27
-rw-r--r--deps/goldrush/src/gr_context.erl71
-rw-r--r--deps/goldrush/src/gr_counter.erl268
-rw-r--r--deps/goldrush/src/gr_counter_sup.erl42
-rw-r--r--deps/goldrush/src/gr_manager.erl185
-rw-r--r--deps/goldrush/src/gr_manager_sup.erl47
-rw-r--r--deps/goldrush/src/gr_param.erl269
-rw-r--r--deps/goldrush/src/gr_param_sup.erl48
-rw-r--r--deps/goldrush/src/gr_sup.erl42
-rw-r--r--deps/goldrush/src/gre.erl104
-rw-r--r--deps/lager/.gitignore12
-rw-r--r--deps/lager/.travis.yml11
-rw-r--r--deps/lager/Makefile45
-rw-r--r--deps/lager/README.md552
-rw-r--r--deps/lager/README.org222
-rw-r--r--deps/lager/dialyzer.ignore-warnings5
-rw-r--r--deps/lager/include/lager.hrl58
-rw-r--r--deps/lager/priv/edoc.css130
-rwxr-xr-xdeps/lager/rebarbin108239 -> 150224 bytes
-rw-r--r--deps/lager/rebar.config52
-rw-r--r--deps/lager/src/error_logger_lager_h.erl268
-rw-r--r--deps/lager/src/lager.app.src45
-rw-r--r--deps/lager/src/lager.erl685
-rw-r--r--deps/lager/src/lager_app.erl341
-rw-r--r--deps/lager/src/lager_backend_throttle.erl105
-rw-r--r--deps/lager/src/lager_common_test_backend.erl121
-rw-r--r--deps/lager/src/lager_config.erl87
-rw-r--r--deps/lager/src/lager_console_backend.erl258
-rw-r--r--deps/lager/src/lager_crash_log.erl47
-rw-r--r--deps/lager/src/lager_default_formatter.erl242
-rw-r--r--deps/lager/src/lager_file_backend.erl761
-rw-r--r--deps/lager/src/lager_format.erl171
-rw-r--r--deps/lager/src/lager_handler_watcher.erl95
-rw-r--r--deps/lager/src/lager_handler_watcher_sup.erl4
-rw-r--r--deps/lager/src/lager_mochiglobal.erl107
-rw-r--r--deps/lager/src/lager_msg.erl64
-rw-r--r--deps/lager/src/lager_sup.erl13
-rw-r--r--deps/lager/src/lager_transform.erl442
-rw-r--r--deps/lager/src/lager_trunc_io.erl465
-rw-r--r--deps/lager/src/lager_util.erl549
-rw-r--r--deps/lager/test/compress_pr_record_test.erl16
-rw-r--r--deps/lager/test/crash.erl114
-rw-r--r--deps/lager/test/lager_crash_backend.erl2
-rw-r--r--deps/lager/test/lager_test_backend.erl1296
-rw-r--r--deps/lager/test/pr_nested_record_test.erl19
-rw-r--r--deps/lager/test/special_process.erl8
-rw-r--r--deps/lager/test/trunc_io_eqc.erl58
-rw-r--r--deps/lager/test/zzzz_gh280_crash.erl33
-rw-r--r--deps/lager/tools.mk149
73 files changed, 11027 insertions, 1796 deletions
diff --git a/deps/ale/.gitignore b/deps/ale/.gitignore
new file mode 100644
index 0000000..d588e7a
--- /dev/null
+++ b/deps/ale/.gitignore
@@ -0,0 +1,3 @@
+rel/
+logs/
+*~ \ No newline at end of file
diff --git a/deps/ale/README.md b/deps/ale/README.md
new file mode 100644
index 0000000..e3c7b5e
--- /dev/null
+++ b/deps/ale/README.md
@@ -0,0 +1,153 @@
+ale
+=====
+
+ale, a [lager](https://github.com/basho/lager) extension, makes it possible for several processes to trace the same modules.
+
+
+### Dependencies
+
+To build ale you will need a working installation of Erlang R15B (or
+later).<br/>
+Information on building and installing [Erlang/OTP](http://www.erlang.org)
+can be found [here](https://github.com/erlang/otp/wiki/Installation)
+([more info](https://github.com/erlang/otp/blob/master/INSTALL.md)).
+
+ale is built using rebar that can be found [here](https://github.com/rebar/rebar), with building instructions [here](https://github.com/rebar/rebar/wiki/Building-rebar). rebar's dynamic configuration mechanism, described [here](https://github.com/rebar/rebar/wiki/Dynamic-configuration), is used so the environment variable `REBAR_DEPS` should be set to the directory where your erlang applications are located.
+
+ale also requires [lager](https://github.com/basho/lager) to be installed.
+
+### Downloading
+
+Clone the repository in a suitable location:
+
+```sh
+$ git clone git://github.com/tonyrog/ale.git
+```
+### Functionality
+#### Concepts
+
+ale extends lager by using a server that keeps track of all trace request thus making it possible for several processes to add the same traces while only adding one to lager and likewise not removing it from lager until all processes have removed it.<br/>
+Available api is:
+<ul>
+<li> trace(on | off, ModuleOrPidOrFilter::atom() | pid() | tuple() | list(tuple)), Loglevel::atom()) - prints trace output on console as long as calling process hasn't terminated.</li>
+<li> trace(on | off, ModuleOrPidOrFilter::atom() | pid() | tuple() | list(tuple)), Loglevel::atom(), File::string()) - prints trace output to File as long as calling process hasn't terminated. File must exist.</li>
+<li> trace_gl(on | off, ModuleOrPidOrFilter::atom() | pid() | tuple() | list(tuple)), Loglevel::atom()) - prints trace output on console as long as calling process' group leader hasn't terminated. Suitable for calls from a shell.</li>
+<li> trace_gl(on | off, ModuleOrPidOrFilter::atom() | pid() | tuple() | list(tuple)), Loglevel::atom(), File::string()) - prints trace output to File as long as calling process' group leader hasn't terminated. Suitable for calls from a shell. File must exist.</li>
+</ul>
+Filter is a tuple {tag, Tag} that lager uses to determine what to output. <br/>
+LogLevel is debug | info | notice | warning | error | critical | alert | emergency. <br/>
+See lager documentations for more details.<br/>
+Examples:<br/>
+<code>
+ale:trace(on, sz_master, debug).<br/>
+ale:trace(on, self(), debug).<br/>
+ale:trace_gl(on, sz_node, info, "/tmp/ale.log").<br/>
+ale:trace(off, sz_master, debug)<br/>
+ale:trace(on, [{module, ale}, {function, start}], debug).<br/>
+</code>
+
+There are also some shortcut functions:
+<ul>
+<li> debug(ModuleOrPidOrList::atom() | pid() | tuple() | list(atom())) - calls trace(on, ModuleOrPidOrList, debug).</li>
+<li> info(ModuleOrPidOrList::atom() | pid() | tuple() | list(atom())) - calls trace(on, ModuleOrPidOrList, info).</li>
+<li> error(ModuleOrPidOrList::atom() | pid() | tuple() | list(atom())) - calls trace(on, ModuleOrPidOrList, error).</li>
+<li> warning(ModuleOrPidOrList::atom() | pid() | tuple() | list(atom())) - calls trace(on, ModuleOrPidOrList, warning).</li>
+</ul><br/>
+These can be called with lists of modules.
+
+#### Config Files
+
+Arguments to all applicable erlang applications are specified in an erlang configuration file.<br/>
+Traces can be added to the ale part of the configuration file. These traces will be active as long as ale i running.<br/>
+Example:<br/>
+<code>
+ {init_traces, [<br/>
+ {[{module, sz_master}], debug}, <br/>
+ {[{module, sz_node}], info, "/tmp/ale.log"}<br/>
+ ]}<br/>
+</code>
+
+An example can be found in ["sys.config"](https://github.com/tonyrog/ale/raw/master/sys.config).<br/>
+
+
+#### Tips
+
+If you start tracing using the non-groupleader function calls from the shell you can stop it by 'crashing' the shell, for ex with a=b. Might be handy if you get more output than expected ;-)
+
+### Building
+
+Rebar will compile all needed dependencies.<br/>
+Compile:
+
+```sh
+$ cd ale
+$ rebar compile
+...
+==> ale (compile)
+```
+
+### Running
+
+There is a quick way to run the application for testing:
+
+```sh
+$ erl -sname ale -config sys -pa <path>/ale/ebin
+>ale:start().
+```
+(Instead of specifing the path to the ebin directory you can set the environment ERL_LIBS.)
+
+Stop:
+
+```sh
+>halt().
+```
+
+### Release
+
+To generate a proper release follow the instructions in
+ [Release Handling](https://github.com/basho/rebar/wiki/Release-handling) or look in the [Rebar tutorial](http://www.metabrew.com/article/erlang-rebar-tutorial-generating-releases-upgrades).
+
+<b>Before</b> the last step you have to update the file "ale/rel/files/sys.config" with your own settings.
+You probably also have to update "ale/rel/reltool.config" with the correct path to your application (normally "{lib_dirs, ["../.."]}") and all apps you need.
+```
+ {app, sasl, [{incl_cond, include}]},
+ {app, stdlib, [{incl_cond, include}]},
+ {app, kernel, [{incl_cond, include}]},
+ {app, lager, [{incl_cond, include}]}
+ {app, ale, [{incl_cond, include}]}
+```
+
+
+And then you run:
+```
+$ rebar generate
+```
+
+Before generating a new version of a release the old version should be (re)moved.
+
+Start node:
+
+```sh
+$ cd rel
+$ ale/bin/ale start
+```
+
+(If you want to have access to the erlang node use
+```
+console
+```
+instead of
+```
+start
+```
+.)
+
+### Documentation
+
+ale is documented using edoc. To generate the documentation do:
+
+```sh
+$ cd ale
+$ rebar doc
+```
+You get the resulting html-files in ale/doc subdirectory.
diff --git a/deps/ale/rebar.config b/deps/ale/rebar.config
new file mode 100644
index 0000000..5c774b2
--- /dev/null
+++ b/deps/ale/rebar.config
@@ -0,0 +1,7 @@
+%% -*- erlang -*-
+%% Config file for ale application
+{deps_dir, ["deps"]}.
+{deps, [{lager, ".*"}]}.
+{erl_opts, [debug_info, fail_on_warning]}.
+{sub_dirs, ["src"]}.
+{ct_extra_params, ""}.
diff --git a/deps/ale/rebar.config.script b/deps/ale/rebar.config.script
new file mode 100644
index 0000000..70787ad
--- /dev/null
+++ b/deps/ale/rebar.config.script
@@ -0,0 +1,7 @@
+%% -*- erlang -*-
+case os:getenv("REBAR_DEPS") of
+ false ->
+ CONFIG;
+ Dir ->
+ lists:keystore(deps_dir, 1, CONFIG, {deps_dir, Dir})
+end.
diff --git a/deps/ale/src/.gitignore b/deps/ale/src/.gitignore
new file mode 100644
index 0000000..b92b763
--- /dev/null
+++ b/deps/ale/src/.gitignore
@@ -0,0 +1,2 @@
+depend.mk
+*~
diff --git a/deps/ale/src/ale.app.src b/deps/ale/src/ale.app.src
new file mode 100644
index 0000000..e3bafac
--- /dev/null
+++ b/deps/ale/src/ale.app.src
@@ -0,0 +1,18 @@
+%% -*- erlang -*-
+%%
+%% Application source file for ale
+%%
+{application, ale,
+ [
+ {description, "ALE - a lager extension"},
+ {vsn, git},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib,
+ sasl,
+ lager
+ ]},
+ {mod, { ale, []}},
+ {env, []}
+ ]}.
diff --git a/deps/ale/src/ale.erl b/deps/ale/src/ale.erl
new file mode 100644
index 0000000..2a99727
--- /dev/null
+++ b/deps/ale/src/ale.erl
@@ -0,0 +1,437 @@
+%%%---- BEGIN COPYRIGHT --------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ----------------------------------------------------------
+%%%-------------------------------------------------------------------
+%%% @author Malotte W Lönne <malotte@malotte.net>
+%%% @copyright (C) 2012, Tony Rogvall
+%%% @doc
+%%% ale application.
+%%% A lager extension.
+%%%
+%%% Created : 2012 by Malotte W Lönne
+%%% @end
+%%%-------------------------------------------------------------------
+
+-module(ale).
+
+-behaviour(application).
+
+%% Application callbacks
+-export([start/2,
+ stop/1]).
+
+%% Shortcut API
+-export([start/0,
+ stop/0]).
+
+%% Start/Stop traces
+-export([trace/3,
+ trace_gl/3,
+ trace/4,
+ trace_gl/4,
+ debug/1,
+ debug_gl/1,
+ info/1,
+ info_gl/1,
+ warning/1,
+ warning_gl/1,
+ error/1,
+ error_gl/1,
+ clear/0]).
+
+%% Info requests
+-export([i/0]).
+
+-define(SRV, ale_srv).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts the application.<br/>
+%% Arguments are ignored, instead the options for the application server are
+%% retreived from the application environment (sys.config).
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec start(StartType:: normal |
+ {takeover, Node::atom()} |
+ {failover, Node::atom()},
+ StartArgs::term()) ->
+ {ok, Pid::pid()} |
+ {ok, Pid::pid(), State::term()} |
+ {error, Reason::term()}.
+
+start(_StartType, _StartArgs) ->
+ error_logger:info_msg("~p: start: arguments ignored.\n", [?MODULE]),
+ Opts = case application:get_env(options) of
+ undefined -> [];
+ {ok, O} -> O
+ end,
+ Traces = case application:get_env(init_traces) of
+ undefined -> [];
+ {ok, T} -> T
+ end,
+ Args = [{options, Opts},{init_traces, Traces}],
+ ale_sup:start_link(Args).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Stops the application.
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec stop(State::term()) -> ok | {error, Error::term()}.
+
+stop(_State) ->
+ ok.
+
+-type log_level() :: debug |
+ info |
+ notice |
+ warning |
+ error |
+ critical |
+ alert |
+ emergency.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Controls tracing.
+%% For details see lager documentation.
+%% @end
+%%--------------------------------------------------------------------
+-spec trace(OnOrOff:: on | off,
+ ModuleOrPidOrFilter::atom() |
+ string() |
+ pid() |
+ tuple() |
+ list(tuple()),
+ Level::log_level()) ->
+ ok | {error, Error::term()}.
+
+trace(OnOrOff, ModulOrPidOrFilter, Level) ->
+ trace(OnOrOff, ModulOrPidOrFilter, Level, console).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Controls tracing.
+%% For details see lager documentation.
+%% @end
+%%--------------------------------------------------------------------
+-spec trace(OnOrOff:: on | off,
+ ModuleOrPidOrFilter::atom() |
+ pid() |
+ tuple() |
+ list(tuple()),
+ Level::log_level(),
+ File::string() | console) ->
+ ok | {error, Error::term()}.
+
+trace(OnOrOff, Module, Level, File)
+ when is_atom(OnOrOff), is_atom(Module), is_atom(Level) ->
+ call({trace, OnOrOff, [{module, Module}], Level, self(), File});
+trace(OnOrOff, Pid, Level, File)
+ when is_atom(OnOrOff), is_pid(Pid), is_atom(Level) ->
+ call({trace, OnOrOff, [{pid, pid_to_list(Pid)}], Level, self(), File});
+trace(OnOrOff, Filter, Level, File)
+ when is_atom(OnOrOff), is_tuple(Filter),is_atom(Level) ->
+ call({trace, OnOrOff, [Filter], Level, self(), File});
+trace(OnOrOff, FilterList, Level, File)
+ when is_atom(OnOrOff), is_list(FilterList),is_atom(Level) ->
+ call({trace, OnOrOff, FilterList, Level, self(), File}).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Controls tracing.
+%% This variant uses the groupleader() instead of self() to monitor
+%% client. Suitable for calls from an erlang shell.
+%% For details see lager documentation.
+%% @end
+%%--------------------------------------------------------------------
+-spec trace_gl(OnOrOff:: on | off,
+ ModuleOrPidOrFilter::atom() |
+ string() |
+ pid() |
+ tuple() |
+ list(tuple()),
+ Level::log_level()) ->
+ ok | {error, Error::term()}.
+
+trace_gl(OnOrOff, Module, Level) ->
+ trace_gl(OnOrOff, Module, Level, console).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Controls tracing.
+%% This variant uses the groupleader() instead of self() to monitor
+%% client. Suitable for calls from an erlang shell.
+%% For details see lager documentation.
+%% @end
+%%--------------------------------------------------------------------
+-spec trace_gl(OnOrOff:: on | off,
+ ModuleOrPidOrFilter::atom() |
+ pid() |
+ tuple() |
+ list(tuple()),
+ Level::log_level(),
+ File::string() | console) ->
+ ok | {error, Error::term()}.
+
+trace_gl(OnOrOff, Module, Level, File)
+ when is_atom(OnOrOff), is_atom(Module), is_atom(Level) ->
+ call({trace, OnOrOff, [{module, Module}], Level, group_leader(), File});
+trace_gl(OnOrOff, Pid, Level, File)
+ when is_atom(OnOrOff), is_pid(Pid), is_atom(Level) ->
+ call({trace, OnOrOff, [{pid, pid_to_list(Pid)}], Level,
+ group_leader(), File});
+trace_gl(OnOrOff, Filter, Level, File)
+ when is_atom(OnOrOff), is_tuple(Filter), is_atom(Level) ->
+ call({trace, OnOrOff, [Filter], Level, group_leader(), File});
+trace_gl(OnOrOff, FilterList, Level, File)
+ when is_atom(OnOrOff), is_list(FilterList), is_atom(Level) ->
+ call({trace, OnOrOff, FilterList, Level, group_leader(), File}).
+
+
+call({trace, _OnOrOff, _FilterList, _Level, _Client, console} = Trace) ->
+ gen_server:call(?SRV, Trace);
+call({trace, _OnOrOff, _FilterList, _Level, _Client, File} = Trace)
+ when is_list(File) ->
+ %% Do we want this check ??
+ case filelib:is_regular(File) of
+ true ->
+ gen_server:call(?SRV, Trace);
+ false ->
+ {error, non_existing_file}
+ end.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Lists existing traces and clients.
+%% @end
+%%--------------------------------------------------------------------
+-spec i() -> list(tuple()).
+
+i() ->
+ gen_server:call(?SRV, i).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Shortcut to trace(on, X, debug).
+%% For details see {@link trace/3}.
+%% Can be called with a list of modules.
+%% @end
+%%--------------------------------------------------------------------
+-spec debug(ModuleOrPidOrList::atom() |
+ string() |
+ pid() |
+ tuple() |
+ list(atom())) ->
+ ok | {error, Error::term()}.
+
+debug(ModulOrPidOrList) ->
+ trace_i(ModulOrPidOrList, debug).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Shortcut to trace_gl(on, X, debug).
+%% For details see {@link trace/3}.
+%% Can be called with a list of modules.
+%% @end
+%%--------------------------------------------------------------------
+-spec debug_gl(ModuleOrPidOrList::atom() |
+ string() |
+ pid() |
+ tuple() |
+ list(atom())) ->
+ ok | {error, Error::term()}.
+
+debug_gl(ModulOrPidOrList) ->
+ trace_i(ModulOrPidOrList, debug, gl).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Shortcut to trace(on, X, info).
+%% For details see {@link trace/3}.
+%% Can be called with a list of modules.
+%% @end
+%%--------------------------------------------------------------------
+-spec info(ModuleOrPidOrList::atom() |
+ string() |
+ pid() |
+ tuple() |
+ list(atom())) ->
+ ok | {error, Error::term()}.
+
+info(ModulOrPidOrList) ->
+ trace_i(ModulOrPidOrList, info).
+%%--------------------------------------------------------------------
+%% @doc
+%% Shortcut to trace_gl(on, X, info).
+%% For details see {@link trace/3}.
+%% Can be called with a list of modules.
+%% @end
+%%--------------------------------------------------------------------
+-spec info_gl(ModuleOrPidOrList::atom() |
+ string() |
+ pid() |
+ tuple() |
+ list(atom())) ->
+ ok | {error, Error::term()}.
+
+info_gl(ModulOrPidOrList) ->
+ trace_i(ModulOrPidOrList, info, gl).
+
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Shortcut to trace(on, X, warning).
+%% For details see {@link trace/3}.
+%% Can be called with a list of modules.
+%% @end
+%%--------------------------------------------------------------------
+-spec warning(ModuleOrPidOrList::atom() |
+ string() |
+ pid() |
+ tuple() |
+ list(atom())) ->
+ ok | {error, Error::term()}.
+
+warning(ModulOrPidOrList) ->
+ trace_i(ModulOrPidOrList, warning).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Shortcut to trace_gl(on, X, warning).
+%% For details see {@link trace/3}.
+%% Can be called with a list of modules.
+%% @end
+%%--------------------------------------------------------------------
+-spec warning_gl(ModuleOrPidOrList::atom() |
+ string() |
+ pid() |
+ tuple() |
+ list(atom())) ->
+ ok | {error, Error::term()}.
+
+warning_gl(ModulOrPidOrList) ->
+ trace_i(ModulOrPidOrList, warning, gl).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Shortcut to trace(on, X, error).
+%% For details see {@link trace/3}.
+%% Can be called with a list of modules.
+%% @end
+%%--------------------------------------------------------------------
+-spec error(ModuleOrPidOrList::atom() |
+ string() |
+ pid() |
+ tuple() |
+ list(atom())) ->
+ ok | {error, Error::term()}.
+
+error(ModulOrPidOrList) ->
+ trace_i(ModulOrPidOrList, error).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Shortcut to trace_gl(on, X, error).
+%% For details see {@link trace/3}.
+%% Can be called with a list of modules.
+%% @end
+%%--------------------------------------------------------------------
+-spec error_gl(ModuleOrPidOrList::atom() |
+ string() |
+ pid() |
+ tuple() |
+ list(atom())) ->
+ ok | {error, Error::term()}.
+
+error_gl(ModulOrPidOrList) ->
+ trace_i(ModulOrPidOrList, error, gl).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Removes all traces for all clients.
+%% @end
+%%--------------------------------------------------------------------
+-spec clear() -> ok.
+
+clear() ->
+ gen_server:call(?SRV, clear).
+
+
+%%--------------------------------------------------------------------
+%% Test functions
+%%--------------------------------------------------------------------
+%% @private
+start() ->
+ app_ctrl([kernel, stdlib, compiler, syntax_tools, sasl, lager, ale],start).
+
+%% @private
+stop() ->
+ app_ctrl([ale, lager],stop).
+
+%%--------------------------------------------------------------------
+%% Internal functions
+%%--------------------------------------------------------------------
+%% @private
+app_ctrl([], _F) ->
+ ok;
+app_ctrl([App|Apps], F) ->
+ error_logger:info_msg("~p: ~p\n", [F,App]),
+ case application:F(App) of
+ {error,{not_started,App1}} ->
+ case F of
+ start ->
+ app_ctrl([App1,App|Apps], F);
+ stop ->
+ app_ctrl(Apps, F)
+ end;
+ {error,{already_started,App}} ->
+ app_ctrl(Apps, F);
+ ok ->
+ app_ctrl(Apps, F);
+ Error ->
+ Error
+ end.
+
+%% @private
+trace_i(Module, Level) ->
+ trace_i(Module, Level, self).
+
+trace_i(Module, Level, Type)
+ when is_atom(Module), is_atom(Level) ->
+ trace_i([Module], Level, Type);
+trace_i([], _Level, _Type) ->
+ ok;
+trace_i([Module | Rest], Level, self)
+ when is_atom(Module), is_atom(Level) ->
+ trace(on, Module, Level),
+ trace_i(Rest, Level, self);
+trace_i([Module | Rest], Level, gl)
+ when is_atom(Module), is_atom(Level) ->
+ trace_gl(on, Module, Level),
+ trace_i(Rest, Level, gl);
+trace_i([Filter | _Rest] = FilterList, Level, self)
+ when is_tuple(Filter), is_atom(Level) ->
+ trace(on, FilterList, Level);
+trace_i([Filter | _Rest] = FilterList, Level, gl)
+ when is_tuple(Filter), is_atom(Level) ->
+ trace_gl(on, FilterList, Level).
+
diff --git a/deps/ale/src/ale_srv.erl b/deps/ale/src/ale_srv.erl
new file mode 100644
index 0000000..f6a56fe
--- /dev/null
+++ b/deps/ale/src/ale_srv.erl
@@ -0,0 +1,590 @@
+%%%---- BEGIN COPYRIGHT --------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ----------------------------------------------------------
+%%%-------------------------------------------------------------------
+%%% @author Malotte Westman Lönne <malotte@malotte.net>
+%%% @copyright (C) 2012, Tony Rogvall
+%%% @doc
+%%% ale server.
+%%%
+%%% Created: 2012 by Malotte W Lönne
+%%% @end
+%%%-------------------------------------------------------------------
+-module(ale_srv).
+
+-behaviour(gen_server).
+
+-include_lib("lager/include/log.hrl").
+
+
+%% API
+-export([start_link/1,
+ stop/0]).
+
+%% gen_server callbacks
+-export([init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3]).
+
+%% Testing
+-export([start/0,
+ start/1,
+ traces/0,
+ clients/0,
+ dump/0,
+ debug/1]).
+
+-record(trace_item,
+ {
+ trace,
+ lager_ref,
+ client
+ }).
+
+-record(client_item,
+ {
+ pid,
+ monitor
+ }).
+
+-record(ctx,
+ {
+ trace_list = [],
+ client_list = [],
+ debug %% Debug of own process
+ }).
+
+-define(dbg(Format, Args),
+ lager:debug("~s(~p): " ++ Format, [?MODULE, self() | Args])).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts the server that will keep track of traces.
+%% @end
+%%--------------------------------------------------------------------
+-spec start_link(Options::list(tuple())) ->
+ {ok, Pid::pid()} |
+ ignore |
+ {error, Error::term()}.
+
+start_link(Args) ->
+ ?dbg("start_link: starting, args ~p",[Args]),
+ Opts = proplists:get_value(options, Args, []),
+ F = case proplists:get_value(linked,Opts,true) of
+ true -> start_link;
+ false -> start
+ end,
+ gen_server:F({local, ?MODULE}, ?MODULE, Args, []).
+
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Stops the server.
+%% @end
+%%--------------------------------------------------------------------
+-spec stop() -> ok | {error, Error::term()}.
+
+stop() ->
+ ?dbg("start_link: stopping",[]),
+ gen_server:call(?MODULE, stop).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Lists existing traces.
+%% @end
+%%--------------------------------------------------------------------
+-spec traces() -> list(tuple()).
+
+traces() ->
+ gen_server:call(?MODULE, traces).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Lists existing clients.
+%% @end
+%%--------------------------------------------------------------------
+-spec clients() -> list(tuple()).
+
+clients() ->
+ gen_server:call(?MODULE, clients).
+
+
+%%--------------------------------------------------------------------
+%% Test functions
+%% @private
+start() ->
+ start([]).
+%% @private
+start(Args) ->
+ gen_server:start({local, ?MODULE}, ?MODULE, Args, []).
+
+%% @private
+dump() ->
+ gen_server:call(?MODULE, dump).
+
+%% @private
+debug(TrueOrFalse) ->
+ gen_server:call(?MODULE, {debug, TrueOrFalse}).
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Initializes the server
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec init(list(tuple())) ->
+ {ok, Ctx::#ctx{}} |
+ {stop, Reason::term()}.
+
+init(Args) ->
+ Opts = proplists:get_value(options, Args, []),
+ {ok,Debug} = set_debug(proplists:get_value(debug, Opts, false), undefined),
+ ?dbg("init: args ~p",[Args]),
+ InitTraces = proplists:get_value(init_traces, Args, []),
+ TL =
+ lists:foldl(fun({Filter, Level}, TraceList) ->
+ {_Result, TmpL} =
+ add_trace({Filter, Level, console},
+ self(), TraceList),
+ TmpL;
+ ({Filter, Level, File}, TraceList) ->
+ %% Do we want this check ??
+ case filelib:is_regular(File) of
+ true ->
+ {_Result, TmpL} =
+ add_trace({Filter, Level, File},
+ self(), TraceList),
+ TmpL;
+ false ->
+ error_logger:error_msg(
+ "ale: non existing file ~p, " ++
+ "no trace added.~n", [File]),
+ TraceList
+ end
+ end,
+ [], InitTraces),
+ {ok, #ctx {debug = Debug, trace_list = TL}}.
+
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling call messages.
+%% Request can be the following:
+%% <ul>
+%% <li> trace</li>
+%% <li> dump</li>
+%% <li> stop</li>
+%% </ul>
+%%
+%% @end
+%%--------------------------------------------------------------------
+-type call_request()::
+ {trace,
+ OnOrOff:: on | off,
+ Filter::list(tuple()),
+ Level::atom(),
+ Pid::pid()} |
+ dump |
+ {debug, TrueOrFalse::boolean()} |
+ clear |
+ stop.
+
+-spec handle_call(Request::call_request(),
+ From::{pid(), Tag::term()},
+ Ctx::#ctx{}) ->
+ {reply, Reply::term(), Ctx::#ctx{}} |
+ {noreply, Ctx::#ctx{}} |
+ {stop, Reason::atom(), Reply::term(), Ctx::#ctx{}}.
+
+
+handle_call({trace, on, Filter, Level, Client, File} = _T, _From,
+ Ctx=#ctx {trace_list = TL, client_list = CL})
+ when is_list(Filter) ->
+ ?dbg("handle_call: trace on ~p.",[_T]),
+ case add_trace({Filter, Level, File}, Client, TL) of
+ {ok, NewTL} ->
+ NewCL = monitor_client(Client, CL),
+ {reply, ok, Ctx#ctx {trace_list = NewTL, client_list = NewCL}};
+ {Error, TL} ->
+ {reply, Error, Ctx}
+ end;
+handle_call({trace, off, Filter, Level, Client, File} = _T, _From,
+ Ctx=#ctx {trace_list = TL, client_list = CL})
+ when is_list(Filter) ->
+ ?dbg("handle_call: trace off ~p.",[_T]),
+ case remove_trace({Filter, Level, File}, Client, TL) of
+ {ok, NewTL} ->
+ NewCL = demonitor_client(Client, NewTL, CL),
+ {reply, ok, Ctx#ctx {trace_list = NewTL, client_list = NewCL}};
+ {E, TL} ->
+ {reply, E, Ctx}
+ end;
+
+handle_call(traces, _From, Ctx=#ctx {trace_list = TL}) ->
+ ?dbg("handle_call: traces.",[]),
+ {reply, TL, Ctx};
+
+handle_call(clients, _From, Ctx=#ctx {client_list = CL}) ->
+ ?dbg("handle_call: clients.",[]),
+ {reply, CL, Ctx};
+
+handle_call(i, _From, Ctx=#ctx {trace_list = TL, client_list = CL}) ->
+ ?dbg("handle_call: i.",[]),
+ PrettyTraceList = {'Trace list:',
+ lists:map(
+ fun(#trace_item {client = C, trace = {F, L, B}}) ->
+ lists:flatten(
+ io_lib:format(
+ "Client ~p, filter ~p, level ~p, backend ~p.",
+ [C, F, L, B]))
+ end, TL)},
+ PrettyClientList = {'Client list:',
+ lists:map(
+ fun(#client_item {pid = P}) ->
+ lists:flatten(io_lib:format("Client ~p.", [P]))
+ end, CL)},
+ {reply, {PrettyTraceList, PrettyClientList}, Ctx};
+
+handle_call(dump, _From, Ctx=#ctx {trace_list = TL, client_list = CL}) ->
+ lists:foreach(fun(#trace_item {client = C, trace = T, lager_ref = LR}) ->
+ io:format("Client ~p, trace ~p, ref ~w.\n",
+ [C, T, LR])
+ end, TL),
+ lists:foreach(fun(#client_item {pid = P, monitor = M}) ->
+ io:format("Client ~p, monitor ~p.\n",
+ [P, M])
+ end, CL),
+ {reply, ok, Ctx};
+
+handle_call({debug, TrueOrFalse}, _From, Ctx=#ctx {debug = Dbg}) ->
+ case set_debug(TrueOrFalse, Dbg) of
+ {ok, NewDbg} ->
+ {reply, ok, Ctx#ctx { debug = NewDbg }};
+ Error ->
+ {reply, Error, Ctx}
+ end;
+
+handle_call(clear, _From, Ctx=#ctx {trace_list = TL, client_list = CL}) ->
+ lists:foldl(fun(#trace_item {trace = Trace, client = Client}, Rest) ->
+ remove_trace(Trace, Client, TL),
+ Rest
+ end, [], TL),
+ ?dbg("clear: traces removed.",[]),
+ lists:foreach(fun( #client_item {monitor = Mon}) ->
+ erlang:demonitor(Mon, [flush])
+ end, CL),
+ ?dbg("clear: clients removed.",[]),
+ {reply, ok, Ctx#ctx {trace_list = [], client_list = []}};
+
+handle_call(stop, _From, Ctx) ->
+ ?dbg("handle_call: stop.",[]),
+ {stop, normal, ok, Ctx};
+
+handle_call(_Request, _From, Ctx) ->
+ ?dbg("handle_call: unknown request ~p.", [_Request]),
+ {reply, {error,bad_call}, Ctx}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling cast messages.
+%%
+%% @end
+%%--------------------------------------------------------------------
+-type cast_msg()::
+ term().
+
+-spec handle_cast(Msg::cast_msg(), Ctx::#ctx{}) ->
+ {noreply, Ctx::#ctx{}} |
+ {stop, Reason::term(), Ctx::#ctx{}}.
+
+handle_cast(_Msg, Ctx) ->
+ ?dbg("handle_cast: unknown msg ~p", [_Msg]),
+ {noreply, Ctx}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling all non call/cast messages.
+%%
+%% @end
+%%--------------------------------------------------------------------
+-type info()::
+ {'DOWN', Ref::reference(), process, Pid::pid(), Reason::term()} |
+ term().
+
+-spec handle_info(Info::info(), Ctx::#ctx{}) ->
+ {noreply, Ctx::#ctx{}} |
+ {noreply, Ctx::#ctx{}, Timeout::timeout()} |
+ {stop, Reason::term(), Ctx::#ctx{}}.
+
+handle_info({'DOWN', MonRef, process, _Pid, Reason},
+ Ctx=#ctx {trace_list = TL, client_list = CL}) ->
+ ?dbg("handle_info: DOWN for process ~p received, reason ~p.",
+ [_Pid, Reason]),
+ %% See if we have this client
+ {NewCL, NewTL} =
+ case lists:keytake(MonRef, #client_item.monitor, CL) of
+ false ->
+ %% Ignore
+ ?dbg("handle_info: client not found.",[]),
+ {CL, TL};
+ {value, #client_item {pid = Pid}, Rest} ->
+ %% Remove all traces for this client
+ ?dbg("handle_info: client found, removing traces.",[]),
+ {Rest, remove_traces(Pid, TL, [])}
+
+ end,
+ {noreply, Ctx#ctx {trace_list = NewTL, client_list = NewCL}};
+
+handle_info(_Info, Ctx) ->
+ ?dbg("handle_info: unknown info ~p received.", [_Info]),
+ {noreply, Ctx}.
+
+%%--------------------------------------------------------------------
+%% @private
+%%--------------------------------------------------------------------
+-spec terminate(Reason::term(), Ctx::#ctx{}) ->
+ no_return().
+
+terminate(_Reason,
+ _Ctx=#ctx {trace_list = TL, client_list = CL, debug = Dbg}) ->
+ ?dbg("terminate: Reason = ~p.",[_Reason]),
+ lists:foldl(fun(#trace_item {trace = Trace, client = Client}, Rest) ->
+ remove_trace(Trace, Client, TL),
+ Rest
+ end, [], TL),
+ ?dbg("terminate: traces removed.",[]),
+ lists:foreach(fun( #client_item {monitor = Mon}) ->
+ erlang:demonitor(Mon, [flush])
+ end, CL),
+ ?dbg("terminate: clients removed.",[]),
+ stop_debug(Dbg),
+ ok.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Convert process ctx when code is changed.
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec code_change(OldVsn::term(), Ctx::#ctx{}, Extra::term()) ->
+ {ok, NewCtx::#ctx{}}.
+
+code_change(_OldVsn, Ctx, _Extra) ->
+ ?dbg("code_change: old version ~p.",[_OldVsn]),
+ {ok, Ctx}.
+
+
+%%--------------------------------------------------------------------
+%% Internal functions
+%%--------------------------------------------------------------------
+%%--------------------------------------------------------------------
+%% @private
+%%--------------------------------------------------------------------
+-spec add_trace({Filter::term(), Level::atom(), File::string() | console},
+ Client::pid(), TL::list(tuple())) ->
+ list(tuple()).
+
+add_trace(Trace = {Filter, Level, File}, Client, TL) ->
+ %% See if we already are tracing this.
+ ?dbg("add_trace: trace ~p for client ~p",[Trace, Client]),
+ case lists:keyfind(Trace, #trace_item.trace, TL) of
+ false ->
+ %% Create new trace in lager
+ ?dbg("add_trace: trace ~p not found.",[Trace]),
+ Res =
+ case File of
+ console ->
+ lager:trace_console(Filter, Level);
+ _F ->
+ lager:trace_file(File, Filter,Level)
+ end,
+ case Res of
+ {ok, LagerRef} ->
+ ?dbg("add_trace: trace added in lager, ref ~p.",[LagerRef]),
+ {ok,
+ [#trace_item {trace = Trace,
+ lager_ref = LagerRef,
+ client = Client}
+ | TL]};
+ {error, _Reason} = E->
+ ?dbg("add_trace: lager call failed, reason ~p.",[_Reason]),
+ {E, TL}
+ end;
+ #trace_item {trace = Trace, client = Client} ->
+ %% Trace already exists, ignore
+ ?dbg("add_trace: trace ~p found.",[Trace]),
+ {ok, TL};
+ #trace_item {trace = Trace, lager_ref = LagerRef, client = _Other} ->
+ %% Trace exists in lager, just add a post locally
+ ?dbg("add_trace: trace ~p found for client ~p.",
+ [Trace, _Other]),
+ {ok,
+ [#trace_item {trace = Trace, lager_ref = LagerRef, client = Client}
+ | TL]}
+ end.
+
+%%--------------------------------------------------------------------
+%% @private
+%%--------------------------------------------------------------------
+-spec remove_trace({Filter::term(), Level::atom(), File::string()},
+ Client::pid(), TL::list(tuple())) ->
+ list(tuple()).
+
+remove_trace(Trace, Client, TL) ->
+ %% See if we are tracing this.
+ ?dbg("remove_trace: trace ~p for client ~p.",[Trace, Client]),
+ case lists:keytake(Trace, #trace_item.trace, TL) of
+ false ->
+ %% Ignore ??
+ ?dbg("remove_trace: trace ~p not found in ~p.",[Trace, TL]),
+ {ok, TL};
+ {value,
+ #trace_item {trace = Trace, lager_ref = LagerRef, client = Client},
+ Rest} ->
+ %% See if it was the last trace of this
+ case lists:keyfind(Trace, #trace_item.trace, Rest) of
+ false ->
+ %% Last trace, remove in lager and locally
+ ?dbg("remove_trace: last trace ~p found.",[Trace]),
+ case lager:stop_trace(LagerRef) of
+ ok ->
+ {ok, Rest};
+ {error, _Reason} = E ->
+ ?dbg("add_trace: lager call failed, reason ~p.",
+ [_Reason]),
+ {E, TL}
+ end;
+ #trace_item {} ->
+ %% We still need this trace in lager,
+ %% only remove locally
+ ?dbg("remove_trace: more traces ~p exist.",[Trace]),
+ {ok, Rest}
+ end;
+ {value, #trace_item {trace = Trace, client = _Other}, _Rest} ->
+ ?dbg("remove_trace: trace ~p found for client ~p.",
+ [Trace, _Other]),
+ {ok, TL}
+ end.
+
+%%--------------------------------------------------------------------
+%% @private
+%%--------------------------------------------------------------------
+-spec monitor_client(Client::pid(), CL::list(pid())) ->
+ list(pid()).
+
+monitor_client(Client, CL) ->
+ %% See if we already have this client
+ case lists:keyfind(Client, #client_item.pid, CL) of
+ false ->
+ %% Monitor the client
+ ?dbg("monitor_client: new client pid ~p.",[Client]),
+ Mon = erlang:monitor(process, Client),
+ [#client_item {pid = Client, monitor = Mon} | CL];
+ #client_item {pid = Client} ->
+ ?dbg("monitor_client: old client pid ~p.",[Client]),
+ CL
+ end.
+
+%%--------------------------------------------------------------------
+%% @private
+%%--------------------------------------------------------------------
+-spec demonitor_client(Client::pid(), CL::list(pid()), TL::list(tuple())) ->
+ list(pid()).
+
+demonitor_client(Client, TL, CL) ->
+ %% See if we have this client
+ case lists:keytake(Client, #client_item.pid, CL) of
+ false ->
+ %% Ignore
+ ?dbg("demonitor_client: client pid ~p not found.",[Client]),
+ CL;
+ {value, #client_item {pid = Client, monitor = Mon}, Rest} ->
+ %% See if this client has any traces left
+ case lists:keyfind(Client, #trace_item.client, TL) of
+ false ->
+ %% No more traces
+ %% Demonitor the client
+ ?dbg("demonitor_client: last trace for client ~p.",
+ [Client]),
+ erlang:demonitor(Mon, [flush]),
+ Rest;
+ #trace_item {} ->
+ %% Continue monitoring
+ ?dbg("demonitor_client: more traces for client ~p.",
+ [Client]),
+ CL
+ end
+ end.
+
+
+%%--------------------------------------------------------------------
+%% @private
+%%--------------------------------------------------------------------
+-spec remove_traces(Client::pid(), TL::list(tuple()), NewTL::list(tuple())) ->
+ list(tuple()).
+
+remove_traces(_Client, [], NewTL) ->
+ ?dbg("remove_traces: all traces for client ~p removed.",[_Client]),
+ NewTL;
+remove_traces(Client,
+ [#trace_item {trace = Trace, lager_ref = LagerRef, client = Client}
+ | RestTL], NewTL) ->
+ %% See if it was the last trace of this
+ case lists:keytake(Trace, #trace_item.trace, RestTL) of
+ false ->
+ %% Last trace, remove in lager and locally
+ ?dbg("remove_traces:last trace for client ~p found.",
+ [Client]),
+ lager:stop_trace(LagerRef),
+ remove_traces(Client, RestTL, NewTL);
+ {value, _TraceItem, _RestOfRest} ->
+ %% We still need this trace in lager,
+ %% only remove locally
+ ?dbg("remove_traces: more traces for client ~p found.",
+ [Client]),
+ remove_traces(Client, RestTL, NewTL)
+ end;
+remove_traces(Client, [Item | RestTL], NewTL) ->
+ %% Not this client
+ ?dbg("remove_traces: traces for other client found.",[]),
+ remove_traces(Client, RestTL, [Item | NewTL]).
+
+
+%% Internal debugging
+stop_debug(undefined) ->
+ undefined;
+stop_debug(Dbg) ->
+ lager:stop_trace(Dbg),
+ undefined.
+
+%% enable/disable module debug
+set_debug(false, Dbg) ->
+ NewDbg = stop_debug(Dbg),
+ lager:set_loglevel(lager_console_backend, info),
+ {ok, NewDbg};
+set_debug(true, undefined) ->
+ lager:trace_console([{module,?MODULE}], debug);
+set_debug(true, Dbg) ->
+ {ok, Dbg}.
+
diff --git a/deps/ale/src/ale_sup.erl b/deps/ale/src/ale_sup.erl
new file mode 100644
index 0000000..cfd0fce
--- /dev/null
+++ b/deps/ale/src/ale_sup.erl
@@ -0,0 +1,95 @@
+%%%---- BEGIN COPYRIGHT --------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ----------------------------------------------------------
+%%%-------------------------------------------------------------------
+%%% @author Malotte W Lönne <malotte@malotte.net>
+%%% @copyright (C) 2012, Tony Rogvall
+%%% @doc
+%%% ale application supervisor.
+%%% @end
+%%% Created : 2012 by Malotte W Lönne
+%%%-------------------------------------------------------------------
+
+-module(ale_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/1,
+ stop/1]).
+
+%% Supervisor callbacks
+-export([init/1]).
+
+%% Helper macro for declaring children of supervisor
+-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
+
+
+%% ===================================================================
+%% API functions
+%% ===================================================================
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts the supervisor. <br/>
+%% Arguments are sent on to the supervisor.
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec start_link(Args::list(term())) ->
+ {ok, Pid::pid()} |
+ ignore |
+ {error, Error::term()}.
+
+start_link(Args) ->
+ error_logger:info_msg("~p: start_link: args = ~p\n", [?MODULE, Args]),
+ try supervisor:start_link({local, ?MODULE}, ?MODULE, Args) of
+ {ok, Pid} ->
+ {ok, Pid, {normal, Args}};
+ Error ->
+ error_logger:error_msg("~p: start_link: Failed to start process, "
+ "reason ~p\n", [?MODULE, Error]),
+ Error
+ catch
+ error:Reason ->
+ error_logger:error_msg("~p: start_link: Try failed, reason ~p\n",
+ [?MODULE,Reason]),
+ Reason
+ end.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Stops the supervisor.
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec stop(StartArgs::list(term())) -> ok | {error, Error::term()}.
+
+stop(_StartArgs) ->
+ exit(stopped).
+
+%% ===================================================================
+%% Supervisor callbacks
+%% ===================================================================
+%% @private
+init(Args) ->
+ error_logger:info_msg("~p: init: args = ~p,\n pid = ~p\n",
+ [?MODULE, Args, self()]),
+ AS = ale_srv,
+ Ale = {AS, {AS, start_link, [Args]}, permanent, 5000, worker, [AS]},
+ error_logger:info_msg("~p: About to start ~p\n", [?MODULE,Ale]),
+ {ok, { {one_for_one, 0, 300}, [Ale]} }.
+
diff --git a/deps/ale/sys.config b/deps/ale/sys.config
new file mode 100644
index 0000000..35c3f7c
--- /dev/null
+++ b/deps/ale/sys.config
@@ -0,0 +1,39 @@
+%% -*- erlang -*-
+%%
+%% ale
+%% Configuration file for included erlang applications.
+[
+ %% SASL config
+ {sasl, [
+ {sasl_error_logger, {file, "log/sasl-error.log"}},
+ {errlog_type, error},
+ {error_logger_mf_dir, "log/sasl"}, % Log directory
+ {error_logger_mf_maxbytes, 10485760}, % 10 MB max file size
+ {error_logger_mf_maxfiles, 5} % 5 files max
+ ]},
+ %% Lager config, see lager documentation
+ {lager, [
+ {handlers,
+ [
+ {lager_console_backend, debug},
+ {lager_file_backend,
+ [
+ {"log/lager/error.log", error, 10485760, "$D0", 5},
+ {"log/lager/console.log", info, 10485760, "$D0", 5}
+ ]}
+ ]}
+ ]},
+ %% Ale config.
+ %% For format of trace filter and level, see lager doc.
+ {ale, [
+ {options, []},
+ %% {options, [{debug, true}]},
+ %% traces - list({list(Filter:tuple()), Level}) %% to console
+ %% list({list(Filter:tuple()), Level, File}) %% to file
+ %% Filter for ex {module, Module}
+ {init_traces, [
+ %% {[{module, sz_master}], debug},
+ %% {[{module, sz_node}], info, "/tmp/ale.log"}
+ ]}
+ ]}
+].
diff --git a/deps/ale/test/.gitignore b/deps/ale/test/.gitignore
new file mode 100644
index 0000000..5d9033f
--- /dev/null
+++ b/deps/ale/test/.gitignore
@@ -0,0 +1,4 @@
+*.beam
+*.html
+ct*
+*~ \ No newline at end of file
diff --git a/deps/ale/test/Makefile b/deps/ale/test/Makefile
new file mode 100644
index 0000000..eb05b29
--- /dev/null
+++ b/deps/ale/test/Makefile
@@ -0,0 +1,34 @@
+#
+# Make the ale test modules
+#
+
+MODULES = \
+ ale_SUITE
+
+
+EBIN = .
+ERLC = erlc
+
+ERLDIR := $(shell erl -noshell -eval "io:format([126,115,126,110],[code:root_dir()])" -s erlang halt)
+
+override ERLC_FLAGS = -Wall -I ../include
+
+debug: ERLC_FLAGS += +debug_info -Ddebug
+
+OBJS = $(MODULES:%=$(EBIN)/%.beam)
+
+all: $(OBJS)
+
+debug: all
+
+depend:
+ edep -MM -o ../ebin $(ERLC_FLAGS) $(MODULES:%=%.erl) > depend.mk
+
+clean:
+ rm -f $(OBJS)
+
+
+-include depend.mk
+
+./%.beam: %.erl
+ erlc -o ../ebin $(ERLC_FLAGS) $<
diff --git a/deps/ale/test/ale_SUITE.erl b/deps/ale/test/ale_SUITE.erl
new file mode 100644
index 0000000..b0b48e6
--- /dev/null
+++ b/deps/ale/test/ale_SUITE.erl
@@ -0,0 +1,361 @@
+%%%---- BEGIN COPYRIGHT --------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ----------------------------------------------------------
+%%%-------------------------------------------------------------------
+%%% @author Marina Westman Lönne <malotte@malotte.net>
+%%% @copyright (C) 2012, Tony Rogvall
+%%% @doc
+%%% Test suite for ale.
+%%% @end
+%%% Created : 2012 by Marina Westman Lönne <malotte@malotte.net>
+%%%-------------------------------------------------------------------
+-module(ale_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+%%--------------------------------------------------------------------
+%% COMMON TEST CALLBACK FUNCTIONS
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Returns list of tuples to set default properties
+%% for the suite.
+%% @spec suite() -> Info
+%% @end
+%%--------------------------------------------------------------------
+suite() ->
+ [{timetrap,{minutes,10}}].
+
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Returns the list of groups and test cases that
+%% are to be executed.
+%%
+%% @spec all() -> GroupsAndTestCases | {skip,Reason}
+%% @end
+%%--------------------------------------------------------------------
+all() ->
+ [start_of_ale_app,
+ start_with_it_console,
+ start_with_it_file,
+ start_with_it_no_file,
+ add_trace,
+ add_trace_gl,
+ add_trace_pid,
+ add_trace_and_die,
+ add_trace_wrong_level,
+ remove_non_existing_trace].
+%% break].
+
+
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Initialization before the whole suite
+%%
+%% @spec init_per_suite(Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% @end
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ start_system(),
+ Config.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Cleanup after the whole suite
+%%
+%% @spec end_per_suite(Config) -> _
+%% @end
+%%--------------------------------------------------------------------
+end_per_suite(_Config) ->
+ application:stop(ale),
+ stop_system(),
+ ok.
+
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Initialization before each test case
+%%
+%% @spec init_per_testcase(TestCase, Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% @end
+%%--------------------------------------------------------------------
+init_per_testcase(TC, Config) ->
+ ct:pal("Init testcase: ~p", [TC]),
+ tc_init(TC, Config).
+
+tc_init(start_with_it_file, Config) ->
+ os:cmd("touch ./ale.log"),
+ Config;
+tc_init(_Any, Config) ->
+ Config.
+
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Cleanup after each test case
+%%
+%% @spec end_per_testcase(TestCase, Config0) ->
+%% void() | {save_config,Config1} | {fail,Reason}
+%% @end
+%%--------------------------------------------------------------------
+end_per_testcase(TC, Config) ->
+ ct:pal("End testcase: ~p", [TC]),
+ tc_end(TC, Config).
+
+tc_end(start_of_ale_app, _Config) ->
+ application:stop(ale),
+ ct:pal("Stopped ale."),
+ ok;
+tc_end(start_with_it_file, Config) ->
+ os:cmd("rm ./ale.log"),
+ tc_end(any, Config);
+tc_end(_Any, _Config) ->
+ ale_srv:stop(),
+ ct:pal("Stopped ale server."),
+ ok.
+
+
+%%--------------------------------------------------------------------
+%% TEST CASES
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Start of ale application.
+%% @end
+%%--------------------------------------------------------------------
+-spec start_of_ale_app(Config::list(tuple())) -> ok.
+
+start_of_ale_app(_Config) ->
+ ale:start(),
+ ok = ale_srv:dump(),
+ ct:pal("Ale up and running"),
+ ok.
+
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Start of ale with init trace to console.
+%% @end
+%%--------------------------------------------------------------------
+-spec start_with_it_console(Config::list(tuple())) -> ok.
+
+start_with_it_console(_Config) ->
+ {ok, Pid} = ale_srv:start([{init_traces,
+ [{[{module, ale_SUITE}], debug}]}]),
+ ok = ale_srv:dump(),
+ [{trace_item, {[{module, ale_SUITE}], debug, console}, _LagRef, Pid}] =
+ ale_srv:traces(),
+ ok.
+
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Start of ale with init trace to file.
+%% @end
+%%--------------------------------------------------------------------
+-spec start_with_it_file(Config::list(tuple())) -> ok.
+
+start_with_it_file(_Config) ->
+ {ok, Pid} = ale_srv:start([{init_traces,
+ [{[{module, ale_SUITE}], debug, "./ale.log"}]}]),
+ ok = ale_srv:dump(),
+ [{trace_item, {[{module, ale_SUITE}], debug, _File}, _LagRef, Pid}] =
+ ale_srv:traces(),
+ ok.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Start of ale with init trace refering to non-existing file.
+%% @end
+%%--------------------------------------------------------------------
+-spec start_with_it_no_file(Config::list(tuple())) -> ok.
+
+start_with_it_no_file(_Config) ->
+ {ok, _Pid} = ale_srv:start([{init_traces,
+ [{[{module, ale_SUITE}], debug, "./ale.log"}]}]),
+ ok = ale_srv:dump(),
+ [] = ale_srv:traces(),
+ ok.
+
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Add/remove trace on module.
+%% @end
+%%--------------------------------------------------------------------
+-spec add_trace(Config::list(tuple())) -> ok.
+
+add_trace(_Config) ->
+ Self = self(),
+ {ok, _Pid} = ale_srv:start(),
+ ok = ale_srv:dump(),
+ ok = ale:trace(on, ale_SUITE, debug),
+ [{trace_item, {[{module, ale_SUITE}], debug, console}, _LagRef, Self}] =
+ ale_srv:traces(),
+ ok = ale:trace(off, ale_SUITE, debug),
+ [] = ale_srv:traces(),
+ ok.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Add/remove trace on module with groupleader.
+%% @end
+%%--------------------------------------------------------------------
+-spec add_trace_gl(Config::list(tuple())) -> ok.
+
+add_trace_gl(_Config) ->
+ GL = group_leader(),
+ {ok, _Pid} = ale_srv:start(),
+ ok = ale:trace_gl(on, ale_SUITE, info),
+ ok = ale_srv:dump(),
+ [{trace_item, {[{module, ale_SUITE}], info, console}, _LagRef, GL}] =
+ ale_srv:traces(),
+ ok = ale:trace_gl(off, ale_SUITE, info),
+ [] = ale_srv:traces(),
+ ok.
+
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Add/remove trace on pid.
+%% @end
+%%--------------------------------------------------------------------
+-spec add_trace_pid(Config::list(tuple())) -> ok.
+
+add_trace_pid(_Config) ->
+ Self = self(),
+ SelfStr = pid_to_list(Self),
+ {ok, _Pid} = ale_srv:start(),
+ ok = ale:trace(on, Self, debug),
+ ok = ale_srv:dump(),
+ [{trace_item, {[{pid, SelfStr}], debug, console}, _LagRef, Self}] =
+ ale_srv:traces(),
+ ok = ale:trace(off, Self, debug),
+ [] = ale_srv:traces(),
+ ok.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Have tmp processes add trace and die.
+%% @end
+%%--------------------------------------------------------------------
+-spec add_trace_and_die(Config::list(tuple())) -> ok.
+
+add_trace_and_die(_Config) ->
+ {ok, _AlePid} = ale_srv:start(),
+ TmpPid = proc_lib:spawn(?MODULE, trace_starter, []),
+ timer:sleep(100),
+ ok = ale_srv:dump(),
+ [{trace_item, {[{module, ale_SUITE}], debug, console}, _LagRef, TmpPid}] =
+ ale_srv:traces(),
+ TmpPid ! die,
+ timer:sleep(100),
+ [] = ale_srv:traces(),
+ ok.
+
+trace_starter() ->
+ ok = ale:trace(on, ale_SUITE, debug),
+ receive
+ die ->
+ ct:pal("Told to die")
+ after
+ 1000 ->
+ ct:fail("Timeout")
+ end.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Add trace with incorrect level.
+%% @end
+%%--------------------------------------------------------------------
+-spec add_trace_wrong_level(Config::list(tuple())) -> ok.
+
+add_trace_wrong_level(_Config) ->
+ {ok, _AlePid} = ale_srv:start(),
+ {error, E} = ale:trace(on, ale_SUITE, wrong),
+ ok = ale_srv:dump(),
+ ct:pal("Error ~p", [E]),
+ [] = ale_srv:traces(),
+ ok.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Remove non existing trace.
+%% @end
+%%--------------------------------------------------------------------
+-spec remove_non_existing_trace(Config::list(tuple())) -> ok.
+
+remove_non_existing_trace(_Config) ->
+ {ok, _AlePid} = ale_srv:start(),
+ %% Is ok OK??
+ ok = ale:trace(off, ale_SUITE, debug),
+ ok = ale_srv:dump(),
+ [] = ale_srv:traces(),
+ ok.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Dummy test case to have a test environment running.
+%% Stores Config in ets table.
+%% @end
+%%--------------------------------------------------------------------
+-spec break(Config::list(tuple())) -> ok.
+
+break(Config) ->
+ ets:new(config, [set, public, named_table]),
+ ets:insert(config, Config),
+ test_server:break("Break for test development\n" ++
+ "Get Config by ets:tab2list(config)"),
+ ok.
+
+
+%%--------------------------------------------------------------------
+%% Help functions
+%%--------------------------------------------------------------------
+start_system() ->
+ app_ctrl([kernel, stdlib, compiler, syntax_tools, sasl, lager],start).
+
+stop_system() ->
+ app_ctrl([lager], stop).
+
+app_ctrl([], _F) ->
+ ok;
+app_ctrl([App|Apps], F) ->
+ error_logger:info_msg("~p: ~p\n", [F,App]),
+ case application:F(App) of
+ {error,{not_started,App1}} ->
+ case F of
+ start ->
+ app_ctrl([App1,App|Apps], F);
+ stop ->
+ app_ctrl(Apps, F)
+ end;
+ {error,{already_started,App}} ->
+ app_ctrl(Apps, F);
+ ok ->
+ app_ctrl(Apps, F);
+ Error ->
+ Error
+ end.
diff --git a/deps/goldrush/.gitignore b/deps/goldrush/.gitignore
new file mode 100644
index 0000000..89efe13
--- /dev/null
+++ b/deps/goldrush/.gitignore
@@ -0,0 +1,10 @@
+.eunit
+*.beam
+.rebar
+*.plt
+ebin
+doc
+*.swp
+erl_crash.dump
+.project
+log
diff --git a/deps/goldrush/LICENSE b/deps/goldrush/LICENSE
new file mode 100644
index 0000000..812980e
--- /dev/null
+++ b/deps/goldrush/LICENSE
@@ -0,0 +1,13 @@
+Copyright (c) 2012, Magnus Klaar <klaar@ninenines.eu>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/deps/goldrush/Makefile b/deps/goldrush/Makefile
new file mode 100644
index 0000000..bd1a7ca
--- /dev/null
+++ b/deps/goldrush/Makefile
@@ -0,0 +1,37 @@
+# See LICENSE for licensing information.
+
+DIALYZER = dialyzer
+REBAR = rebar
+APPNAME = goldrush
+
+all: app
+
+app: deps
+ @$(REBAR) compile
+
+deps:
+ @$(REBAR) get-deps
+
+clean:
+ @$(REBAR) clean
+ rm -f test/*.beam
+ rm -f erl_crash.dump
+
+tests: clean app eunit ct
+
+eunit:
+ @$(REBAR) -C rebar.test.config eunit skip_deps=true
+
+ct:
+ @$(REBAR) -C rebar.test.config ct skip_deps=true
+
+build-plt:
+ @$(DIALYZER) --build_plt --output_plt .$(APPNAME)_dialyzer.plt \
+ --apps kernel stdlib sasl inets crypto public_key ssl compiler syntax_tools
+
+dialyze:
+ @$(DIALYZER) --src src --plt .$(APPNAME)_dialyzer.plt --no_native \
+ -Werror_handling -Wrace_conditions -Wunmatched_returns # -Wunderspecs
+
+docs:
+ @$(REBAR) doc skip_deps=true
diff --git a/deps/goldrush/README.org b/deps/goldrush/README.org
new file mode 100644
index 0000000..73ec4c8
--- /dev/null
+++ b/deps/goldrush/README.org
@@ -0,0 +1,237 @@
+# Goldrush #
+
+Goldrush is a small Erlang app that provides fast event stream processing
+
+# Features #
+* Event processing compiled to a query module
+ - per module private event processing statistics
+ - query module logic can be combined for any/all filters
+ - query module logic can be reduced to efficiently match event processing
+
+* Complex event processing logic
+ - match input events with greater than (gt) logic
+ - match input events with less than (lt) logic
+ - match input events with equal to (eq) logic
+ - match input events with wildcard (wc) logic
+ - match input events with notfound (nf) logic
+ - match no input events (null blackhole) logic
+ - match all input events (null passthrough) logic
+
+* Handle output events
+ - Once a query has been composed the output action can be overriden
+ with one or more erlang functions. The functions will be applied to each
+ output event from the query.
+
+* Handle low latency retrieval of compile-time stored values.
+- Values stored are also provided to functions called on event output.
+
+* Usage
+ To use goldrush in your application, you need to define it as a rebar dep or
+ include it in erlang's path.
+
+
+Before composing modules, you'll need to define a query. The query syntax
+matches any number of `{erlang, terms}' and is composed as follows:
+
+* Simple Logic
+ - Simple logic is defined as any logic matching a single event filter
+
+Select all events where 'a' exists and is greater than 0.
+#+BEGIN_EXAMPLE
+ glc:gt(a, 0).
+#+END_EXAMPLE
+
+Select all events where 'a' exists and is greater than or equal to 0.
+#+BEGIN_EXAMPLE
+ glc:gte(a, 0).
+#+END_EXAMPLE
+
+Select all events where 'a' exists and is equal to 0.
+#+BEGIN_EXAMPLE
+ glc:eq(a, 0).
+#+END_EXAMPLE
+
+Select all events where 'a' exists and is less than 0.
+#+BEGIN_EXAMPLE
+ glc:lt(a, 0).
+#+END_EXAMPLE
+
+Select all events where 'a' exists and is less than or equal to 0.
+#+BEGIN_EXAMPLE
+ glc:lte(a, 0).
+#+END_EXAMPLE
+
+Select all events where 'a' exists.
+#+BEGIN_EXAMPLE
+ glc:wc(a).
+#+END_EXAMPLE
+
+Select all events where 'a' does not exist.
+#+BEGIN_EXAMPLE
+ glc:nf(a).
+#+END_EXAMPLE
+
+Select no input events. User as a black hole query.
+#+BEGIN_EXAMPLE
+ glc:null(false).
+#+END_EXAMPLE
+
+Select all input events. Used as a passthrough query.
+#+BEGIN_EXAMPLE
+ glc:null(true).
+#+END_EXAMPLE
+
+
+* Combined Logic
+ - Combined logic is defined as logic matching multiple event filters
+
+Select all events where both 'a' AND 'b' exists and are greater than 0.
+#+BEGIN_EXAMPLE
+ glc:all([glc:gt(a, 0), glc:gt(b, 0)]).
+#+END_EXAMPLE
+
+Select all events where 'a' OR 'b' exists and are greater than 0.
+#+BEGIN_EXAMPLE
+ glc:any([glc:gt(a, 0), glc:gt(b, 0)]).
+#+END_EXAMPLE
+
+Select all events where 'a' AND 'b' exists where 'a' is greater than 1 and 'b' is less than 2.
+#+BEGIN_EXAMPLE
+ glc:all([glc:gt(a, 1), glc:lt(b, 2)]).
+#+END_EXAMPLE
+
+Select all events where 'a' OR 'b' exists where 'a' is greater than 1 and 'b' is less than 2.
+#+BEGIN_EXAMPLE
+ glc:any([glc:gt(a, 1), glc:lt(b, 2)]).
+#+END_EXAMPLE
+
+
+* Reduced Logic
+ - Reduced logic is defined as logic which can be simplified to improve efficiency.
+
+Select all events where 'a' is equal to 1, 'b' is equal to 2 and 'c' is equal to 3 and collapse any duplicate logic.
+#+BEGIN_EXAMPLE
+ glc_lib:reduce(
+ glc:all([
+ glc:any([glc:eq(a, 1), glc:eq(b, 2)]),
+ glc:any([glc:eq(a, 1), glc:eq(c, 3)])])).
+#+END_EXAMPLE
+
+The previous example will produce and is equivalent to:
+#+BEGIN_EXAMPLE
+ glc:all([glc:eq(a, 1), glc:eq(b, 2), glc:eq(c, 3)]).
+#+END_EXAMPLE
+
+
+
+# Composing Modules #
+
+To compose a module you will take your Query defined above and compile it.
+#+BEGIN_EXAMPLE
+ glc:compile(Module, Query).
+#+END_EXAMPLE
+
+
+- At this point you will be able to handle an event using a compiled query.
+
+Begin by constructing an event list.
+#+BEGIN_EXAMPLE
+ Event = gre:make([{'a', 2}], [list]).
+#+END_EXAMPLE
+
+Now pass it to your query module to be handled.
+#+BEGIN_EXAMPLE
+ glc:handle(Module, Event).
+#+END_EXAMPLE
+
+* Handling output events
+ - You can override the output action with an erlang function.
+
+Write all input events as info reports to the error logger.
+#+BEGIN_EXAMPLE
+ glc:with(glc:null(true), fun(E) ->
+ error_logger:info_report(gre:pairs(E)) end).
+#+END_EXAMPLE
+
+Write all input events where `error_level' exists and is less than 5 as info reports to the error logger.
+#+BEGIN_EXAMPLE
+ glc:with(glc:lt(error_level, 5), fun(E) ->
+ error_logger:info_report(gre:pairs(E)) end).
+#+END_EXAMPLE
+
+Write all input events where `error_level' exists and is 3 or 5 as info reports to the error logger.
+#+BEGIN_EXAMPLE
+ glc:any([
+ glc:with(glc:lt(error_level, 3), fun(E) ->
+ error_logger:info_report(gre:pairs(E)) end),
+ glc:with(glc:lt(error_level, 5), fun(E) ->
+ error_logger:info_report(gre:pairs(E)) end)]).
+
+#+END_EXAMPLE
+
+
+# Composing Modules with stored state #
+
+To compose a module with state data you will add a third argument (orddict).
+#+BEGIN_EXAMPLE
+ glc:compile(Module, Query, [{stored, value}]).
+#+END_EXAMPLE
+
+# Accessing stored state data #
+
+Return the stored value in this query module.
+#+BEGIN_EXAMPLE
+{ok, value} = glc:get(stored).
+#+END_EXAMPLE
+
+Return all stored values in this query module.
+#+BEGIN_EXAMPLE
+[...] = Module:get().
+#+END_EXAMPLE
+
+# Event Processing Statistics #
+
+Return the number of input events for this query module.
+#+BEGIN_EXAMPLE
+glc:input(Module).
+#+END_EXAMPLE
+
+Return the number of output events for this query module.
+#+BEGIN_EXAMPLE
+glc:output(Module).
+#+END_EXAMPLE
+
+Return the number of filtered events for this query module.
+#+BEGIN_EXAMPLE
+glc:filter(Module).
+#+END_EXAMPLE
+
+
+* Build
+
+#+BEGIN_EXAMPLE
+ $ ./rebar compile
+#+END_EXAMPLE
+
+or
+
+#+BEGIN_EXAMPLE
+ $ make
+#+END_EXAMPLE
+
+* CHANGELOG
+
+0.1.7
+- Support multiple functions specified using `with/2`
+- Add support for greater than or less than operators
+- Add state storage option for output events or lookup
+
+0.1.6
+- Add notfound event matching
+
+0.1.5
+- Rewrite to make highly crash resilient
+ - per module supervision
+ - statistics data recovery
+- Add wildcard event matching
+- Add reset counters
diff --git a/deps/goldrush/priv/edoc.css b/deps/goldrush/priv/edoc.css
new file mode 100644
index 0000000..1d50def
--- /dev/null
+++ b/deps/goldrush/priv/edoc.css
@@ -0,0 +1,130 @@
+/* Baseline rhythm */
+body {
+ font-size: 16px;
+ font-family: Helvetica, sans-serif;
+ margin: 8px;
+}
+
+p {
+ font-size: 1em; /* 16px */
+ line-height: 1.5em; /* 24px */
+ margin: 0 0 1.5em 0;
+}
+
+h1 {
+ font-size: 1.5em; /* 24px */
+ line-height: 1em; /* 24px */
+ margin-top: 1em;
+ margin-bottom: 0em;
+}
+
+h2 {
+ font-size: 1.375em; /* 22px */
+ line-height: 1.0909em; /* 24px */
+ margin-top: 1.0909em;
+ margin-bottom: 0em;
+}
+
+h3 {
+ font-size: 1.25em; /* 20px */
+ line-height: 1.2em; /* 24px */
+ margin-top: 1.2em;
+ margin-bottom: 0em;
+}
+
+h4 {
+ font-size: 1.125em; /* 18px */
+ line-height: 1.3333em; /* 24px */
+ margin-top: 1.3333em;
+ margin-bottom: 0em;
+}
+
+.class-for-16px {
+ font-size: 1em; /* 16px */
+ line-height: 1.5em; /* 24px */
+ margin-top: 1.5em;
+ margin-bottom: 0em;
+}
+
+.class-for-14px {
+ font-size: 0.875em; /* 14px */
+ line-height: 1.7143em; /* 24px */
+ margin-top: 1.7143em;
+ margin-bottom: 0em;
+}
+
+ul {
+ margin: 0 0 1.5em 0;
+}
+
+/* Customizations */
+body {
+ color: #333;
+}
+
+tt, code, pre {
+ font-family: "Andale Mono", "Inconsolata", "Monaco", "DejaVu Sans Mono", monospaced;
+}
+
+tt, code { font-size: 0.875em }
+
+pre {
+ font-size: 0.875em; /* 14px */
+ line-height: 1.7143em; /* 24px */
+ margin: 0 1em 1.7143em;
+ padding: 0 1em;
+ background: #eee;
+}
+
+.navbar img, hr { display: none }
+
+table {
+ border-collapse: collapse;
+}
+
+h1 {
+ border-left: 0.5em solid #fa0;
+ padding-left: 0.5em;
+}
+
+h2.indextitle {
+ font-size: 1.25em; /* 20px */
+ line-height: 1.2em; /* 24px */
+ margin: -8px -8px 0.6em;
+ background-color: #fa0;
+ color: white;
+ padding: 0.3em;
+}
+
+ul.index {
+ list-style: none;
+ margin-left: 0em;
+ padding-left: 0;
+}
+
+ul.index li {
+ display: inline;
+ padding-right: 0.75em
+}
+
+div.spec p {
+ margin-bottom: 0;
+ padding-left: 1.25em;
+ background-color: #eee;
+}
+
+h3.function {
+ border-left: 0.5em solid #fa0;
+ padding-left: 0.5em;
+ background: #fc9;
+}
+a, a:visited, a:hover, a:active { color: #C60 }
+h2 a, h3 a { color: #333 }
+
+i {
+ font-size: 0.875em; /* 14px */
+ line-height: 1.7143em; /* 24px */
+ margin-top: 1.7143em;
+ margin-bottom: 0em;
+ font-style: normal;
+}
diff --git a/deps/goldrush/rebar.config b/deps/goldrush/rebar.config
new file mode 100644
index 0000000..370a078
--- /dev/null
+++ b/deps/goldrush/rebar.config
@@ -0,0 +1,8 @@
+{cover_enabled, true}.
+{erl_opts, [
+%% bin_opt_info,
+%% warn_missing_spec,
+ warn_export_all
+]}.
+{edoc_opts, [{stylesheet_file, "./priv/edoc.css"}]}.
+
diff --git a/deps/goldrush/src/glc.erl b/deps/goldrush/src/glc.erl
new file mode 100644
index 0000000..969c9ca
--- /dev/null
+++ b/deps/goldrush/src/glc.erl
@@ -0,0 +1,793 @@
+%% Copyright (c) 2012, Magnus Klaar <klaar@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+
+%% @doc Event filter implementation.
+%%
+%% An event query is constructed using the built in operators exported from
+%% this module. The filtering operators are used to specify which events
+%% should be included in the output of the query. The default output action
+%% is to copy all events matching the input filters associated with a query
+%% to the output. This makes it possible to construct and compose multiple
+%% queries at runtime.
+%%
+%% === Examples of built in filters ===
+%% ```
+%% %% Select all events where 'a' exists and is greater than 0.
+%% glc:gt(a, 0).
+%% %% Select all events where 'a' exists and is equal to 0.
+%% glc:eq(a, 0).
+%% %% Select all events where 'a' exists and is less than 0.
+%% glc:lt(a, 0).
+%% %% Select all events where 'a' exists and is anything.
+%% glc:wc(a).
+%%
+%% %% Select no input events. Used as black hole query.
+%% glc:null(false).
+%% %% Select all input events. Used as passthrough query.
+%% glc:null(true).
+%% '''
+%%
+%% === Examples of combining filters ===
+%% ```
+%% %% Select all events where both 'a' and 'b' exists and are greater than 0.
+%% glc:all([glc:gt(a, 0), glc:gt(b, 0)]).
+%% %% Select all events where 'a' or 'b' exists and are greater than 0.
+%% glc:any([glc:gt(a, 0), glc:gt(b, 0)]).
+%% '''
+%%
+%% === Handling output events ===
+%%
+%% Once a query has been composed it is possible to override the output action
+%% with an erlang function. The function will be applied to each output event
+%% from the query. The return value from the function will be ignored.
+%%
+%% ```
+%% %% Write all input events as info reports to the error logger.
+%% glc:with(glc:null(true), fun(E) ->
+%% error_logger:info_report(gre:pairs(E)) end).
+%% '''
+%%
+-module(glc).
+
+-export([
+ compile/2,
+ compile/3,
+ compile/4,
+ handle/2,
+ get/2,
+ delete/1,
+ reset_counters/1,
+ reset_counters/2
+]).
+
+-export([
+ lt/2, lte/2,
+ eq/2,
+ gt/2, gte/2,
+ wc/1,
+ nf/1
+]).
+
+-export([
+ all/1,
+ any/1,
+ null/1,
+ with/2
+]).
+
+-export([
+ input/1,
+ output/1,
+ filter/1,
+ union/1
+]).
+
+-record(module, {
+ 'query' :: term(),
+ tables :: [{atom(), atom()}],
+ qtree :: term(),
+ store :: term()
+}).
+
+-spec lt(atom(), term()) -> glc_ops:op().
+lt(Key, Term) ->
+ glc_ops:lt(Key, Term).
+
+-spec lte(atom(), term()) -> glc_ops:op().
+lte(Key, Term) ->
+ glc_ops:lte(Key, Term).
+
+-spec eq(atom(), term()) -> glc_ops:op().
+eq(Key, Term) ->
+ glc_ops:eq(Key, Term).
+
+-spec gt(atom(), term()) -> glc_ops:op().
+gt(Key, Term) ->
+ glc_ops:gt(Key, Term).
+
+-spec gte(atom(), term()) -> glc_ops:op().
+gte(Key, Term) ->
+ glc_ops:gte(Key, Term).
+
+-spec wc(atom()) -> glc_ops:op().
+wc(Key) ->
+ glc_ops:wc(Key).
+
+-spec nf(atom()) -> glc_ops:op().
+nf(Key) ->
+ glc_ops:nf(Key).
+
+%% @doc Filter the input using multiple filters.
+%%
+%% For an input to be considered valid output the all filters specified
+%% in the list must hold for the input event. The list is expected to
+%% be a non-empty list. If the list of filters is an empty list a `badarg'
+%% error will be thrown.
+-spec all([glc_ops:op()]) -> glc_ops:op().
+all(Filters) ->
+ glc_ops:all(Filters).
+
+
+%% @doc Filter the input using one of multiple filters.
+%%
+%% For an input to be considered valid output on of the filters specified
+%% in the list must hold for the input event. The list is expected to be
+%% a non-empty list. If the list of filters is an empty list a `badarg'
+%% error will be thrown.
+-spec any([glc_ops:op()]) -> glc_ops:op().
+any(Filters) ->
+ glc_ops:any(Filters).
+
+
+%% @doc Always return `true' or `false'.
+-spec null(boolean()) -> glc_ops:op().
+null(Result) ->
+ glc_ops:null(Result).
+
+
+%% @doc Apply a function to each output of a query.
+%%
+%% Updating the output action of a query finalizes it. Attempting
+%% to use a finalized query to construct a new query will result
+%% in a `badarg' error.
+-spec with(glc_ops:op(), fun((gre:event()) -> term())) -> glc_ops:op().
+with(Query, Action) ->
+ glc_ops:with(Query, Action).
+
+
+%% @doc Return a union of multiple queries.
+%%
+%% The union of multiple queries is the equivalent of executing multiple
+%% queries separately on the same input event. The advantage is that filter
+%% conditions that are common to all or some of the queries only need to
+%% be tested once.
+%%
+%% All queries are expected to be valid and have an output action other
+%% than the default which is `output'. If these expectations don't hold
+%% a `badarg' error will be thrown.
+-spec union([glc_ops:op()]) -> glc_ops:op().
+union(Queries) ->
+ glc_ops:union(Queries).
+
+
+%% @doc Compile a query to a module.
+%%
+%% On success the module representing the query is returned. The module and
+%% data associated with the query must be released using the {@link delete/1}
+%% function. The name of the query module is expected to be unique.
+%% The counters are reset by default, unless Reset is set to false
+-spec compile(atom(), glc_ops:op() | [glc_ops:op()]) -> {ok, atom()}.
+compile(Module, Query) ->
+ compile(Module, Query, undefined, true).
+
+-spec compile(atom(), glc_ops:op() | [glc_ops:op()], boolean()) -> {ok, atom()}.
+compile(Module, Query, Reset) when is_boolean(Reset) ->
+ compile(Module, Query, undefined, Reset);
+compile(Module, Query, undefined) ->
+ compile(Module, Query, undefined, true);
+compile(Module, Query, Store) when is_list(Store) ->
+ compile(Module, Query, Store, true).
+
+compile(Module, Query, Store, Reset) ->
+ {ok, ModuleData} = module_data(Module, Query, Store),
+ case glc_code:compile(Module, ModuleData) of
+ {ok, Module} when Reset ->
+ reset_counters(Module),
+ {ok, Module};
+ {ok, Module} ->
+ {ok, Module}
+ end.
+
+
+%% @doc Handle an event using a compiled query.
+%%
+%% The input event is expected to have been returned from {@link gre:make/2}.
+-spec handle(atom(), list({atom(), term()}) | gre:event()) -> ok.
+handle(Module, Event) when is_list(Event) ->
+ Module:handle(gre:make(Event, [list]));
+handle(Module, Event) ->
+ Module:handle(Event).
+
+get(Module, Key) ->
+ Module:get(Key).
+%% @doc The number of input events for this query module.
+-spec input(atom()) -> non_neg_integer().
+input(Module) ->
+ Module:info(input).
+
+%% @doc The number of output events for this query module.
+-spec output(atom()) -> non_neg_integer().
+output(Module) ->
+ Module:info(output).
+
+%% @doc The number of filtered events for this query module.
+-spec filter(atom()) -> non_neg_integer().
+filter(Module) ->
+ Module:info(filter).
+
+
+%% @doc Release a compiled query.
+%%
+%% This releases all resources allocated by a compiled query. The query name
+%% is expected to be associated with an existing query module. Calling this
+%% function will shutdown all relevant processes and purge/delete the module.
+-spec delete(atom()) -> ok.
+delete(Module) ->
+ Params = params_name(Module),
+ Counts = counts_name(Module),
+ ManageParams = manage_params_name(Module),
+ ManageCounts = manage_counts_name(Module),
+
+ _ = [ begin
+ ok = supervisor:terminate_child(Sup, Name),
+ ok = supervisor:delete_child(Sup, Name)
+ end || {Sup, Name} <-
+ [{gr_manager_sup, ManageParams}, {gr_manager_sup, ManageCounts},
+ {gr_param_sup, Params}, {gr_counter_sup, Counts}]
+ ],
+
+ code:soft_purge(Module),
+ code:delete(Module),
+ ok.
+
+%% @doc Reset all counters
+%%
+%% This resets all the counters associated with a module
+-spec reset_counters(atom()) -> ok.
+reset_counters(Module) ->
+ Module:reset_counters(all).
+
+%% @doc Reset a specific counter
+%%
+%% This resets a specific counter associated with a module
+-spec reset_counters(atom(), atom()) -> ok.
+reset_counters(Module, Counter) ->
+ Module:reset_counters(Counter).
+
+%% @private Map a query to a module data term.
+-spec module_data(atom(), term(), term()) -> {ok, #module{}}.
+module_data(Module, Query, Store) ->
+ %% terms in the query which are not valid arguments to the
+ %% erl_syntax:abstract/1 functions are stored in ETS.
+ %% the terms are only looked up once they are necessary to
+ %% continue evaluation of the query.
+
+ %% query counters are stored in a shared ETS table. this should
+ %% be an optional feature. enabled by defaults to simplify tests.
+ %% the abstract_tables/1 function expects a list of name-atom pairs.
+ %% tables are referred to by name in the generated code. the table/1
+ %% function maps names to registered processes response for those tables.
+ Tables = module_tables(Module),
+ Query2 = glc_lib:reduce(Query),
+ {ok, #module{'query'=Query, tables=Tables, qtree=Query2, store=Store}}.
+
+%% @private Create a data managed supervised process for params, counter tables
+module_tables(Module) ->
+ Params = params_name(Module),
+ Counts = counts_name(Module),
+ ManageParams = manage_params_name(Module),
+ ManageCounts = manage_counts_name(Module),
+ Counters = [{input,0}, {filter,0}, {output,0}],
+
+ _ = supervisor:start_child(gr_param_sup,
+ {Params, {gr_param, start_link, [Params]},
+ transient, brutal_kill, worker, [Params]}),
+ _ = supervisor:start_child(gr_counter_sup,
+ {Counts, {gr_counter, start_link, [Counts]},
+ transient, brutal_kill, worker, [Counts]}),
+ _ = supervisor:start_child(gr_manager_sup,
+ {ManageParams, {gr_manager, start_link, [ManageParams, Params, []]},
+ transient, brutal_kill, worker, [ManageParams]}),
+ _ = supervisor:start_child(gr_manager_sup, {ManageCounts,
+ {gr_manager, start_link, [ManageCounts, Counts, Counters]},
+ transient, brutal_kill, worker, [ManageCounts]}),
+ [{params,Params}, {counters, Counts}].
+
+reg_name(Module, Name) ->
+ list_to_atom("gr_" ++ atom_to_list(Module) ++ Name).
+
+params_name(Module) -> reg_name(Module, "_params").
+counts_name(Module) -> reg_name(Module, "_counters").
+manage_params_name(Module) -> reg_name(Module, "_params_mgr").
+manage_counts_name(Module) -> reg_name(Module, "_counters_mgr").
+
+
+
+%% @todo Move comment.
+%% @private Map a query to a simplified query tree term.
+%%
+%% The simplified query tree is used to combine multiple queries into one
+%% query module. The goal of this is to reduce the filtering and dispatch
+%% overhead when multiple concurrent queries are executed.
+%%
+%% A fixed selection condition may be used to specify a property that an event
+%% must have in order to be considered part of the input stream for a query.
+%%
+%% For the sake of simplicity it is only possible to define selection
+%% conditions using the fields present in the context and identifiers
+%% of an event. The fields in the context are bound to the reserved
+%% names:
+%%
+%% - '$n': node name
+%% - '$a': application name
+%% - '$p': process identifier
+%% - '$t': timestamp
+%%
+%%
+%% If an event must be selected based on the runtime state of an event handler
+%% this must be done in the body of the handler.
+
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+setup_query(Module, Query) ->
+ setup_query(Module, Query, undefined).
+
+setup_query(Module, Query, Store) ->
+ ?assertNot(erlang:module_loaded(Module)),
+ ?assertEqual({ok, Module}, case (catch compile(Module, Query, Store)) of
+ {'EXIT',_}=Error -> ?debugFmt("~p", [Error]), Error; Else -> Else end),
+ ?assert(erlang:function_exported(Module, table, 1)),
+ ?assert(erlang:function_exported(Module, handle, 1)),
+ {compiled, Module}.
+
+events_test_() ->
+ {foreach,
+ fun() ->
+ error_logger:tty(false),
+ application:start(syntax_tools),
+ application:start(compiler),
+ application:start(goldrush)
+ end,
+ fun(_) ->
+ application:stop(goldrush),
+ application:stop(compiler),
+ application:stop(syntax_tools),
+ error_logger:tty(true)
+ end,
+ [
+ {"null query compiles",
+ fun() ->
+ {compiled, Mod} = setup_query(testmod1, glc:null(false)),
+ ?assertError(badarg, Mod:table(noexists))
+ end
+ },
+ {"params table exists",
+ fun() ->
+ {compiled, Mod} = setup_query(testmod2, glc:null(false)),
+ ?assert(is_atom(Mod:table(params))),
+ ?assertMatch([_|_], gr_param:info(Mod:table(params)))
+ end
+ },
+ {"null query exists",
+ fun() ->
+ {compiled, Mod} = setup_query(testmod3, glc:null(false)),
+ ?assert(erlang:function_exported(Mod, info, 1)),
+ ?assertError(badarg, Mod:info(invalid)),
+ ?assertEqual({null, false}, Mod:info('query'))
+ end
+ },
+ {"init counters test",
+ fun() ->
+ {compiled, Mod} = setup_query(testmod4, glc:null(false)),
+ ?assertEqual(0, Mod:info(input)),
+ ?assertEqual(0, Mod:info(filter)),
+ ?assertEqual(0, Mod:info(output))
+ end
+ },
+ {"filtered events test",
+ fun() ->
+ %% If no selection condition is specified no inputs can match.
+ {compiled, Mod} = setup_query(testmod5, glc:null(false)),
+ glc:handle(Mod, gre:make([], [list])),
+ ?assertEqual(1, Mod:info(input)),
+ ?assertEqual(1, Mod:info(filter)),
+ ?assertEqual(0, Mod:info(output))
+ end
+ },
+ {"nomatch event test",
+ fun() ->
+ %% If a selection condition but no body is specified the event
+ %% is expected to count as filtered out if the condition does
+ %% not hold.
+ {compiled, Mod} = setup_query(testmod6, glc:eq('$n', 'noexists@nohost')),
+ glc:handle(Mod, gre:make([{'$n', 'noexists2@nohost'}], [list])),
+ ?assertEqual(1, Mod:info(input)),
+ ?assertEqual(1, Mod:info(filter)),
+ ?assertEqual(0, Mod:info(output))
+ end
+ },
+ {"opfilter equal test",
+ fun() ->
+ %% If a selection condition but no body is specified the event
+ %% counts as input to the query, but not as filtered out.
+ {compiled, Mod} = setup_query(testmod7, glc:eq('$n', 'noexists@nohost')),
+ glc:handle(Mod, gre:make([{'$n', 'noexists@nohost'}], [list])),
+ ?assertEqual(1, Mod:info(input)),
+ ?assertEqual(0, Mod:info(filter)),
+ ?assertEqual(1, Mod:info(output))
+ end
+ },
+ {"opfilter wildcard test",
+ fun() ->
+ {compiled, Mod} = setup_query(testmod8, glc:wc(a)),
+ glc:handle(Mod, gre:make([{b, 2}], [list])),
+ ?assertEqual(1, Mod:info(input)),
+ ?assertEqual(1, Mod:info(filter)),
+ ?assertEqual(0, Mod:info(output)),
+ glc:handle(Mod, gre:make([{a, 2}], [list])),
+ ?assertEqual(2, Mod:info(input)),
+ ?assertEqual(1, Mod:info(filter)),
+ ?assertEqual(1, Mod:info(output))
+ end
+ },
+ {"opfilter notfound test",
+ fun() ->
+ {compiled, Mod} = setup_query(testmod9, glc:nf(a)),
+ glc:handle(Mod, gre:make([{a, 2}], [list])),
+ ?assertEqual(1, Mod:info(input)),
+ ?assertEqual(1, Mod:info(filter)),
+ ?assertEqual(0, Mod:info(output)),
+ glc:handle(Mod, gre:make([{b, 2}], [list])),
+ ?assertEqual(2, Mod:info(input)),
+ ?assertEqual(1, Mod:info(filter)),
+ ?assertEqual(1, Mod:info(output))
+ end
+ },
+ {"opfilter greater than test",
+ fun() ->
+ {compiled, Mod} = setup_query(testmod10a, glc:gt(a, 1)),
+ glc:handle(Mod, gre:make([{'a', 2}], [list])),
+ ?assertEqual(1, Mod:info(input)),
+ ?assertEqual(0, Mod:info(filter)),
+ glc:handle(Mod, gre:make([{'a', 0}], [list])),
+ ?assertEqual(2, Mod:info(input)),
+ ?assertEqual(1, Mod:info(filter)),
+ ?assertEqual(1, Mod:info(output))
+ end
+ },
+ {"opfilter greater than or equal to test",
+ fun() ->
+ {compiled, Mod} = setup_query(testmod10b, glc:gte(a, 1)),
+ glc:handle(Mod, gre:make([{'a', 2}], [list])),
+ ?assertEqual(1, Mod:info(input)),
+ ?assertEqual(0, Mod:info(filter)),
+ glc:handle(Mod, gre:make([{'a', 1}], [list])),
+ ?assertEqual(2, Mod:info(input)),
+ ?assertEqual(0, Mod:info(filter)),
+ glc:handle(Mod, gre:make([{'a', 0}], [list])),
+ ?assertEqual(3, Mod:info(input)),
+ ?assertEqual(1, Mod:info(filter)),
+ ?assertEqual(2, Mod:info(output))
+ end
+ },
+ {"opfilter less than test",
+ fun() ->
+ {compiled, Mod} = setup_query(testmod11a, glc:lt(a, 1)),
+ glc:handle(Mod, gre:make([{'a', 0}], [list])),
+ ?assertEqual(1, Mod:info(input)),
+ ?assertEqual(0, Mod:info(filter)),
+ ?assertEqual(1, Mod:info(output)),
+ glc:handle(Mod, gre:make([{'a', 2}], [list])),
+ ?assertEqual(2, Mod:info(input)),
+ ?assertEqual(1, Mod:info(filter)),
+ ?assertEqual(1, Mod:info(output))
+ end
+ },
+ {"opfilter less than or equal to test",
+ fun() ->
+ {compiled, Mod} = setup_query(testmod11b, glc:lte(a, 1)),
+ glc:handle(Mod, gre:make([{'a', 0}], [list])),
+ ?assertEqual(1, Mod:info(input)),
+ ?assertEqual(0, Mod:info(filter)),
+ ?assertEqual(1, Mod:info(output)),
+ glc:handle(Mod, gre:make([{'a', 1}], [list])),
+ ?assertEqual(2, Mod:info(input)),
+ ?assertEqual(0, Mod:info(filter)),
+ ?assertEqual(2, Mod:info(output)),
+ glc:handle(Mod, gre:make([{'a', 2}], [list])),
+ ?assertEqual(3, Mod:info(input)),
+ ?assertEqual(1, Mod:info(filter)),
+ ?assertEqual(2, Mod:info(output))
+ end
+ },
+ {"allholds op test",
+ fun() ->
+ {compiled, Mod} = setup_query(testmod12,
+ glc:all([glc:eq(a, 1), glc:eq(b, 2)])),
+ glc:handle(Mod, gre:make([{'a', 1}], [list])),
+ glc:handle(Mod, gre:make([{'a', 2}], [list])),
+ ?assertEqual(2, Mod:info(input)),
+ ?assertEqual(2, Mod:info(filter)),
+ glc:handle(Mod, gre:make([{'b', 1}], [list])),
+ glc:handle(Mod, gre:make([{'b', 2}], [list])),
+ ?assertEqual(4, Mod:info(input)),
+ ?assertEqual(4, Mod:info(filter)),
+ glc:handle(Mod, gre:make([{'a', 1},{'b', 2}], [list])),
+ ?assertEqual(5, Mod:info(input)),
+ ?assertEqual(4, Mod:info(filter)),
+ ?assertEqual(1, Mod:info(output))
+ end
+ },
+ {"anyholds op test",
+ fun() ->
+ {compiled, Mod} = setup_query(testmod13,
+ glc:any([glc:eq(a, 1), glc:eq(b, 2)])),
+ glc:handle(Mod, gre:make([{'a', 2}], [list])),
+ glc:handle(Mod, gre:make([{'b', 1}], [list])),
+ ?assertEqual(2, Mod:info(input)),
+ ?assertEqual(2, Mod:info(filter)),
+ glc:handle(Mod, gre:make([{'a', 1}], [list])),
+ glc:handle(Mod, gre:make([{'b', 2}], [list])),
+ ?assertEqual(4, Mod:info(input)),
+ ?assertEqual(2, Mod:info(filter))
+ end
+ },
+ {"with function test",
+ fun() ->
+ Self = self(),
+ {compiled, Mod} = setup_query(testmod14,
+ glc:with(glc:eq(a, 1), fun(Event) -> Self ! gre:fetch(a, Event) end)),
+ glc:handle(Mod, gre:make([{a,1}], [list])),
+ ?assertEqual(1, Mod:info(output)),
+ ?assertEqual(1, receive Msg -> Msg after 0 -> notcalled end)
+ end
+ },
+ {"with function storage test",
+ fun() ->
+ Self = self(),
+ Store = [{stored, value}],
+ {compiled, Mod} = setup_query(testmod15,
+ glc:with(glc:eq(a, 1), fun(Event, EStore) ->
+ Self ! {gre:fetch(a, Event), EStore} end),
+ Store),
+ glc:handle(Mod, gre:make([{a,1}], [list])),
+ ?assertEqual(1, Mod:info(output)),
+ ?assertEqual(1, receive {Msg, Store} -> Msg after 0 -> notcalled end)
+ end
+ },
+ {"delete test",
+ fun() ->
+ {compiled, Mod} = setup_query(testmod16, glc:null(false)),
+ ?assert(is_atom(Mod:table(params))),
+ ?assertMatch([_|_], gr_param:info(Mod:table(params))),
+ ?assert(is_list(code:which(Mod))),
+ ?assert(is_pid(whereis(params_name(Mod)))),
+ ?assert(is_pid(whereis(counts_name(Mod)))),
+ ?assert(is_pid(whereis(manage_params_name(Mod)))),
+ ?assert(is_pid(whereis(manage_counts_name(Mod)))),
+
+ glc:delete(Mod),
+
+ ?assertEqual(non_existing, code:which(Mod)),
+ ?assertEqual(undefined, whereis(params_name(Mod))),
+ ?assertEqual(undefined, whereis(counts_name(Mod))),
+ ?assertEqual(undefined, whereis(manage_params_name(Mod))),
+ ?assertEqual(undefined, whereis(manage_counts_name(Mod)))
+ end
+ },
+ {"reset counters test",
+ fun() ->
+ {compiled, Mod} = setup_query(testmod17,
+ glc:any([glc:eq(a, 1), glc:eq(b, 2)])),
+ glc:handle(Mod, gre:make([{'a', 2}], [list])),
+ glc:handle(Mod, gre:make([{'b', 1}], [list])),
+ ?assertEqual(2, Mod:info(input)),
+ ?assertEqual(2, Mod:info(filter)),
+ glc:handle(Mod, gre:make([{'a', 1}], [list])),
+ glc:handle(Mod, gre:make([{'b', 2}], [list])),
+ ?assertEqual(4, Mod:info(input)),
+ ?assertEqual(2, Mod:info(filter)),
+ ?assertEqual(2, Mod:info(output)),
+
+ glc:reset_counters(Mod, input),
+ ?assertEqual(0, Mod:info(input)),
+ ?assertEqual(2, Mod:info(filter)),
+ ?assertEqual(2, Mod:info(output)),
+ glc:reset_counters(Mod, filter),
+ ?assertEqual(0, Mod:info(input)),
+ ?assertEqual(0, Mod:info(filter)),
+ ?assertEqual(2, Mod:info(output)),
+ glc:reset_counters(Mod),
+ ?assertEqual(0, Mod:info(input)),
+ ?assertEqual(0, Mod:info(filter)),
+ ?assertEqual(0, Mod:info(output))
+ end
+ },
+ {"ets data recovery test",
+ fun() ->
+ Self = self(),
+ {compiled, Mod} = setup_query(testmod18,
+ glc:with(glc:eq(a, 1), fun(Event) -> Self ! gre:fetch(a, Event) end)),
+ glc:handle(Mod, gre:make([{a,1}], [list])),
+ ?assertEqual(1, Mod:info(output)),
+ ?assertEqual(1, receive Msg -> Msg after 0 -> notcalled end),
+ ?assertEqual(1, length(gr_param:list(Mod:table(params)))),
+ ?assertEqual(3, length(gr_param:list(Mod:table(counters)))),
+ true = exit(whereis(Mod:table(params)), kill),
+ true = exit(whereis(Mod:table(counters)), kill),
+ ?assertEqual(1, Mod:info(input)),
+ glc:handle(Mod, gre:make([{'a', 1}], [list])),
+ ?assertEqual(2, Mod:info(input)),
+ ?assertEqual(2, Mod:info(output)),
+ ?assertEqual(1, length(gr_param:list(Mod:table(params)))),
+ ?assertEqual(3, length(gr_counter:list(Mod:table(counters))))
+ end
+ },
+ {"variable storage test",
+ fun() ->
+ {compiled, Mod} = setup_query(testmod19,
+ glc:eq(a, 2), [{stream, time}]),
+ glc:handle(Mod, gre:make([{'a', 2}], [list])),
+ glc:handle(Mod, gre:make([{'b', 1}], [list])),
+ ?assertEqual(2, Mod:info(input)),
+ ?assertEqual(1, Mod:info(filter)),
+ glc:handle(Mod, gre:make([{'b', 2}], [list])),
+ ?assertEqual(3, Mod:info(input)),
+ ?assertEqual(2, Mod:info(filter)),
+ ?assertEqual({ok, time}, glc:get(Mod, stream)),
+ ?assertEqual({error, undefined}, glc:get(Mod, beam))
+ end
+ },
+ {"with multi function any test",
+ fun() ->
+ Self = self(),
+ Store = [{stored, value}],
+
+ G1 = glc:with(glc:eq(a, 1), fun(_Event, EStore) ->
+ Self ! {a, EStore} end),
+ G2 = glc:with(glc:eq(b, 2), fun(_Event, EStore) ->
+ Self ! {b, EStore} end),
+
+ {compiled, Mod} = setup_query(testmod20, any([G1, G2]),
+ Store),
+ glc:handle(Mod, gre:make([{a,1}], [list])),
+ ?assertEqual(1, Mod:info(output)),
+ ?assertEqual(a, receive {Msg, _Store} -> Msg after 0 -> notcalled end),
+ ?assertEqual(b, receive {Msg, _Store} -> Msg after 0 -> notcalled end)
+ end
+ },
+ {"with multi function all test",
+ fun() ->
+ Self = self(),
+ Store = [{stored, value}],
+
+ G1 = glc:with(glc:eq(a, 1), fun(_Event, EStore) ->
+ Self ! {a, EStore} end),
+ G2 = glc:with(glc:eq(b, 2), fun(_Event, EStore) ->
+ Self ! {b, EStore} end),
+ G3 = glc:with(glc:eq(c, 3), fun(_Event, EStore) ->
+ Self ! {c, EStore} end),
+
+ {compiled, Mod} = setup_query(testmod21, all([G1, G2, G3]),
+ Store),
+ glc:handle(Mod, gre:make([{a,1}], [list])),
+ ?assertEqual(0, Mod:info(output)),
+ ?assertEqual(1, Mod:info(filter)),
+ glc:handle(Mod, gre:make([{a,1}, {b, 2}], [list])),
+ ?assertEqual(0, Mod:info(output)),
+ ?assertEqual(2, Mod:info(filter)),
+ glc:handle(Mod, gre:make([{a,1}, {b, 2}, {c, 3}], [list])),
+ ?assertEqual(1, Mod:info(output)),
+ ?assertEqual(a, receive {Msg, _Store} -> Msg after 0 -> notcalled end),
+ ?assertEqual(b, receive {Msg, _Store} -> Msg after 0 -> notcalled end),
+ ?assertEqual(c, receive {Msg, _Store} -> Msg after 0 -> notcalled end)
+ end
+ },
+ {"with multi-function output match test",
+ fun() ->
+ Self = self(),
+ Store = [{stored, value}],
+
+ {compiled, Mod} = setup_query(testmod22,
+ [glc:with(glc:eq(a, 1), fun(Event, _EStore) ->
+ Self ! {a, gre:fetch(a, Event)} end),
+ glc:with(glc:gt(b, 1), fun(Event, _EStore) ->
+ Self ! {b, gre:fetch(b, Event)} end)],
+ Store),
+ glc:handle(Mod, gre:make([{a,1}, {b, 1}], [list])),
+ ?assertEqual(1, Mod:info(output)),
+ ?assertEqual(a, receive {a=Msg, _Store} -> Msg after 0 -> notcalled end)
+
+ end
+ },
+ {"with multi-function output double-match test",
+ fun() ->
+ Self = self(),
+ Store = [{stored, value}],
+ {compiled, Mod} = setup_query(testmod23,
+ [glc:with(glc:eq(a, 1), fun(Event, _EStore) ->
+ Self ! {a, gre:fetch(a, Event)} end),
+ glc:with(glc:eq(b, 1), fun(Event, _EStore) ->
+ Self ! {b, gre:fetch(b, Event)} end)],
+ Store),
+ glc:handle(Mod, gre:make([{a,1}, {b, 1}], [list])),
+ ?assertEqual(2, Mod:info(output)),
+ ?assertEqual(a, receive {a=Msg, _Store} -> Msg after 0 -> notcalled end),
+ ?assertEqual(b, receive {b=Msg, _Store} -> Msg after 0 -> notcalled end)
+ end
+ },
+ {"with multi function complex match test",
+ fun() ->
+ Self = self(),
+ Store = [{stored, value}],
+
+ G1 = glc:with(glc:gt(r, 0.1), fun(_Event, EStore) ->
+ Self ! {a, EStore} end),
+ G2 = glc:with(glc:all([glc:eq(a, 1), glc:gt(r, 0.5)]), fun(_Event, EStore) ->
+ Self ! {b, EStore} end),
+ G3 = glc:with(glc:all([glc:eq(a, 1), glc:eq(b, 2), glc:gt(r, 0.6)]), fun(_Event, EStore) ->
+ Self ! {c, EStore} end),
+
+ {compiled, Mod} = setup_query(testmod24, [G1, G2, G3],
+ Store),
+ glc:handle(Mod, gre:make([{a,1}, {r, 0.7}, {b, 3}], [list])),
+ ?assertEqual(2, Mod:info(output)),
+ ?assertEqual(1, Mod:info(input)),
+ ?assertEqual(1, Mod:info(filter)),
+ ?assertEqual(b, receive {Msg, _Store} -> Msg after 0 -> notcalled end),
+ ?assertEqual(a, receive {Msg, _Store} -> Msg after 0 -> notcalled end),
+ %
+ glc:handle(Mod, gre:make([{a,1}, {r, 0.6}], [list])),
+ ?assertEqual(4, Mod:info(output)),
+ ?assertEqual(2, Mod:info(input)),
+ ?assertEqual(2, Mod:info(filter)),
+ ?assertEqual(b, receive {Msg, _Store} -> Msg after 0 -> notcalled end),
+ ?assertEqual(a, receive {Msg, _Store} -> Msg after 0 -> notcalled end),
+ %
+ glc:handle(Mod, gre:make([{a,2}, {r, 0.7}, {b, 3}], [list])),
+ ?assertEqual(5, Mod:info(output)),
+ ?assertEqual(3, Mod:info(input)),
+ ?assertEqual(4, Mod:info(filter)),
+ ?assertEqual(a, receive {Msg, _Store} -> Msg after 0 -> notcalled end),
+
+ glc:handle(Mod, gre:make([{a,1}, {r, 0.7}, {b, 2}], [list])),
+ ?assertEqual(8, Mod:info(output)),
+ ?assertEqual(4, Mod:info(input)),
+ ?assertEqual(4, Mod:info(filter)),
+ ?assertEqual(c, receive {Msg, _Store} -> Msg after 0 -> notcalled end),
+ ?assertEqual(b, receive {Msg, _Store} -> Msg after 0 -> notcalled end),
+ ?assertEqual(a, receive {Msg, _Store} -> Msg after 0 -> notcalled end)
+ end
+ }
+ ]
+ }.
+
+union_error_test() ->
+ ?assertError(badarg, glc:union([glc:eq(a, 1)])),
+ done.
+
+-endif.
diff --git a/deps/goldrush/src/glc_code.erl b/deps/goldrush/src/glc_code.erl
new file mode 100644
index 0000000..5325652
--- /dev/null
+++ b/deps/goldrush/src/glc_code.erl
@@ -0,0 +1,528 @@
+%% @doc Code generation functions.
+-module(glc_code).
+-compile({nowarn_unused_function, {abstract_module,2}}).
+-compile({nowarn_unused_function, {abstract_tables,1}}).
+-compile({nowarn_unused_function, {abstract_reset,0}}).
+-compile({nowarn_unused_function, {abstract_filter,3}}).
+-compile({nowarn_unused_function, {abstract_filter_,4}}).
+-compile({nowarn_unused_function, {abstract_opfilter,6}}).
+-compile({nowarn_unused_function, {abstract_all,4}}).
+-compile({nowarn_unused_function, {abstract_any,4}}).
+-compile({nowarn_unused_function, {abstract_with,3}}).
+-compile({nowarn_unused_function, {abstract_within,3}}).
+-compile({nowarn_unused_function, {abstract_getkey,4}}).
+-compile({nowarn_unused_function, {abstract_getkey_,4}}).
+-compile({nowarn_unused_function, {abstract_getparam,3}}).
+-compile({nowarn_unused_function, {param_variable,1}}).
+-compile({nowarn_unused_function, {field_variable,1}}).
+-compile({nowarn_unused_function, {field_variable_,1}}).
+-compile({nowarn_unused_function, {compile_forms,2}}).
+-compile({nowarn_unused_function, {load_binary,2}}).
+
+
+-export([
+ compile/2
+]).
+
+-define(erl, erl_syntax).
+
+-record(module, {
+ 'query' :: term(),
+ tables :: [{atom(), atom()}],
+ qtree :: term(),
+ store :: term()
+}).
+
+-type syntaxTree() :: erl_syntax:syntaxTree().
+
+-record(state, {
+ event = undefined :: syntaxTree(),
+ fields = [] :: [{atom(), syntaxTree()}],
+ fieldc = 0 :: non_neg_integer(),
+ paramvars = [] :: [{term(), syntaxTree()}],
+ paramstab = undefined :: atom(),
+ countstab = undefined :: atom()
+}).
+
+-type nextFun() :: fun((#state{}) -> [syntaxTree()]).
+
+compile(Module, ModuleData) ->
+ {ok, forms, Forms} = abstract_module(Module, ModuleData),
+ {ok, Module, Binary} = compile_forms(Forms, [nowarn_unused_vars]),
+ {ok, loaded, Module} = load_binary(Module, Binary),
+ {ok, Module}.
+
+%% abstract code generation functions
+
+%% @private Generate an abstract dispatch module.
+-spec abstract_module(atom(), #module{}) -> {ok, forms, list()}.
+abstract_module(Module, Data) ->
+ Forms = [?erl:revert(E) || E <- abstract_module_(Module, Data)],
+ case lists:keyfind(errors, 1, erl_syntax_lib:analyze_forms(Forms)) of
+ false -> {ok, forms, Forms};
+ {_, []} -> {ok, forms, Forms};
+ {_, [_|_]}=Errors -> Errors
+ end.
+
+%% @private Generate an abstract dispatch module.
+-spec abstract_module_(atom(), #module{}) -> [?erl:syntaxTree()].
+abstract_module_(Module, #module{tables=Tables, qtree=Tree}=Data) ->
+ {_, ParamsTable} = lists:keyfind(params, 1, Tables),
+ {_, CountsTable} = lists:keyfind(counters, 1, Tables),
+ AbstractMod = [
+ %% -module(Module)
+ ?erl:attribute(?erl:atom(module), [?erl:atom(Module)]),
+ %% -export([
+ ?erl:attribute(
+ ?erl:atom(export),
+ [?erl:list([
+ %% get/1
+ ?erl:arity_qualifier(
+ ?erl:atom(get),
+ ?erl:integer(1)),
+ %% info/1
+ ?erl:arity_qualifier(
+ ?erl:atom(info),
+ ?erl:integer(1)),
+ %% reset_counters/1
+ ?erl:arity_qualifier(
+ ?erl:atom(reset_counters),
+ ?erl:integer(1)),
+ %% table/1
+ ?erl:arity_qualifier(
+ ?erl:atom(table),
+ ?erl:integer(1)),
+ %% handle/1
+ ?erl:arity_qualifier(
+ ?erl:atom(handle),
+ ?erl:integer(1))])]),
+ %% ]).
+ %% get(Name) -> Term.
+ ?erl:function(
+ ?erl:atom(get),
+ abstract_get(Data) ++
+ [?erl:clause(
+ [?erl:underscore()], none,
+ [?erl:abstract({error, undefined})])]),
+ %% info(Name) -> Term.
+ ?erl:function(
+ ?erl:atom(info),
+ abstract_info(Data) ++
+ [?erl:clause(
+ [?erl:underscore()], none,
+ [abstract_apply(erlang, error, [?erl:atom(badarg)])])]),
+ %% reset_counters(Name) -> boolean().
+ ?erl:function(
+ ?erl:atom(reset_counters),
+ abstract_reset() ++
+ [?erl:clause(
+ [?erl:underscore()], none,
+ [abstract_apply(erlang, error, [?erl:atom(badarg)])])]),
+ %% table(Name) -> atom().
+ ?erl:function(
+ ?erl:atom(table),
+ abstract_tables(Tables) ++
+ [?erl:clause(
+ [?erl:underscore()], none,
+ [abstract_apply(erlang, error, [?erl:atom(badarg)])])]),
+ %% handle(Event) - entry function
+ ?erl:function(
+ ?erl:atom(handle),
+ [?erl:clause([?erl:variable("Event")], none,
+ [abstract_count(input),
+ ?erl:application(none,
+ ?erl:atom(handle_), [?erl:variable("Event")])])]),
+ %% input_(Node, App, Pid, Tags, Values) - filter roots
+ ?erl:function(
+ ?erl:atom(handle_),
+ [?erl:clause([?erl:variable("Event")], none,
+ abstract_filter(Tree, Data, #state{
+ event=?erl:variable("Event"),
+ paramstab=ParamsTable,
+ countstab=CountsTable}))])
+ ],
+ %% Transform Term -> Key to Key -> Term
+ gr_param:transform(ParamsTable),
+ AbstractMod.
+
+%% @private Return the clauses of the table/1 function.
+abstract_tables(Tables) ->
+ [?erl:clause(
+ [?erl:abstract(K)], none,
+ [?erl:abstract(V)])
+ || {K, V} <- Tables].
+
+abstract_query_find(K, Store) ->
+ case lists:keyfind(K, 1, Store) of
+ {_, Val} ->
+ {ok, Val};
+ _ ->
+ {error, notfound}
+ end.
+
+%% @private Return the original query as an expression.
+abstract_query({with, Query, _}) ->
+ [?erl:abstract(Query)];
+abstract_query([{with, _Query, _}|_] = I) ->
+ [?erl:abstract([Query || {with, Query, _} <- I])];
+ %[?erl:abstract(_Query)];
+abstract_query({any, [{with, _Q, _A}|_] = I}) ->
+ Queries = glc_lib:reduce(glc:any([Q || {with, Q, _} <- I])),
+ [?erl:abstract(Queries)];
+abstract_query({all, [{with, _Q, _A}|_] = I}) ->
+ Queries = glc_lib:reduce(glc:all([Q || {with, Q, _} <- I])),
+ [?erl:abstract(Queries)];
+abstract_query(Query) ->
+ [?erl:abstract(Query)].
+
+
+%% @private Return the clauses of the get/1 function.
+abstract_get(#module{'query'=_Query, store=undefined}) ->
+ [];
+abstract_get(#module{'query'=_Query, store=Store}) ->
+ [?erl:clause([?erl:abstract(K)], none,
+ abstract_query(abstract_query_find(K, Store)))
+ || {K, _} <- Store].
+%% @private Return the clauses of the info/1 function.
+abstract_info(#module{'query'=Query}) ->
+ [?erl:clause([?erl:abstract(K)], none, V)
+ || {K, V} <- [
+ {'query', abstract_query(Query)},
+ {input, abstract_getcount(input)},
+ {filter, abstract_getcount(filter)},
+ {output, abstract_getcount(output)}
+ ]].
+
+
+abstract_reset() ->
+ [?erl:clause([?erl:abstract(K)], none, V)
+ || {K, V} <- [
+ {all, abstract_resetcount([input, filter, output])},
+ {input, abstract_resetcount(input)},
+ {filter, abstract_resetcount(filter)},
+ {output, abstract_resetcount(output)}
+ ]].
+
+
+%% @private Return a list of expressions to apply a filter.
+%% @todo Allow mulitple functions to be specified using `with/2'.
+-spec abstract_filter(glc_ops:op() | [glc_ops:op()], #module{}, #state{}) -> [syntaxTree()].
+abstract_filter({Type, [{with, _Cond, _Fun}|_] = I}, Data, State) when Type =:= all; Type =:= any ->
+ Cond = glc_lib:reduce(glc:Type([Q || {with, Q, _} <- I])),
+ abstract_filter_(Cond,
+ _OnMatch=fun(State2) ->
+ Funs = [ F || {with, _, F} <- I ],
+ [abstract_count(output)] ++
+ abstract_with(Funs, Data, State2) end,
+ _OnNomatch=fun(_State2) -> [abstract_count(filter)] end, State);
+abstract_filter([{with, _Cond, _Fun}|_] = I, Data, State) ->
+ OnNomatch = fun(_State2) -> [abstract_count(filter, 0)] end,
+ Funs = lists:foldl(fun({with, Cond, Fun}, Acc) ->
+ [{Cond, Fun, Data}|Acc]
+ end, [], I),
+ abstract_within(Funs, OnNomatch, State);
+abstract_filter({with, Cond, Fun}, Data, State) ->
+ abstract_filter_(Cond,
+ _OnMatch=fun(State2) ->
+ [abstract_count(output)] ++
+ abstract_with(Fun, Data, State2) end,
+ _OnNomatch=fun(_State2) -> [abstract_count(filter)] end, State);
+abstract_filter(Cond, _Data, State) ->
+ abstract_filter_(Cond,
+ _OnMatch=fun(_State2) -> [abstract_count(output)] end,
+ _OnNomatch=fun(_State2) -> [abstract_count(filter)] end, State).
+
+%% @private Return a list of expressions to apply a filter.
+%% A filter expects two continuation functions which generates the expressions
+%% to apply when the filter matches or fails to match. The state passed to the
+%% functions will contain all the variable bindings of previously accessed
+%% fields and parameters.
+-spec abstract_filter_(glc_ops:op(), nextFun(), nextFun(), #state{}) ->
+ syntaxTree().
+abstract_filter_({null, true}, OnMatch, _OnNomatch, State) ->
+ OnMatch(State);
+abstract_filter_({null, false}, _OnMatch, OnNomatch, State) ->
+ OnNomatch(State);
+abstract_filter_({Key, '*'}, OnMatch, OnNomatch, State) ->
+ abstract_getkey(Key,
+ _OnMatch=fun(#state{}=State2) -> OnMatch(State2) end,
+ _OnNomatch=fun(State2) -> OnNomatch(State2) end, State);
+abstract_filter_({Key, '!'}, OnMatch, OnNomatch, State) ->
+ abstract_getkey(Key,
+ _OnNomatch=fun(State2) -> OnNomatch(State2) end,
+ _OnMatch=fun(#state{}=State2) -> OnMatch(State2) end,
+ State);
+abstract_filter_({Key, Op, Value}, OnMatch, OnNomatch, State)
+ when Op =:= '>'; Op =:= '='; Op =:= '<';
+ Op =:= '>='; Op =:= '=<'; Op =:= '<=' ->
+ Op2 = case Op of
+ '=' -> '=:=';
+ '<=' -> '=<';
+ Op -> Op
+ end,
+ abstract_opfilter(Key, Op2, Value, OnMatch, OnNomatch, State);
+abstract_filter_({'any', Conds}, OnMatch, OnNomatch, State) ->
+ abstract_any(Conds, OnMatch, OnNomatch, State);
+abstract_filter_({'all', Conds}, OnMatch, OnNomatch, State) ->
+ abstract_all(Conds, OnMatch, OnNomatch, State).
+
+%% @private Return a branch based on a built in operator.
+-spec abstract_opfilter(atom(), atom(), term(), nextFun(),
+ nextFun(), #state{}) -> [syntaxTree()].
+abstract_opfilter(Key, Opname, Value, OnMatch, OnNomatch, State) ->
+ abstract_getkey(Key,
+ _OnMatch=fun(#state{}=State2) ->
+ [?erl:case_expr(
+ abstract_apply(erlang, Opname, [
+ ?erl:variable(field_variable(Key)),
+ ?erl:abstract(Value)
+ ]),
+ [?erl:clause([?erl:atom(true)], none,
+ OnMatch(State2)),
+ ?erl:clause([?erl:atom(false)], none,
+ OnNomatch(State2))])] end,
+ _OnNomatch=fun(State2) -> OnNomatch(State2) end, State).
+
+%% @private Generate an `all' filter.
+%% An `all' filter is evaluated by testing all conditions that must hold. If
+%% any of the conditions does not hold the evaluation is short circuted at that
+%% point. This means that the `OnNomatch' branch is executed once for each
+%% condition. The `OnMatch' branch is only executed once.
+-spec abstract_all([glc_ops:op()], nextFun(), nextFun(), #state{}) ->
+ [syntaxTree()].
+abstract_all([H|T], OnMatch, OnNomatch, State) ->
+ abstract_filter_(H,
+ _OnMatch=fun(State2) -> abstract_all(T, OnMatch, OnNomatch, State2)
+ end, OnNomatch, State);
+abstract_all([], OnMatch, _OnNomatch, State) ->
+ OnMatch(State).
+
+%% @private
+-spec abstract_any([glc_ops:op()], nextFun(), nextFun(), #state{}) ->
+ [syntaxTree()].
+abstract_any([H|T], OnMatch, OnNomatch, State) ->
+ abstract_filter_(H, OnMatch,
+ _OnNomatch=fun(State2) -> abstract_any(T, OnMatch, OnNomatch, State2)
+ end, State);
+abstract_any([], _OnMatch, OnNomatch, State) ->
+ OnNomatch(State).
+
+%% @private
+-spec abstract_with(fun((gre:event()) -> term()),
+ #module{}, #state{}) -> [syntaxTree()].
+abstract_with([Fun0|_] = Funs, Data, State)
+ when is_function(Fun0, 1); is_function(Fun0, 2) ->
+ abstract_getparam(Funs, fun(#state{event=Event, paramvars=Params}) ->
+ lists:map(fun(Fun) ->
+ {_, Fun2} = lists:keyfind(Fun, 1, Params),
+ abstract_with_({Fun, Fun2}, Event, Data)
+ end, Funs)
+ end, State);
+abstract_with(Fun, Data, State) when is_function(Fun, 1); is_function(Fun, 2) ->
+ abstract_getparam(Fun, fun(#state{event=Event, paramvars=Params}) ->
+ {_, Fun2} = lists:keyfind(Fun, 1, Params),
+ [abstract_with_({Fun, Fun2}, Event, Data)]
+ end, State).
+
+abstract_within([{H, Fun, Data}|T], OnNomatch, State) ->
+ OnMatch = fun(State2) -> [abstract_count(output)] ++
+ abstract_with(Fun, Data, State2)
+ ++ abstract_within(T, OnNomatch, State2)
+ end,
+ abstract_filter_(H, OnMatch,
+ _OnNomatch=fun(State2) ->
+ [abstract_count(filter)] ++
+ abstract_within(T, OnNomatch, State2)
+ end, State);
+abstract_within([], OnNomatch, State) ->
+ OnNomatch(State).
+
+abstract_with_({Fun, Fun2}, Event, #module{store=Store}) ->
+ ?erl:application(none, Fun2,
+ case Fun of
+ _ when is_function(Fun, 1) ->
+ [Event];
+ _ when is_function(Fun, 2) ->
+ [Event, ?erl:abstract(Store)]
+ end).
+
+%% @private Bind the value of a field to a variable.
+%% If the value of a field has already been bound to a variable the previous
+%% binding is reused over re-accessing the value. The `OnMatch' function is
+%% expected to access the variable stored in the state record. The `OnNomatch'
+%% function must not attempt to access the variable.
+-spec abstract_getkey(atom(), nextFun(), nextFun(), #state{}) ->
+ [syntaxTree()].
+abstract_getkey(Key, OnMatch, OnNomatch, #state{fields=Fields}=State) ->
+ case lists:keyfind(Key, 1, Fields) of
+ {Key, _Variable} -> OnMatch(State);
+ false -> abstract_getkey_(Key, OnMatch, OnNomatch, State)
+ end.
+
+
+-spec abstract_getkey_(atom(), nextFun(), nextFun(), #state{}) ->
+ [syntaxTree()].
+abstract_getkey_(Key, OnMatch, OnNomatch, #state{
+ event=Event, fields=Fields}=State) ->
+ [?erl:case_expr(
+ abstract_apply(gre, find, [?erl:atom(Key), Event]),
+ [?erl:clause([
+ ?erl:tuple([
+ ?erl:atom(true),
+ ?erl:variable(field_variable(Key))])], none,
+ OnMatch(State#state{
+ fields=[{Key, ?erl:variable(field_variable(Key))}
+ |Fields]})),
+ ?erl:clause([
+ ?erl:atom(false)], none,
+ OnNomatch(State))
+ ]
+ )].
+
+%% @private Bind the value of a parameter to a variable.
+%% During code generation the parameter value is used as the identity of the
+%% parameter. At runtime a unique integer is used as the identity.
+-spec abstract_getparam(term(), nextFun(), #state{}) -> [syntaxTree()].
+abstract_getparam([_|_]=Terms, OnBound, #state{paramvars=_Params, fields=_Fields,
+ paramstab=_ParamsTable}=State)
+ when is_list(Terms) ->
+
+ {Keys, Bound} = lists:foldl(fun(Term, {Acc0, #state{paramvars=Params,
+ paramstab=ParamsTable}=State0}) ->
+ case lists:keyfind(Term, 1, Params) of
+ {_, _Variable} ->
+ {Acc0, State0};
+
+ false ->
+ Key = abstract_getparam_key(Term, ParamsTable),
+ Lookup = abstract_apply(gr_param, lookup_element,
+ [abstract_apply(table, [?erl:atom(params)]),
+ ?erl:abstract(Key)]),
+ Expr = ?erl:match_expr(param_variable(Key), Lookup),
+ State1 = State0#state{paramvars=[{Term, param_variable(Key)}|Params]},
+ {[Expr|Acc0], State1}
+
+ end
+ end, {[], State}, Terms),
+ Keys ++ OnBound(Bound);
+abstract_getparam(Term, OnBound, #state{paramvars=Params}=State) ->
+ case lists:keyfind(Term, 1, Params) of
+ {_, _Variable} -> OnBound(State);
+ %% parameter not bound to variable in this scope.
+ false -> abstract_getparam([Term], OnBound, State)
+ end.
+
+abstract_getparam_key(Term, ParamsTable) ->
+ case gr_param:lookup(ParamsTable, Term) of
+ [{_, Key2}] ->
+ Key2;
+ [] ->
+ Key2 = gr_param:info_size(ParamsTable),
+ gr_param:insert(ParamsTable, {Term, Key2}),
+ Key2
+ end.
+
+%% @private Generate a variable name for the value of a field.
+-spec field_variable(atom()) -> string().
+field_variable(Key) ->
+ "Field_" ++ field_variable_(atom_to_list(Key)).
+
+%% @private Escape non-alphanumeric values.
+-spec field_variable_(string()) -> string().
+field_variable_([H|T]) when H >= $0, H =< $9 ->
+ [H|field_variable_(T)];
+field_variable_([H|T]) when H >= $A, H =< $Z ->
+ [H|field_variable_(T)];
+field_variable_([H|T]) when H >= $a, H =< $z ->
+ [H|field_variable_(T)];
+field_variable_([H|T]) ->
+ "_" ++ integer_to_list(H, 16) ++ "_" ++ field_variable_(T);
+field_variable_([]) ->
+ [].
+
+%% @private Generate a variable name for the value of a parameter.
+-spec param_variable(integer()) -> syntaxTree().
+param_variable(Key) ->
+ ?erl:variable("Param_" ++ integer_to_list(Key)).
+
+%% @ private Generate a list of field variable names.
+%% Walk the query tree and generate a safe variable name string for each field
+%% that is accessed by the conditions in the query. Only allow alpha-numeric.
+%%-spec field_variables(glc_ops:op()) -> [{atom(), string()}].
+%%field_variables(Query) ->
+%% lists:usort(field_variables_(Query)).
+
+%%-spec field_variables(glc_ops:op()) -> [{atom(), string()}].
+%%field_variables_({Key, '=', _Term}) ->
+%% [{Key, field_variable(Key)}].
+
+
+
+%% @private Return an expression to increment a counter.
+%% @todo Pass state record. Only Generate code if `statistics' is enabled.
+-spec abstract_count(atom()) -> syntaxTree().
+abstract_count(Counter) ->
+ abstract_count(Counter, 1).
+abstract_count(Counter, Value) when is_integer(Value) ->
+ abstract_apply(gr_counter, update_counter,
+ [abstract_apply(table, [?erl:atom(counters)]),
+ ?erl:abstract(Counter),
+ ?erl:abstract({2,Value})]);
+abstract_count(Counter, Value) ->
+ abstract_apply(gr_counter, update_counter,
+ [abstract_apply(table, [?erl:atom(counters)]),
+ ?erl:abstract(Counter),
+ ?erl:tuple([?erl:abstract(2), Value])
+ ]).
+
+
+%% @private Return an expression to get the value of a counter.
+%% @todo Pass state record. Only Generate code if `statistics' is enabled.
+-spec abstract_getcount(atom()) -> [syntaxTree()].
+abstract_getcount(Counter) when is_atom(Counter) ->
+ abstract_getcount(?erl:abstract(Counter));
+abstract_getcount(Counter) ->
+ [abstract_apply(gr_counter, lookup_element,
+ [abstract_apply(table, [?erl:atom(counters)]), Counter])].
+
+%% @private Return an expression to reset a counter.
+-spec abstract_resetcount(atom() | [filter | input | output]) -> [syntaxTree()].
+abstract_resetcount(Counter) ->
+ [abstract_apply(gr_counter, reset_counters,
+ [abstract_apply(table, [?erl:atom(counters)]),
+ ?erl:abstract(Counter)])].
+
+
+
+%% abstract code util functions
+
+
+%% @private Compile an abstract module.
+-spec compile_forms(term(), [term()]) -> {ok, atom(), binary()}.
+compile_forms(Forms, Opts) ->
+ case compile:forms(Forms, Opts) of
+ {ok, Module, Binary} ->
+ {ok, Module, Binary};
+ {ok, Module, Binary, _Warnings} ->
+ {ok, Module, Binary};
+ Error ->
+ erlang:error({compile_forms, Error})
+ end.
+
+%% @private Load a module binary.
+-spec load_binary(atom(), binary()) -> {ok, loaded, atom()}.
+load_binary(Module, Binary) ->
+ case code:load_binary(Module, "", Binary) of
+ {module, Module} -> {ok, loaded, Module};
+ {error, Reason} -> exit({error_loading_module, Module, Reason})
+ end.
+
+%% @private Apply an exported function.
+-spec abstract_apply(atom(), atom(), [syntaxTree()]) -> syntaxTree().
+abstract_apply(Module, Function, Arguments) ->
+ ?erl:application(?erl:atom(Module), ?erl:atom(Function), Arguments).
+
+%% @private Apply a module local function.
+-spec abstract_apply(atom(), [syntaxTree()]) -> syntaxTree().
+abstract_apply(Function, Arguments) ->
+ ?erl:application(?erl:atom(Function), Arguments).
diff --git a/deps/goldrush/src/glc_lib.erl b/deps/goldrush/src/glc_lib.erl
new file mode 100644
index 0000000..c0ac3e5
--- /dev/null
+++ b/deps/goldrush/src/glc_lib.erl
@@ -0,0 +1,409 @@
+%% Copyright (c) 2012, Magnus Klaar <klaar@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% @doc Query processing functions.
+-module(glc_lib).
+
+-export([
+ reduce/1,
+ matches/2,
+ onoutput/1,
+ onoutput/2
+]).
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-undef(LET).
+-ifdef(PROPER).
+-include_lib("proper/include/proper.hrl").
+-endif.
+-endif.
+
+%% @doc Return a reduced version of a query.
+%%
+%% The purpose of this function is to ensure that a query filter
+%% is in the simplest possible form. The query filter returned
+%% from this function is functionally equivalent to the original.
+reduce(Query) ->
+ repeat(Query, fun(Q0) ->
+ Q1 = repeat(Q0, fun flatten/1),
+ Q2 = required(Q1),
+ Q3 = repeat(Q2, fun flatten/1),
+ Q4 = common(Q3),
+ Q5 = repeat(Q4, fun flatten/1),
+ Q6 = constants(Q5),
+ Q6
+ end).
+
+
+%% @doc Test if an event matches a query.
+%% This function is only intended to be used for testing purposes.
+matches({any, Conds}, Event) ->
+ lists:any(fun(Cond) -> matches(Cond, Event) end, Conds);
+matches({all, Conds}, Event) ->
+ lists:all(fun(Cond) -> matches(Cond, Event) end, Conds);
+matches({null, Const}, _Event) ->
+ Const;
+matches({Key, '<', Term}, Event) ->
+ case gre:find(Key, Event) of
+ {true, Term2} -> Term2 < Term;
+ false -> false
+ end;
+matches({Key, '=', Term}, Event) ->
+ case gre:find(Key, Event) of
+ {true, Term2} -> Term2 =:= Term;
+ false -> false
+ end;
+matches({Key, '>', Term}, Event) ->
+ case gre:find(Key, Event) of
+ {true, Term2} -> Term2 > Term;
+ false -> false
+ end;
+matches({Key, '*'}, Event) ->
+ case gre:find(Key, Event) of
+ {true, _} -> true;
+ false -> false
+ end;
+matches({Key, '!'}, Event) ->
+ not matches({Key, '*'}, Event).
+
+%% @private Repeatedly apply a function to a query.
+%% This is used for query transformation functions that must be applied
+%% multiple times to yield the simplest possible version of a query.
+repeat(Query, Fun) ->
+ case Fun(Query) of
+ Query -> Query;
+ Query2 -> repeat(Query2, Fun)
+ end.
+
+
+%% @doc Return the output action of a query.
+-spec onoutput(glc_ops:op()) -> output | no_return().
+onoutput({_, '<', _}) ->
+ output;
+onoutput({_, '=', _}) ->
+ output;
+onoutput({_, '>', _}) ->
+ output;
+onoutput({_, '*'}) ->
+ output;
+onoutput({_, '!'}) ->
+ output;
+onoutput(Query) ->
+ erlang:error(badarg, [Query]).
+
+%% @doc Modify the output action of a query.
+-spec onoutput(Action :: any(), Query :: glc_ops:op()) -> no_return().
+onoutput(Action, Query) ->
+ erlang:error(badarg, [Action, Query]).
+
+
+%% @private Flatten a condition tree.
+flatten({all, [Cond]}) ->
+ Cond;
+flatten({any, [Cond]}) ->
+ Cond;
+flatten({all, Conds}) ->
+ flatten_all([flatten(Cond) || Cond <- Conds]);
+flatten({any, [_|_]=Conds}) ->
+ flatten_any([flatten(Cond) || Cond <- Conds]);
+flatten({with, Cond, Action}) ->
+ {with, flatten(Cond), Action};
+flatten([{with, _Cond, _Action}|_] = I) ->
+ [{with, flatten(Cond), Action} || {with, Cond, Action} <- I];
+flatten(Other) ->
+ valid(Other).
+
+
+%% @private Flatten and remove duplicate members of an "all" filter.
+flatten_all(Conds) ->
+ {all, lists:usort(flatten_tag(all, Conds))}.
+
+%% @private Flatten and remove duplicate members of an "any" filter.
+flatten_any(Conds) ->
+ {any, lists:usort(flatten_tag(any, Conds))}.
+
+%% @private Common function for flattening "all" or "and" filters.
+flatten_tag(Tag, [{Tag, Conds}|T]) ->
+ Conds ++ flatten_tag(Tag, T);
+flatten_tag(Tag, [H|T]) ->
+ [H|flatten_tag(Tag, T)];
+flatten_tag(_Tag, []) ->
+ [].
+
+%% @private Factor out required filters.
+%%
+%% Identical conditions may occur in all sub-filters of an "any" filter. These
+%% filters can be tested once before testing the conditions that are unique to
+%% each sub-filter.
+%%
+%% Assume that the input has been flattened first in order to eliminate all
+%% occurances of an "any" filters being "sub-filters" of "any" filters.
+required({any, [H|_]=Conds}) ->
+ Init = ordsets:from_list(case H of {all, Init2} -> Init2; H -> [H] end),
+ case required(Conds, Init) of
+ nonefound ->
+ Conds2 = [required(Cond) || Cond <- Conds],
+ {any, Conds2};
+ {found, Req} ->
+ Conds2 = [required(deleteall(Cond, Req)) || Cond <- Conds],
+ {all, [{all, Req}, {any, Conds2}]}
+ end;
+required({all, Conds}) ->
+ {all, [required(Cond) || Cond <- Conds]};
+required(Other) ->
+ Other.
+
+required([{all, Conds}|T], Acc) ->
+ required(T, ordsets:intersection(ordsets:from_list(Conds), Acc));
+required([{any, _}|_]=Cond, Acc) ->
+ erlang:error(badarg, [Cond, Acc]);
+required([H|T], Acc) ->
+ required(T, ordsets:intersection(ordsets:from_list([H]), Acc));
+required([], [_|_]=Req) ->
+ {found, Req};
+required([], []) ->
+ nonefound.
+
+%% @private Factor our common filters.
+%%
+%% Identical conditions may occur in some sub-filters of an "all" filter. These
+%% filters can be tested once before testing the conditions that are unique to
+%% each sub-filter. This is different from factoring out common sub-filters
+%% in an "any" filter where the only those sub-filters that exist in all
+%% members.
+%%
+%% Assume that the input has been flattened first in order to eliminate all
+%% occurances of an "any" filters being "sub-filters" of "any" filters.
+common({all, Conds}) ->
+ case common_(Conds, []) of
+ {found, Found} ->
+ {all, [Found|[delete(Cond, Found) || Cond <- Conds]]};
+ nonefound ->
+ {all, [common(Cond) || Cond <- Conds]}
+ end;
+common({any, Conds}) ->
+ {any, [common(Cond) || Cond <- Conds]};
+common(Other) ->
+ Other.
+
+
+common_([{any, Conds}|T], Seen) ->
+ Set = ordsets:from_list(Conds),
+ case ordsets:intersection(Set, Seen) of
+ [] -> common_(T, ordsets:union(Set, Seen));
+ [H|_] -> {found, H}
+ end;
+common_([H|T], Seen) ->
+ case ordsets:is_element(H, Seen) of
+ false -> common_(T, ordsets:union(ordsets:from_list([H]), Seen));
+ true -> {found, H}
+ end;
+common_([], _Seen) ->
+ nonefound.
+
+%% @private Delete all occurances of constants.
+%%
+%% An "all" or "any" filter may be reduced to a constant outcome when all
+%% sub-filters has been factored out from the filter. In these cases the
+%% filter can be removed from the query.
+constants(Query) ->
+ delete(Query, {null, true}).
+
+
+
+%% @private Delete all occurances of a filter.
+%%
+%% Assume that the function is called because a filter is tested
+%% by a parent filter. It is therefore safe to replace occurances
+%% with a null filter that always returns true.
+delete({all, Conds}, Filter) ->
+ {all, [delete(Cond, Filter) || Cond <- Conds, Cond =/= Filter]};
+delete({any, Conds}, Filter) ->
+ {any, [delete(Cond, Filter) || Cond <- Conds, Cond =/= Filter]};
+delete(Filter, Filter) ->
+ {null, true};
+delete(Cond, _Filter) ->
+ Cond.
+
+%% @private Delete all occurances of multiple filters.
+deleteall(Filter, [H|T]) ->
+ deleteall(delete(Filter, H), T);
+deleteall(Filter, []) ->
+ Filter.
+
+
+
+%% @private Test if a term is a valid filter.
+-spec is_valid(glc_ops:op()) -> boolean().
+is_valid({Field, '<', _Term}) when is_atom(Field) ->
+ true;
+is_valid({Field, '=<', _Term}) when is_atom(Field) ->
+ true;
+is_valid({Field, '=', _Term}) when is_atom(Field) ->
+ true;
+is_valid({Field, '>=', _Term}) when is_atom(Field) ->
+ true;
+is_valid({Field, '>', _Term}) when is_atom(Field) ->
+ true;
+is_valid({Field, '*'}) when is_atom(Field) ->
+ true;
+is_valid({Field, '!'}) when is_atom(Field) ->
+ true;
+is_valid({null, true}) ->
+ true;
+is_valid({null, false}) ->
+ true;
+is_valid(_Other) ->
+ false.
+
+%% @private Assert that a term is a valid filter.
+%% If the term is a valid filter. The original term will be returned.
+%% If the term is not a valid filter. A `badarg' error is thrown.
+valid(Term) ->
+ is_valid(Term) orelse erlang:error(badarg, [Term]),
+ Term.
+
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+all_one_test() ->
+ ?assertEqual(glc:eq(a, 1),
+ glc_lib:reduce(glc:all([glc:eq(a, 1)]))
+ ).
+
+all_sort_test() ->
+ ?assertEqual(glc:all([glc:eq(a, 1), glc:eq(b, 2)]),
+ glc_lib:reduce(glc:all([glc:eq(b, 2), glc:eq(a, 1)]))
+ ).
+
+any_one_test() ->
+ ?assertEqual(glc:eq(a, 1),
+ glc_lib:reduce(glc:any([glc:eq(a, 1)]))
+ ).
+
+all_two_test() ->
+ ?assertEqual(glc_lib:reduce(glc:all([glc:wc(a), glc:nf(b)])),
+ glc_lib:reduce(glc:any([
+ glc:all([glc:wc(a)]),
+ glc:all([glc:wc(a), glc:nf(b)])]))
+ ).
+
+any_sort_test() ->
+ ?assertEqual(glc:any([glc:eq(a, 1), glc:eq(b, 2)]),
+ glc_lib:reduce(glc:any([glc:eq(b, 2), glc:eq(a, 1)]))
+ ).
+
+all_nest_test() ->
+ ?assertEqual(glc:all([glc:eq(a, 1), glc:eq(b, 2)]),
+ glc_lib:reduce(glc:all([glc:eq(a, 1), glc:all([glc:eq(b, 2)])]))
+ ),
+ ?assertEqual(glc:all([glc:eq(a, 1), glc:eq(b, 2), glc:eq(c, 3)]),
+ glc_lib:reduce(glc:all([glc:eq(c, 3),
+ glc:all([glc:eq(a, 1),
+ glc:all([glc:eq(b, 2)])])]))
+ ).
+
+any_nest_test() ->
+ ?assertEqual(glc:any([glc:eq(a, 1), glc:eq(b, 2)]),
+ glc_lib:reduce(glc:any([glc:eq(a, 1), glc:any([glc:eq(b, 2)])]))
+ ),
+ ?assertEqual(glc:any([glc:eq(a, 1), glc:eq(b, 2), glc:eq(c, 3)]),
+ glc_lib:reduce(glc:any([glc:eq(c, 3),
+ glc:any([glc:eq(a, 1),
+ glc:any([glc:eq(b, 2)])])]))
+ ).
+
+all_equiv_test() ->
+ ?assertEqual(glc:eq(a, 1),
+ glc_lib:reduce(glc:all([glc:eq(a, 1), glc:eq(a, 1)]))
+ ).
+
+any_equiv_test() ->
+ ?assertEqual(glc:eq(a, 1),
+ glc_lib:reduce(glc:any([glc:eq(a, 1), glc:eq(a, 1)]))
+ ).
+
+any_required_test() ->
+ ?assertEqual(
+ glc:all([
+ glc:any([glc:nf(d), glc:eq(b, 2), glc:eq(c, 3)]),
+ glc:eq(a, 1)
+ ]),
+ glc_lib:reduce(
+ glc:any([
+ glc:all([glc:eq(a, 1), glc:nf(d)]),
+ glc:all([glc:eq(a, 1), glc:eq(b, 2)]),
+ glc:all([glc:eq(a, 1), glc:eq(c, 3)])]))
+ ).
+
+
+all_common_test() ->
+ ?assertEqual(
+ glc:all([glc:eq(a, 1), glc:eq(b, 2), glc:eq(c, 3)]),
+ glc_lib:reduce(
+ glc:all([
+ glc:any([glc:eq(a, 1), glc:eq(b, 2)]),
+ glc:any([glc:eq(a, 1), glc:eq(c, 3)])]))
+ ).
+
+delete_from_all_test() ->
+ ?assertEqual(
+ glc:all([glc:eq(b,2)]),
+ deleteall(
+ glc:all([glc:eq(a, 1),glc:eq(b,2)]), [glc:eq(a, 1), glc:nf(a)])
+ ).
+
+delete_from_any_test() ->
+ ?assertEqual(
+ glc:any([glc:eq(b,2)]),
+ deleteall(
+ glc:any([glc:eq(a, 1),glc:eq(b,2)]), [glc:eq(a, 1), glc:wc(a)])
+ ).
+
+default_is_output_test_() ->
+ [?_assertEqual(output, glc_lib:onoutput(glc:lt(a, 1))),
+ ?_assertEqual(output, glc_lib:onoutput(glc:eq(a, 1))),
+ ?_assertEqual(output, glc_lib:onoutput(glc:gt(a, 1))),
+ ?_assertEqual(output, glc_lib:onoutput(glc:wc(a))),
+ ?_assertEqual(output, glc_lib:onoutput(glc:nf(a)))
+ ].
+
+-ifdef(PROPER).
+
+
+prop_reduce_returns() ->
+ ?FORALL(Query, glc_ops:op(),
+ returns(fun() -> glc_lib:reduce(Query) end)).
+
+reduce_returns_test() ->
+ ?assert(proper:quickcheck(prop_reduce_returns())).
+
+prop_matches_returns_boolean() ->
+ ?FORALL({Query, Event}, {glc_ops:op(), [{atom(), term()}]},
+ is_boolean(glc_lib:matches(Query, gre:make(Event, [list])))).
+
+matches_returns_boolean_test() ->
+ ?assert(proper:quickcheck(prop_matches_returns_boolean())).
+
+returns(Fun) ->
+ try Fun(),
+ true
+ catch _:_ ->
+ false
+ end.
+
+-endif.
+-endif.
diff --git a/deps/goldrush/src/glc_ops.erl b/deps/goldrush/src/glc_ops.erl
new file mode 100644
index 0000000..8b093ce
--- /dev/null
+++ b/deps/goldrush/src/glc_ops.erl
@@ -0,0 +1,145 @@
+%% @doc Built in operators.
+-module(glc_ops).
+
+-export([
+ lt/2, lte/2,
+ eq/2,
+ gt/2, gte/2,
+ wc/1,
+ nf/1
+]).
+
+-export([
+ all/1,
+ any/1,
+ null/1,
+ with/2
+]).
+
+-export([
+ union/1
+]).
+
+-type op() ::
+ {atom(), '=<', term()} |
+ {atom(), '=', term()} |
+ {atom(), '>', term()} |
+ {atom(), '>=', term()} |
+ {atom(), '*'} |
+ {atom(), '!'} |
+ {any, [op(), ...]} |
+ {all, [op(), ...]} |
+ {null, true|false}.
+
+-export_type([op/0]).
+
+%% @doc Test that a field value is less than a term.
+-spec lt(atom(), term()) -> op().
+lt(Key, Term) when is_atom(Key) ->
+ {Key, '<', Term};
+lt(Key, Term) ->
+ erlang:error(badarg, [Key, Term]).
+
+
+%% @doc Test that a field value is less than or equal to a term.
+-spec lte(atom(), term()) -> op().
+lte(Key, Term) when is_atom(Key) ->
+ {Key, '=<', Term};
+lte(Key, Term) ->
+ erlang:error(badarg, [Key, Term]).
+
+%% @doc Test that a field value is equal to a term.
+-spec eq(atom(), term()) -> op().
+eq(Key, Term) when is_atom(Key) ->
+ {Key, '=', Term};
+eq(Key, Term) ->
+ erlang:error(badarg, [Key, Term]).
+
+%% @doc Test that a field value is greater than a term.
+-spec gt(atom(), term()) -> op().
+gt(Key, Term) when is_atom(Key) ->
+ {Key, '>', Term};
+gt(Key, Term) ->
+ erlang:error(badarg, [Key, Term]).
+
+%% @doc Test that a field value is greater than or equal to a term.
+-spec gte(atom(), term()) -> op().
+gte(Key, Term) when is_atom(Key) ->
+ {Key, '>=', Term};
+gte(Key, Term) ->
+ erlang:error(badarg, [Key, Term]).
+
+%% @doc Test that a field exists.
+-spec wc(atom()) -> op().
+wc(Key) when is_atom(Key) ->
+ {Key, '*'};
+wc(Key) ->
+ erlang:error(badarg, [Key]).
+
+%% @doc Test that a field is not found.
+-spec nf(atom()) -> op().
+nf(Key) when is_atom(Key) ->
+ {Key, '!'};
+nf(Key) ->
+ erlang:error(badarg, [Key]).
+
+%% @doc Filter the input using multiple filters.
+%%
+%% For an input to be considered valid output the all filters specified
+%% in the list must hold for the input event. The list is expected to
+%% be a non-empty list. If the list of filters is an empty list a `badarg'
+%% error will be thrown.
+-spec all([op()]) -> op().
+all([_|_]=Conds) ->
+ {all, Conds};
+all(Other) ->
+ erlang:error(badarg, [Other]).
+
+%% @doc Filter the input using one of multiple filters.
+%%
+%% For an input to be considered valid output on of the filters specified
+%% in the list must hold for the input event. The list is expected to be
+%% a non-empty list. If the list of filters is an empty list a `badarg'
+%% error will be thrown.
+-spec any([op()]) -> op().
+any([_|_]=Conds) ->
+ {any, Conds};
+any(Other) ->
+ erlang:error(badarg, [Other]).
+
+
+%% @doc Always return `true' or `false'.
+-spec null(boolean()) -> op().
+null(Result) when is_boolean(Result) ->
+ {null, Result};
+null(Result) ->
+ erlang:error(badarg, [Result]).
+
+%% @doc Apply a function to each output of a query.
+%%
+%% Updating the output action of a query finalizes it. Attempting
+%% to use a finalized query to construct a new query will result
+%% in a `badarg' error.
+-spec with(op(), fun((gre:event()) -> term())) -> op().
+with(Query, Fun) when is_function(Fun, 1);
+ is_function(Fun, 2) ->
+ {with, Query, Fun};
+with(Query, Fun) ->
+ erlang:error(badarg, [Query, Fun]).
+
+%% @doc Return a union of multiple queries.
+%%
+%% The union of multiple queries is the equivalent of executing multiple
+%% queries separately on the same input event. The advantage is that filter
+%% conditions that are common to all or some of the queries only need to
+%% be tested once.
+%%
+%% All queries are expected to be valid and have an output action other
+%% than the default which is `output'. If these expectations don't hold
+%% a `badarg' error will be thrown.
+-spec union([op()]) -> op().
+union(Queries) ->
+ case [Query || Query <- Queries, glc_lib:onoutput(Query) =:= output] of
+ [] -> {union, Queries};
+ [_|_] -> erlang:error(badarg, [Queries])
+ end.
diff --git a/deps/goldrush/src/goldrush.app.src b/deps/goldrush/src/goldrush.app.src
new file mode 100644
index 0000000..6235a55
--- /dev/null
+++ b/deps/goldrush/src/goldrush.app.src
@@ -0,0 +1,8 @@
+{application, goldrush, [
+ {description, "Erlang event stream processor"},
+ {vsn, "0.1.7"},
+ {registered, []},
+ {applications, [kernel, stdlib, syntax_tools, compiler]},
+ {mod, {gr_app, []}},
+ {env, []}
+]}.
diff --git a/deps/goldrush/src/gr_app.erl b/deps/goldrush/src/gr_app.erl
new file mode 100644
index 0000000..20b3f9b
--- /dev/null
+++ b/deps/goldrush/src/gr_app.erl
@@ -0,0 +1,27 @@
+%% Copyright (c) 2012, Magnus Klaar <klaar@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(gr_app).
+-behaviour(application).
+
+-export([
+ start/2,
+ stop/1
+]).
+
+start(_Type, _Args) ->
+ gr_sup:start_link().
+
+stop(_State) ->
+ ok.
diff --git a/deps/goldrush/src/gr_context.erl b/deps/goldrush/src/gr_context.erl
new file mode 100644
index 0000000..c25899b
--- /dev/null
+++ b/deps/goldrush/src/gr_context.erl
@@ -0,0 +1,71 @@
+%% Copyright (c) 2012, Magnus Klaar <klaar@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% @doc Runtime context for events.
+-module(gr_context).
+
+-export([
+ make/1
+]).
+
+make(Options) ->
+ make_(undefined, undefined, undefined, undefined, Options).
+
+make_(_Node, App, Pid, Time, [{'$n', Node}|T]) ->
+ make_(Node, App, Pid, Time, T);
+make_(Node, _App, Pid, Time, [{'$a', App}|T]) ->
+ make_(Node, App, Pid, Time, T);
+make_(Node, App, _Pid, Time, [{'$p', Pid}|T]) ->
+ make_(Node, App, Pid, Time, T);
+make_(Node, App, Pid, _Time, [{'$t', Time}|T]) ->
+ make_(Node, App, Pid, Time, T);
+make_(Node, App, Pid, Time, []) ->
+ Pid2 = case Pid of undefined -> self(); _ -> Pid end,
+ Node2 = case Node of undefined -> node(Pid2); _ -> Node end,
+ App2 = case App of undefined -> application(Pid2); _ -> App end,
+ Time2 = case Time of undefined -> os:timestamp(); _ -> Time end,
+ {Node2, App2, Pid2, Time2}.
+
+application(Pid) when Pid =:= self() ->
+ case application:get_application(group_leader()) of
+ {ok, App} -> App;
+ undefined -> undefined
+ end;
+application(Pid) ->
+ {_, GroupLeader} = erlang:process_info(Pid, group_leader),
+ case application:get_application(GroupLeader) of
+ {ok, App} -> App;
+ undefined -> undefined
+ end.
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+make_defaults_test() ->
+ {Node, App, Pid, Time} = gr_context:make([]),
+ ?assertEqual(Node, node()),
+ ?assertEqual(Pid, self()),
+ ?assert(is_atom(App)),
+ ?assertMatch({_,_,_}, Time).
+
+
+make_override_test() ->
+ Pid = spawn(fun() -> ok end),
+ {Node, App, Pid, Time} = gr_context:make([
+ {'$n', nodename}, {'$a', appname}, {'$p', Pid}, {'$t', timeval}]),
+ ?assertEqual(nodename, Node),
+ ?assertEqual(appname, App),
+ ?assertEqual(timeval, Time).
+
+-endif.
diff --git a/deps/goldrush/src/gr_counter.erl b/deps/goldrush/src/gr_counter.erl
new file mode 100644
index 0000000..bcf3b5f
--- /dev/null
+++ b/deps/goldrush/src/gr_counter.erl
@@ -0,0 +1,268 @@
+%% Copyright (c) 2013, Pedram Nimreezi <deadzen@deadzen.com>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(gr_counter).
+
+-behaviour(gen_server).
+
+%% API
+-export([start_link/1,
+ list/1, lookup_element/2,
+ insert_counter/3,
+ update_counter/3, reset_counters/2]).
+
+%% gen_server callbacks
+-export([init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3]).
+
+-record(state, {table_id, waiting=[]}).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+list(Server) ->
+ case (catch gen_server:call(Server, list)) of
+ {'EXIT', _Reason} ->
+ list(gr_manager:wait_for_pid(Server));
+ Else -> Else
+ end.
+
+lookup_element(Server, Term) ->
+ case (catch gen_server:call(Server, {lookup_element, Term})) of
+ {'EXIT', _Reason} ->
+ lookup_element(gr_manager:wait_for_pid(Server), Term);
+ Else -> Else
+ end.
+
+insert_counter(Server, Counter, Value) when is_atom(Server) ->
+ case whereis(Server) of
+ undefined ->
+ insert_counter(gr_manager:wait_for_pid(Server), Counter, Value);
+ Pid ->
+ case erlang:is_process_alive(Pid) of
+ true ->
+ insert_counter(Pid, Counter, Value);
+ false ->
+ ServerPid = gr_manager:wait_for_pid(Server),
+ insert_counter(ServerPid, Counter, Value)
+ end
+ end;
+insert_counter(Server, Counter, Value) when is_pid(Server) ->
+ case (catch gen_server:call(Server, {insert_counter, Counter, Value})) of
+ {'EXIT', _Reason} ->
+ insert_counter(gr_manager:wait_for_pid(Server), Counter, Value);
+ Else -> Else
+ end.
+
+update_counter(Server, Counter, Value) when is_atom(Server) ->
+ case whereis(Server) of
+ undefined ->
+ update_counter(gr_manager:wait_for_pid(Server), Counter, Value);
+ Pid ->
+ case erlang:is_process_alive(Pid) of
+ true ->
+ update_counter(Pid, Counter, Value);
+ false ->
+ ServerPid = gr_manager:wait_for_pid(Server),
+ update_counter(ServerPid, Counter, Value)
+ end
+ end;
+update_counter(Server, Counter, Value) when is_pid(Server) ->
+ gen_server:cast(Server, {update, Counter, Value}).
+
+reset_counters(Server, Counter) ->
+ case (catch gen_server:call(Server, {reset_counters, Counter})) of
+ {'EXIT', _Reason} ->
+ reset_counters(gr_manager:wait_for_pid(Server), Counter);
+ Else -> Else
+ end.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts the server
+%%
+%% @spec start_link(Name) -> {ok, Pid} | ignore | {error, Error}
+%% @end
+%%--------------------------------------------------------------------
+start_link(Name) ->
+ gen_server:start_link({local, Name}, ?MODULE, [], []).
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Initializes the server
+%%
+%% @spec init(Args) -> {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%% @end
+%%--------------------------------------------------------------------
+init([]) ->
+ {ok, #state{}}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling call messages
+%%
+%% @spec handle_call(Request, From, State) ->
+%% {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_call(list=Call, From, State) ->
+ TableId = State#state.table_id,
+ Waiting = State#state.waiting,
+ case TableId of
+ undefined -> {noreply, State#state{waiting=[{Call, From}|Waiting]}};
+ _ -> {reply, lists:sort(handle_list(TableId)), State}
+ end;
+handle_call({lookup_element, Term}=Call, From, State) ->
+ TableId = State#state.table_id,
+ Waiting = State#state.waiting,
+ case TableId of
+ undefined -> {noreply, State#state{waiting=[{Call, From}|Waiting]}};
+ _ -> {reply, handle_lookup_element(TableId, Term), State}
+ end;
+handle_call({insert_counter, Counter, Value}, From, State) ->
+ Term = [{Counter, Value}],
+ Call = {insert, Term},
+ TableId = State#state.table_id,
+ Waiting = State#state.waiting,
+ case TableId of
+ undefined -> {noreply, State#state{waiting=[{Call, From}|Waiting]}};
+ _ -> {reply, handle_insert(TableId, Term), State}
+ end;
+handle_call({reset_counters, Counter}, From, State) ->
+ Term = case Counter of
+ _ when is_list(Counter) ->
+ [{Item, 0} || Item <- Counter];
+ _ when is_atom(Counter) ->
+ [{Counter, 0}]
+ end,
+ Call = {insert, Term},
+ TableId = State#state.table_id,
+ Waiting = State#state.waiting,
+ case TableId of
+ undefined -> {noreply, State#state{waiting=[{Call, From}|Waiting]}};
+ _ -> {reply, handle_insert(TableId, Term), State}
+ end;
+handle_call(_Request, _From, State) ->
+ Reply = {error, unhandled_message},
+ {reply, Reply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling cast messages
+%%
+%% @spec handle_cast(Msg, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_cast({update, Counter, Value}=Call, State) ->
+ TableId = State#state.table_id,
+ Waiting = State#state.waiting,
+ State2 = case TableId of
+ undefined -> State#state{waiting=[Call|Waiting]};
+ _ -> _ = handle_update_counter(TableId, Counter, Value),
+ State
+ end,
+ {noreply, State2};
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling all non call/cast messages
+%%
+%% @spec handle_info(Info, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_info({'ETS-TRANSFER', TableId, _Pid, _Data}, State) ->
+ _ = [ gen_server:reply(From, perform_call(TableId, Call))
+ || {Call, From} <- State#state.waiting ],
+ _ = [ handle_update_counter(TableId, Counter, Value)
+ || {update, Counter, Value} <- State#state.waiting ],
+ {noreply, State#state{table_id=TableId, waiting=[]}};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any
+%% necessary cleaning up. When it returns, the gen_server terminates
+%% with Reason. The return value is ignored.
+%%
+%% @spec terminate(Reason, State) -> void()
+%% @end
+%%--------------------------------------------------------------------
+terminate(_Reason, _State) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Convert process state when code is changed
+%%
+%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
+%% @end
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+
+perform_call(TableId, Call) ->
+ case Call of
+ list ->
+ handle_list(TableId);
+ {insert, Term} ->
+ handle_insert(TableId, Term);
+ {lookup_element, Term} ->
+ handle_lookup_element(TableId, Term)
+ end.
+
+handle_list(TableId) ->
+ ets:tab2list(TableId).
+
+handle_update_counter(TableId, Counter, Value) ->
+ ets:update_counter(TableId, Counter, Value).
+
+handle_insert(TableId, Term) ->
+ ets:insert(TableId, Term).
+
+handle_lookup_element(TableId, Term) ->
+ ets:lookup_element(TableId, Term, 2).
diff --git a/deps/goldrush/src/gr_counter_sup.erl b/deps/goldrush/src/gr_counter_sup.erl
new file mode 100644
index 0000000..234919b
--- /dev/null
+++ b/deps/goldrush/src/gr_counter_sup.erl
@@ -0,0 +1,42 @@
+%% Copyright (c) 2013, Pedram Nimreezi <deadzen@deadzen.com>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(gr_counter_sup).
+
+-behaviour(supervisor).
+
+-type startlink_err() :: {'already_started', pid()} | 'shutdown' | term().
+-type startlink_ret() :: {'ok', pid()} | 'ignore' | {'error', startlink_err()}.
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callbacks
+-export([init/1]).
+
+%% ===================================================================
+%% API functions
+%% ===================================================================
+%% @hidden
+-spec start_link() -> startlink_ret().
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%% ===================================================================
+%% Supervisor callbacks
+%% ===================================================================
+%% @hidden
+-spec init([]) -> {ok, { {one_for_one, 50, 10}, [supervisor:child_spec()]} }.
+init(_Args) ->
+ {ok, { {one_for_one, 50, 10}, []} }.
diff --git a/deps/goldrush/src/gr_manager.erl b/deps/goldrush/src/gr_manager.erl
new file mode 100644
index 0000000..ce6c55c
--- /dev/null
+++ b/deps/goldrush/src/gr_manager.erl
@@ -0,0 +1,185 @@
+%% Copyright (c) 2013, Pedram Nimreezi <deadzen@deadzen.com>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+
+%% @doc Process table manager for goldrush.
+%%
+%% Manager responsible for the processes, which serve as heir of the
+%% {@link gr_counter:start_link/0. <em>Counter</em>} and
+%% {@link gr_param:start_link/0. <em>Param</em>} ets table processes.
+%% This process creates the table and initial data then assigns itself
+%% to inherit the ets table if any process responsible for it is killed.
+%% It then waits to give it back while that process is recreated by its
+%% supervisor.
+-module(gr_manager).
+-behaviour(gen_server).
+
+%% API
+-export([start_link/3, wait_for_pid/1]).
+
+%% gen_server callbacks
+-export([init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3]).
+
+-define(SERVER, ?MODULE).
+
+-record(state, {table_id :: ets:tab(), managee :: atom()}).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+
+%% Setup the initial data for the ets table
+-spec setup(atom() | pid(), term()) -> ok.
+setup(Name, Data) ->
+ gen_server:cast(Name, {setup, Data}).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts the server
+%%
+%% @spec start_link(Name, Managee, Data) -> {ok, Pid} | ignore |
+%% {error, Error}
+%% @end
+%%--------------------------------------------------------------------
+start_link(Name, Managee, Data) ->
+ gen_server:start_link({local, Name}, ?MODULE, [Managee, Data], []).
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Initializes the server
+%%
+%% @spec init(Args) -> {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%% @end
+%%--------------------------------------------------------------------
+init([Managee, Data]) ->
+ process_flag(trap_exit, true),
+ setup(self(), Data),
+ {ok, #state{managee=Managee}}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling call messages
+%%
+%% @spec handle_call(Request, From, State) ->
+%% {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_call(_Request, _From, State) ->
+ Reply = {error, unhandled_message},
+ {reply, Reply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling cast messages
+%%
+%% @spec handle_cast(Msg, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_cast({setup, Data}, State = #state{managee=Managee}) ->
+ ManageePid = whereis(Managee),
+ link(ManageePid),
+ TableId = ets:new(?MODULE, [set, private]),
+ ets:insert(TableId, Data),
+ ets:setopts(TableId, {heir, self(), Data}),
+ ets:give_away(TableId, ManageePid, Data),
+ {noreply, State#state{table_id=TableId}};
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling all non call/cast messages
+%%
+%% @spec handle_info(Info, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_info({'EXIT', _Pid, _Reason}, State) ->
+ {noreply, State};
+handle_info({'ETS-TRANSFER', TableId, _Pid, Data}, State = #state{managee=Managee}) ->
+ ManageePid = wait_for_pid(Managee),
+ link(ManageePid),
+ ets:give_away(TableId, ManageePid, Data),
+ {noreply, State#state{table_id=TableId}}.
+
+%% @doc Wait for a registered process to be associated to a process identifier.
+%% @spec wait_for_pid(Managee) -> ManageePid
+-spec wait_for_pid(atom()) -> pid().
+wait_for_pid(Managee) when is_pid(Managee) ->
+ Managee;
+wait_for_pid(Managee) when is_atom(Managee), Managee =/= undefined ->
+ case whereis(Managee) of
+ undefined ->
+ timer:sleep(1),
+ wait_for_pid(Managee);
+ ManageePid -> ManageePid
+ end.
+
+
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any
+%% necessary cleaning up. When it returns, the gen_server terminates
+%% with Reason. The return value is ignored.
+%%
+%% @spec terminate(Reason, State) -> void()
+%% @end
+%%--------------------------------------------------------------------
+terminate(_Reason, _State) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Convert process state when code is changed
+%%
+%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
+%% @end
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+
+
+
diff --git a/deps/goldrush/src/gr_manager_sup.erl b/deps/goldrush/src/gr_manager_sup.erl
new file mode 100644
index 0000000..30e6bba
--- /dev/null
+++ b/deps/goldrush/src/gr_manager_sup.erl
@@ -0,0 +1,47 @@
+%% Copyright (c) 2013, Pedram Nimreezi <deadzen@deadzen.com>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% @doc Table manager supervisor for all goldrush ets process tables.
+%%
+%% Manager supervisor responsible for the {@link gr_manager:start_link/3.
+%% <em>Manager</em>} processes, which serve as heir of the
+%% {@link gr_counter:start_link/0. <em>Counter</em>} and
+%% {@link gr_param:start_link/0. <em>Param</em>} ets table processes.
+-module(gr_manager_sup).
+-behaviour(supervisor).
+
+-type startlink_err() :: {'already_started', pid()} | 'shutdown' | term().
+-type startlink_ret() :: {'ok', pid()} | 'ignore' | {'error', startlink_err()}.
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callbacks
+-export([init/1]).
+
+%% ===================================================================
+%% API functions
+%% ===================================================================
+%% @hidden
+-spec start_link() -> startlink_ret().
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%% ===================================================================
+%% Supervisor callbacks
+%% ===================================================================
+%% @hidden
+-spec init([]) -> {ok, { {one_for_one, 50, 10}, [supervisor:child_spec()]} }.
+init(_Args) ->
+ {ok, { {one_for_one, 50, 10}, []} }.
diff --git a/deps/goldrush/src/gr_param.erl b/deps/goldrush/src/gr_param.erl
new file mode 100644
index 0000000..96da689
--- /dev/null
+++ b/deps/goldrush/src/gr_param.erl
@@ -0,0 +1,269 @@
+%% Copyright (c) 2013, Pedram Nimreezi <deadzen@deadzen.com>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(gr_param).
+
+-behaviour(gen_server).
+
+%% API
+-export([start_link/1,
+ list/1, insert/2,
+ lookup/2, lookup_element/2,
+ info/1, info_size/1, transform/1]).
+
+%% gen_server callbacks
+-export([init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3]).
+
+-record(state, {table_id, waiting=[]}).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+list(Server) ->
+ case (catch gen_server:call(Server, list)) of
+ {'EXIT', _Reason} ->
+ list(gr_manager:wait_for_pid(Server));
+ Else -> Else
+ end.
+
+info_size(Server) ->
+ case (catch gen_server:call(Server, info_size)) of
+ {'EXIT', _Reason} ->
+ info_size(gr_manager:wait_for_pid(Server));
+ Else -> Else
+ end.
+
+insert(Server, Term) ->
+ case (catch gen_server:call(Server, {insert, Term})) of
+ {'EXIT', _Reason} ->
+ insert(gr_manager:wait_for_pid(Server), Term);
+ Else -> Else
+ end.
+
+lookup(Server, Term) ->
+ case (catch gen_server:call(Server, {lookup, Term})) of
+ {'EXIT', _Reason} ->
+ lookup(gr_manager:wait_for_pid(Server), Term);
+ Else -> Else
+ end.
+
+lookup_element(Server, Term) ->
+ case (catch gen_server:call(Server, {lookup_element, Term})) of
+ {'EXIT', _Reason} ->
+ lookup_element(gr_manager:wait_for_pid(Server), Term);
+ Else -> Else
+ end.
+
+info(Server) ->
+ case (catch gen_server:call(Server, info)) of
+ {'EXIT', _Reason} ->
+ info(gr_manager:wait_for_pid(Server));
+ Else -> Else
+ end.
+
+%% @doc Transform Term -> Key to Key -> Term
+transform(Server) ->
+ case (catch gen_server:call(Server, transform)) of
+ {'EXIT', _Reason} ->
+ transform(gr_manager:wait_for_pid(Server));
+ Else -> Else
+ end.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts the server
+%%
+%% @spec start_link(Name) -> {ok, Pid} | ignore | {error, Error}
+%% @end
+%%--------------------------------------------------------------------
+start_link(Name) ->
+ gen_server:start_link({local, Name}, ?MODULE, [], []).
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Initializes the server
+%%
+%% @spec init(Args) -> {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%% @end
+%%--------------------------------------------------------------------
+init([]) ->
+ {ok, #state{}}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling call messages
+%%
+%% @spec handle_call(Request, From, State) ->
+%% {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_call(Call, From, State) when is_atom(Call), Call =:= list;
+ Call =:= info; Call =:= info_size;
+ Call =:= transform ->
+ TableId = State#state.table_id,
+ Waiting = State#state.waiting,
+ case TableId of
+ undefined -> {noreply, State#state{waiting=[{Call, From}|Waiting]}};
+ _ when Call =:= list ->
+ {reply, handle_list(TableId), State};
+ _ when Call =:= info ->
+ {reply, handle_info(TableId), State};
+ _ when Call =:= info_size ->
+ {reply, handle_info_size(TableId), State};
+ _ when Call =:= transform ->
+ {reply, handle_transform(TableId), State}
+ end;
+
+handle_call({Call, Term}, From, State) when is_atom(Call), Call =:= insert;
+ Call =:= lookup;
+ Call =:= lookup_element ->
+ TableId = State#state.table_id,
+ Waiting = State#state.waiting,
+ case TableId of
+ undefined ->
+ {noreply, State#state{waiting=[{{Call, Term}, From}|Waiting]}};
+ _ when Call =:= insert ->
+ {reply, handle_insert(TableId, Term), State};
+ _ when Call =:= lookup ->
+ {reply, handle_lookup(TableId, Term), State};
+ _ when Call =:= lookup_element ->
+ {reply, handle_lookup_element(TableId, Term), State}
+ end;
+
+handle_call(_Request, _From, State) ->
+ Reply = {error, unhandled_message},
+ {reply, Reply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling cast messages
+%%
+%% @spec handle_cast(Msg, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling all non call/cast messages
+%%
+%% @spec handle_info(Info, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_info({'ETS-TRANSFER', TableId, _Pid, _Data}, State) ->
+ _ = [ gen_server:reply(From, perform_call(TableId, Call))
+ || {Call, From} <- State#state.waiting ],
+ {noreply, State#state{table_id=TableId, waiting=[]}};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any
+%% necessary cleaning up. When it returns, the gen_server terminates
+%% with Reason. The return value is ignored.
+%%
+%% @spec terminate(Reason, State) -> void()
+%% @end
+%%--------------------------------------------------------------------
+terminate(_Reason, _State) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Convert process state when code is changed
+%%
+%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
+%% @end
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+
+perform_call(TableId, Call) ->
+ case Call of
+ list ->
+ handle_list(TableId);
+ info ->
+ handle_info(TableId);
+ info_size ->
+ handle_info_size(TableId);
+ transform ->
+ handle_transform(TableId);
+ {insert, Term} ->
+ handle_insert(TableId, Term);
+ {lookup, Term} ->
+ handle_lookup(TableId, Term);
+ {lookup_element, Term} ->
+ handle_lookup_element(TableId, Term)
+ end.
+
+
+handle_list(TableId) ->
+ ets:tab2list(TableId).
+
+handle_info(TableId) ->
+ ets:info(TableId).
+
+handle_info_size(TableId) ->
+ ets:info(TableId, size).
+
+handle_transform(TableId) ->
+ ParamsList = [{K, V} || {V, K} <- ets:tab2list(TableId)],
+ ets:delete_all_objects(TableId),
+ ets:insert(TableId, ParamsList).
+
+handle_insert(TableId, Term) ->
+ ets:insert(TableId, Term).
+
+handle_lookup(TableId, Term) ->
+ ets:lookup(TableId, Term).
+
+handle_lookup_element(TableId, Term) ->
+ ets:lookup_element(TableId, Term, 2).
+
diff --git a/deps/goldrush/src/gr_param_sup.erl b/deps/goldrush/src/gr_param_sup.erl
new file mode 100644
index 0000000..5a2e925
--- /dev/null
+++ b/deps/goldrush/src/gr_param_sup.erl
@@ -0,0 +1,48 @@
+%% Copyright (c) 2013, Pedram Nimreezi <deadzen@deadzen.com>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% @doc Second level supervisor for goldrush.
+%%
+%% Supervisor for the {@link gr_param:start_link/0.
+%% <em>Param</em>}, process table responsible for params
+%% {@link gr_param:start_link/0. <em>Counter</em>} and
+%% their {@link gr_counter:start_link/0. <em>Manager</em>} supervisors.
+
+-module(gr_param_sup).
+-behaviour(supervisor).
+
+-type startlink_err() :: {'already_started', pid()} | 'shutdown' | term().
+-type startlink_ret() :: {'ok', pid()} | 'ignore' | {'error', startlink_err()}.
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callbacks
+-export([init/1]).
+
+%% ===================================================================
+%% API functions
+%% ===================================================================
+%% @hidden
+-spec start_link() -> startlink_ret().
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%% ===================================================================
+%% Supervisor callbacks
+%% ===================================================================
+%% @hidden
+-spec init([]) -> {ok, { {one_for_one, 50, 10}, [supervisor:child_spec()]} }.
+init(_Args) ->
+ {ok, { {one_for_one, 50, 10}, []} }.
diff --git a/deps/goldrush/src/gr_sup.erl b/deps/goldrush/src/gr_sup.erl
new file mode 100644
index 0000000..4fd6056
--- /dev/null
+++ b/deps/goldrush/src/gr_sup.erl
@@ -0,0 +1,42 @@
+%% Copyright (c) 2012, Magnus Klaar <klaar@ninenines.eu>
+%% Copyright (c) 2013, Pedram Nimreezi <deadzen@deadzen.com>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+
+%% @doc Top level supervisor for goldrush.
+%%
+%% Main supervisor responsible for the {@link gr_counter_sup:start_link/0.
+%% <em>Counter</em>}, {@link gr_param_sup:start_link/0. <em>Param</em>} and
+%% their {@link gr_manager_sup:start_link/0. <em>Manager</em>} supervisors.
+-module(gr_sup).
+-behaviour(supervisor).
+
+-type startlink_err() :: {'already_started', pid()} | 'shutdown' | term().
+-type startlink_ret() :: {'ok', pid()} | 'ignore' | {'error', startlink_err()}.
+
+-export([start_link/0]).
+-export([init/1]).
+
+-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
+
+-spec start_link() -> startlink_ret().
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+-spec init([]) -> {ok, { {one_for_one, 50, 10}, [supervisor:child_spec()]} }.
+init([]) ->
+ CounterSup = ?CHILD(gr_counter_sup, supervisor),
+ ParamSup = ?CHILD(gr_param_sup, supervisor),
+ MgrSup = ?CHILD(gr_manager_sup, supervisor),
+ {ok, {{one_for_one, 50, 10}, [CounterSup, ParamSup, MgrSup]}}.
diff --git a/deps/goldrush/src/gre.erl b/deps/goldrush/src/gre.erl
new file mode 100644
index 0000000..a812978
--- /dev/null
+++ b/deps/goldrush/src/gre.erl
@@ -0,0 +1,104 @@
+%% Copyright (c) 2012, Magnus Klaar <klaar@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% @doc Accessor function for goldrush event terms.
+-module(gre).
+
+-export([
+ make/2,
+ has/2,
+ fetch/2,
+ append/2,
+ merge/2,
+ find/2,
+ keys/1,
+ pairs/1
+]).
+
+-type event() :: {list, [{atom(), term()}]}.
+-export_type([event/0]).
+
+%% @doc Construct an event term.
+-spec make(term(), [list]) -> event().
+make(Term, [Type]) ->
+ {Type, Term}.
+
+
+%% @doc Check if a field exists in an event.
+-spec has(atom(), event()) -> boolean().
+has(Key, {list, List}) ->
+ lists:keymember(Key, 1, List).
+
+-spec append(term(), event()) -> event().
+append(KeyVal, {list, List}) ->
+ {list, [KeyVal|List]}.
+
+-spec merge(event(), event()) -> event().
+merge({list, AList}, {list, BList}) ->
+ {list, lists:merge(AList, BList)}.
+
+%% @doc Get the value of a field in an event.
+%% The field is expected to exist in the event.
+-spec fetch(atom(), event()) -> term().
+fetch(Key, {list, List}=Event) ->
+ case lists:keyfind(Key, 1, List) of
+ {_, Value} -> Value;
+ false -> erlang:error(badarg, [Key, Event])
+ end.
+
+
+%% @doc Find the value of a field in an event.
+%% This is equivalent to testing if a field exists using {@link has/2}
+%% before accessing the value of the field using {@link fetch/2}.
+-spec find(atom(), event()) -> {true, term()} | false.
+find(Key, {list, List}) ->
+ case lists:keyfind(Key, 1, List) of
+ {_, Value} -> {true, Value};
+ false -> false
+ end.
+
+
+%% @doc Get the names of all fields in an event.
+-spec keys(event()) -> [atom()].
+keys({list, List}) ->
+ kv_keys_(List).
+
+%% @private Get the names of all fields in a key-value list.
+-spec kv_keys_([{atom(), term()}]) -> [atom()].
+kv_keys_([{Key, _}|T]) ->
+ [Key|kv_keys_(T)];
+kv_keys_([]) ->
+ [].
+
+%% @doc Get the name and value of all fields in an event.
+-spec pairs(event()) -> [{atom(), term()}].
+pairs({list, List}) ->
+ lists:sort(List).
+
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+gre_test_() ->
+ [?_assert(gre:has(a, gre:make([{a,1}], [list]))),
+ ?_assertNot(gre:has(b, gre:make([{a,1}], [list]))),
+ ?_assertEqual(1, gre:fetch(a, gre:make([{a,1}], [list]))),
+ ?_assertError(badarg, gre:fetch(a, gre:make([], [list]))),
+ ?_assertEqual([], gre:keys(gre:make([], [list]))),
+ ?_assertEqual([a], gre:keys(gre:make([{a,1}], [list]))),
+ ?_assertEqual([a,b], gre:keys(gre:make([{a,1},{b,2}], [list]))),
+ ?_assertEqual([{a,1},{b,2}], gre:pairs(gre:make([{b,2},{a,1}], [list])))
+ ].
+
+-endif.
diff --git a/deps/lager/.gitignore b/deps/lager/.gitignore
new file mode 100644
index 0000000..7f9053c
--- /dev/null
+++ b/deps/lager/.gitignore
@@ -0,0 +1,12 @@
+.eunit
+*.beam
+ebin
+doc
+*.swp
+erl_crash.dump
+.project
+log
+deps
+.local_dialyzer_plt
+dialyzer_unhandled_warnings
+dialyzer_warnings
diff --git a/deps/lager/.travis.yml b/deps/lager/.travis.yml
new file mode 100644
index 0000000..582c194
--- /dev/null
+++ b/deps/lager/.travis.yml
@@ -0,0 +1,11 @@
+language: erlang
+notifications:
+ webhooks: http://basho-engbot.herokuapp.com/travis?key=8c2550739c2f776d4b05d993aa032f0724fe5450
+ email: eng@basho.com
+otp_release:
+ - R16B
+ - R15B03
+ - R15B01
+ - R15B
+ - R14B04
+ - R14B03
diff --git a/deps/lager/Makefile b/deps/lager/Makefile
index 560bc84..a864971 100644
--- a/deps/lager/Makefile
+++ b/deps/lager/Makefile
@@ -1,12 +1,13 @@
-.PHONY: rel stagedevrel deps test
+.PHONY: all compile deps clean distclean test check_plt build_plt dialyzer \
+ cleanplt
all: deps compile
-compile:
+compile: deps
./rebar compile
deps:
- ./rebar get-deps
+ test -d deps || ./rebar get-deps
clean:
./rebar clean
@@ -14,39 +15,7 @@ clean:
distclean: clean
./rebar delete-deps
-test:
- ./rebar compile eunit
-
-##
-## Doc targets
-##
-docs:
- ./rebar doc
-
-APPS = kernel stdlib sasl erts ssl tools os_mon runtime_tools crypto inets \
- xmerl webtool snmp public_key mnesia eunit syntax_tools compiler
-COMBO_PLT = $(HOME)/.riak_combo_dialyzer_plt
-
-check_plt: compile
- dialyzer --check_plt --plt $(COMBO_PLT) --apps $(APPS)
-
-build_plt: compile
- dialyzer --build_plt --output_plt $(COMBO_PLT) --apps $(APPS)
-
-dialyzer: compile
- @echo
- @echo Use "'make check_plt'" to check PLT prior to using this target.
- @echo Use "'make build_plt'" to build PLT prior to using this target.
- @echo
- @sleep 1
- dialyzer -Wno_return --plt $(COMBO_PLT) ebin | \
- fgrep -v -f ./dialyzer.ignore-warnings
-
-cleanplt:
- @echo
- @echo "Are you sure? It takes about 1/2 hour to re-build."
- @echo Deleting $(COMBO_PLT) in 5 seconds.
- @echo
- sleep 5
- rm $(COMBO_PLT)
+DIALYZER_APPS = kernel stdlib erts sasl eunit syntax_tools compiler crypto \
+ common_test
+include tools.mk
diff --git a/deps/lager/README.md b/deps/lager/README.md
new file mode 100644
index 0000000..645d3aa
--- /dev/null
+++ b/deps/lager/README.md
@@ -0,0 +1,552 @@
+Overview
+--------
+Lager (as in the beer) is a logging framework for Erlang. Its purpose is
+to provide a more traditional way to perform logging in an erlang application
+that plays nicely with traditional UNIX logging tools like logrotate and
+syslog.
+
+ [Travis-CI](http://travis-ci.org/basho/lager) :: ![Travis-CI](https://secure.travis-ci.org/basho/lager.png)
+
+Features
+--------
+* Finer grained log levels (debug, info, notice, warning, error, critical,
+ alert, emergency)
+* Logger calls are transformed using a parse transform to allow capturing
+ Module/Function/Line/Pid information
+* When no handler is consuming a log level (eg. debug) no event is sent
+ to the log handler
+* Supports multiple backends, including console and file.
+* Supports multiple sinks
+* Rewrites common OTP error messages into more readable messages
+* Support for pretty printing records encountered at compile time
+* Tolerant in the face of large or many log messages, won't out of memory the node
+* Optional feature to bypass log size truncation ("unsafe")
+* Supports internal time and date based rotation, as well as external rotation tools
+* Syslog style log level comparison flags
+* Colored terminal output (requires R16+)
+* Map support (requires 17+)
+
+Usage
+-----
+To use lager in your application, you need to define it as a rebar dep or have
+some other way of including it in Erlang's path. You can then add the
+following option to the erlang compiler flags:
+
+```erlang
+{parse_transform, lager_transform}
+```
+
+Alternately, you can add it to the module you wish to compile with logging
+enabled:
+
+```erlang
+-compile([{parse_transform, lager_transform}]).
+```
+
+Before logging any messages, you'll need to start the lager application. The
+lager module's `start` function takes care of loading and starting any dependencies
+lager requires.
+
+```erlang
+lager:start().
+```
+
+You can also start lager on startup with a switch to `erl`:
+
+```erlang
+erl -pa path/to/lager/ebin -s lager
+```
+
+Once you have built your code with lager and started the lager application,
+you can then generate log messages by doing the following:
+
+```erlang
+lager:error("Some message")
+```
+
+ Or:
+
+```erlang
+lager:warning("Some message with a term: ~p", [Term])
+```
+
+The general form is `lager:Severity()` where `Severity` is one of the log levels
+mentioned above.
+
+Configuration
+-------------
+To configure lager's backends, you use an application variable (probably in
+your app.config):
+
+```erlang
+{lager, [
+ {log_root, "/var/log/hello"},
+ {handlers, [
+ {lager_console_backend, info},
+ {lager_file_backend, [{file, "error.log"}, {level, error}]},
+ {lager_file_backend, [{file, "console.log"}, {level, info}]}
+ ]}
+]}.
+```
+
+```log_root``` variable is optional, by default file paths are relative to CWD.
+
+The available configuration options for each backend are listed in their
+module's documentation.
+
+Sinks
+-----
+Lager has traditionally supported a single sink (implemented as a
+`gen_event` manager) named `lager_event` to which all backends were
+connected.
+
+Lager now supports extra sinks; each sink can have different
+sync/async message thresholds and different backends.
+
+### Sink configuration
+
+To use multiple sinks (beyond the built-in sink of lager and lager_event), you
+need to:
+
+1. Setup rebar.config
+2. Configure the backends in app.config
+
+#### Names
+
+Each sink has two names: one atom to be used like a module name for
+sending messages, and that atom with `_lager_event` appended for backend
+configuration.
+
+This reflects the legacy behavior: `lager:info` (or `critical`, or
+`debug`, etc) is a way of sending a message to a sink named
+`lager_event`. Now developers can invoke `audit:info` or
+`myCompanyName:debug` so long as the corresponding `audit_lager_event` or
+`myCompanyName_lager_event` sinks are configured.
+
+#### rebar.config
+
+In `rebar.config` for the project that requires lager, include a list
+of sink names (without the `_lager_event` suffix) in `erl_opts`:
+
+`{lager_extra_sinks, [audit]}`
+
+#### Runtime requirements
+
+To be useful, sinks must be configured at runtime with backends.
+
+In `app.config` for the project that requires lager, for example,
+extend the lager configuration to include an `extra_sinks` tuple with
+backends (aka "handlers") and optionally `async_threshold` and
+`async_threshold_window` values (see **Overload Protection**
+below). If async values are not configured, no overload protection
+will be applied on that sink.
+
+```erlang
+[{lager, [
+ {log_root, "/tmp"},
+
+ %% Default handlers for lager/lager_event
+ {handlers, [
+ {lager_console_backend, info},
+ {lager_file_backend, [{file, "error.log"}, {level, error}]},
+ {lager_file_backend, [{file, "console.log"}, {level, info}]}
+ ]},
+
+ %% Any other sinks
+ {extra_sinks,
+ [
+ {audit_lager_event,
+ [{handlers,
+ [{lager_file_backend,
+ [{file, "sink1.log"},
+ {level, info}
+ ]
+ }]
+ },
+ {async_threshold, 500},
+ {async_threshold_window, 50}]
+ }]
+ }
+ ]
+ }
+].
+```
+
+Custom Formatting
+-----------------
+All loggers have a default formatting that can be overriden. A formatter is any module that
+exports `format(#lager_log_message{},Config#any())`. It is specified as part of the configuration
+for the backend:
+
+```erlang
+{lager, [
+ {handlers, [
+ {lager_console_backend, [info, {lager_default_formatter, [time," [",severity,"] ", message, "\n"]}]},
+ {lager_file_backend, [{file, "error.log"}, {level, error}, {formatter, lager_default_formatter},
+ {formatter_config, [date, " ", time," [",severity,"] ",pid, " ", message, "\n"]}]},
+ {lager_file_backend, [{file, "console.log"}, {level, info}]}
+ ]}
+]}.
+```
+
+Included is `lager_default_formatter`. This provides a generic, default formatting for log messages using a structure similar to Erlang's [iolist](http://learnyousomeerlang.com/buckets-of-sockets#io-lists) which we call "semi-iolist":
+
+* Any traditional iolist elements in the configuration are printed verbatim.
+* Atoms in the configuration are treated as placeholders for lager metadata and extracted from the log message.
+ * The placeholders `date`, `time`, `message`, `sev` and `severity` will always exist.
+ * `sev` is an abbreviated severity which is interpreted as a capitalized single letter encoding of the severity level
+ (e.g. `'debug'` -> `$D`)
+ * The placeholders `pid`, `file`, `line`, `module`, `function`, and `node` will always exist if the parse transform is used.
+ * Applications can define their own metadata placeholder.
+ * A tuple of `{atom(), semi-iolist()}` allows for a fallback for
+ the atom placeholder. If the value represented by the atom
+ cannot be found, the semi-iolist will be interpreted instead.
+ * A tuple of `{atom(), semi-iolist(), semi-iolist()}` represents a
+ conditional operator: if a value for the atom placeholder can be
+ found, the first semi-iolist will be output; otherwise, the
+ second will be used.
+
+Examples:
+
+```
+["Foo"] -> "Foo", regardless of message content.
+[message] -> The content of the logged message, alone.
+[{pid,"Unknown Pid"}] -> "<?.?.?>" if pid is in the metadata, "Unknown Pid" if not.
+[{pid, ["My pid is ", pid], ["Unknown Pid"]}] -> if pid is in the metadata print "My pid is <?.?.?>", otherwise print "Unknown Pid"
+[{server,{pid, ["(", pid, ")"], ["(Unknown Server)"]}}] -> user provided server metadata, otherwise "(<?.?.?>)", otherwise "(Unknown Server)"
+```
+
+Error logger integration
+------------------------
+Lager is also supplied with a `error_logger` handler module that translates
+traditional erlang error messages into a friendlier format and sends them into
+lager itself to be treated like a regular lager log call. To disable this, set
+the lager application variable `error_logger_redirect` to `false`.
+
+The `error_logger` handler will also log more complete error messages (protected
+with use of `trunc_io`) to a "crash log" which can be referred to for further
+information. The location of the crash log can be specified by the crash_log
+application variable. If set to `undefined` it is not written at all.
+
+Messages in the crash log are subject to a maximum message size which can be
+specified via the `crash_log_msg_size` application variable.
+
+Overload Protection
+-------------------
+
+Prior to lager 2.0, the `gen_event` at the core of lager operated purely in
+synchronous mode. Asynchronous mode is faster, but has no protection against
+message queue overload. In lager 2.0, the `gen_event` takes a hybrid approach. it
+polls its own mailbox size and toggles the messaging between synchronous and
+asynchronous depending on mailbox size.
+
+```erlang
+{async_threshold, 20},
+{async_threshold_window, 5}
+```
+
+This will use async messaging until the mailbox exceeds 20 messages, at which
+point synchronous messaging will be used, and switch back to asynchronous, when
+size reduces to `20 - 5 = 15`.
+
+If you wish to disable this behaviour, simply set it to `undefined`. It defaults
+to a low number to prevent the mailbox growing rapidly beyond the limit and causing
+problems. In general, lager should process messages as fast as they come in, so getting
+20 behind should be relatively exceptional anyway.
+
+If you want to limit the number of messages per second allowed from `error_logger`,
+which is a good idea if you want to weather a flood of messages when lots of
+related processes crash, you can set a limit:
+
+```erlang
+{error_logger_hwm, 50}
+```
+
+It is probably best to keep this number small.
+
+"Unsafe"
+--------
+The unsafe code pathway bypasses the normal lager formatting code and uses the
+same code as error_logger in OTP. This provides a marginal speedup to your logging
+code (we measured between 0.5-1.3% improvement during our benchmarking; others have
+reported better improvements.)
+
+This is a **dangerous** feature. It *will not* protect you against
+large log messages - large messages can kill your application and even your
+Erlang VM dead due to memory exhaustion as large terms are copied over and
+over in a failure cascade. We strongly recommend that this code pathway
+only be used by log messages with a well bounded upper size of around 500 bytes.
+
+If there's any possibility the log messages could exceed that limit, you should
+use the normal lager message formatting code which will provide the appropriate
+size limitations and protection against memory exhaustion.
+
+If you want to format an unsafe log message, you may use the severity level (as
+usual) followed by `_unsafe`. Here's an example:
+
+```erlang
+lager:info_unsafe("The quick brown ~s jumped over the lazy ~s", ["fox", "dog"]).
+```
+
+Runtime loglevel changes
+------------------------
+You can change the log level of any lager backend at runtime by doing the
+following:
+
+```erlang
+lager:set_loglevel(lager_console_backend, debug).
+```
+
+ Or, for the backend with multiple handles (files, mainly):
+
+```erlang
+lager:set_loglevel(lager_file_backend, "console.log", debug).
+```
+
+Lager keeps track of the minimum log level being used by any backend and
+suppresses generation of messages lower than that level. This means that debug
+log messages, when no backend is consuming debug messages, are effectively
+free. A simple benchmark of doing 1 million debug log messages while the
+minimum threshold was above that takes less than half a second.
+
+Syslog style loglevel comparison flags
+--------------------------------------
+In addition to the regular log level names, you can also do finer grained masking
+of what you want to log:
+
+```
+info - info and higher (>= is implicit)
+=debug - only the debug level
+!=info - everything but the info level
+<=notice - notice and below
+<warning - anything less than warning
+```
+
+These can be used anywhere a loglevel is supplied, although they need to be either
+a quoted atom or a string.
+
+Internal log rotation
+---------------------
+Lager can rotate its own logs or have it done via an external process. To
+use internal rotation, use the `size`, `date` and `count` values in the file
+backend's config:
+
+```erlang
+[{file, "error.log"}, {level, error}, {size, 10485760}, {date, "$D0"}, {count, 5}]
+```
+
+This tells lager to log error and above messages to `error.log` and to
+rotate the file at midnight or when it reaches 10mb, whichever comes first,
+and to keep 5 rotated logs in addition to the current one. Setting the
+count to 0 does not disable rotation, it instead rotates the file and keeps
+no previous versions around. To disable rotation set the size to 0 and the
+date to "".
+
+The `$D0` syntax is taken from the syntax newsyslog uses in newsyslog.conf.
+The relevant extract follows:
+
+```
+Day, week and month time format: The lead-in character
+for day, week and month specification is a `$'-sign.
+The particular format of day, week and month
+specification is: [Dhh], [Ww[Dhh]] and [Mdd[Dhh]],
+respectively. Optional time fields default to
+midnight. The ranges for day and hour specifications
+are:
+
+ hh hours, range 0 ... 23
+ w day of week, range 0 ... 6, 0 = Sunday
+ dd day of month, range 1 ... 31, or the
+ letter L or l to specify the last day of
+ the month.
+
+Some examples:
+ $D0 rotate every night at midnight
+ $D23 rotate every day at 23:00 hr
+ $W0D23 rotate every week on Sunday at 23:00 hr
+ $W5D16 rotate every week on Friday at 16:00 hr
+ $M1D0 rotate on the first day of every month at
+ midnight (i.e., the start of the day)
+ $M5D6 rotate on every 5th day of the month at
+ 6:00 hr
+```
+
+To configure the crash log rotation, the following application variables are
+used:
+* `crash_log_size`
+* `crash_log_date`
+* `crash_log_count`
+
+See the `.app.src` file for further details.
+
+Syslog Support
+--------------
+Lager syslog output is provided as a separate application:
+[lager_syslog](https://github.com/basho/lager_syslog). It is packaged as a
+separate application so lager itself doesn't have an indirect dependency on a
+port driver. Please see the `lager_syslog` README for configuration information.
+
+Older Backends
+--------------
+Lager 2.0 changed the backend API, there are various 3rd party backends for
+lager available, but they may not have been updated to the new API. As they
+are updated, links to them can be re-added here.
+
+Record Pretty Printing
+----------------------
+Lager's parse transform will keep track of any record definitions it encounters
+and store them in the module's attributes. You can then, at runtime, print any
+record a module compiled with the lager parse transform knows about by using the
+`lager:pr/2` function, which takes the record and the module that knows about the record:
+
+```erlang
+lager:info("My state is ~p", [lager:pr(State, ?MODULE)])
+```
+
+Often, `?MODULE` is sufficent, but you can obviously substitute that for a literal module name.
+`lager:pr` also works from the shell.
+
+Colored terminal output
+-----------------------
+If you have Erlang R16 or higher, you can tell lager's console backend to be colored. Simply
+add to lager's application environment config:
+
+```erlang
+{colored, true}
+```
+
+If you don't like the default colors, they are also configurable; see
+the `.app.src` file for more details.
+
+The output will be colored from the first occurrence of the atom color
+in the formatting configuration. For example:
+
+```erlang
+{lager_console_backend, [info, {lager_default_formatter, [time, color, " [",severity,"] ", message, "\e[0m\r\n"]}]}
+```
+
+This will make the entire log message, except time, colored. The
+escape sequence before the line break is needed in order to reset the
+color after each log message.
+
+Tracing
+-------
+Lager supports basic support for redirecting log messages based on log message
+attributes. Lager automatically captures the pid, module, function and line at the
+log message callsite. However, you can add any additional attributes you wish:
+
+```erlang
+lager:warning([{request, RequestID},{vhost, Vhost}], "Permission denied to ~s", [User])
+```
+
+Then, in addition to the default trace attributes, you'll be able to trace
+based on request or vhost:
+
+```erlang
+lager:trace_file("logs/example.com.error", [{vhost, "example.com"}], error)
+```
+
+To persist metadata for the life of a process, you can use `lager:md/1` to store metadata
+in the process dictionary:
+
+```erlang
+lager:md([{zone, forbidden}])
+```
+
+Note that `lager:md` will *only* accept a list of key/value pairs keyed by atoms.
+
+You can also omit the final argument, and the loglevel will default to
+`debug`.
+
+Tracing to the console is similar:
+
+```erlang
+lager:trace_console([{request, 117}])
+```
+
+In the above example, the loglevel is omitted, but it can be specified as the
+second argument if desired.
+
+You can also specify multiple expressions in a filter, or use the `*` atom as
+a wildcard to match any message that has that attribute, regardless of its
+value.
+
+Tracing to an existing logfile is also supported (but see **Multiple
+sink support** below):
+
+```erlang
+lager:trace_file("log/error.log", [{module, mymodule}, {function, myfunction}], warning)
+```
+
+To view the active log backends and traces, you can use the `lager:status()`
+function. To clear all active traces, you can use `lager:clear_all_traces()`.
+
+To delete a specific trace, store a handle for the trace when you create it,
+that you later pass to `lager:stop_trace/1`:
+
+```erlang
+{ok, Trace} = lager:trace_file("log/error.log", [{module, mymodule}]),
+...
+lager:stop_trace(Trace)
+```
+
+Tracing to a pid is somewhat of a special case, since a pid is not a
+data-type that serializes well. To trace by pid, use the pid as a string:
+
+```erlang
+lager:trace_console([{pid, "<0.410.0>"}])
+```
+
+As of lager 2.0, you can also use a 3 tuple while tracing, where the second
+element is a comparison operator. The currently supported comparison operators
+are:
+
+* `<` - less than
+* `=` - equal to
+* `>` - greater than
+
+```erlang
+lager:trace_console([{request, '>', 117}, {request, '<', 120}])
+```
+
+Using `=` is equivalent to the 2-tuple form.
+
+### Multiple sink support
+
+If using multiple sinks, there are limitations on tracing that you
+should be aware of.
+
+Traces are specific to a sink, which can be specified via trace
+filters:
+
+```erlang
+lager:trace_file("log/security.log", [{sink, audit}, {function, myfunction}], warning)
+```
+
+If no sink is thus specified, the default lager sink will be used.
+
+This has two ramifications:
+
+* Traces cannot intercept messages sent to a different sink.
+* Tracing to a file already opened via `lager:trace_file` will only be
+ successful if the same sink is specified.
+
+The former can be ameliorated by opening multiple traces; the latter
+can be fixed by rearchitecting lager's file backend, but this has not
+been tackled.
+
+Setting the truncation limit at compile-time
+--------------------------------------------
+Lager defaults to truncating messages at 4096 bytes, you can alter this by
+using the `{lager_truncation_size, X}` option. In rebar, you can add it to
+`erl_opts`:
+
+```erlang
+{erl_opts, [{parse_transform, lager_transform}, {lager_truncation_size, 1024}]}.
+```
+
+You can also pass it to `erlc`, if you prefer:
+
+```
+erlc -pa lager/ebin +'{parse_transform, lager_transform}' +'{lager_truncation_size, 1024}' file.erl
+```
diff --git a/deps/lager/README.org b/deps/lager/README.org
deleted file mode 100644
index 691dd96..0000000
--- a/deps/lager/README.org
+++ /dev/null
@@ -1,222 +0,0 @@
-* Overview
- Lager (as in the beer) is a logging framework for Erlang. Its purpose is
- to provide a more traditional way to perform logging in an erlang application
- that plays nicely with traditional UNIX logging tools like logrotate and
- syslog.
-
-* Features
- - Finer grained log levels (debug, info, notice, warning, error, critical,
- alert, emergency)
- - Logger calls are transformed using a parse transform to allow capturing
- Module/Function/Line/Pid information
- - When no handler is consuming a log level (eg. debug) no event is even sent
- to the log handler
- - Supports multiple backends, including console and file. More are planned.
-
-* Usage
- To use lager in your application, you need to define it as a rebar dep or have
- some other way of including it in erlang's path. You can then add the
- following option to the erlang compiler flags
-
-#+BEGIN_EXAMPLE
- {parse_transform, lager_transform}
-#+END_EXAMPLE
-
- Alternately, you can add it to the module you wish to compile with logging
- enabled:
-
-#+BEGIN_EXAMPLE
- -compile([{parse_transform, lager_transform}]).
-#+END_EXAMPLE
-
- Once you have built your code with lager, you can then generate log messages
- by doing the following:
-
-#+BEGIN_EXAMPLE
- lager:error("Some message")
-#+END_EXAMPLE
-
- Or:
-
-#+BEGIN_EXAMPLE
- lager:warning("Some message with a term: ~p", [Term])
-#+END_EXAMPLE
-
- The general form is lager:Severity() where Severity is one of the log levels
- mentioned above.
-
-* Configuration
- To configure lager's backends, you use an application variable (probably in
- your app.config):
-
-#+BEGIN_EXAMPLE
- {lager, [
- {handlers, [
- {lager_console_backend, info},
- {lager_file_backend, [
- {"error.log", error, 10485760, "$D0", 5},
- {"console.log", info, 10485760, "$D0", 5}
- ]}
- ]}
- ]}.
-#+END_EXAMPLE
-
- The available configuration options for each backend are listed in their
- module's documentation.
-
-* Error logger integration
- Lager is also supplied with a error_logger handler module that translates
- traditional erlang error messages into a friendlier format and sends them into
- lager itself to be treated like a regular lager log call. To disable this, set
- the lager application variable `error_logger_redirect' to `false'.
-
- The error_logger handler will also log more complete error messages (protected
- with use of trunc_io) to a "crash log" which can be referred to for further
- information. The location of the crash log can be specified by the crash_log
- application variable. If undefined it is not written at all.
-
- Messages in the crash log are subject to a maximum message size which can be
- specified via the crash_log_msg_size application variable.
-
-* Runtime loglevel changes
- You can change the log level of any lager backend at runtime by doing the
- following:
-
-#+BEGIN_EXAMPLE
- lager:set_loglevel(lager_console_backend, debug).
-#+END_EXAMPLE
-
- Or, for the backend with multiple handles (files, mainly):
-
-#+BEGIN_EXAMPLE
- lager:set_loglevel(lager_file_backend, "console.log", debug).
-#+END_EXAMPLE
-
- Lager keeps track of the minium log level being used by any backend and
- supresses generation of messages lower than that level. This means that debug
- log messages, when no backend is consuming debug messages, are effectively
- free. A simple benchmark of doing 1 million debug log messages while the
- minimum threshold was above that takes less than half a second.
-
-* Internal log rotation
- Lager can rotate its own logs or have it done via an external process. To
- use internal rotation, use the last 3 values in the file backend's
- configuration tuple. For example
-
-#+BEGIN_EXAMPLE
- {"error.log", error, 10485760, "$D0", 5}
-#+END_EXAMPLE
-
- This tells lager to log error and above messages to "error.log" and to
- rotate the file at midnight or when it reaches 10mb, whichever comes first
- and to keep 5 rotated logs, in addition to the current one. Setting the
- count to 0 does not disable rotation, it instead rotates the file and keeps
- no previous versions around. To disable rotation set the size to 0 and the
- date to "".
-
- The "$D0" syntax is taken from the syntax newsyslog uses in newsyslog.conf.
- The relevant extract follows:
-
-#+BEGIN_EXAMPLE
- Day, week and month time format: The lead-in character
- for day, week and month specification is a `$'-sign.
- The particular format of day, week and month
- specification is: [Dhh], [Ww[Dhh]] and [Mdd[Dhh]],
- respectively. Optional time fields default to
- midnight. The ranges for day and hour specifications
- are:
-
- hh hours, range 0 ... 23
- w day of week, range 0 ... 6, 0 = Sunday
- dd day of month, range 1 ... 31, or the
- letter L or l to specify the last day of
- the month.
-
- Some examples:
- $D0 rotate every night at midnight
- $D23 rotate every day at 23:00 hr
- $W0D23 rotate every week on Sunday at 23:00 hr
- $W5D16 rotate every week on Friday at 16:00 hr
- $M1D0 rotate on the first day of every month at
- midnight (i.e., the start of the day)
- $M5D6 rotate on every 5th day of the month at
- 6:00 hr
-#+END_EXAMPLE
-
- To configure the crash log rotation, the following application variables are
- used:
- - crash_log_size
- - crash_log_date
- - crash_log_count
-
- See the .app.src file for further details.
-
-* Syslog Support
- Lager syslog output is provided as a separate application;
- [[https://github.com/basho/lager_syslog][lager_syslog]]. It is packaged as a
- separate application so Lager itself doesn't have an indirect dependancy on a
- port driver. Please see the lager_syslog README for configuration information.
-
-* AMQP Support
- Jon Brisbin has written a lager backend to send lager messages into AMQP, so
- you can aggregate logs from a cluster into a central point. You can find it
- under the [[https://github.com/jbrisbin/lager_amqp_backend][lager_amqp_backend]]
- project on github.
-
-* Tracing
- Lager supports basic support for redirecting log messages based on log message
- attributes. Lager automatically captures the pid, module, function and line at the
- log message callsite. However, you can add any additional attributes you wish:
-
-#+BEGIN_EXAMPLE
- lager:warning([{request, RequestID},{vhost, Vhost}], "Permission denied to ~s", [User])
-#+END_EXAMPLE
-
- Then, in addition to the default trace attributes, you'll be able to trace
- based on request or vhost:
-
-#+BEGIN_EXAMPLE
- lager:trace_file("logs/example.com.error", [{vhost, "example.com"}], error)
-#+END_EXAMPLE
-
- You can also omit the final argument, and the loglevel will default to
- 'debug'.
-
- Tracing to the console is similar:
-
-#+BEGIN_EXAMPLE
- lager:trace_console([{request, 117}])
-#+END_EXAMPLE
-
- In the above example, the loglevel is omitted, but it can be specified as the
- second argument if desired.
-
- You can also specify multiple expressions in a filter, or use the '*' atom as
- a wildcard to match any message that has that attribute, regardless of its
- value.
-
- Tracing to an existing logfile is also supported, if you wanted to log
- warnings from a particular module to the default error.log:
-
-#+BEGIN_EXAMPLE
- lager:trace_file("log/error.log", [{module, mymodule}], warning)
-#+END_EXAMPLE
-
- To view the active log backends and traces, you can use the lager:status()
- function. To clear all active traces, you can use lager:clear_all_traces().
-
- To delete a specific trace, store a handle for the trace when you create it,
- that you later pass to lager:stop_trace/1:
-
-#+BEGIN_EXAMPLE
- {ok, Trace} = lager:trace_file("log/error.log", [{module, mymodule}]),
- ...
- lager:stop_trace(Trace)
-#+END_EXAMPLE
-
- Tracing to a pid is somewhat of a special case, since a pid is not a
- data-type that serializes well. To trace by pid, use the pid as a string:
-
-#+BEGIN_EXAMPLE
- lager:trace_console([{pid, "<0.410.0>"}])
-#+END_EXAMPLE
diff --git a/deps/lager/dialyzer.ignore-warnings b/deps/lager/dialyzer.ignore-warnings
index e69de29..a0e8e5d 100644
--- a/deps/lager/dialyzer.ignore-warnings
+++ b/deps/lager/dialyzer.ignore-warnings
@@ -0,0 +1,5 @@
+lager_trunc_io.erl:283: Call to missing or unexported function erlang:is_map/1
+lager_trunc_io.erl:335: Call to missing or unexported function erlang:map_size/1
+Unknown functions:
+ lager_default_tracer:info/1
+ maps:to_list/1
diff --git a/deps/lager/include/lager.hrl b/deps/lager/include/lager.hrl
index 7ea7b8c..82116f0 100644
--- a/deps/lager/include/lager.hrl
+++ b/deps/lager/include/lager.hrl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
@@ -13,21 +13,29 @@
%% KIND, either express or implied. See the License for the
%% specific language governing permissions and limitations
%% under the License.
--ifndef(__LAGER_HRL__).
--define(__LAGER_HRL__, true).
+
+
+-define(DEFAULT_TRUNCATION, 4096).
+-define(DEFAULT_TRACER, lager_default_tracer).
+-define(DEFAULT_SINK, lager_event).
-define(LEVELS,
[debug, info, notice, warning, error, critical, alert, emergency, none]).
--define(DEBUG, 7).
--define(INFO, 6).
--define(NOTICE, 5).
--define(WARNING, 4).
--define(ERROR, 3).
--define(CRITICAL, 2).
--define(ALERT, 1).
--define(EMERGENCY, 0).
--define(LOG_NONE, -1).
+%% Use of these "functions" means that the argument list will not be
+%% truncated for safety
+-define(LEVELS_UNSAFE,
+ [{debug_unsafe, debug}, {info_unsafe, info}, {notice_unsafe, notice}, {warning_unsafe, warning}, {error_unsafe, error}, {critical_unsafe, critical}, {alert_unsafe, alert}, {emergency_unsafe, emergency}]).
+
+-define(DEBUG, 128).
+-define(INFO, 64).
+-define(NOTICE, 32).
+-define(WARNING, 16).
+-define(ERROR, 8).
+-define(CRITICAL, 4).
+-define(ALERT, 2).
+-define(EMERGENCY, 1).
+-define(LOG_NONE, 0).
-define(LEVEL2NUM(Level),
case Level of
@@ -53,14 +61,15 @@
?EMERGENCY -> emergency
end).
-
-define(SHOULD_LOG(Level),
- lager_util:level_to_num(Level) =< element(1, lager_mochiglobal:get(loglevel, {?LOG_NONE, []}))).
+ (lager_util:level_to_num(Level) band element(1, lager_config:get(loglevel, {?LOG_NONE, []}))) /= 0).
-define(NOTIFY(Level, Pid, Format, Args),
- gen_event:notify(lager_event, {log, lager_util:level_to_num(Level),
- lager_util:format_time(), [io_lib:format("[~p] ", [Level]),
- io_lib:format("~p ", [Pid]), io_lib:format(Format, Args)]})).
+ gen_event:notify(lager_event, {log, lager_msg:new(io_lib:format(Format, Args),
+ Level,
+ [{pid,Pid},{line,?LINE},{file,?FILE},{module,?MODULE}],
+ [])}
+ )).
%% FOR INTERNAL USE ONLY
%% internal non-blocking logging call
@@ -81,7 +90,7 @@
%% from a gen_event handler
spawn(fun() ->
case catch(gen_event:which_handlers(lager_event)) of
- X when X == []; X == {'EXIT', noproc} ->
+ X when X == []; X == {'EXIT', noproc}; X == [lager_backend_throttle] ->
%% there's no handlers yet or lager isn't running, try again
%% in half a second.
timer:sleep(500),
@@ -97,4 +106,15 @@
end)).
-endif.
--endif.
+-record(lager_shaper, {
+ %% how many messages per second we try to deliver
+ hwm = undefined :: 'undefined' | pos_integer(),
+ %% how many messages we've received this second
+ mps = 0 :: non_neg_integer(),
+ %% the current second
+ lasttime = os:timestamp() :: erlang:timestamp(),
+ %% count of dropped messages this second
+ dropped = 0 :: non_neg_integer()
+ }).
+
+-type lager_shaper() :: #lager_shaper{}.
diff --git a/deps/lager/priv/edoc.css b/deps/lager/priv/edoc.css
new file mode 100644
index 0000000..1d50def
--- /dev/null
+++ b/deps/lager/priv/edoc.css
@@ -0,0 +1,130 @@
+/* Baseline rhythm */
+body {
+ font-size: 16px;
+ font-family: Helvetica, sans-serif;
+ margin: 8px;
+}
+
+p {
+ font-size: 1em; /* 16px */
+ line-height: 1.5em; /* 24px */
+ margin: 0 0 1.5em 0;
+}
+
+h1 {
+ font-size: 1.5em; /* 24px */
+ line-height: 1em; /* 24px */
+ margin-top: 1em;
+ margin-bottom: 0em;
+}
+
+h2 {
+ font-size: 1.375em; /* 22px */
+ line-height: 1.0909em; /* 24px */
+ margin-top: 1.0909em;
+ margin-bottom: 0em;
+}
+
+h3 {
+ font-size: 1.25em; /* 20px */
+ line-height: 1.2em; /* 24px */
+ margin-top: 1.2em;
+ margin-bottom: 0em;
+}
+
+h4 {
+ font-size: 1.125em; /* 18px */
+ line-height: 1.3333em; /* 24px */
+ margin-top: 1.3333em;
+ margin-bottom: 0em;
+}
+
+.class-for-16px {
+ font-size: 1em; /* 16px */
+ line-height: 1.5em; /* 24px */
+ margin-top: 1.5em;
+ margin-bottom: 0em;
+}
+
+.class-for-14px {
+ font-size: 0.875em; /* 14px */
+ line-height: 1.7143em; /* 24px */
+ margin-top: 1.7143em;
+ margin-bottom: 0em;
+}
+
+ul {
+ margin: 0 0 1.5em 0;
+}
+
+/* Customizations */
+body {
+ color: #333;
+}
+
+tt, code, pre {
+ font-family: "Andale Mono", "Inconsolata", "Monaco", "DejaVu Sans Mono", monospaced;
+}
+
+tt, code { font-size: 0.875em }
+
+pre {
+ font-size: 0.875em; /* 14px */
+ line-height: 1.7143em; /* 24px */
+ margin: 0 1em 1.7143em;
+ padding: 0 1em;
+ background: #eee;
+}
+
+.navbar img, hr { display: none }
+
+table {
+ border-collapse: collapse;
+}
+
+h1 {
+ border-left: 0.5em solid #fa0;
+ padding-left: 0.5em;
+}
+
+h2.indextitle {
+ font-size: 1.25em; /* 20px */
+ line-height: 1.2em; /* 24px */
+ margin: -8px -8px 0.6em;
+ background-color: #fa0;
+ color: white;
+ padding: 0.3em;
+}
+
+ul.index {
+ list-style: none;
+ margin-left: 0em;
+ padding-left: 0;
+}
+
+ul.index li {
+ display: inline;
+ padding-right: 0.75em
+}
+
+div.spec p {
+ margin-bottom: 0;
+ padding-left: 1.25em;
+ background-color: #eee;
+}
+
+h3.function {
+ border-left: 0.5em solid #fa0;
+ padding-left: 0.5em;
+ background: #fc9;
+}
+a, a:visited, a:hover, a:active { color: #C60 }
+h2 a, h3 a { color: #333 }
+
+i {
+ font-size: 0.875em; /* 14px */
+ line-height: 1.7143em; /* 24px */
+ margin-top: 1.7143em;
+ margin-bottom: 0em;
+ font-style: normal;
+}
diff --git a/deps/lager/rebar b/deps/lager/rebar
index 8645775..9ccb678 100755
--- a/deps/lager/rebar
+++ b/deps/lager/rebar
Binary files differ
diff --git a/deps/lager/rebar.config b/deps/lager/rebar.config
index bfaa56f..10e49bc 100644
--- a/deps/lager/rebar.config
+++ b/deps/lager/rebar.config
@@ -1,4 +1,54 @@
-{erl_opts, [debug_info]}.
+%% -*- erlang -*-
+%% -------------------------------------------------------------------
+%%
+%% Copyright (c) 2011-2015 Basho Technologies, Inc.
+%%
+%% This file is provided to you 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.
+%%
+%% -------------------------------------------------------------------
+
+{erl_opts, [
+ {lager_extra_sinks, ['__lager_test_sink']},
+ debug_info,
+ report,
+ verbose,
+ warn_deprecated_function,
+ warn_deprecated_type,
+ warn_export_all,
+ warn_export_vars,
+ warn_obsolete_guard,
+ warn_untyped_record,
+ warn_unused_import
+ % do NOT include warnings_as_errors, as rebar includes these options
+ % when compiling for eunit, and at least one test module has code that
+ % is deliberatly broken and will generate an un-maskable warning
+]}.
+
{erl_first_files, ["src/lager_util.erl"]}.
+{eunit_opts, [verbose]}.
+{eunit_compile_opts, [
+ nowarn_untyped_record,
+ nowarn_export_all
+]}.
+{deps, [
+ {goldrush, ".*", {git, "git://github.com/DeadZen/goldrush.git", {tag, "0.1.7"}}}
+]}.
+
+{xref_checks, []}.
+{xref_queries, [{"(XC - UC) || (XU - X - B - lager_default_tracer : Mod - erlang:\"(is_map|map_size)\"/1 - maps:to_list/1)", []}]}.
+
{cover_enabled, true}.
+{edoc_opts, [{stylesheet_file, "./priv/edoc.css"}]}.
diff --git a/deps/lager/src/error_logger_lager_h.erl b/deps/lager/src/error_logger_lager_h.erl
index f52fed5..2bdab73 100644
--- a/deps/lager/src/error_logger_lager_h.erl
+++ b/deps/lager/src/error_logger_lager_h.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% Copyright (c) 2011-2015 Basho Technologies, Inc. All Rights Reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
@@ -27,26 +27,36 @@
-behaviour(gen_event).
+-export([set_high_water/1]).
-export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2,
code_change/3]).
-export([format_reason/1]).
--define(LOG(Level, Pid, Msg),
+-record(state, {
+ shaper :: lager_shaper(),
+ %% group leader strategy
+ groupleader_strategy :: handle | ignore | mirror
+ }).
+
+-define(LOGMSG(Level, Pid, Msg),
case ?SHOULD_LOG(Level) of
true ->
- lager:log(Level, Pid, Msg);
+ _ =lager:log(Level, Pid, Msg),
+ ok;
_ -> ok
end).
--define(LOG(Level, Pid, Fmt, Args),
+-define(LOGFMT(Level, Pid, Fmt, Args),
case ?SHOULD_LOG(Level) of
true ->
- lager:log(Level, Pid, Fmt, Args);
+ _ = lager:log(Level, Pid, Fmt, Args),
+ ok;
_ -> ok
end).
-ifdef(TEST).
+-compile(export_all).
%% Make CRASH synchronous when testing, to avoid timing headaches
-define(CRASH_LOG(Event),
catch(gen_server:call(lager_crash_log, {log, Event}))).
@@ -55,14 +65,59 @@
gen_server:cast(lager_crash_log, {log, Event})).
-endif.
--spec init(any()) -> {ok, {}}.
-init(_) ->
- {ok, {}}.
+set_high_water(N) ->
+ gen_event:call(error_logger, ?MODULE, {set_high_water, N}, infinity).
+
+-spec init(any()) -> {ok, #state{}}.
+init([HighWaterMark, GlStrategy]) ->
+ Shaper = #lager_shaper{hwm=HighWaterMark},
+ {ok, #state{shaper=Shaper, groupleader_strategy=GlStrategy}}.
+handle_call({set_high_water, N}, #state{shaper=Shaper} = State) ->
+ NewShaper = Shaper#lager_shaper{hwm=N},
+ {ok, ok, State#state{shaper = NewShaper}};
handle_call(_Request, State) ->
- {ok, ok, State}.
+ {ok, unknown_call, State}.
+
+handle_event(Event, #state{shaper=Shaper} = State) ->
+ case lager_util:check_hwm(Shaper) of
+ {true, 0, NewShaper} ->
+ eval_gl(Event, State#state{shaper=NewShaper});
+ {true, Drop, #lager_shaper{hwm=Hwm} = NewShaper} when Drop > 0 ->
+ ?LOGFMT(warning, self(),
+ "lager_error_logger_h dropped ~p messages in the last second that exceeded the limit of ~p messages/sec",
+ [Drop, Hwm]),
+ eval_gl(Event, State#state{shaper=NewShaper});
+ {false, _, NewShaper} ->
+ {ok, State#state{shaper=NewShaper}}
+ end.
+
+handle_info(_Info, State) ->
+ {ok, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%% internal functions
+
+eval_gl(Event, #state{groupleader_strategy=GlStrategy0}=State) when is_pid(element(2, Event)) ->
+ case element(2, Event) of
+ GL when node(GL) =/= node(), GlStrategy0 =:= ignore ->
+ gen_event:notify({error_logger, node(GL)}, Event),
+ {ok, State};
+ GL when node(GL) =/= node(), GlStrategy0 =:= mirror ->
+ gen_event:notify({error_logger, node(GL)}, Event),
+ log_event(Event, State);
+ _ ->
+ log_event(Event, State)
+ end;
+eval_gl(Event, State) ->
+ log_event(Event, State).
-handle_event(Event, State) ->
+log_event(Event, State) ->
case Event of
{error, _GL, {Pid, Fmt, Args}} ->
case Fmt of
@@ -70,128 +125,177 @@ handle_event(Event, State) ->
%% gen_server terminate
[Name, _Msg, _State, Reason] = Args,
?CRASH_LOG(Event),
- ?LOG(error, Pid, "gen_server ~w terminated with reason: ~s",
+ ?LOGFMT(error, Pid, "gen_server ~w terminated with reason: ~s",
[Name, format_reason(Reason)]);
"** State machine "++_ ->
%% gen_fsm terminate
[Name, _Msg, StateName, _StateData, Reason] = Args,
?CRASH_LOG(Event),
- ?LOG(error, Pid, "gen_fsm ~w in state ~w terminated with reason: ~s",
+ ?LOGFMT(error, Pid, "gen_fsm ~w in state ~w terminated with reason: ~s",
[Name, StateName, format_reason(Reason)]);
"** gen_event handler"++_ ->
%% gen_event handler terminate
[ID, Name, _Msg, _State, Reason] = Args,
?CRASH_LOG(Event),
- ?LOG(error, Pid, "gen_event ~w installed in ~w terminated with reason: ~s",
+ ?LOGFMT(error, Pid, "gen_event ~w installed in ~w terminated with reason: ~s",
[ID, Name, format_reason(Reason)]);
+ "** Cowboy handler"++_ ->
+ %% Cowboy HTTP server error
+ ?CRASH_LOG(Event),
+ case Args of
+ [Module, Function, Arity, _Request, _State] ->
+ %% we only get the 5-element list when its a non-exported function
+ ?LOGFMT(error, Pid,
+ "Cowboy handler ~p terminated with reason: call to undefined function ~p:~p/~p",
+ [Module, Module, Function, Arity]);
+ [Module, Function, Arity, _Class, Reason | Tail] ->
+ %% any other cowboy error_format list *always* ends with the stacktrace
+ StackTrace = lists:last(Tail),
+ ?LOGFMT(error, Pid,
+ "Cowboy handler ~p terminated in ~p:~p/~p with reason: ~s",
+ [Module, Module, Function, Arity, format_reason({Reason, StackTrace})])
+ end;
+ "Ranch listener "++_ ->
+ %% Ranch errors
+ ?CRASH_LOG(Event),
+ case Args of
+ [Ref, _Protocol, Worker, {[{reason, Reason}, {mfa, {Module, Function, Arity}}, {stacktrace, StackTrace} | _], _}] ->
+ ?LOGFMT(error, Worker,
+ "Ranch listener ~p terminated in ~p:~p/~p with reason: ~s",
+ [Ref, Module, Function, Arity, format_reason({Reason, StackTrace})]);
+ [Ref, _Protocol, Worker, Reason] ->
+ ?LOGFMT(error, Worker,
+ "Ranch listener ~p terminated with reason: ~s",
+ [Ref, format_reason(Reason)])
+ end;
+ "webmachine error"++_ ->
+ %% Webmachine HTTP server error
+ ?CRASH_LOG(Event),
+ [Path, Error] = Args,
+ %% webmachine likes to mangle the stack, for some reason
+ StackTrace = case Error of
+ {error, {error, Reason, Stack}} ->
+ {Reason, Stack};
+ _ ->
+ Error
+ end,
+ ?LOGFMT(error, Pid, "Webmachine error at path ~p : ~s", [Path, format_reason(StackTrace)]);
_ ->
?CRASH_LOG(Event),
- ?LOG(error, Pid, lager:safe_format(Fmt, Args, 4096))
+ ?LOGFMT(error, Pid, Fmt, Args)
end;
{error_report, _GL, {Pid, std_error, D}} ->
?CRASH_LOG(Event),
- ?LOG(error, Pid, print_silly_list(D));
+ ?LOGMSG(error, Pid, print_silly_list(D));
{error_report, _GL, {Pid, supervisor_report, D}} ->
?CRASH_LOG(Event),
case lists:sort(D) of
[{errorContext, Ctx}, {offender, Off}, {reason, Reason}, {supervisor, Name}] ->
Offender = format_offender(Off),
- ?LOG(error, Pid,
+ ?LOGFMT(error, Pid,
"Supervisor ~w had child ~s exit with reason ~s in context ~w",
- [element(2, Name), Offender, format_reason(Reason), Ctx]);
+ [supervisor_name(Name), Offender, format_reason(Reason), Ctx]);
_ ->
- ?LOG(error, Pid, ["SUPERVISOR REPORT ", print_silly_list(D)])
+ ?LOGMSG(error, Pid, "SUPERVISOR REPORT " ++ print_silly_list(D))
end;
{error_report, _GL, {Pid, crash_report, [Self, Neighbours]}} ->
?CRASH_LOG(Event),
- ?LOG(error, Pid, ["CRASH REPORT ", format_crash_report(Self, Neighbours)]);
+ ?LOGMSG(error, Pid, "CRASH REPORT " ++ format_crash_report(Self, Neighbours));
{warning_msg, _GL, {Pid, Fmt, Args}} ->
- ?LOG(warning, Pid, lager:safe_format(Fmt, Args, 4096));
+ ?LOGFMT(warning, Pid, Fmt, Args);
{warning_report, _GL, {Pid, std_warning, Report}} ->
- ?LOG(warning, Pid, print_silly_list(Report));
+ ?LOGMSG(warning, Pid, print_silly_list(Report));
{info_msg, _GL, {Pid, Fmt, Args}} ->
- ?LOG(info, Pid, lager:safe_format(Fmt, Args, 4096));
+ ?LOGFMT(info, Pid, Fmt, Args);
{info_report, _GL, {Pid, std_info, D}} when is_list(D) ->
Details = lists:sort(D),
case Details of
[{application, App}, {exited, Reason}, {type, _Type}] ->
- ?LOG(info, Pid, "Application ~w exited with reason: ~s",
- [App, format_reason(Reason)]);
+ case application:get_env(lager, suppress_application_start_stop) of
+ {ok, true} when Reason == stopped ->
+ ok;
+ _ ->
+ ?LOGFMT(info, Pid, "Application ~w exited with reason: ~s",
+ [App, format_reason(Reason)])
+ end;
_ ->
- ?LOG(info, Pid, print_silly_list(D))
+ ?LOGMSG(info, Pid, print_silly_list(D))
end;
{info_report, _GL, {Pid, std_info, D}} ->
- ?LOG(info, Pid, "~w", [D]);
+ ?LOGFMT(info, Pid, "~w", [D]);
{info_report, _GL, {P, progress, D}} ->
Details = lists:sort(D),
case Details of
[{application, App}, {started_at, Node}] ->
- ?LOG(info, P, "Application ~w started on node ~w",
- [App, Node]);
+ case application:get_env(lager, suppress_application_start_stop) of
+ {ok, true} ->
+ ok;
+ _ ->
+ ?LOGFMT(info, P, "Application ~w started on node ~w",
+ [App, Node])
+ end;
[{started, Started}, {supervisor, Name}] ->
- MFA = format_mfa(proplists:get_value(mfargs, Started)),
- Pid = proplists:get_value(pid, Started),
- ?LOG(debug, P, "Supervisor ~w started ~s at pid ~w",
- [element(2, Name), MFA, Pid]);
+ MFA = format_mfa(get_value(mfargs, Started)),
+ Pid = get_value(pid, Started),
+ ?LOGFMT(debug, P, "Supervisor ~w started ~s at pid ~w",
+ [supervisor_name(Name), MFA, Pid]);
_ ->
- ?LOG(info, P, ["PROGRESS REPORT ", print_silly_list(D)])
+ ?LOGMSG(info, P, "PROGRESS REPORT " ++ print_silly_list(D))
end;
_ ->
- ?LOG(warning, self(), "Unexpected error_logger event ~w", [Event])
+ ?LOGFMT(warning, self(), "Unexpected error_logger event ~w", [Event])
end,
{ok, State}.
-handle_info(_Info, State) ->
- {ok, State}.
-
-terminate(_Reason, _State) ->
- ok.
-
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
-%% internal functions
-
format_crash_report(Report, Neighbours) ->
- Name = case proplists:get_value(registered_name, Report, []) of
+ Name = case get_value(registered_name, Report, []) of
[] ->
%% process_info(Pid, registered_name) returns [] for unregistered processes
- proplists:get_value(pid, Report);
+ get_value(pid, Report);
Atom -> Atom
end,
- {_Class, Reason, Trace} = proplists:get_value(error_info, Report),
- ReasonStr = case is_atom(Reason) of
- true ->
- format_reason({Reason, Trace});
- _ ->
- format_reason(Reason)
+ {Class, Reason, Trace} = get_value(error_info, Report),
+ ReasonStr = format_reason({Reason, Trace}),
+ Type = case Class of
+ exit -> "exited";
+ _ -> "crashed"
end,
- io_lib:format("Process ~w with ~w neighbours crashed with reason: ~s",
- [Name, length(Neighbours), ReasonStr]).
+ io_lib:format("Process ~w with ~w neighbours ~s with reason: ~s",
+ [Name, length(Neighbours), Type, ReasonStr]).
format_offender(Off) ->
- case proplists:get_value(mfargs, Off) of
+ case get_value(mfargs, Off) of
undefined ->
%% supervisor_bridge
io_lib:format("at module ~w at ~w",
- [proplists:get_value(mod, Off), proplists:get_value(pid, Off)]);
+ [get_value(mod, Off), get_value(pid, Off)]);
MFArgs ->
%% regular supervisor
MFA = format_mfa(MFArgs),
- Name = proplists:get_value(name, Off),
+ Name = get_value(name, Off),
io_lib:format("~p started with ~s at ~w",
- [Name, MFA, proplists:get_value(pid, Off)])
+ [Name, MFA, get_value(pid, Off)])
end.
format_reason({'function not exported', [{M, F, A},MFA|_]}) ->
["call to undefined function ", format_mfa({M, F, length(A)}),
" from ", format_mfa(MFA)];
+format_reason({'function not exported', [{M, F, A, _Props},MFA|_]}) ->
+ %% R15 line numbers
+ ["call to undefined function ", format_mfa({M, F, length(A)}),
+ " from ", format_mfa(MFA)];
format_reason({undef, [MFA|_]}) ->
["call to undefined function ", format_mfa(MFA)];
+format_reason({bad_return, {_MFA, {'EXIT', Reason}}}) ->
+ format_reason(Reason);
+format_reason({bad_return, {MFA, Val}}) ->
+ ["bad return value ", print_val(Val), " from ", format_mfa(MFA)];
format_reason({bad_return_value, Val}) ->
["bad return value: ", print_val(Val)];
format_reason({{bad_return_value, Val}, MFA}) ->
["bad return value: ", print_val(Val), " in ", format_mfa(MFA)];
+format_reason({{badrecord, Record}, [MFA|_]}) ->
+ ["bad record ", print_val(Record), " in ", format_mfa(MFA)];
format_reason({{case_clause, Val}, [MFA|_]}) ->
["no case clause matching ", print_val(Val), " in ", format_mfa(MFA)];
format_reason({function_clause, [MFA|_]}) ->
@@ -199,7 +303,7 @@ format_reason({function_clause, [MFA|_]}) ->
format_reason({if_clause, [MFA|_]}) ->
["no true branch found while evaluating if expression in ", format_mfa(MFA)];
format_reason({{try_clause, Val}, [MFA|_]}) ->
- ["no try clause matching ", print_val(Val), " in ", format_mfa(MFA)];
+ ["no try clause matching ", print_val(Val), " in ", format_mfa(MFA)];
format_reason({badarith, [MFA|_]}) ->
["bad arithmetic expression in ", format_mfa(MFA)];
format_reason({{badmatch, Val}, [MFA|_]}) ->
@@ -225,12 +329,17 @@ format_reason({system_limit, [{M, F, _}|_] = Trace}) ->
["system limit: ", Limit];
format_reason({badarg, [MFA,MFA2|_]}) ->
case MFA of
+ {_M, _F, A, _Props} when is_list(A) ->
+ %% R15 line numbers
+ ["bad argument in call to ", format_mfa(MFA), " in ", format_mfa(MFA2)];
{_M, _F, A} when is_list(A) ->
["bad argument in call to ", format_mfa(MFA), " in ", format_mfa(MFA2)];
_ ->
%% seems to be generated by a bad call to a BIF
["bad argument in ", format_mfa(MFA)]
end;
+format_reason({{badarg, Stack}, _}) ->
+ format_reason({badarg, Stack});
format_reason({{badarity, {Fun, Args}}, [MFA|_]}) ->
{arity, Arity} = lists:keyfind(arity, 1, erlang:fun_info(Fun)),
[io_lib:format("fun called with wrong arity of ~w instead of ~w in ",
@@ -239,6 +348,11 @@ format_reason({noproc, MFA}) ->
["no such process or port in call to ", format_mfa(MFA)];
format_reason({{badfun, Term}, [MFA|_]}) ->
["bad function ", print_val(Term), " in ", format_mfa(MFA)];
+format_reason({Reason, [{M, F, A}|_]}) when is_atom(M), is_atom(F), is_integer(A) ->
+ [format_reason(Reason), " in ", format_mfa({M, F, A})];
+format_reason({Reason, [{M, F, A, Props}|_]}) when is_atom(M), is_atom(F), is_integer(A), is_list(Props) ->
+ %% line numbers
+ [format_reason(Reason), " in ", format_mfa({M, F, A, Props})];
format_reason(Reason) ->
{Str, _} = lager_trunc_io:print(Reason, 500),
Str.
@@ -248,6 +362,19 @@ format_mfa({M, F, A}) when is_list(A) ->
io_lib:format("~w:~w("++FmtStr++")", [M, F | Args]);
format_mfa({M, F, A}) when is_integer(A) ->
io_lib:format("~w:~w/~w", [M, F, A]);
+format_mfa({M, F, A, Props}) when is_list(Props) ->
+ case get_value(line, Props) of
+ undefined ->
+ format_mfa({M, F, A});
+ Line ->
+ [format_mfa({M, F, A}), io_lib:format(" line ~w", [Line])]
+ end;
+format_mfa([{M, F, A}, _]) ->
+ %% this kind of weird stacktrace can be generated by a uncaught throw in a gen_server
+ format_mfa({M, F, A});
+format_mfa([{M, F, A, Props}, _]) when is_list(Props) ->
+ %% this kind of weird stacktrace can be generated by a uncaught throw in a gen_server
+ format_mfa({M, F, A, Props});
format_mfa(Other) ->
io_lib:format("~w", [Other]).
@@ -260,17 +387,17 @@ format_args([H|T], FmtAcc, ArgsAcc) ->
print_silly_list(L) when is_list(L) ->
case lager_stdlib:string_p(L) of
true ->
- lager_trunc_io:format("~s", [L], 4096);
+ lager_trunc_io:format("~s", [L], ?DEFAULT_TRUNCATION);
_ ->
print_silly_list(L, [], [])
end;
print_silly_list(L) ->
- {Str, _} = lager_trunc_io:print(L, 4096),
+ {Str, _} = lager_trunc_io:print(L, ?DEFAULT_TRUNCATION),
Str.
print_silly_list([], Fmt, Acc) ->
lager_trunc_io:format(string:join(lists:reverse(Fmt), ", "),
- lists:reverse(Acc), 4096);
+ lists:reverse(Acc), ?DEFAULT_TRUNCATION);
print_silly_list([{K,V}|T], Fmt, Acc) ->
print_silly_list(T, ["~p: ~p" | Fmt], [V, K | Acc]);
print_silly_list([H|T], Fmt, Acc) ->
@@ -279,3 +406,18 @@ print_silly_list([H|T], Fmt, Acc) ->
print_val(Val) ->
{Str, _} = lager_trunc_io:print(Val, 500),
Str.
+
+
+%% @doc Faster than proplists, but with the same API as long as you don't need to
+%% handle bare atom keys
+get_value(Key, Value) ->
+ get_value(Key, Value, undefined).
+
+get_value(Key, List, Default) ->
+ case lists:keyfind(Key, 1, List) of
+ false -> Default;
+ {Key, Value} -> Value
+ end.
+
+supervisor_name({local, Name}) -> Name;
+supervisor_name(Name) -> Name.
diff --git a/deps/lager/src/lager.app.src b/deps/lager/src/lager.app.src
index 18bbadd..d6d6355 100644
--- a/deps/lager/src/lager.app.src
+++ b/deps/lager/src/lager.app.src
@@ -3,25 +3,33 @@
{application, lager,
[
{description, "Erlang logging framework"},
- {vsn, git},
+ {vsn, "3.0.1"},
{modules, []},
{applications, [
kernel,
stdlib,
- compiler,
- syntax_tools
+ goldrush
]},
- {registered, []},
+ {registered, [lager_sup, lager_event, lager_crash_log, lager_handler_watcher_sup]},
{mod, {lager_app, []}},
{env, [
- %% What handlers to install with what arguments
- {handlers, [
- {lager_console_backend, info},
- {lager_file_backend, [
- {"log/error.log", error, 10485760, "$D0", 5},
- {"log/console.log", info, 10485760, "$D0", 5}
- ]}
+ %% Note: application:start(lager) overwrites previously defined environment variables
+ %% thus declaration of default handlers is done at lager_app.erl
+
+ %% What colors to use with what log levels
+ {colored, false},
+ {colors, [
+ {debug, "\e[0;38m" },
+ {info, "\e[1;37m" },
+ {notice, "\e[1;36m" },
+ {warning, "\e[1;33m" },
+ {error, "\e[1;31m" },
+ {critical, "\e[1;35m" },
+ {alert, "\e[1;44m" },
+ {emergency, "\e[1;41m" }
+
]},
+
%% Whether to write a crash log, and where. Undefined means no crash logger.
{crash_log, "log/crash.log"},
%% Maximum size in bytes of events in the crash log - defaults to 65536
@@ -35,7 +43,18 @@
%% Number of rotated crash logs to keep, 0 means keep only the
%% current one - default is 0
{crash_log_count, 5},
- %% Whether to redirect error_logger messages into lager - defaults to true
- {error_logger_redirect, true}
+ %% Whether to redirect error_logger messages into the default lager_event sink - defaults to true
+ {error_logger_redirect, true},
+ %% How many messages per second to allow from error_logger before we start dropping them
+ {error_logger_hwm, 50},
+ %% How big the gen_event mailbox can get before it is
+ %% switched into sync mode. This value only applies to
+ %% the default sink; extra sinks can supply their own.
+ {async_threshold, 20},
+ %% Switch back to async mode, when gen_event mailbox size
+ %% decrease from `async_threshold' to async_threshold -
+ %% async_threshold_window. This value only applies to the
+ %% default sink; extra sinks can supply their own.
+ {async_threshold_window, 5}
]}
]}.
diff --git a/deps/lager/src/lager.erl b/deps/lager/src/lager.erl
index db40103..90386c3 100644
--- a/deps/lager/src/lager.erl
+++ b/deps/lager/src/lager.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
@@ -20,26 +20,20 @@
-include("lager.hrl").
+-define(LAGER_MD_KEY, '__lager_metadata').
+-define(TRACE_SINK, '__trace_sink').
+
%% API
-export([start/0,
- log/8, log_dest/9, log/3, log/4,
- trace_file/2, trace_file/3, trace_console/1, trace_console/2,
- clear_all_traces/0, stop_trace/1, status/0,
- get_loglevel/1, set_loglevel/2, set_loglevel/3, get_loglevels/0,
- minimum_loglevel/1, posix_error/1,
- safe_format/3, safe_format_chop/3,dispatch_log/8,
- dispatch_log1/9]).
-
-%% Fallback when parse transform is not available
--export([debug/1,debug/2,debug/3]).
--export([info/1,info/2,info/3]).
--export([notice/1,notice/2,notice/3]).
--export([warning/1,warning/2,warning/3]).
--export([error/1,error/2,error/3]).
--export([critical/1,critical/2,critical/3]).
--export([alert/1,alert/2,alert/3]).
--export([emergency/1,emergency/2,emergency/3]).
--export([none/1,none/2,none/3]).
+ log/3, log/4, log/5,
+ log_unsafe/4,
+ md/0, md/1,
+ trace/2, trace/3, trace_file/2, trace_file/3, trace_file/4, trace_console/1, trace_console/2,
+ list_all_sinks/0, clear_all_traces/0, stop_trace/1, stop_trace/3, status/0,
+ get_loglevel/1, get_loglevel/2, set_loglevel/2, set_loglevel/3, set_loglevel/4, get_loglevels/1,
+ update_loglevel_config/1, posix_error/1, set_loghwm/2, set_loghwm/3, set_loghwm/4,
+ safe_format/3, safe_format_chop/3, unsafe_format/2, dispatch_log/5, dispatch_log/7, dispatch_log/9,
+ do_log/9, do_log/10, do_log_unsafe/10, pr/2, pr/3]).
-type log_level() :: debug | info | notice | warning | error | critical | alert | emergency.
-type log_level_number() :: 0..7.
@@ -57,125 +51,194 @@ start(App) ->
start_ok(_App, ok) -> ok;
start_ok(_App, {error, {already_started, _App}}) -> ok;
-start_ok(App, {error, {not_started, Dep}}) ->
+start_ok(App, {error, {not_started, Dep}}) ->
ok = start(Dep),
start(App);
-start_ok(App, {error, Reason}) ->
+start_ok(App, {error, Reason}) ->
erlang:error({app_start_failed, App, Reason}).
--spec dispatch_log(log_level(), atom(), atom(), pos_integer(), pid(), list(), string(), list()) ->
- ok | {error, lager_not_running}.
-%% Still used by dyn_log
-dispatch_log(Severity, Module, Function, Line, Pid, Traces, Format, Args) ->
- {LevelThreshold,TraceFilters} = lager_mochiglobal:get(loglevel,{?LOG_NONE,[]}),
- Result=
- case LevelThreshold >= lager_util:level_to_num(Severity) of
- true -> lager:log(Severity,Module,Function,Line,Pid,
- lager_util:maybe_utc(lager_util:localtime_ms()),
- Format,Args);
- _ -> ok
+%% @doc Get lager metadata for current process
+-spec md() -> [{atom(), any()}].
+md() ->
+ case erlang:get(?LAGER_MD_KEY) of
+ undefined -> [];
+ MD -> MD
+ end.
+
+%% @doc Set lager metadata for current process.
+%% Will badarg if you don't supply a list of {key, value} tuples keyed by atoms.
+-spec md([{atom(), any()},...]) -> ok.
+md(NewMD) when is_list(NewMD) ->
+ %% make sure its actually a real proplist
+ case lists:all(
+ fun({Key, _Value}) when is_atom(Key) -> true;
+ (_) -> false
+ end, NewMD) of
+ true ->
+ erlang:put(?LAGER_MD_KEY, NewMD),
+ ok;
+ false ->
+ erlang:error(badarg)
+ end;
+md(_) ->
+ erlang:error(badarg).
+
+
+-spec dispatch_log(atom(), log_level(), list(), string(), list() | none, pos_integer(), safe | unsafe) -> ok | {error, lager_not_running} | {error, {sink_not_configured, atom()}}.
+%% this is the same check that the parse transform bakes into the module at compile time
+%% see lager_transform (lines 173-216)
+dispatch_log(Sink, Severity, Metadata, Format, Args, Size, Safety) when is_atom(Severity)->
+ SeverityAsInt=lager_util:level_to_num(Severity),
+ case {whereis(Sink), whereis(?DEFAULT_SINK), lager_config:get({Sink, loglevel}, {?LOG_NONE, []})} of
+ {undefined, undefined, _} -> {error, lager_not_running};
+ {undefined, _LagerEventPid0, _} -> {error, {sink_not_configured, Sink}};
+ {SinkPid, _LagerEventPid1, {Level, Traces}} when Safety =:= safe andalso ( (Level band SeverityAsInt) /= 0 orelse Traces /= [] ) ->
+ do_log(Severity, Metadata, Format, Args, Size, SeverityAsInt, Level, Traces, Sink, SinkPid);
+ {SinkPid, _LagerEventPid1, {Level, Traces}} when Safety =:= unsafe andalso ( (Level band SeverityAsInt) /= 0 orelse Traces /= [] ) ->
+ do_log_unsafe(Severity, Metadata, Format, Args, Size, SeverityAsInt, Level, Traces, Sink, SinkPid);
+ _ -> ok
+ end.
+
+%% @private Should only be called externally from code generated from the parse transform
+do_log(Severity, Metadata, Format, Args, Size, SeverityAsInt, LevelThreshold, TraceFilters, Sink, SinkPid) when is_atom(Severity) ->
+ FormatFun = fun() -> safe_format_chop(Format, Args, Size) end,
+ do_log_impl(Severity, Metadata, Format, Args, SeverityAsInt, LevelThreshold, TraceFilters, Sink, SinkPid, FormatFun).
+
+do_log_impl(Severity, Metadata, Format, Args, SeverityAsInt, LevelThreshold, TraceFilters, Sink, SinkPid, FormatFun) ->
+ {Destinations, TraceSinkPid} = case TraceFilters of
+ [] ->
+ {[], undefined};
+ _ ->
+ {lager_util:check_traces(Metadata,SeverityAsInt,TraceFilters,[]), whereis(?TRACE_SINK)}
end,
- case TraceFilters of
- [] -> Result;
- Match when is_list(Match) ->
- lager:log_dest(Severity,Module,Function,Line,Pid,
- lager_util:maybe_utc(lager_util:localtime_ms()),
- lager_util:check_traces(Traces,
- lager_util:level_to_num(Severity),
- TraceFilters,
- []),
- Format,Args);
- _ -> ok
+ case (LevelThreshold band SeverityAsInt) /= 0 orelse Destinations /= [] of
+ true ->
+ Msg = case Args of
+ A when is_list(A) ->
+ FormatFun();
+ _ ->
+ Format
+ end,
+ LagerMsg = lager_msg:new(Msg,
+ Severity, Metadata, Destinations),
+ case lager_config:get({Sink, async}, false) of
+ true ->
+ gen_event:notify(SinkPid, {log, LagerMsg});
+ false ->
+ gen_event:sync_notify(SinkPid, {log, LagerMsg})
+ end,
+ case TraceSinkPid /= undefined of
+ true ->
+ gen_event:notify(TraceSinkPid, {log, LagerMsg});
+ false ->
+ ok
+ end;
+ false ->
+ ok
end.
-%%
-%% Like dispatch_log/8 but uses a new transform where
-%% level is checked before arguments are evaluated.
-%%
-dispatch_log1([],Severity,Mod,Fun,Line,Pid,_FTraces,Format,Args) ->
- lager:log(Severity,Mod,Fun,Line,Pid,
- lager_util:maybe_utc(lager_util:localtime_ms()),
- Format,Args);
-dispatch_log1(Match,Severity,Mod,Fun,Line,Pid,FTraces,Format,Args)
- when is_list(Match) ->
- lager:log(Severity,Mod,Fun,Line,Pid,
- lager_util:maybe_utc(lager_util:localtime_ms()),
- Format,Args),
- lager:log_dest(Severity,Mod,Fun,Line,Pid,
- lager_util:maybe_utc(lager_util:localtime_ms()),
- lager_util:check_f_traces(FTraces,
- lager_util:level_to_num(Severity),
- Match,[]),
- Format,Args);
-dispatch_log1(_,_Severity,_Mod,_Func,_Line,_Pid,_FTraces,_Format,_Args) ->
- ok.
+%% @private Should only be called externally from code generated from the parse transform
+%% Specifically, it would be level ++ `_unsafe' as in `info_unsafe'.
+do_log_unsafe(Severity, Metadata, Format, Args, _Size, SeverityAsInt, LevelThreshold, TraceFilters, Sink, SinkPid) when is_atom(Severity) ->
+ FormatFun = fun() -> unsafe_format(Format, Args) end,
+ do_log_impl(Severity, Metadata, Format, Args, SeverityAsInt, LevelThreshold, TraceFilters, Sink, SinkPid, FormatFun).
-%% @private
--spec log(log_level(), atom(), atom(), pos_integer(), pid(), tuple(), string(), list()) ->
- ok | {error, lager_not_running}.
-log(Level, Module, Function, Line, Pid, Time, Format, Args) ->
- Timestamp = lager_util:format_time(Time),
- Msg = [["[", atom_to_list(Level), "] "],
- io_lib:format("~p@~p:~p:~p ", [Pid, Module, Function, Line]),
- safe_format_chop(Format, Args, 4096)],
- safe_notify({log, lager_util:level_to_num(Level), Timestamp, Msg}).
+%% backwards compatible with beams compiled with lager 1.x
+dispatch_log(Severity, _Module, _Function, _Line, _Pid, Metadata, Format, Args, Size) ->
+ dispatch_log(Severity, Metadata, Format, Args, Size).
-%% @private
--spec log_dest(log_level(), atom(), atom(), pos_integer(), pid(), tuple(), list(), string(), list()) ->
- ok | {error, lager_not_running}.
-log_dest(_Level, _Module, _Function, _Line, _Pid, _Time, [], _Format, _Args) ->
- ok;
-log_dest(Level, Module, Function, Line, Pid, Time, Dest, Format, Args) ->
- Timestamp = lager_util:format_time(Time),
- Msg = [["[", atom_to_list(Level), "] "],
- io_lib:format("~p@~p:~p:~p ", [Pid, Module, Function, Line]),
- safe_format_chop(Format, Args, 4096)],
- safe_notify({log, Dest, lager_util:level_to_num(Level), Timestamp, Msg}).
+%% backwards compatible with beams compiled with lager 2.x
+dispatch_log(Severity, Metadata, Format, Args, Size) ->
+ dispatch_log(?DEFAULT_SINK, Severity, Metadata, Format, Args, Size, safe).
+%% backwards compatible with beams compiled with lager 2.x
+do_log(Severity, Metadata, Format, Args, Size, SeverityAsInt, LevelThreshold, TraceFilters, SinkPid) ->
+ do_log(Severity, Metadata, Format, Args, Size, SeverityAsInt,
+ LevelThreshold, TraceFilters, ?DEFAULT_SINK, SinkPid).
+
+
+%% TODO:
+%% Consider making log2/4 that takes the Level, Pid and Message params of log/3
+%% along with a Sink param??
%% @doc Manually log a message into lager without using the parse transform.
--spec log(log_level(), pid(), list()) -> ok | {error, lager_not_running}.
-log(Level, Pid, Message) ->
- Timestamp = lager_util:format_time(),
- Msg = [["[", atom_to_list(Level), "] "], io_lib:format("~p ", [Pid]),
- safe_format_chop("~s", [Message], 4096)],
- safe_notify({log, lager_util:level_to_num(Level), Timestamp, Msg}).
+-spec log(log_level(), pid() | atom() | [tuple(),...], list()) -> ok | {error, lager_not_running}.
+log(Level, Pid, Message) when is_pid(Pid); is_atom(Pid) ->
+ dispatch_log(Level, [{pid,Pid}], Message, [], ?DEFAULT_TRUNCATION);
+log(Level, Metadata, Message) when is_list(Metadata) ->
+ dispatch_log(Level, Metadata, Message, [], ?DEFAULT_TRUNCATION).
+
+%% @doc Manually log a message into lager without using the parse transform.
+-spec log(log_level(), pid() | atom() | [tuple(),...], string(), list()) -> ok | {error, lager_not_running}.
+log(Level, Pid, Format, Args) when is_pid(Pid); is_atom(Pid) ->
+ dispatch_log(Level, [{pid,Pid}], Format, Args, ?DEFAULT_TRUNCATION);
+log(Level, Metadata, Format, Args) when is_list(Metadata) ->
+ dispatch_log(Level, Metadata, Format, Args, ?DEFAULT_TRUNCATION).
+
+log_unsafe(Level, Metadata, Format, Args) when is_list(Metadata) ->
+ dispatch_log(?DEFAULT_SINK, Level, Metadata, Format, Args, ?DEFAULT_TRUNCATION, unsafe).
+
%% @doc Manually log a message into lager without using the parse transform.
--spec log(log_level(), pid(), string(), list()) -> ok | {error, lager_not_running}.
-log(Level, Pid, Format, Args) ->
- Timestamp = lager_util:format_time(),
- Msg = [["[", atom_to_list(Level), "] "], io_lib:format("~p ", [Pid]),
- safe_format_chop(Format, Args, 4096)],
- safe_notify({log, lager_util:level_to_num(Level), Timestamp, Msg}).
+-spec log(atom(), log_level(), pid() | atom() | [tuple(),...], string(), list()) -> ok | {error, lager_not_running}.
+log(Sink, Level, Pid, Format, Args) when is_pid(Pid); is_atom(Pid) ->
+ dispatch_log(Sink, Level, [{pid,Pid}], Format, Args, ?DEFAULT_TRUNCATION, safe);
+log(Sink, Level, Metadata, Format, Args) when is_list(Metadata) ->
+ dispatch_log(Sink, Level, Metadata, Format, Args, ?DEFAULT_TRUNCATION, safe).
+
+validate_trace_filters(Filters, Level, Backend) ->
+ Sink = proplists:get_value(sink, Filters, ?DEFAULT_SINK),
+ {Sink,
+ lager_util:validate_trace({
+ proplists:delete(sink, Filters),
+ Level,
+ Backend
+ })
+ }.
trace_file(File, Filter) ->
- trace_file(File, Filter, debug).
+ trace_file(File, Filter, debug, []).
+
+trace_file(File, Filter, Level) when is_atom(Level) ->
+ trace_file(File, Filter, Level, []);
+
+trace_file(File, Filter, Options) when is_list(Options) ->
+ trace_file(File, Filter, debug, Options).
-trace_file(File, Filter, Level) ->
- Trace0 = {Filter, Level, {lager_file_backend, File}},
- case lager_util:validate_trace(Trace0) of
- {ok, Trace} ->
- Handlers = gen_event:which_handlers(lager_event),
+trace_file(File, Filter, Level, Options) ->
+ FileName = lager_util:expand_path(File),
+ case validate_trace_filters(Filter, Level, {lager_file_backend, FileName}) of
+ {Sink, {ok, Trace}} ->
+ Handlers = lager_config:global_get(handlers, []),
%% check if this file backend is already installed
- case lists:member({lager_file_backend, File}, Handlers) of
- false ->
- %% install the handler
- supervisor:start_child(lager_handler_watcher_sup,
- [lager_event, {lager_file_backend, File}, {File, none}]);
- _ ->
- ok
- end,
- %% install the trace.
- {MinLevel, Traces} = lager_mochiglobal:get(loglevel),
- case lists:member(Trace, Traces) of
- false ->
- lager_mochiglobal:put(loglevel, {MinLevel, [Trace|Traces]});
- _ -> ok
+ Res = case lists:keyfind({lager_file_backend, FileName}, 1, Handlers) of
+ false ->
+ %% install the handler
+ LogFileConfig =
+ lists:keystore(level, 1,
+ lists:keystore(file, 1,
+ Options,
+ {file, FileName}),
+ {level, none}),
+ HandlerInfo =
+ lager_app:start_handler(Sink, {lager_file_backend, FileName},
+ LogFileConfig),
+ lager_config:global_set(handlers, [HandlerInfo|Handlers]),
+ {ok, installed};
+ {_Watcher, _Handler, Sink} ->
+ {ok, exists};
+ {_Watcher, _Handler, _OtherSink} ->
+ {error, file_in_use}
end,
- {ok, Trace};
- Error ->
+ case Res of
+ {ok, _} ->
+ add_trace_to_loglevel_config(Trace, Sink),
+ {ok, {{lager_file_backend, FileName}, Filter, Level}};
+ {error, _} = E ->
+ E
+ end;
+ {_Sink, Error} ->
Error
end.
@@ -183,30 +246,46 @@ trace_console(Filter) ->
trace_console(Filter, debug).
trace_console(Filter, Level) ->
- Trace0 = {Filter, Level, lager_console_backend},
- case lager_util:validate_trace(Trace0) of
- {ok, Trace} ->
- {MinLevel, Traces} = lager_mochiglobal:get(loglevel),
- case lists:member(Trace, Traces) of
- false ->
- lager_mochiglobal:put(loglevel, {MinLevel, [Trace|Traces]});
- _ -> ok
- end,
- {ok, Trace};
- Error ->
+ trace(lager_console_backend, Filter, Level).
+
+trace(Backend, Filter) ->
+ trace(Backend, Filter, debug).
+
+trace({lager_file_backend, File}, Filter, Level) ->
+ trace_file(File, Filter, Level);
+
+trace(Backend, Filter, Level) ->
+ case validate_trace_filters(Filter, Level, Backend) of
+ {Sink, {ok, Trace}} ->
+ add_trace_to_loglevel_config(Trace, Sink),
+ {ok, {Backend, Filter, Level}};
+ {_Sink, Error} ->
Error
end.
-stop_trace({_Filter, _Level, Target} = Trace) ->
- {MinLevel, Traces} = lager_mochiglobal:get(loglevel),
+stop_trace(Backend, Filter, Level) ->
+ case validate_trace_filters(Filter, Level, Backend) of
+ {Sink, {ok, Trace}} ->
+ stop_trace_int(Trace, Sink);
+ {_Sink, Error} ->
+ Error
+ end.
+
+stop_trace({Backend, Filter, Level}) ->
+ stop_trace(Backend, Filter, Level).
+
+stop_trace_int({Backend, _Filter, _Level} = Trace, Sink) ->
+ {Level, Traces} = lager_config:get({Sink, loglevel}),
NewTraces = lists:delete(Trace, Traces),
- lager_mochiglobal:put(loglevel, {MinLevel, NewTraces}),
- case get_loglevel(Target) of
+ _ = lager_util:trace_filter([ element(1, T) || T <- NewTraces ]),
+ %MinLevel = minimum_loglevel(get_loglevels() ++ get_trace_levels(NewTraces)),
+ lager_config:set({Sink, loglevel}, {Level, NewTraces}),
+ case get_loglevel(Sink, Backend) of
none ->
%% check no other traces point here
- case lists:keyfind(Target, 3, NewTraces) of
+ case lists:keyfind(Backend, 3, NewTraces) of
false ->
- gen_event:delete_handler(lager_event, Target, []);
+ gen_event:delete_handler(Sink, Backend, []);
_ ->
ok
end;
@@ -215,64 +294,136 @@ stop_trace({_Filter, _Level, Target} = Trace) ->
end,
ok.
+list_all_sinks() ->
+ sets:to_list(
+ lists:foldl(fun({_Watcher, _Handler, Sink}, Set) ->
+ sets:add_element(Sink, Set)
+ end,
+ sets:new(),
+ lager_config:global_get(handlers, []))).
+
+clear_traces_by_sink(Sinks) ->
+ lists:foreach(fun(S) ->
+ {Level, _Traces} =
+ lager_config:get({S, loglevel}),
+ lager_config:set({S, loglevel},
+ {Level, []})
+ end,
+ Sinks).
+
clear_all_traces() ->
- {MinLevel, _Traces} = lager_mochiglobal:get(loglevel),
- lager_mochiglobal:put(loglevel, {MinLevel, []}),
- [begin
- case get_loglevel(Handler) of
- none ->
- gen_event:delete_handler(lager_event, Handler, []);
- _ ->
- ok
- end
- end || Handler <- gen_event:which_handlers(lager_event)],
- ok.
+ Handlers = lager_config:global_get(handlers, []),
+ clear_traces_by_sink(list_all_sinks()),
+ _ = lager_util:trace_filter(none),
+ lager_config:global_set(handlers,
+ lists:filter(
+ fun({Handler, _Watcher, Sink}) ->
+ case get_loglevel(Sink, Handler) of
+ none ->
+ gen_event:delete_handler(Sink, Handler, []),
+ false;
+ _ ->
+ true
+ end
+ end, Handlers)).
+
+find_traces(Sinks) ->
+ lists:foldl(fun(S, Acc) ->
+ {_Level, Traces} = lager_config:get({S, loglevel}),
+ Acc ++ lists:map(fun(T) -> {S, T} end, Traces)
+ end,
+ [],
+ Sinks).
status() ->
- Handlers = gen_event:which_handlers(lager_event),
+ Handlers = lager_config:global_get(handlers, []),
+ Sinks = lists:sort(list_all_sinks()),
+ Traces = find_traces(Sinks),
+ TraceCount = case length(Traces) of
+ 0 -> 1;
+ N -> N
+ end,
Status = ["Lager status:\n",
[begin
- Level = get_loglevel(Handler),
+ Level = get_loglevel(Sink, Handler),
case Handler of
{lager_file_backend, File} ->
- io_lib:format("File ~s at level ~p\n", [File, Level]);
+ io_lib:format("File ~s (~s) at level ~p\n", [File, Sink, Level]);
lager_console_backend ->
- io_lib:format("Console at level ~p\n", [Level]);
+ io_lib:format("Console (~s) at level ~p\n", [Sink, Level]);
_ ->
[]
end
- end || Handler <- Handlers],
+ end || {Handler, _Watcher, Sink} <- lists:sort(fun({_, _, S1},
+ {_, _, S2}) -> S1 =< S2 end,
+ Handlers)],
"Active Traces:\n",
[begin
- io_lib:format("Tracing messages matching ~p at level ~p to ~p\n",
- [Filter, lager_util:num_to_level(Level), Destination])
- end || {Filter, Level, Destination} <- element(2, lager_mochiglobal:get(loglevel))]],
+ LevelName = case Level of
+ {mask, Mask} ->
+ case lager_util:mask_to_levels(Mask) of
+ [] -> none;
+ Levels -> hd(Levels)
+ end;
+ Num ->
+ lager_util:num_to_level(Num)
+ end,
+ io_lib:format("Tracing messages matching ~p (sink ~s) at level ~p to ~p\n",
+ [Filter, Sink, LevelName, Destination])
+ end || {Sink, {Filter, Level, Destination}} <- Traces],
+ [
+ "Tracing Reductions:\n",
+ case ?DEFAULT_TRACER:info('query') of
+ {null, false} -> "";
+ Query -> io_lib:format("~p~n", [Query])
+ end
+ ],
+ [
+ "Tracing Statistics:\n ",
+ [ begin
+ [" ", atom_to_list(Table), ": ",
+ integer_to_list(?DEFAULT_TRACER:info(Table) div TraceCount),
+ "\n"]
+ end || Table <- [input, output, filter] ]
+ ]],
io:put_chars(Status).
+
%% @doc Set the loglevel for a particular backend.
set_loglevel(Handler, Level) when is_atom(Level) ->
- Reply = gen_event:call(lager_event, Handler, {set_loglevel, Level}, infinity),
- %% recalculate min log level
- MinLog = minimum_loglevel(get_loglevels()),
- {_, Traces} = lager_mochiglobal:get(loglevel),
- lager_mochiglobal:put(loglevel, {MinLog, Traces}),
- Reply.
+ set_loglevel(?DEFAULT_SINK, Handler, undefined, Level).
%% @doc Set the loglevel for a particular backend that has multiple identifiers
%% (eg. the file backend).
set_loglevel(Handler, Ident, Level) when is_atom(Level) ->
- io:format("handler: ~p~n", [{Handler, Ident}]),
- Reply = gen_event:call(lager_event, {Handler, Ident}, {set_loglevel, Level}, infinity),
- %% recalculate min log level
- MinLog = minimum_loglevel(get_loglevels()),
- {_, Traces} = lager_mochiglobal:get(loglevel),
- lager_mochiglobal:put(loglevel, {MinLog, Traces}),
+ set_loglevel(?DEFAULT_SINK, Handler, Ident, Level).
+
+%% @doc Set the loglevel for a particular sink's backend that potentially has
+%% multiple identifiers. (Use `undefined' if it doesn't have any.)
+set_loglevel(Sink, Handler, Ident, Level) when is_atom(Level) ->
+ HandlerArg = case Ident of
+ undefined -> Handler;
+ _ -> {Handler, Ident}
+ end,
+ Reply = gen_event:call(Sink, HandlerArg, {set_loglevel, Level}, infinity),
+ update_loglevel_config(Sink),
Reply.
-%% @doc Get the loglevel for a particular backend. In the case that the backend
-%% has multiple identifiers, the lowest is returned
+
+%% @doc Get the loglevel for a particular backend on the default sink. In the case that the backend
+%% has multiple identifiers, the lowest is returned.
get_loglevel(Handler) ->
- case gen_event:call(lager_event, Handler, get_loglevel, infinity) of
+ get_loglevel(?DEFAULT_SINK, Handler).
+
+%% @doc Get the loglevel for a particular sink's backend. In the case that the backend
+%% has multiple identifiers, the lowest is returned.
+get_loglevel(Sink, Handler) ->
+ case gen_event:call(Sink, Handler, get_loglevel, infinity) of
+ {mask, Mask} ->
+ case lager_util:mask_to_levels(Mask) of
+ [] -> none;
+ Levels -> hd(Levels)
+ end;
X when is_integer(X) ->
lager_util:num_to_level(X);
Y -> Y
@@ -286,28 +437,58 @@ posix_error(Error) when is_atom(Error) ->
Message -> Message
end;
posix_error(Error) ->
- safe_format_chop("~p", [Error], 4096).
+ safe_format_chop("~p", [Error], ?DEFAULT_TRUNCATION).
%% @private
-get_loglevels() ->
- [gen_event:call(lager_event, Handler, get_loglevel, infinity) ||
- Handler <- gen_event:which_handlers(lager_event)].
+get_loglevels(Sink) ->
+ [gen_event:call(Sink, Handler, get_loglevel, infinity) ||
+ Handler <- gen_event:which_handlers(Sink)].
+
+%% @doc Set the loghwm for the default sink.
+set_loghwm(Handler, Hwm) when is_integer(Hwm) ->
+ set_loghwm(?DEFAULT_SINK, Handler, Hwm).
+
+%% @doc Set the loghwm for a particular backend.
+set_loghwm(Sink, Handler, Hwm) when is_integer(Hwm) ->
+ gen_event:call(Sink, Handler, {set_loghwm, Hwm}, infinity).
+
+%% @doc Set the loghwm (log high water mark) for file backends with multiple identifiers
+set_loghwm(Sink, Handler, Ident, Hwm) when is_integer(Hwm) ->
+ gen_event:call(Sink, {Handler, Ident}, {set_loghwm, Hwm}, infinity).
%% @private
-minimum_loglevel([]) ->
- -1; %% lower than any log level, logging off
-minimum_loglevel(Levels) ->
- erlang:hd(lists:reverse(lists:sort(Levels))).
-
-safe_notify(Event) ->
- case whereis(lager_event) of
- undefined ->
- %% lager isn't running
- {error, lager_not_running};
- Pid ->
- gen_event:sync_notify(Pid, Event)
+add_trace_to_loglevel_config(Trace, Sink) ->
+ {MinLevel, Traces} = lager_config:get({Sink, loglevel}),
+ case lists:member(Trace, Traces) of
+ false ->
+ NewTraces = [Trace|Traces],
+ _ = lager_util:trace_filter([ element(1, T) || T <- NewTraces]),
+ lager_config:set({Sink, loglevel}, {MinLevel, [Trace|Traces]});
+ _ ->
+ ok
end.
+%% @doc recalculate min log level
+update_loglevel_config(error_logger) ->
+ %% Not a sink under our control, part of the Erlang logging
+ %% utility that error_logger_lager_h attaches to
+ true;
+update_loglevel_config(Sink) ->
+ {_, Traces} = lager_config:get({Sink, loglevel}, {ignore_me, []}),
+ MinLog = minimum_loglevel(get_loglevels(Sink)),
+ lager_config:set({Sink, loglevel}, {MinLog, Traces}).
+
+%% @private
+minimum_loglevel(Levels) ->
+ lists:foldl(fun({mask, Mask}, Acc) ->
+ Mask bor Acc;
+ (Level, Acc) when is_integer(Level) ->
+ {mask, Mask} = lager_util:config_to_mask(lager_util:num_to_level(Level)),
+ Mask bor Acc;
+ (_, Acc) ->
+ Acc
+ end, 0, Levels).
+
%% @doc Print the format string `Fmt' with `Args' safely with a size
%% limit of `Limit'. If the format string is invalid, or not enough
%% arguments are supplied 'FORMAT ERROR' is printed with the offending
@@ -317,8 +498,7 @@ safe_format(Fmt, Args, Limit) ->
safe_format(Fmt, Args, Limit, []).
safe_format(Fmt, Args, Limit, Options) ->
- try lager_trunc_io:format(Fmt, Args, Limit, Options) of
- Result -> Result
+ try lager_trunc_io:format(Fmt, Args, Limit, Options)
catch
_:_ -> lager_trunc_io:format("FORMAT ERROR: ~p ~p", [Fmt, Args], Limit)
end.
@@ -326,70 +506,77 @@ safe_format(Fmt, Args, Limit, Options) ->
%% @private
safe_format_chop(Fmt, Args, Limit) ->
safe_format(Fmt, Args, Limit, [{chomp, true}]).
+
+%% @private Print the format string `Fmt' with `Args' without a size limit.
+%% This is unsafe because the output of this function is unbounded.
%%
-%% when code is not compiled with parse_transform the following code
-%% is used instead. maybe warn about the fact?
+%% Log messages with unbounded size will kill your application dead as
+%% OTP mechanisms stuggle to cope with them. So this function is
+%% intended <b>only</b> for messages which have a reasonable bounded
+%% size before they're formatted.
%%
-debug(Fmt) -> dyn_log(debug, [], Fmt, []).
-debug(Fmt,Args) -> dyn_log(debug, [], Fmt, Args).
-debug(Attrs,Fmt,Args) -> dyn_log(debug, Attrs, Fmt, Args).
-
-info(Fmt) -> dyn_log(info, [], Fmt, []).
-info(Fmt,Args) -> dyn_log(info, [], Fmt, Args).
-info(Attrs,Fmt,Args) -> dyn_log(info, Attrs, Fmt, Args).
-
-notice(Fmt) -> dyn_log(notice, [], Fmt, []).
-notice(Fmt,Args) -> dyn_log(notice, [], Fmt, Args).
-notice(Attrs,Fmt,Args) -> dyn_log(notice, Attrs, Fmt, Args).
-
-warning(Fmt) -> dyn_log(warning, [], Fmt, []).
-warning(Fmt,Args) -> dyn_log(warning, [], Fmt, Args).
-warning(Attrs,Fmt,Args) -> dyn_log(warning, Attrs, Fmt, Args).
-
-error(Fmt) -> dyn_log(error, [], Fmt, []).
-error(Fmt,Args) -> dyn_log(error, [], Fmt, Args).
-error(Attrs,Fmt,Args) -> dyn_log(error, Attrs, Fmt, Args).
-
-critical(Fmt) -> dyn_log(critical, [], Fmt, []).
-critical(Fmt,Args) -> dyn_log(critical, [], Fmt, Args).
-critical(Attrs,Fmt,Args) -> dyn_log(critical, Attrs, Fmt, Args).
-
-alert(Fmt) -> dyn_log(alert, [], Fmt, []).
-alert(Fmt,Args) -> dyn_log(alert, [], Fmt, Args).
-alert(Attrs,Fmt,Args) -> dyn_log(alert, Attrs, Fmt, Args).
-
-emergency(Fmt) -> dyn_log(emergency, [], Fmt, []).
-emergency(Fmt,Args) -> dyn_log(emergency, [], Fmt, Args).
-emergency(Attrs,Fmt,Args) -> dyn_log(emergency, Attrs, Fmt, Args).
-
-none(Fmt) -> dyn_log(none, [], Fmt, []).
-none(Fmt,Args) -> dyn_log(none, [], Fmt, Args).
-none(Attrs,Fmt,Args) -> dyn_log(none, Attrs, Fmt, Args).
-
-%% @private
--spec dyn_log(log_level(), list(), string(), list()) ->
- ok | {error, lager_not_running}.
+%% If the format string is invalid or not enough arguments are
+%% supplied a 'FORMAT ERROR' message is printed instead with the
+%% offending arguments. The caller is NOT crashed.
+unsafe_format(Fmt, Args) ->
+ try io_lib:format(Fmt, Args)
+ catch
+ _:_ -> io_lib:format("FORMAT ERROR: ~p ~p", [Fmt, Args])
+ end.
-dyn_log(Severity, Attrs, Fmt, Args) ->
- try erlang:error(fail) of
- _ -> strange
+%% @doc Print a record lager found during parse transform
+pr(Record, Module) when is_tuple(Record), is_atom(element(1, Record)) ->
+ pr(Record, Module, []);
+pr(Record, _) ->
+ Record.
+
+%% @doc Print a record lager found during parse transform
+pr(Record, Module, Options) when is_tuple(Record), is_atom(element(1, Record)), is_list(Options) ->
+ try
+ case is_record_known(Record, Module) of
+ false ->
+ Record;
+ {RecordName, RecordFields} ->
+ {'$lager_record', RecordName,
+ zip(RecordFields, tl(tuple_to_list(Record)), Module, Options, [])}
+ end
catch
- error:_ ->
- case erlang:get_stacktrace() of
- [_,{M,F,_A}|_] ->
- dispatch_log(Severity, M, F, 0, self(),
- [{module,M},{function,F},
- {pid,pid_to_list(self())}|
- Attrs],
- Fmt, Args);
- [_,{M,F,_A,Loc}|_] ->
- L = proplists:get_value(line,Loc,0),
- dispatch_log(Severity, M, F, L, self(),
- [{module,M},{function,F},
- {line,L},{pid,pid_to_list(self())}|
- Attrs],
- Fmt, Args);
- [] ->
- erlang:display({Severity, Fmt})
- end
+ error:undef ->
+ Record
+ end;
+pr(Record, _, _) ->
+ Record.
+
+zip([FieldName|RecordFields], [FieldValue|Record], Module, Options, ToReturn) ->
+ Compress = lists:member(compress, Options),
+ case is_tuple(FieldValue) andalso
+ tuple_size(FieldValue) > 0 andalso
+ is_atom(element(1, FieldValue)) andalso
+ is_record_known(FieldValue, Module) of
+ false when Compress andalso FieldValue =:= undefined ->
+ zip(RecordFields, Record, Module, Options, ToReturn);
+ false ->
+ zip(RecordFields, Record, Module, Options, [{FieldName, FieldValue}|ToReturn]);
+ _Else ->
+ F = {FieldName, pr(FieldValue, Module, Options)},
+ zip(RecordFields, Record, Module, Options, [F|ToReturn])
+ end;
+zip([], [], _Module, _Compress, ToReturn) ->
+ lists:reverse(ToReturn).
+
+is_record_known(Record, Module) ->
+ Name = element(1, Record),
+ Attrs = Module:module_info(attributes),
+ case lists:keyfind(lager_records, 1, Attrs) of
+ false -> false;
+ {lager_records, Records} ->
+ case lists:keyfind(Name, 1, Records) of
+ false -> false;
+ {Name, RecordFields} ->
+ case (tuple_size(Record) - 1) =:= length(RecordFields) of
+ false -> false;
+ true -> {Name, RecordFields}
+ end
+ end
end.
+
diff --git a/deps/lager/src/lager_app.erl b/deps/lager/src/lager_app.erl
index 3a4f3fe..3d8791d 100644
--- a/deps/lager/src/lager_app.erl
+++ b/deps/lager/src/lager_app.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
@@ -22,56 +22,337 @@
-behaviour(application).
-include("lager.hrl").
-
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-endif.
-export([start/0,
start/2,
+ start_handler/3,
stop/1]).
+-define(FILENAMES, '__lager_file_backend_filenames').
+-define(THROTTLE, lager_backend_throttle).
+-define(DEFAULT_HANDLER_CONF,
+ [{lager_console_backend, info},
+ {lager_file_backend,
+ [{file, "log/error.log"}, {level, error},
+ {size, 10485760}, {date, "$D0"}, {count, 5}]
+ },
+ {lager_file_backend,
+ [{file, "log/console.log"}, {level, info},
+ {size, 10485760}, {date, "$D0"}, {count, 5}]
+ }
+ ]).
+
start() ->
application:start(lager).
-start(_StartType, _StartArgs) ->
- %% until lager is completely started, allow all messages to go through
- lager_mochiglobal:put(loglevel, {?DEBUG, []}),
- {ok, Pid} = lager_sup:start_link(),
- Handlers = case application:get_env(lager, handlers) of
- undefined ->
- [{lager_console_backend, info},
- {lager_file_backend, [{"log/error.log", error, 10485760, "", 5},
- {"log/console.log", info, 10485760, "", 5}]}];
- {ok, Val} ->
- Val
+start_throttle(Sink, Threshold, Window) ->
+ _ = supervisor:start_child(lager_handler_watcher_sup,
+ [Sink, ?THROTTLE, [Threshold, Window]]),
+ ok.
+
+determine_async_behavior(_Sink, {ok, undefined}, _Window) ->
+ ok;
+determine_async_behavior(_Sink, undefined, _Window) ->
+ ok;
+determine_async_behavior(_Sink, {ok, Threshold}, _Window) when not is_integer(Threshold) orelse Threshold < 0 ->
+ error_logger:error_msg("Invalid value for 'async_threshold': ~p~n",
+ [Threshold]),
+ throw({error, bad_config});
+determine_async_behavior(Sink, {ok, Threshold}, undefined) ->
+ start_throttle(Sink, Threshold, erlang:trunc(Threshold * 0.2));
+determine_async_behavior(_Sink, {ok, Threshold}, {ok, Window}) when not is_integer(Window) orelse Window > Threshold orelse Window < 0 ->
+ error_logger:error_msg(
+ "Invalid value for 'async_threshold_window': ~p~n", [Window]),
+ throw({error, bad_config});
+determine_async_behavior(Sink, {ok, Threshold}, {ok, Window}) ->
+ start_throttle(Sink, Threshold, Window).
+
+start_handlers(_Sink, undefined) ->
+ ok;
+start_handlers(_Sink, Handlers) when not is_list(Handlers) ->
+ error_logger:error_msg(
+ "Invalid value for 'handlers' (must be list): ~p~n", [Handlers]),
+ throw({error, bad_config});
+start_handlers(Sink, Handlers) ->
+ %% handlers failing to start are handled in the handler_watcher
+ lager_config:global_set(handlers,
+ lager_config:global_get(handlers, []) ++
+ lists:map(fun({Module, Config}) ->
+ check_handler_config(Module, Config),
+ start_handler(Sink, Module, Config);
+ (_) ->
+ throw({error, bad_config})
+ end,
+ expand_handlers(Handlers))),
+ ok.
+
+start_handler(Sink, Module, Config) ->
+ {ok, Watcher} = supervisor:start_child(lager_handler_watcher_sup,
+ [Sink, Module, Config]),
+ {Module, Watcher, Sink}.
+
+check_handler_config({lager_file_backend, F}, Config) when is_list(Config) ->
+ Fs = case get(?FILENAMES) of
+ undefined -> ordsets:new();
+ X -> X
end,
+ case ordsets:is_element(F, Fs) of
+ true ->
+ error_logger:error_msg(
+ "Cannot have same file (~p) in multiple file backends~n", [F]),
+ throw({error, bad_config});
+ false ->
+ put(?FILENAMES,
+ ordsets:add_element(F, Fs))
+ end,
+ ok;
+check_handler_config(_Handler, Config) when is_list(Config) orelse is_atom(Config) ->
+ ok;
+check_handler_config(Handler, _BadConfig) ->
+ throw({error, {bad_config, Handler}}).
- [supervisor:start_child(lager_handler_watcher_sup, [lager_event, Module, Config]) ||
- {Module, Config} <- expand_handlers(Handlers)],
+clean_up_config_checks() ->
+ erase(?FILENAMES).
- %% mask the messages we have no use for
- MinLog = lager:minimum_loglevel(lager:get_loglevels()),
- {_, Traces} = lager_mochiglobal:get(loglevel),
- lager_mochiglobal:put(loglevel, {MinLog, Traces}),
+interpret_hwm(undefined) ->
+ undefined;
+interpret_hwm({ok, undefined}) ->
+ undefined;
+interpret_hwm({ok, HWM}) when not is_integer(HWM) orelse HWM < 0 ->
+ _ = lager:log(warning, self(), "Invalid error_logger high water mark: ~p, disabling", [HWM]),
+ undefined;
+interpret_hwm({ok, HWM}) ->
+ HWM.
- SavedHandlers = case application:get_env(lager, error_logger_redirect) of
- {ok, false} ->
- [];
- _ ->
- supervisor:start_child(lager_handler_watcher_sup, [error_logger, error_logger_lager_h, []]),
- %% Should we allow user to whitelist handlers to not be removed?
+start_error_logger_handler({ok, false}, _HWM, _Whitelist) ->
+ [];
+start_error_logger_handler(_, HWM, undefined) ->
+ start_error_logger_handler(ignore_me, HWM, {ok, []});
+start_error_logger_handler(_, HWM, {ok, WhiteList}) ->
+ GlStrategy = case application:get_env(lager, error_logger_groupleader_strategy) of
+ undefined ->
+ handle;
+ {ok, GlStrategy0} when
+ GlStrategy0 =:= handle;
+ GlStrategy0 =:= ignore;
+ GlStrategy0 =:= mirror ->
+ GlStrategy0;
+ {ok, BadGlStrategy} ->
+ error_logger:error_msg(
+ "Invalid value for 'error_logger_groupleader_strategy': ~p~n",
+ [BadGlStrategy]),
+ throw({error, bad_config})
+ end,
+
+ case supervisor:start_child(lager_handler_watcher_sup, [error_logger, error_logger_lager_h, [HWM, GlStrategy]]) of
+ {ok, _} ->
[begin error_logger:delete_report_handler(X), X end ||
- X <- gen_event:which_handlers(error_logger) -- [error_logger_lager_h]]
- end,
+ X <- gen_event:which_handlers(error_logger) -- [error_logger_lager_h | WhiteList]];
+ {error, _} ->
+ []
+ end.
+
+%% `determine_async_behavior/3' is called with the results from either
+%% `application:get_env/2' and `proplists:get_value/2'. Since
+%% `application:get_env/2' wraps a successful retrieval in an `{ok,
+%% Value}' tuple, do the same for the result from
+%% `proplists:get_value/2'.
+wrap_proplist_value(undefined) ->
+ undefined;
+wrap_proplist_value(Value) ->
+ {ok, Value}.
+
+configure_sink(Sink, SinkDef) ->
+ lager_config:new_sink(Sink),
+ ChildId = lager_util:make_internal_sink_name(Sink),
+ _ = supervisor:start_child(lager_sup,
+ {ChildId,
+ {gen_event, start_link,
+ [{local, Sink}]},
+ permanent, 5000, worker, dynamic}),
+ determine_async_behavior(Sink,
+ wrap_proplist_value(
+ proplists:get_value(async_threshold, SinkDef)),
+ wrap_proplist_value(
+ proplists:get_value(async_threshold_window, SinkDef))
+ ),
+ start_handlers(Sink,
+ proplists:get_value(handlers, SinkDef, [])),
+
+ lager:update_loglevel_config(Sink).
+
+
+configure_extra_sinks(Sinks) ->
+ lists:foreach(fun({Sink, Proplist}) -> configure_sink(Sink, Proplist) end,
+ Sinks).
+
+start(_StartType, _StartArgs) ->
+ {ok, Pid} = lager_sup:start_link(),
+
+ %% Handle the default sink.
+ determine_async_behavior(?DEFAULT_SINK,
+ application:get_env(lager, async_threshold),
+ application:get_env(lager, async_threshold_window)),
+ start_handlers(?DEFAULT_SINK,
+ application:get_env(lager, handlers, ?DEFAULT_HANDLER_CONF)),
+
+ ok = add_configured_traces(),
+
+ lager:update_loglevel_config(?DEFAULT_SINK),
+
+ SavedHandlers = start_error_logger_handler(
+ application:get_env(lager, error_logger_redirect),
+ interpret_hwm(application:get_env(lager, error_logger_hwm)),
+ application:get_env(lager, error_logger_whitelist)
+ ),
+
+ _ = lager_util:trace_filter(none),
+
+ %% Now handle extra sinks
+ configure_extra_sinks(application:get_env(lager, extra_sinks, [])),
+
+ clean_up_config_checks(),
{ok, Pid, SavedHandlers}.
stop(Handlers) ->
- [error_logger:add_report_handler(Handler) || Handler <- Handlers],
- ok.
+ lists:foreach(fun(Handler) ->
+ error_logger:add_report_handler(Handler)
+ end, Handlers).
expand_handlers([]) ->
[];
+expand_handlers([{lager_file_backend, [{Key, _Value}|_]=Config}|T]) when is_atom(Key) ->
+ %% this is definitely a new-style config, no expansion needed
+ [maybe_make_handler_id(lager_file_backend, Config) | expand_handlers(T)];
expand_handlers([{lager_file_backend, Configs}|T]) ->
- [{{lager_file_backend, element(1, Config)}, Config} || Config <- Configs] ++
+ ?INT_LOG(notice, "Deprecated lager_file_backend config detected, please consider updating it", []),
+ [ {lager_file_backend:config_to_id(Config), Config} || Config <- Configs] ++
expand_handlers(T);
+expand_handlers([{Mod, Config}|T]) when is_atom(Mod) ->
+ [maybe_make_handler_id(Mod, Config) | expand_handlers(T)];
expand_handlers([H|T]) ->
[H | expand_handlers(T)].
+
+add_configured_traces() ->
+ Traces = case application:get_env(lager, traces) of
+ undefined ->
+ [];
+ {ok, TraceVal} ->
+ TraceVal
+ end,
+
+ lists:foreach(fun({Handler, Filter, Level}) ->
+ {ok, _} = lager:trace(Handler, Filter, Level)
+ end,
+ Traces),
+ ok.
+
+maybe_make_handler_id(Mod, Config) ->
+ %% Allow the backend to generate a gen_event handler id, if it wants to.
+ %% We don't use erlang:function_exported here because that requires the module
+ %% already be loaded, which is unlikely at this phase of startup. Using code:load
+ %% caused undesireable side-effects with generating code-coverage reports.
+ try Mod:config_to_id(Config) of
+ Id ->
+ {Id, Config}
+ catch
+ error:undef ->
+ {Mod, Config}
+ end.
+
+-ifdef(TEST).
+application_config_mangling_test_() ->
+ [
+ {"Explode the file backend handlers",
+ ?_assertMatch(
+ [{lager_console_backend, info},
+ {{lager_file_backend,"error.log"},{"error.log",error,10485760, "$D0",5}},
+ {{lager_file_backend,"console.log"},{"console.log",info,10485760, "$D0",5}}
+ ],
+ expand_handlers([{lager_console_backend, info},
+ {lager_file_backend, [
+ {"error.log", error, 10485760, "$D0", 5},
+ {"console.log", info, 10485760, "$D0", 5}
+ ]}]
+ ))
+ },
+ {"Explode the short form of backend file handlers",
+ ?_assertMatch(
+ [{lager_console_backend, info},
+ {{lager_file_backend,"error.log"},{"error.log",error}},
+ {{lager_file_backend,"console.log"},{"console.log",info}}
+ ],
+ expand_handlers([{lager_console_backend, info},
+ {lager_file_backend, [
+ {"error.log", error},
+ {"console.log", info}
+ ]}]
+ ))
+ },
+ {"Explode with formatter info",
+ ?_assertMatch(
+ [{{lager_file_backend,"test.log"}, [{"test.log", debug, 10485760, "$D0", 5},{lager_default_formatter,["[",severity,"] ", message, "\n"]}]},
+ {{lager_file_backend,"test2.log"}, [{"test2.log",debug, 10485760, "$D0", 5},{lager_default_formatter,["2>[",severity,"] ", message, "\n"]}]}],
+ expand_handlers([{lager_file_backend, [
+ [{"test.log", debug, 10485760, "$D0", 5},{lager_default_formatter,["[",severity,"] ", message, "\n"]}],
+ [{"test2.log",debug, 10485760, "$D0", 5},{lager_default_formatter,["2>[",severity,"] ",message, "\n"]}]
+ ]
+ }])
+ )
+ },
+ {"Explode short form with short formatter info",
+ ?_assertMatch(
+ [{{lager_file_backend,"test.log"}, [{"test.log", debug},{lager_default_formatter,["[",severity,"] ", message, "\n"]}]},
+ {{lager_file_backend,"test2.log"}, [{"test2.log",debug},{lager_default_formatter}]}],
+ expand_handlers([{lager_file_backend, [
+ [{"test.log", debug},{lager_default_formatter,["[",severity,"] ", message, "\n"]}],
+ [{"test2.log",debug},{lager_default_formatter}]
+ ]
+ }])
+ )
+ },
+ {"New form needs no expansion",
+ ?_assertMatch([
+ {{lager_file_backend,"test.log"}, [{file, "test.log"}]},
+ {{lager_file_backend,"test2.log"}, [{file, "test2.log"}, {level, info}, {sync_on, none}]},
+ {{lager_file_backend,"test3.log"}, [{formatter, lager_default_formatter}, {file, "test3.log"}]}
+ ],
+ expand_handlers([
+ {lager_file_backend, [{file, "test.log"}]},
+ {lager_file_backend, [{file, "test2.log"}, {level, info}, {sync_on, none}]},
+ {lager_file_backend, [{formatter, lager_default_formatter},{file, "test3.log"}]}
+ ])
+ )
+ }
+ ].
+
+check_handler_config_test_() ->
+ Good = expand_handlers(?DEFAULT_HANDLER_CONF),
+ Bad = expand_handlers([{lager_console_backend, info},
+ {lager_file_backend, [{file, "same_file.log"}]},
+ {lager_file_backend, [{file, "same_file.log"}, {level, info}]}]),
+ AlsoBad = [{lager_logstash_backend,
+ {level, info},
+ {output, {udp, "localhost", 5000}},
+ {format, json},
+ {json_encoder, jiffy}}],
+ BadToo = [{fail, {fail}}],
+ [
+ {"lager_file_backend_good",
+ ?_assertEqual([ok, ok, ok], [ check_handler_config(M,C) || {M,C} <- Good ])
+ },
+ {"lager_file_backend_bad",
+ ?_assertThrow({error, bad_config}, [ check_handler_config(M,C) || {M,C} <- Bad ])
+ },
+ {"Invalid config dies",
+ ?_assertThrow({error, bad_config}, start_handlers(foo, AlsoBad))
+ },
+ {"Invalid config dies",
+ ?_assertThrow({error, {bad_config, _}}, start_handlers(foo, BadToo))
+ }
+ ].
+-endif.
diff --git a/deps/lager/src/lager_backend_throttle.erl b/deps/lager/src/lager_backend_throttle.erl
new file mode 100644
index 0000000..90b9c40
--- /dev/null
+++ b/deps/lager/src/lager_backend_throttle.erl
@@ -0,0 +1,105 @@
+%% Copyright (c) 2011-2013 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This file is provided to you 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.
+
+%% @doc A simple gen_event backend used to monitor mailbox size and
+%% switch log messages between synchronous and asynchronous modes.
+%% A gen_event handler is used because a process getting its own mailbox
+%% size doesn't involve getting a lock, and gen_event handlers run in their
+%% parent's process.
+
+-module(lager_backend_throttle).
+
+-include("lager.hrl").
+
+-behaviour(gen_event).
+
+-export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2,
+ code_change/3]).
+
+%%
+%% Allow test code to verify that we're doing the needful.
+-ifdef(TEST).
+-define(ETS_TABLE, async_threshold_test).
+-define(TOGGLE_SYNC(), test_increment(sync_toggled)).
+-define(TOGGLE_ASYNC(), test_increment(async_toggled)).
+-else.
+-define(TOGGLE_SYNC(), true).
+-define(TOGGLE_ASYNC(), true).
+-endif.
+
+-record(state, {
+ sink :: atom(),
+ hwm :: non_neg_integer(),
+ window_min :: non_neg_integer(),
+ async = true :: boolean()
+ }).
+
+init([{sink, Sink}, Hwm, Window]) ->
+ lager_config:set({Sink, async}, true),
+ {ok, #state{sink=Sink, hwm=Hwm, window_min=Hwm - Window}}.
+
+
+handle_call(get_loglevel, State) ->
+ {ok, {mask, ?LOG_NONE}, State};
+handle_call({set_loglevel, _Level}, State) ->
+ {ok, ok, State};
+handle_call(_Request, State) ->
+ {ok, ok, State}.
+
+handle_event({log, _Message},State) ->
+ {message_queue_len, Len} = erlang:process_info(self(), message_queue_len),
+ case {Len > State#state.hwm, Len < State#state.window_min, State#state.async} of
+ {true, _, true} ->
+ %% need to flip to sync mode
+ ?TOGGLE_SYNC(),
+ lager_config:set({State#state.sink, async}, false),
+ {ok, State#state{async=false}};
+ {_, true, false} ->
+ %% need to flip to async mode
+ ?TOGGLE_ASYNC(),
+ lager_config:set({State#state.sink, async}, true),
+ {ok, State#state{async=true}};
+ _ ->
+ %% nothing needs to change
+ {ok, State}
+ end;
+handle_event(_Event, State) ->
+ {ok, State}.
+
+handle_info(_Info, State) ->
+ {ok, State}.
+
+%% @private
+terminate(_Reason, _State) ->
+ ok.
+
+%% @private
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+-ifdef(TEST).
+test_get(Key) ->
+ get_default(ets:lookup(?ETS_TABLE, Key)).
+
+test_increment(Key) ->
+ ets:insert(?ETS_TABLE,
+ {Key, test_get(Key) + 1}).
+
+get_default([]) ->
+ 0;
+get_default([{_Key, Value}]) ->
+ Value.
+-endif.
diff --git a/deps/lager/src/lager_common_test_backend.erl b/deps/lager/src/lager_common_test_backend.erl
new file mode 100644
index 0000000..0e03d6f
--- /dev/null
+++ b/deps/lager/src/lager_common_test_backend.erl
@@ -0,0 +1,121 @@
+-module(lager_common_test_backend).
+
+-behavior(gen_event).
+
+%% gen_event callbacks
+-export([init/1,
+ handle_call/2,
+ handle_event/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3]).
+-export([get_logs/0,
+ bounce/0,
+ bounce/1]).
+
+%% holds the log messages for retreival on terminate
+-record(state, {level :: {mask, integer()},
+ formatter :: atom(),
+ format_config :: any(),
+ log = [] :: list()}).
+
+-include("lager.hrl").
+-define(TERSE_FORMAT,[time, " ", color, "[", severity,"] ", message]).
+
+%% @doc Before every test, just
+%% lager_common_test_backend:bounce(Level) with the log level of your
+%% choice. Every message will be passed along to ct:pal for your
+%% viewing in the common_test reports. Also, you can call
+%% lager_common_test_backend:get_logs/0 to get a list of all log
+%% messages this backend has received during your test. You can then
+%% search that list for expected log messages.
+
+
+-spec get_logs() -> [iolist()] | {error, term()}.
+get_logs() ->
+ gen_event:call(lager_event, ?MODULE, get_logs, infinity).
+
+bounce() ->
+ bounce(error).
+
+bounce(Level) ->
+ _ = application:stop(lager),
+ application:set_env(lager, suppress_application_start_stop, true),
+ application:set_env(lager, handlers,
+ [
+ {lager_common_test_backend, [Level, false]}
+ ]),
+ ok = lager:start(),
+ %% we care more about getting all of our messages here than being
+ %% careful with the amount of memory that we're using.
+ error_logger_lager_h:set_high_water(100000),
+ ok.
+
+-spec(init(integer()|atom()|[term()]) -> {ok, #state{}} | {error, atom()}).
+%% @private
+%% @doc Initializes the event handler
+init([Level, true]) -> % for backwards compatibility
+ init([Level,{lager_default_formatter,[{eol, "\n"}]}]);
+init([Level,false]) -> % for backwards compatibility
+ init([Level,{lager_default_formatter,?TERSE_FORMAT ++ ["\n"]}]);
+init([Level,{Formatter,FormatterConfig}]) when is_atom(Formatter) ->
+ case lists:member(Level, ?LEVELS) of
+ true ->
+ {ok, #state{level=lager_util:config_to_mask(Level),
+ formatter=Formatter,
+ format_config=FormatterConfig}};
+ _ ->
+ {error, bad_log_level}
+ end;
+init(Level) ->
+ init([Level,{lager_default_formatter,?TERSE_FORMAT ++ ["\n"]}]).
+
+-spec(handle_event(tuple(), #state{}) -> {ok, #state{}}).
+%% @private
+handle_event({log, Message},
+ #state{level=L,formatter=Formatter,format_config=FormatConfig,log=Logs} = State) ->
+ case lager_util:is_loggable(Message,L,?MODULE) of
+ true ->
+ Log = Formatter:format(Message,FormatConfig),
+ ct:pal(Log),
+ {ok, State#state{log=[Log|Logs]}};
+ false ->
+ {ok, State}
+ end;
+handle_event(Event, State) ->
+ ct:pal(Event),
+ {ok, State#state{log = [Event|State#state.log]}}.
+
+-spec(handle_call(any(), #state{}) -> {ok, any(), #state{}}).
+%% @private
+%% @doc gets and sets loglevel. This is part of the lager backend api.
+handle_call(get_loglevel, #state{level=Level} = State) ->
+ {ok, Level, State};
+handle_call({set_loglevel, Level}, State) ->
+ case lists:member(Level, ?LEVELS) of
+ true ->
+ {ok, ok, State#state{level=lager_util:config_to_mask(Level)}};
+ _ ->
+ {ok, {error, bad_log_level}, State}
+ end;
+handle_call(get_logs, #state{log = Logs} = State) ->
+ {ok, lists:reverse(Logs), State};
+handle_call(_, State) ->
+ {ok, ok, State}.
+
+-spec(handle_info(any(), #state{}) -> {ok, #state{}}).
+%% @private
+%% @doc gen_event callback, does nothing.
+handle_info(_, State) ->
+ {ok, State}.
+
+-spec(code_change(any(), #state{}, any()) -> {ok, #state{}}).
+%% @private
+%% @doc gen_event callback, does nothing.
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+-spec(terminate(any(), #state{}) -> {ok, list()}).
+%% @doc gen_event callback, does nothing.
+terminate(_Reason, #state{log=Logs}) ->
+ {ok, lists:reverse(Logs)}.
diff --git a/deps/lager/src/lager_config.erl b/deps/lager/src/lager_config.erl
new file mode 100644
index 0000000..253894f
--- /dev/null
+++ b/deps/lager/src/lager_config.erl
@@ -0,0 +1,87 @@
+%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This file is provided to you 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.
+
+%% @doc Helper functions for working with lager's runtime config
+
+-module(lager_config).
+
+-include("lager.hrl").
+
+-export([new/0, new_sink/1, get/1, get/2, set/2,
+ global_get/1, global_get/2, global_set/2]).
+
+-define(TBL, lager_config).
+-define(GLOBAL, '_global').
+
+%% For multiple sinks, the key is now the registered event name and the old key
+%% as a tuple.
+%%
+%% {{lager_event, loglevel}, Value} instead of {loglevel, Value}
+
+new() ->
+ %% set up the ETS configuration table
+ _ = try ets:new(?TBL, [named_table, public, set, {keypos, 1}, {read_concurrency, true}]) of
+ _Result ->
+ ok
+ catch
+ error:badarg ->
+ ?INT_LOG(warning, "Table ~p already exists", [?TBL])
+ end,
+ new_sink(?DEFAULT_SINK),
+ %% Need to be able to find the `lager_handler_watcher' for all handlers
+ ets:insert_new(?TBL, {{?GLOBAL, handlers}, []}),
+ ok.
+
+new_sink(Sink) ->
+ %% use insert_new here so that if we're in an appup we don't mess anything up
+ %%
+ %% until lager is completely started, allow all messages to go through
+ ets:insert_new(?TBL, {{Sink, loglevel}, {element(2, lager_util:config_to_mask(debug)), []}}).
+
+global_get(Key) ->
+ global_get(Key, undefined).
+
+global_get(Key, Default) ->
+ get({?GLOBAL, Key}, Default).
+
+global_set(Key, Value) ->
+ set({?GLOBAL, Key}, Value).
+
+
+get({_Sink, _Key}=FullKey) ->
+ get(FullKey, undefined);
+get(Key) ->
+ get({?DEFAULT_SINK, Key}, undefined).
+
+get({Sink, Key}, Default) ->
+ try
+ case ets:lookup(?TBL, {Sink, Key}) of
+ [] ->
+ Default;
+ [{{Sink, Key}, Res}] ->
+ Res
+ end
+ catch
+ _:_ ->
+ Default
+ end;
+get(Key, Default) ->
+ get({?DEFAULT_SINK, Key}, Default).
+
+set({Sink, Key}, Value) ->
+ ets:insert(?TBL, {{Sink, Key}, Value});
+set(Key, Value) ->
+ set({?DEFAULT_SINK, Key}, Value).
diff --git a/deps/lager/src/lager_console_backend.erl b/deps/lager/src/lager_console_backend.erl
index 1e98fe5..e922ec3 100644
--- a/deps/lager/src/lager_console_backend.erl
+++ b/deps/lager/src/lager_console_backend.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% Copyright (c) 2011-2012, 2014 Basho Technologies, Inc. All Rights Reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
@@ -24,7 +24,10 @@
-export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2,
code_change/3]).
--record(state, {level, verbose}).
+-record(state, {level :: {'mask', integer()},
+ formatter :: atom(),
+ format_config :: any(),
+ colors=[] :: list()}).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
@@ -32,61 +35,72 @@
-endif.
-include("lager.hrl").
+-define(TERSE_FORMAT,[time, " ", color, "[", severity,"] ", message]).
%% @private
-init(Level) when is_atom(Level) ->
- case lists:member(Level, ?LEVELS) of
- true ->
- {ok, #state{level=lager_util:level_to_num(Level), verbose=false}};
- _ ->
- {error, bad_log_level}
- end;
-init([Level, Verbose]) ->
- case lists:member(Level, ?LEVELS) of
- true ->
- {ok, #state{level=lager_util:level_to_num(Level), verbose=Verbose}};
- _ ->
- {error, bad_log_level}
- end.
+init([Level]) when is_atom(Level) ->
+ init(Level);
+init([Level, true]) -> % for backwards compatibility
+ init([Level,{lager_default_formatter,[{eol, eol()}]}]);
+init([Level,false]) -> % for backwards compatibility
+ init([Level,{lager_default_formatter,?TERSE_FORMAT ++ [eol()]}]);
+init([Level,{Formatter,FormatterConfig}]) when is_atom(Formatter) ->
+ Colors = case application:get_env(lager, colored) of
+ {ok, true} ->
+ {ok, LagerColors} = application:get_env(lager, colors),
+ LagerColors;
+ _ -> []
+ end,
+ try {is_new_style_console_available(), lager_util:config_to_mask(Level)} of
+ {false, _} ->
+ Msg = "Lager's console backend is incompatible with the 'old' shell, not enabling it",
+ %% be as noisy as possible, log to every possible place
+ try
+ alarm_handler:set_alarm({?MODULE, "WARNING: " ++ Msg})
+ catch
+ _:_ ->
+ error_logger:warning_msg(Msg ++ "~n")
+ end,
+ io:format("WARNING: " ++ Msg ++ "~n"),
+ ?INT_LOG(warning, Msg, []),
+ {error, {fatal, old_shell}};
+ {true, Levels} ->
+ {ok, #state{level=Levels,
+ formatter=Formatter,
+ format_config=FormatterConfig,
+ colors=Colors}}
+ catch
+ _:_ ->
+ {error, {fatal, bad_log_level}}
+ end;
+init(Level) ->
+ init([Level,{lager_default_formatter,?TERSE_FORMAT ++ [eol()]}]).
%% @private
handle_call(get_loglevel, #state{level=Level} = State) ->
{ok, Level, State};
handle_call({set_loglevel, Level}, State) ->
- case lists:member(Level, ?LEVELS) of
- true ->
- {ok, ok, State#state{level=lager_util:level_to_num(Level)}};
- _ ->
+ try lager_util:config_to_mask(Level) of
+ Levels ->
+ {ok, ok, State#state{level=Levels}}
+ catch
+ _:_ ->
{ok, {error, bad_log_level}, State}
end;
handle_call(_Request, State) ->
{ok, ok, State}.
%% @private
-handle_event({log, Dest, Level, {Date, Time}, [LevelStr, Location, Message]},
- #state{level=L, verbose=Verbose} = State) when Level > L ->
- case lists:member(lager_console_backend, Dest) of
+handle_event({log, Message},
+ #state{level=L,formatter=Formatter,format_config=FormatConfig,colors=Colors} = State) ->
+ case lager_util:is_loggable(Message, L, ?MODULE) of
true ->
- case Verbose of
- true ->
- io:put_chars([Date, " ", Time, " ", LevelStr, Location, Message, "\n"]);
- _ ->
- io:put_chars([Time, " ", LevelStr, Message, "\n"])
- end,
+ io:put_chars(user, Formatter:format(Message,FormatConfig,Colors)),
{ok, State};
false ->
{ok, State}
end;
-handle_event({log, Level, {Date, Time}, [LevelStr, Location, Message]},
- #state{level=LogLevel, verbose=Verbose} = State) when Level =< LogLevel ->
- case Verbose of
- true ->
- io:put_chars([Date, " ", Time, " ", LevelStr, Location, Message, "\n"]);
- _ ->
- io:put_chars([Time, " ", LevelStr, Message, "\n"])
- end,
- {ok, State};
handle_event(_Event, State) ->
{ok, State}.
@@ -102,6 +116,34 @@ terminate(_Reason, _State) ->
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
+eol() ->
+ case application:get_env(lager, colored) of
+ {ok, true} ->
+ "\e[0m\r\n";
+ _ ->
+ "\r\n"
+ end.
+
+-ifdef(TEST).
+is_new_style_console_available() ->
+ true.
+-else.
+is_new_style_console_available() ->
+ %% Criteria:
+ %% 1. If the user has specified '-noshell' on the command line,
+ %% then we will pretend that the new-style console is available.
+ %% If there is no shell at all, then we don't have to worry
+ %% about log events being blocked by the old-style shell.
+ %% 2. Windows doesn't support the new shell, so all windows users
+ %% have is the oldshell.
+ %% 3. If the user_drv process is registered, all is OK.
+ %% 'user_drv' is a registered proc name used by the "new"
+ %% console driver.
+ init:get_argument(noshell) /= error orelse
+ element(1, os:type()) /= win32 orelse
+ is_pid(whereis(user_drv)).
+-endif.
+
-ifdef(TEST).
console_log_test_() ->
%% tiny recursive fun that pretends to be a group leader
@@ -121,32 +163,37 @@ console_log_test_() ->
YComb(YComb)
end
end,
-
{foreach,
fun() ->
error_logger:tty(false),
application:load(lager),
application:set_env(lager, handlers, []),
application:set_env(lager, error_logger_redirect, false),
- application:start(compiler),
- application:start(syntax_tools),
- application:start(lager)
+ lager:start(),
+ whereis(user)
end,
- fun(_) ->
+ fun(User) ->
+ unregister(user),
+ register(user, User),
application:stop(lager),
+ application:stop(goldrush),
error_logger:tty(true)
end,
[
{"regular console logging",
fun() ->
Pid = spawn(F(self())),
- gen_event:add_handler(lager_event, lager_console_backend, info),
+ unregister(user),
+ register(user, Pid),
erlang:group_leader(Pid, whereis(lager_event)),
+ gen_event:add_handler(lager_event, lager_console_backend, info),
+ lager_config:set({lager_event, loglevel}, {element(2, lager_util:config_to_mask(info)), []}),
lager:log(info, self(), "Test message"),
receive
{io_request, From, ReplyAs, {put_chars, unicode, Msg}} ->
From ! {io_reply, ReplyAs, ok},
- ?assertMatch([_, "[info]", "Test message\n"], re:split(Msg, " ", [{return, list}, {parts, 3}]))
+ TestMsg = "Test message" ++ eol(),
+ ?assertMatch([_, "[info]", TestMsg], re:split(Msg, " ", [{return, list}, {parts, 3}]))
after
500 ->
?assert(false)
@@ -156,14 +203,42 @@ console_log_test_() ->
{"verbose console logging",
fun() ->
Pid = spawn(F(self())),
+ unregister(user),
+ register(user, Pid),
erlang:group_leader(Pid, whereis(lager_event)),
gen_event:add_handler(lager_event, lager_console_backend, [info, true]),
- lager:log(info, self(), "Test message"),
- lager:log(info, self(), "Test message"),
+ lager_config:set({lager_event, loglevel}, {element(2, lager_util:config_to_mask(info)), []}),
+ lager:info("Test message"),
PidStr = pid_to_list(self()),
receive
{io_request, _, _, {put_chars, unicode, Msg}} ->
- ?assertMatch([_, _, "[info]", PidStr, "Test message\n"], re:split(Msg, " ", [{return, list}, {parts, 5}]))
+ TestMsg = "Test message" ++ eol(),
+ ?assertMatch([_, _, "[info]", PidStr, _, TestMsg], re:split(Msg, "[ @]", [{return, list}, {parts, 6}]))
+ after
+ 500 ->
+ ?assert(false)
+ end
+ end
+ },
+ {"custom format console logging",
+ fun() ->
+ Pid = spawn(F(self())),
+ unregister(user),
+ register(user, Pid),
+ erlang:group_leader(Pid, whereis(lager_event)),
+ gen_event:add_handler(lager_event, lager_console_backend,
+ [info, {lager_default_formatter, [date,"#",time,"#",severity,"#",node,"#",pid,"#",
+ module,"#",function,"#",file,"#",line,"#",message,"\r\n"]}]),
+ lager_config:set({lager_event, loglevel}, {?INFO, []}),
+ lager:info("Test message"),
+ PidStr = pid_to_list(self()),
+ NodeStr = atom_to_list(node()),
+ ModuleStr = atom_to_list(?MODULE),
+ receive
+ {io_request, _, _, {put_chars, unicode, Msg}} ->
+ TestMsg = "Test message" ++ eol(),
+ ?assertMatch([_, _, "info", NodeStr, PidStr, ModuleStr, _, _, _, TestMsg],
+ re:split(Msg, "#", [{return, list}, {parts, 10}]))
after
500 ->
?assert(false)
@@ -173,9 +248,11 @@ console_log_test_() ->
{"tracing should work",
fun() ->
Pid = spawn(F(self())),
+ unregister(user),
+ register(user, Pid),
gen_event:add_handler(lager_event, lager_console_backend, info),
erlang:group_leader(Pid, whereis(lager_event)),
- lager_mochiglobal:put(loglevel, {?INFO, []}),
+ lager_config:set({lager_event, loglevel}, {element(2, lager_util:config_to_mask(info)), []}),
lager:debug("Test message"),
receive
{io_request, From, ReplyAs, {put_chars, unicode, _Msg}} ->
@@ -190,7 +267,8 @@ console_log_test_() ->
receive
{io_request, From1, ReplyAs1, {put_chars, unicode, Msg1}} ->
From1 ! {io_reply, ReplyAs1, ok},
- ?assertMatch([_, "[debug]", "Test message\n"], re:split(Msg1, " ", [{return, list}, {parts, 3}]))
+ TestMsg = "Test message" ++ eol(),
+ ?assertMatch([_, "[debug]", TestMsg], re:split(Msg1, " ", [{return, list}, {parts, 3}]))
after
500 ->
?assert(false)
@@ -200,8 +278,10 @@ console_log_test_() ->
{"tracing doesn't duplicate messages",
fun() ->
Pid = spawn(F(self())),
+ unregister(user),
+ register(user, Pid),
gen_event:add_handler(lager_event, lager_console_backend, info),
- lager_mochiglobal:put(loglevel, {?INFO, []}),
+ lager_config:set({lager_event, loglevel}, {element(2, lager_util:config_to_mask(info)), []}),
erlang:group_leader(Pid, whereis(lager_event)),
lager:debug("Test message"),
receive
@@ -217,7 +297,8 @@ console_log_test_() ->
receive
{io_request, From1, ReplyAs1, {put_chars, unicode, Msg1}} ->
From1 ! {io_reply, ReplyAs1, ok},
- ?assertMatch([_, "[error]", "Test message\n"], re:split(Msg1, " ", [{return, list}, {parts, 3}]))
+ TestMsg = "Test message" ++ eol(),
+ ?assertMatch([_, "[error]", TestMsg], re:split(Msg1, " ", [{return, list}, {parts, 3}]))
after
1000 ->
?assert(false)
@@ -232,8 +313,69 @@ console_log_test_() ->
?assert(true)
end
end
+ },
+ {"blacklisting a loglevel works",
+ fun() ->
+ Pid = spawn(F(self())),
+ unregister(user),
+ register(user, Pid),
+ gen_event:add_handler(lager_event, lager_console_backend, info),
+ lager_config:set({lager_event, loglevel}, {element(2, lager_util:config_to_mask(info)), []}),
+ lager:set_loglevel(lager_console_backend, '!=info'),
+ erlang:group_leader(Pid, whereis(lager_event)),
+ lager:debug("Test message"),
+ receive
+ {io_request, From1, ReplyAs1, {put_chars, unicode, Msg1}} ->
+ From1 ! {io_reply, ReplyAs1, ok},
+ TestMsg = "Test message" ++ eol(),
+ ?assertMatch([_, "[debug]", TestMsg], re:split(Msg1, " ", [{return, list}, {parts, 3}]))
+ after
+ 1000 ->
+ ?assert(false)
+ end,
+ %% info is blacklisted
+ lager:info("Test message"),
+ receive
+ {io_request, From2, ReplyAs2, {put_chars, unicode, _Msg2}} ->
+ From2 ! {io_reply, ReplyAs2, ok},
+ ?assert(false)
+ after
+ 500 ->
+ ?assert(true)
+ end
+ end
+ },
+ {"whitelisting a loglevel works",
+ fun() ->
+ Pid = spawn(F(self())),
+ unregister(user),
+ register(user, Pid),
+ gen_event:add_handler(lager_event, lager_console_backend, info),
+ lager_config:set({lager_event, loglevel}, {element(2, lager_util:config_to_mask(info)), []}),
+ lager:set_loglevel(lager_console_backend, '=debug'),
+ erlang:group_leader(Pid, whereis(lager_event)),
+ lager:debug("Test message"),
+ receive
+ {io_request, From1, ReplyAs1, {put_chars, unicode, Msg1}} ->
+ From1 ! {io_reply, ReplyAs1, ok},
+ TestMsg = "Test message" ++ eol(),
+ ?assertMatch([_, "[debug]", TestMsg], re:split(Msg1, " ", [{return, list}, {parts, 3}]))
+ after
+ 1000 ->
+ ?assert(false)
+ end,
+ %% info is blacklisted
+ lager:error("Test message"),
+ receive
+ {io_request, From2, ReplyAs2, {put_chars, unicode, _Msg2}} ->
+ From2 ! {io_reply, ReplyAs2, ok},
+ ?assert(false)
+ after
+ 500 ->
+ ?assert(true)
+ end
+ end
}
-
]
}.
@@ -244,10 +386,11 @@ set_loglevel_test_() ->
application:load(lager),
application:set_env(lager, handlers, [{lager_console_backend, info}]),
application:set_env(lager, error_logger_redirect, false),
- application:start(lager)
+ lager:start()
end,
fun(_) ->
application:stop(lager),
+ application:stop(goldrush),
error_logger:tty(true)
end,
[
@@ -255,7 +398,12 @@ set_loglevel_test_() ->
fun() ->
?assertEqual(info, lager:get_loglevel(lager_console_backend)),
lager:set_loglevel(lager_console_backend, debug),
- ?assertEqual(debug, lager:get_loglevel(lager_console_backend))
+ ?assertEqual(debug, lager:get_loglevel(lager_console_backend)),
+ lager:set_loglevel(lager_console_backend, '!=debug'),
+ ?assertEqual(info, lager:get_loglevel(lager_console_backend)),
+ lager:set_loglevel(lager_console_backend, '!=info'),
+ ?assertEqual(debug, lager:get_loglevel(lager_console_backend)),
+ ok
end
},
{"Get/set invalid loglevel test",
diff --git a/deps/lager/src/lager_crash_log.erl b/deps/lager/src/lager_crash_log.erl
index e4aaa33..9908a59 100644
--- a/deps/lager/src/lager_crash_log.erl
+++ b/deps/lager/src/lager_crash_log.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
@@ -45,15 +45,15 @@
-export([start_link/5, start/5]).
-record(state, {
- name,
- fd,
- inode,
- fmtmaxbytes,
- size,
- date,
- count,
- flap=false
- }).
+ name :: string(),
+ fd :: pid(),
+ inode :: integer(),
+ fmtmaxbytes :: integer(),
+ size :: integer(),
+ date :: undefined | string(),
+ count :: integer(),
+ flap=false :: boolean()
+}).
%% @private
start_link(Filename, MaxBytes, Size, Date, Count) ->
@@ -66,7 +66,8 @@ start(Filename, MaxBytes, Size, Date, Count) ->
Date, Count], []).
%% @private
-init([Filename, MaxBytes, Size, Date, Count]) ->
+init([RelFilename, MaxBytes, Size, Date, Count]) ->
+ Filename = lager_util:expand_path(RelFilename),
case lager_util:open_logfile(Filename, false) of
{ok, {FD, Inode, _}} ->
schedule_rotation(Date),
@@ -95,7 +96,7 @@ handle_cast(_Request, State) ->
%% @private
handle_info(rotate, #state{name=Name, count=Count, date=Date} = State) ->
- lager_util:rotate_logfile(Name, Count),
+ _ = lager_util:rotate_logfile(Name, Count),
schedule_rotation(Date),
{noreply, State};
handle_info(_Info, State) ->
@@ -110,9 +111,10 @@ code_change(_OldVsn, State, _Extra) ->
{ok, State}.
schedule_rotation(undefined) ->
- undefined;
+ ok;
schedule_rotation(Date) ->
- erlang:send_after(lager_util:calculate_next_rotation(Date) * 1000, self(), rotate).
+ erlang:send_after(lager_util:calculate_next_rotation(Date) * 1000, self(), rotate),
+ ok.
%% ===== Begin code lifted from riak_err =====
@@ -187,7 +189,7 @@ do_log({log, Event}, #state{name=Name, fd=FD, inode=Inode, flap=Flap,
{error, _GL, {Pid1, Fmt, Args}} ->
{"ERROR REPORT", Pid1, limited_fmt(Fmt, Args, FmtMaxBytes), true};
{error_report, _GL, {Pid1, std_error, Rep}} ->
- {"ERROR REPORT", Pid1, limited_str(Rep, FmtMaxBytes), true};
+ {"ERROR REPORT", Pid1, limited_str(Rep, FmtMaxBytes) ++ "\n", true};
{error_report, _GL, Other} ->
perhaps_a_sasl_report(error_report, Other, FmtMaxBytes);
_ ->
@@ -198,7 +200,7 @@ do_log({log, Event}, #state{name=Name, fd=FD, inode=Inode, flap=Flap,
true ->
case lager_util:ensure_logfile(Name, FD, Inode, false) of
{ok, {_, _, Size}} when RotSize /= 0, Size > RotSize ->
- lager_util:rotate_logfile(Name, Count),
+ _ = lager_util:rotate_logfile(Name, Count),
handle_cast({log, Event}, State);
{ok, {NewFD, NewInode, _Size}} ->
{Date, TS} = lager_util:format_time(
@@ -206,7 +208,7 @@ do_log({log, Event}, #state{name=Name, fd=FD, inode=Inode, flap=Flap,
Time = [Date, " ", TS," =", ReportStr, "====\n"],
NodeSuffix = other_node_suffix(Pid),
Msg = io_lib:format("~s~s~s", [Time, MsgStr, NodeSuffix]),
- case file:write(NewFD, Msg) of
+ case file:write(NewFD, unicode:characters_to_binary(Msg)) of
{error, Reason} when Flap == false ->
?INT_LOG(error, "Failed to write log message to file ~s: ~s",
[Name, file:format_error(Reason)]),
@@ -240,9 +242,7 @@ filesystem_test_() ->
application:set_env(lager, handlers, [{lager_test_backend, info}]),
application:set_env(lager, error_logger_redirect, true),
application:unset_env(lager, crash_log),
- application:start(compiler),
- application:start(syntax_tools),
- application:start(lager),
+ lager:start(),
timer:sleep(100),
lager_test_backend:flush()
end,
@@ -254,6 +254,7 @@ filesystem_test_() ->
end,
file:delete("crash_test.log"),
application:stop(lager),
+ application:stop(goldrush),
error_logger:tty(true)
end,
[
@@ -272,7 +273,7 @@ filesystem_test_() ->
file:write_file_info("crash_test.log", FInfo#file_info{mode = 0}),
{ok, _} = ?MODULE:start_link("crash_test.log", 65535, 0, undefined, 0),
?assertEqual(1, lager_test_backend:count()),
- {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
+ {_Level, _Time, Message,_Metadata} = lager_test_backend:pop(),
?assertEqual("Failed to open crash log file crash_test.log with error: permission denied", lists:flatten(Message))
end
},
@@ -291,7 +292,7 @@ filesystem_test_() ->
_ = gen_event:which_handlers(error_logger),
?assertEqual(3, lager_test_backend:count()),
lager_test_backend:pop(),
- {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
+ {_Level, _Time, Message,_Metadata} = lager_test_backend:pop(),
?assertEqual("Failed to reopen crash log crash_test.log with error: permission denied", lists:flatten(Message))
end
},
@@ -302,7 +303,7 @@ filesystem_test_() ->
file:write_file_info("crash_test.log", FInfo#file_info{mode = 0}),
{ok, _} = ?MODULE:start_link("crash_test.log", 65535, 0, undefined, 0),
?assertEqual(1, lager_test_backend:count()),
- {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
+ {_Level, _Time, Message,_Metadata} = lager_test_backend:pop(),
?assertEqual("Failed to open crash log file crash_test.log with error: permission denied", lists:flatten(Message)),
file:write_file_info("crash_test.log", FInfo#file_info{mode = OldPerms}),
sync_error_logger:error_msg("Test message~n"),
diff --git a/deps/lager/src/lager_default_formatter.erl b/deps/lager/src/lager_default_formatter.erl
new file mode 100644
index 0000000..0a7166d
--- /dev/null
+++ b/deps/lager/src/lager_default_formatter.erl
@@ -0,0 +1,242 @@
+%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This file is provided to you 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(lager_default_formatter).
+
+%%
+%% Include files
+%%
+-include("lager.hrl").
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-endif.
+
+%%
+%% Exported Functions
+%%
+-export([format/2, format/3]).
+
+%%
+%% API Functions
+%%
+
+%% @doc Provides a generic, default formatting for log messages using a semi-iolist as configuration. Any iolist allowed
+%% elements in the configuration are printed verbatim. Atoms in the configuration are treated as metadata properties
+%% and extracted from the log message. Optionally, a tuple of {atom(),semi-iolist()} can be used. The atom will look
+%% up the property, but if not found it will use the semi-iolist() instead. These fallbacks can be similarly nested
+%% or refer to other properties, if desired. You can also use a {atom, semi-iolist(), semi-iolist()} formatter, which
+%% acts like a ternary operator's true/false branches.
+%%
+%% The metadata properties date,time, message, severity, and sev will always exist.
+%% The properties pid, file, line, module, and function will always exist if the parser transform is used.
+%%
+%% Example:
+%%
+%% `["Foo"]' -> "Foo", regardless of message content.
+%%
+%% `[message]' -> The content of the logged message, alone.
+%%
+%% `[{pid,"Unknown Pid"}]' -> "?.?.?" if pid is in the metadata, "Unknown Pid" if not.
+%%
+%% `[{pid, ["My pid is ", pid], ["Unknown Pid"]}]' -> if pid is in the metada print "My pid is ?.?.?", otherwise print "Unknown Pid"
+%% @end
+-spec format(lager_msg:lager_msg(),list(),list()) -> any().
+format(Msg,[], Colors) ->
+ format(Msg, [{eol, "\n"}], Colors);
+format(Msg,[{eol, EOL}], Colors) ->
+ format(Msg,
+ [date, " ", time, " ", color, "[", severity, "] ",
+ {pid, ""},
+ {module, [
+ {pid, ["@"], ""},
+ module,
+ {function, [":", function], ""},
+ {line, [":",line], ""}], ""},
+ " ", message, EOL], Colors);
+format(Message,Config,Colors) ->
+ [ case V of
+ color -> output_color(Message,Colors);
+ _ -> output(V,Message)
+ end || V <- Config ].
+
+-spec format(lager_msg:lager_msg(),list()) -> any().
+format(Msg, Config) ->
+ format(Msg, Config, []).
+
+-spec output(term(),lager_msg:lager_msg()) -> iolist().
+output(message,Msg) -> lager_msg:message(Msg);
+output(date,Msg) ->
+ {D, _T} = lager_msg:datetime(Msg),
+ D;
+output(time,Msg) ->
+ {_D, T} = lager_msg:datetime(Msg),
+ T;
+output(severity,Msg) ->
+ atom_to_list(lager_msg:severity(Msg));
+output(sev,Msg) ->
+ %% Write brief acronym for the severity level (e.g. debug -> $D)
+ [lager_util:level_to_chr(lager_msg:severity(Msg))];
+output(Prop,Msg) when is_atom(Prop) ->
+ Metadata = lager_msg:metadata(Msg),
+ make_printable(get_metadata(Prop,Metadata,<<"Undefined">>));
+output({Prop,Default},Msg) when is_atom(Prop) ->
+ Metadata = lager_msg:metadata(Msg),
+ make_printable(get_metadata(Prop,Metadata,output(Default,Msg)));
+output({Prop, Present, Absent}, Msg) when is_atom(Prop) ->
+ %% sort of like a poor man's ternary operator
+ Metadata = lager_msg:metadata(Msg),
+ case get_metadata(Prop, Metadata) of
+ undefined ->
+ [ output(V, Msg) || V <- Absent];
+ _ ->
+ [ output(V, Msg) || V <- Present]
+ end;
+output(Other,_) -> make_printable(Other).
+
+output_color(_Msg,[]) -> [];
+output_color(Msg,Colors) ->
+ Level = lager_msg:severity(Msg),
+ case lists:keyfind(Level, 1, Colors) of
+ {_, Color} -> Color;
+ _ -> []
+ end.
+
+-spec make_printable(any()) -> iolist().
+make_printable(A) when is_atom(A) -> atom_to_list(A);
+make_printable(P) when is_pid(P) -> pid_to_list(P);
+make_printable(L) when is_list(L) orelse is_binary(L) -> L;
+make_printable(Other) -> io_lib:format("~p",[Other]).
+
+get_metadata(Key, Metadata) ->
+ get_metadata(Key, Metadata, undefined).
+
+get_metadata(Key, Metadata, Default) ->
+ case lists:keyfind(Key, 1, Metadata) of
+ false ->
+ Default;
+ {Key, Value} ->
+ Value
+ end.
+
+-ifdef(TEST).
+date_time_now() ->
+ Now = os:timestamp(),
+ {Date, Time} = lager_util:format_time(lager_util:maybe_utc(lager_util:localtime_ms(Now))),
+ {Date, Time, Now}.
+
+basic_test_() ->
+ {Date, Time, Now} = date_time_now(),
+ [{"Default formatting test",
+ ?_assertEqual(iolist_to_binary([Date, " ", Time, " [error] ", pid_to_list(self()), " Message\n"]),
+ iolist_to_binary(format(lager_msg:new("Message",
+ Now,
+ error,
+ [{pid, self()}],
+ []),
+ [])))
+ },
+ {"Basic Formatting",
+ ?_assertEqual(<<"Simplist Format">>,
+ iolist_to_binary(format(lager_msg:new("Message",
+ Now,
+ error,
+ [{pid, self()}],
+ []),
+ ["Simplist Format"])))
+ },
+ {"Default equivalent formatting test",
+ ?_assertEqual(iolist_to_binary([Date, " ", Time, " [error] ", pid_to_list(self()), " Message\n"]),
+ iolist_to_binary(format(lager_msg:new("Message",
+ Now,
+ error,
+ [{pid, self()}],
+ []),
+ [date, " ", time," [",severity,"] ",pid, " ", message, "\n"]
+ )))
+ },
+ {"Non existant metadata can default to string",
+ ?_assertEqual(iolist_to_binary([Date, " ", Time, " [error] Fallback Message\n"]),
+ iolist_to_binary(format(lager_msg:new("Message",
+ Now,
+ error,
+ [{pid, self()}],
+ []),
+ [date, " ", time," [",severity,"] ",{does_not_exist,"Fallback"}, " ", message, "\n"]
+ )))
+ },
+ {"Non existant metadata can default to other metadata",
+ ?_assertEqual(iolist_to_binary([Date, " ", Time, " [error] Fallback Message\n"]),
+ iolist_to_binary(format(lager_msg:new("Message",
+ Now,
+ error,
+ [{pid, "Fallback"}],
+ []),
+ [date, " ", time," [",severity,"] ",{does_not_exist,pid}, " ", message, "\n"]
+ )))
+ },
+ {"Non existant metadata can default to a string2",
+ ?_assertEqual(iolist_to_binary(["Unknown Pid"]),
+ iolist_to_binary(format(lager_msg:new("Message",
+ Now,
+ error,
+ [],
+ []),
+ [{pid, ["My pid is ", pid], ["Unknown Pid"]}]
+ )))
+ },
+ {"Metadata can have extra formatting",
+ ?_assertEqual(iolist_to_binary(["My pid is hello"]),
+ iolist_to_binary(format(lager_msg:new("Message",
+ Now,
+ error,
+ [{pid, hello}],
+ []),
+ [{pid, ["My pid is ", pid], ["Unknown Pid"]}]
+ )))
+ },
+ {"Metadata can have extra formatting1",
+ ?_assertEqual(iolist_to_binary(["servername"]),
+ iolist_to_binary(format(lager_msg:new("Message",
+ Now,
+ error,
+ [{pid, hello}, {server, servername}],
+ []),
+ [{server,{pid, ["(", pid, ")"], ["(Unknown Server)"]}}]
+ )))
+ },
+ {"Metadata can have extra formatting2",
+ ?_assertEqual(iolist_to_binary(["(hello)"]),
+ iolist_to_binary(format(lager_msg:new("Message",
+ Now,
+ error,
+ [{pid, hello}],
+ []),
+ [{server,{pid, ["(", pid, ")"], ["(Unknown Server)"]}}]
+ )))
+ },
+ {"Metadata can have extra formatting3",
+ ?_assertEqual(iolist_to_binary(["(Unknown Server)"]),
+ iolist_to_binary(format(lager_msg:new("Message",
+ Now,
+ error,
+ [],
+ []),
+ [{server,{pid, ["(", pid, ")"], ["(Unknown Server)"]}}]
+ )))
+ }
+ ].
+
+-endif.
diff --git a/deps/lager/src/lager_file_backend.erl b/deps/lager/src/lager_file_backend.erl
index 1faceea..fd645f0 100644
--- a/deps/lager/src/lager_file_backend.erl
+++ b/deps/lager/src/lager_file_backend.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
@@ -17,14 +17,16 @@
%% @doc File backend for lager, with multiple file support.
%% Multiple files are supported, each with the path and the loglevel being
%% configurable. The configuration paramter for this backend is a list of
-%% 5-tuples of the form
-%% `{FileName, Level, RotationSize, RotationDate, RotationCount}'.
+%% key-value 2-tuples. See the init() function for the available options.
%% This backend supports external and internal log
%% rotation and will re-open handles to files if the inode changes. It will
%% also rotate the files itself if the size of the file exceeds the
-%% `RotationSize' and keep `RotationCount' rotated files. `RotationDate' is
+%% `size' and keep `count' rotated files. `date' is
%% an alternate rotation trigger, based on time. See the README for
%% documentation.
+%% For performance, the file backend does delayed writes, although it will
+%% sync at specific log levels, configured via the `sync_on' option. By default
+%% the error level or above will trigger a sync.
-module(lager_file_backend).
@@ -41,65 +43,141 @@
-export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2,
code_change/3]).
+-export([config_to_id/1]).
+
+-define(DEFAULT_LOG_LEVEL, info).
+-define(DEFAULT_ROTATION_SIZE, 10485760). %% 10mb
+-define(DEFAULT_ROTATION_DATE, "$D0"). %% midnight
+-define(DEFAULT_ROTATION_COUNT, 5).
+-define(DEFAULT_SYNC_LEVEL, error).
+-define(DEFAULT_SYNC_INTERVAL, 1000).
+-define(DEFAULT_SYNC_SIZE, 1024*64). %% 64kb
+-define(DEFAULT_CHECK_INTERVAL, 1000).
+
-record(state, {
name :: string(),
- level :: integer(),
+ level :: {'mask', integer()},
fd :: file:io_device(),
inode :: integer(),
flap=false :: boolean(),
size = 0 :: integer(),
- date,
- count = 10
+ date :: undefined | string(),
+ count = 10 :: integer(),
+ shaper :: lager_shaper(),
+ formatter :: atom(),
+ formatter_config :: any(),
+ sync_on :: {'mask', integer()},
+ check_interval = ?DEFAULT_CHECK_INTERVAL :: non_neg_integer(),
+ sync_interval = ?DEFAULT_SYNC_INTERVAL :: non_neg_integer(),
+ sync_size = ?DEFAULT_SYNC_SIZE :: non_neg_integer(),
+ last_check = os:timestamp() :: erlang:timestamp()
}).
-%% @private
--spec init([{string(), lager:log_level()},...]) -> {ok, #state{}}.
-init(LogFile) ->
- case validate_logfile(LogFile) of
- {Name, Level, Size, Date, Count} ->
+-type option() :: {file, string()} | {level, lager:log_level()} |
+ {size, non_neg_integer()} | {date, string()} |
+ {count, non_neg_integer()} | {high_water_mark, non_neg_integer()} |
+ {sync_interval, non_neg_integer()} |
+ {sync_size, non_neg_integer()} | {sync_on, lager:log_level()} |
+ {check_interval, non_neg_integer()} | {formatter, atom()} |
+ {formatter_config, term()}.
+
+-spec init([option(),...]) -> {ok, #state{}} | {error, bad_config}.
+init({FileName, LogLevel}) when is_list(FileName), is_atom(LogLevel) ->
+ %% backwards compatability hack
+ init([{file, FileName}, {level, LogLevel}]);
+init({FileName, LogLevel, Size, Date, Count}) when is_list(FileName), is_atom(LogLevel) ->
+ %% backwards compatability hack
+ init([{file, FileName}, {level, LogLevel}, {size, Size}, {date, Date}, {count, Count}]);
+init([{FileName, LogLevel, Size, Date, Count}, {Formatter,FormatterConfig}]) when is_list(FileName), is_atom(LogLevel), is_atom(Formatter) ->
+ %% backwards compatability hack
+ init([{file, FileName}, {level, LogLevel}, {size, Size}, {date, Date}, {count, Count}, {formatter, Formatter}, {formatter_config, FormatterConfig}]);
+init([LogFile,{Formatter}]) ->
+ %% backwards compatability hack
+ init([LogFile,{Formatter,[]}]);
+init([{FileName, LogLevel}, {Formatter,FormatterConfig}]) when is_list(FileName), is_atom(LogLevel), is_atom(Formatter) ->
+ %% backwards compatability hack
+ init([{file, FileName}, {level, LogLevel}, {formatter, Formatter}, {formatter_config, FormatterConfig}]);
+init(LogFileConfig) when is_list(LogFileConfig) ->
+ case validate_logfile_proplist(LogFileConfig) of
+ false ->
+ %% falied to validate config
+ {error, {fatal, bad_config}};
+ Config ->
+ %% probabably a better way to do this, but whatever
+ [RelName, Level, Date, Size, Count, HighWaterMark, SyncInterval, SyncSize, SyncOn, CheckInterval, Formatter, FormatterConfig] =
+ [proplists:get_value(Key, Config) || Key <- [file, level, date, size, count, high_water_mark, sync_interval, sync_size, sync_on, check_interval, formatter, formatter_config]],
+ Name = lager_util:expand_path(RelName),
schedule_rotation(Name, Date),
- State = case lager_util:open_logfile(Name, true) of
+ Shaper = #lager_shaper{hwm=HighWaterMark},
+ State0 = #state{name=Name, level=Level, size=Size, date=Date, count=Count, shaper=Shaper, formatter=Formatter,
+ formatter_config=FormatterConfig, sync_on=SyncOn, sync_interval=SyncInterval, sync_size=SyncSize,
+ check_interval=CheckInterval},
+ State = case lager_util:open_logfile(Name, {SyncSize, SyncInterval}) of
{ok, {FD, Inode, _}} ->
- #state{name=Name, level=lager_util:level_to_num(Level),
- fd=FD, inode=Inode, size=Size, date=Date, count=Count};
+ State0#state{fd=FD, inode=Inode};
{error, Reason} ->
- ?INT_LOG(error, "Failed to open log file ~s with error ~s",
- [Name, file:format_error(Reason)]),
- #state{name=Name, level=lager_util:level_to_num(Level),
- flap=true, size=Size, date=Date, count=Count}
+ ?INT_LOG(error, "Failed to open log file ~s with error ~s", [Name, file:format_error(Reason)]),
+ State0#state{flap=true}
end,
- {ok, State};
- false ->
- ignore
+ {ok, State}
end.
%% @private
handle_call({set_loglevel, Level}, #state{name=Ident} = State) ->
- ?INT_LOG(notice, "Changed loglevel of ~s to ~p", [Ident, Level]),
- {ok, ok, State#state{level=lager_util:level_to_num(Level)}};
+ case validate_loglevel(Level) of
+ false ->
+ {ok, {error, bad_loglevel}, State};
+ Levels ->
+ ?INT_LOG(notice, "Changed loglevel of ~s to ~p", [Ident, Level]),
+ {ok, ok, State#state{level=Levels}}
+ end;
handle_call(get_loglevel, #state{level=Level} = State) ->
{ok, Level, State};
+handle_call({set_loghwm, Hwm}, #state{shaper=Shaper, name=Name} = State) ->
+ case validate_logfile_proplist([{file, Name}, {high_water_mark, Hwm}]) of
+ false ->
+ {ok, {error, bad_log_hwm}, State};
+ _ ->
+ NewShaper = Shaper#lager_shaper{hwm=Hwm},
+ ?INT_LOG(notice, "Changed loghwm of ~s to ~p", [Name, Hwm]),
+ {ok, {last_loghwm, Shaper#lager_shaper.hwm}, State#state{shaper=NewShaper}}
+ end;
handle_call(_Request, State) ->
{ok, ok, State}.
%% @private
-handle_event({log, Dest, Level, {Date, Time}, Message},
- #state{name=Name, level=L} = State) when Level > L ->
- case lists:member({lager_file_backend, Name}, Dest) of
+handle_event({log, Message},
+ #state{name=Name, level=L, shaper=Shaper, formatter=Formatter,formatter_config=FormatConfig} = State) ->
+ case lager_util:is_loggable(Message,L,{lager_file_backend, Name}) of
true ->
- {ok, write(State, Level, [Date, " ", Time, " ", Message, "\n"])};
+ case lager_util:check_hwm(Shaper) of
+ {true, Drop, #lager_shaper{hwm=Hwm} = NewShaper} ->
+ NewState = case Drop > 0 of
+ true ->
+ Report = io_lib:format(
+ "lager_file_backend dropped ~p messages in the last second that exceeded the limit of ~p messages/sec",
+ [Drop, Hwm]),
+ ReportMsg = lager_msg:new(Report, warning, [], []),
+ write(State, lager_msg:timestamp(ReportMsg),
+ lager_msg:severity_as_int(ReportMsg), Formatter:format(ReportMsg, FormatConfig));
+ false ->
+ State
+ end,
+ {ok,write(NewState#state{shaper=NewShaper},
+ lager_msg:timestamp(Message), lager_msg:severity_as_int(Message),
+ Formatter:format(Message,FormatConfig))};
+ {false, _, NewShaper} ->
+ {ok, State#state{shaper=NewShaper}}
+ end;
false ->
{ok, State}
end;
-handle_event({log, Level, {Date, Time}, Message}, #state{level=L} = State) when Level =< L->
- NewState = write(State, Level, [Date, " ", Time, " ", Message, "\n"]),
- {ok, NewState};
handle_event(_Event, State) ->
{ok, State}.
%% @private
handle_info({rotate, File}, #state{name=File,count=Count,date=Date} = State) ->
- lager_util:rotate_logfile(File, Count),
+ _ = lager_util:rotate_logfile(File, Count),
schedule_rotation(File, Date),
{ok, State};
handle_info(_Info, State) ->
@@ -108,164 +186,382 @@ handle_info(_Info, State) ->
%% @private
terminate(_Reason, #state{fd=FD}) ->
%% flush and close any file handles
- file:datasync(FD),
- file:close(FD),
+ _ = file:datasync(FD),
+ _ = file:close(FD),
ok.
%% @private
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
+%% Convert the config into a gen_event handler ID
+config_to_id({Name,_Severity}) when is_list(Name) ->
+ {?MODULE, Name};
+config_to_id({Name,_Severity,_Size,_Rotation,_Count}) ->
+ {?MODULE, Name};
+config_to_id([{Name,_Severity,_Size,_Rotation,_Count}, _Format]) ->
+ {?MODULE, Name};
+config_to_id([{Name,_Severity}, _Format]) when is_list(Name) ->
+ {?MODULE, Name};
+config_to_id(Config) ->
+ case proplists:get_value(file, Config) of
+ undefined ->
+ erlang:error(no_file);
+ File ->
+ {?MODULE, File}
+ end.
+
+
write(#state{name=Name, fd=FD, inode=Inode, flap=Flap, size=RotSize,
- count=Count} = State, Level, Msg) ->
- case lager_util:ensure_logfile(Name, FD, Inode, true) of
- {ok, {_, _, Size}} when RotSize /= 0, Size > RotSize ->
- lager_util:rotate_logfile(Name, Count),
- write(State, Level, Msg);
- {ok, {NewFD, NewInode, _}} ->
- file:write(NewFD, Msg),
- case Level of
- _ when Level =< ?ERROR ->
- %% force a sync on any message at error severity or above
- Flap2 = case file:datasync(NewFD) of
- {error, Reason2} when Flap == false ->
- ?INT_LOG(error, "Failed to write log message to file ~s: ~s",
- [Name, file:format_error(Reason2)]),
- true;
+ count=Count} = State, Timestamp, Level, Msg) ->
+ LastCheck = timer:now_diff(Timestamp, State#state.last_check) div 1000,
+ case LastCheck >= State#state.check_interval orelse FD == undefined of
+ true ->
+ %% need to check for rotation
+ case lager_util:ensure_logfile(Name, FD, Inode, {State#state.sync_size, State#state.sync_interval}) of
+ {ok, {_, _, Size}} when RotSize /= 0, Size > RotSize ->
+ case lager_util:rotate_logfile(Name, Count) of
ok ->
- false;
+ %% go around the loop again, we'll do another rotation check and hit the next clause of ensure_logfile
+ write(State, Timestamp, Level, Msg);
+ {error, Reason} ->
+ case Flap of
+ true ->
+ State;
+ _ ->
+ ?INT_LOG(error, "Failed to rotate log file ~s with error ~s", [Name, file:format_error(Reason)]),
+ State#state{flap=true}
+ end
+ end;
+ {ok, {NewFD, NewInode, _}} ->
+ %% update our last check and try again
+ do_write(State#state{last_check=Timestamp, fd=NewFD, inode=NewInode}, Level, Msg);
+ {error, Reason} ->
+ case Flap of
+ true ->
+ State;
_ ->
- Flap
- end,
- State#state{fd=NewFD, inode=NewInode, flap=Flap2};
- _ ->
- State#state{fd=NewFD, inode=NewInode}
+ ?INT_LOG(error, "Failed to reopen log file ~s with error ~s", [Name, file:format_error(Reason)]),
+ State#state{flap=true}
+ end
end;
- {error, Reason} ->
- case Flap of
- true ->
- State;
- _ ->
- ?INT_LOG(error, "Failed to reopen log file ~s with error ~s",
- [Name, file:format_error(Reason)]),
- State#state{flap=true}
- end
+ false ->
+ do_write(State, Level, Msg)
end.
-validate_logfile({Name, Level}) ->
- case lists:member(Level, ?LEVELS) of
+do_write(#state{fd=FD, name=Name, flap=Flap} = State, Level, Msg) ->
+ %% delayed_write doesn't report errors
+ _ = file:write(FD, unicode:characters_to_binary(Msg)),
+ {mask, SyncLevel} = State#state.sync_on,
+ case (Level band SyncLevel) /= 0 of
true ->
- {Name, Level, 0, undefined, 0};
+ %% force a sync on any message that matches the 'sync_on' bitmask
+ Flap2 = case file:datasync(FD) of
+ {error, Reason2} when Flap == false ->
+ ?INT_LOG(error, "Failed to write log message to file ~s: ~s",
+ [Name, file:format_error(Reason2)]),
+ true;
+ ok ->
+ false;
+ _ ->
+ Flap
+ end,
+ State#state{flap=Flap2};
_ ->
- ?INT_LOG(error, "Invalid log level of ~p for ~s.",
- [Level, Name]),
+ State
+ end.
+
+validate_loglevel(Level) ->
+ try lager_util:config_to_mask(Level) of
+ Levels ->
+ Levels
+ catch
+ _:_ ->
false
- end;
-validate_logfile({Name, Level, Size, Date, Count}) ->
- ValidLevel = (lists:member(Level, ?LEVELS)),
- ValidSize = (is_integer(Size) andalso Size >= 0),
- ValidCount = (is_integer(Count) andalso Count >= 0),
- case {ValidLevel, ValidSize, ValidCount} of
- {false, _, _} ->
- ?INT_LOG(error, "Invalid log level of ~p for ~s.",
- [Level, Name]),
- false;
- {_, false, _} ->
- ?INT_LOG(error, "Invalid rotation size of ~p for ~s.",
- [Size, Name]),
- false;
- {_, _, false} ->
- ?INT_LOG(error, "Invalid rotation count of ~p for ~s.",
- [Count, Name]),
- false;
- {true, true, true} ->
- case lager_util:parse_rotation_date_spec(Date) of
- {ok, Spec} ->
- {Name, Level, Size, Spec, Count};
- {error, _} when Date == "" ->
- %% blank ones are fine.
- {Name, Level, Size, undefined, Count};
- {error, _} ->
- ?INT_LOG(error, "Invalid rotation date of ~p for ~s.",
- [Date, Name]),
- false
+ end.
+
+validate_logfile_proplist(List) ->
+ try validate_logfile_proplist(List, []) of
+ Res ->
+ case proplists:get_value(file, Res) of
+ undefined ->
+ ?INT_LOG(error, "Missing required file option", []),
+ false;
+ _File ->
+ %% merge with the default options
+ {ok, DefaultRotationDate} = lager_util:parse_rotation_date_spec(?DEFAULT_ROTATION_DATE),
+ lists:keymerge(1, lists:sort(Res), lists:sort([
+ {level, validate_loglevel(?DEFAULT_LOG_LEVEL)}, {date, DefaultRotationDate},
+ {size, ?DEFAULT_ROTATION_SIZE}, {count, ?DEFAULT_ROTATION_COUNT},
+ {sync_on, validate_loglevel(?DEFAULT_SYNC_LEVEL)}, {sync_interval, ?DEFAULT_SYNC_INTERVAL},
+ {sync_size, ?DEFAULT_SYNC_SIZE}, {check_interval, ?DEFAULT_CHECK_INTERVAL},
+ {formatter, lager_default_formatter}, {formatter_config, []}
+ ]))
end
+ catch
+ {bad_config, Msg, Value} ->
+ ?INT_LOG(error, "~s ~p for file ~p",
+ [Msg, Value, proplists:get_value(file, List)]),
+ false
+ end.
+
+validate_logfile_proplist([], Acc) ->
+ Acc;
+validate_logfile_proplist([{file, File}|Tail], Acc) ->
+ %% is there any reasonable validation we can do here?
+ validate_logfile_proplist(Tail, [{file, File}|Acc]);
+validate_logfile_proplist([{level, Level}|Tail], Acc) ->
+ case validate_loglevel(Level) of
+ false ->
+ throw({bad_config, "Invalid loglevel", Level});
+ Res ->
+ validate_logfile_proplist(Tail, [{level, Res}|Acc])
+ end;
+validate_logfile_proplist([{size, Size}|Tail], Acc) ->
+ case Size of
+ S when is_integer(S), S >= 0 ->
+ validate_logfile_proplist(Tail, [{size, Size}|Acc]);
+ _ ->
+ throw({bad_config, "Invalid rotation size", Size})
+ end;
+validate_logfile_proplist([{count, Count}|Tail], Acc) ->
+ case Count of
+ C when is_integer(C), C >= 0 ->
+ validate_logfile_proplist(Tail, [{count, Count}|Acc]);
+ _ ->
+ throw({bad_config, "Invalid rotation count", Count})
+ end;
+validate_logfile_proplist([{high_water_mark, HighWaterMark}|Tail], Acc) ->
+ case HighWaterMark of
+ Hwm when is_integer(Hwm), Hwm >= 0 ->
+ validate_logfile_proplist(Tail, [{high_water_mark, Hwm}|Acc]);
+ _ ->
+ throw({bad_config, "Invalid high water mark", HighWaterMark})
+ end;
+validate_logfile_proplist([{date, Date}|Tail], Acc) ->
+ case lager_util:parse_rotation_date_spec(Date) of
+ {ok, Spec} ->
+ validate_logfile_proplist(Tail, [{date, Spec}|Acc]);
+ {error, _} when Date == "" ->
+ %% legacy config allowed blanks
+ validate_logfile_proplist(Tail, [{date, undefined}|Acc]);
+ {error, _} ->
+ throw({bad_config, "Invalid rotation date", Date})
+ end;
+validate_logfile_proplist([{sync_interval, SyncInt}|Tail], Acc) ->
+ case SyncInt of
+ Val when is_integer(Val), Val >= 0 ->
+ validate_logfile_proplist(Tail, [{sync_interval, Val}|Acc]);
+ _ ->
+ throw({bad_config, "Invalid sync interval", SyncInt})
+ end;
+validate_logfile_proplist([{sync_size, SyncSize}|Tail], Acc) ->
+ case SyncSize of
+ Val when is_integer(Val), Val >= 0 ->
+ validate_logfile_proplist(Tail, [{sync_size, Val}|Acc]);
+ _ ->
+ throw({bad_config, "Invalid sync size", SyncSize})
+ end;
+validate_logfile_proplist([{check_interval, CheckInt}|Tail], Acc) ->
+ case CheckInt of
+ Val when is_integer(Val), Val >= 0 ->
+ validate_logfile_proplist(Tail, [{check_interval, Val}|Acc]);
+ always ->
+ validate_logfile_proplist(Tail, [{check_interval, 0}|Acc]);
+ _ ->
+ throw({bad_config, "Invalid check interval", CheckInt})
end;
-validate_logfile(H) ->
- ?INT_LOG(error, "Invalid log file config ~p.", [H]),
- false.
+validate_logfile_proplist([{sync_on, Level}|Tail], Acc) ->
+ case validate_loglevel(Level) of
+ false ->
+ throw({bad_config, "Invalid sync on level", Level});
+ Res ->
+ validate_logfile_proplist(Tail, [{sync_on, Res}|Acc])
+ end;
+validate_logfile_proplist([{formatter, Fmt}|Tail], Acc) ->
+ case is_atom(Fmt) of
+ true ->
+ validate_logfile_proplist(Tail, [{formatter, Fmt}|Acc]);
+ false ->
+ throw({bad_config, "Invalid formatter module", Fmt})
+ end;
+validate_logfile_proplist([{formatter_config, FmtCfg}|Tail], Acc) ->
+ case is_list(FmtCfg) of
+ true ->
+ validate_logfile_proplist(Tail, [{formatter_config, FmtCfg}|Acc]);
+ false ->
+ throw({bad_config, "Invalid formatter config", FmtCfg})
+ end;
+validate_logfile_proplist([Other|_Tail], _Acc) ->
+ throw({bad_config, "Invalid option", Other}).
schedule_rotation(_, undefined) ->
- undefined;
+ ok;
schedule_rotation(Name, Date) ->
- erlang:send_after(lager_util:calculate_next_rotation(Date) * 1000, self(), {rotate, Name}).
+ erlang:send_after(lager_util:calculate_next_rotation(Date) * 1000, self(), {rotate, Name}),
+ ok.
-ifdef(TEST).
get_loglevel_test() ->
{ok, Level, _} = handle_call(get_loglevel,
- #state{name="bar", level=lager_util:level_to_num(info), fd=0, inode=0}),
- ?assertEqual(Level, lager_util:level_to_num(info)),
+ #state{name="bar", level=lager_util:config_to_mask(info), fd=0, inode=0}),
+ ?assertEqual(Level, lager_util:config_to_mask(info)),
{ok, Level2, _} = handle_call(get_loglevel,
- #state{name="foo", level=lager_util:level_to_num(warning), fd=0, inode=0}),
- ?assertEqual(Level2, lager_util:level_to_num(warning)).
-
-rotation_test() ->
- {ok, {FD, Inode, _}} = lager_util:open_logfile("test.log", true),
- ?assertMatch(#state{name="test.log", level=?DEBUG, fd=FD, inode=Inode},
- write(#state{name="test.log", level=?DEBUG, fd=FD, inode=Inode}, 0, "hello world")),
- file:delete("test.log"),
- Result = write(#state{name="test.log", level=?DEBUG, fd=FD, inode=Inode}, 0, "hello world"),
- %% assert file has changed
- ?assert(#state{name="test.log", level=?DEBUG, fd=FD, inode=Inode} =/= Result),
- ?assertMatch(#state{name="test.log", level=?DEBUG}, Result),
- file:rename("test.log", "test.log.1"),
- Result2 = write(Result, 0, "hello world"),
- %% assert file has changed
- ?assert(Result =/= Result2),
- ?assertMatch(#state{name="test.log", level=?DEBUG}, Result2),
- ok.
+ #state{name="foo", level=lager_util:config_to_mask(warning), fd=0, inode=0}),
+ ?assertEqual(Level2, lager_util:config_to_mask(warning)).
+
+rotation_test_() ->
+ {foreach,
+ fun() ->
+ [file:delete(F)||F <- filelib:wildcard("test.log*")],
+ SyncLevel = validate_loglevel(?DEFAULT_SYNC_LEVEL),
+ SyncSize = ?DEFAULT_SYNC_SIZE,
+ SyncInterval = ?DEFAULT_SYNC_INTERVAL,
+ CheckInterval = 0, %% hard to test delayed mode
+
+ #state{name="test.log", level=?DEBUG, sync_on=SyncLevel,
+ sync_size=SyncSize, sync_interval=SyncInterval, check_interval=CheckInterval}
+
+ end,
+ fun(_) ->
+ [file:delete(F)||F <- filelib:wildcard("test.log*")]
+ end,
+ [fun(DefaultState = #state{sync_size=SyncSize, sync_interval = SyncInterval}) ->
+ {"External rotation should work",
+ fun() ->
+ {ok, {FD, Inode, _}} = lager_util:open_logfile("test.log", {SyncSize, SyncInterval}),
+ State0 = DefaultState#state{fd=FD, inode=Inode},
+ ?assertMatch(#state{name="test.log", level=?DEBUG, fd=FD, inode=Inode},
+ write(State0, os:timestamp(), ?DEBUG, "hello world")),
+ file:delete("test.log"),
+ Result = write(State0, os:timestamp(), ?DEBUG, "hello world"),
+ %% assert file has changed
+ ?assert(#state{name="test.log", level=?DEBUG, fd=FD, inode=Inode} =/= Result),
+ ?assertMatch(#state{name="test.log", level=?DEBUG}, Result),
+ file:rename("test.log", "test.log.1"),
+ Result2 = write(Result, os:timestamp(), ?DEBUG, "hello world"),
+ %% assert file has changed
+ ?assert(Result =/= Result2),
+ ?assertMatch(#state{name="test.log", level=?DEBUG}, Result2),
+ ok
+ end}
+ end,
+ fun(DefaultState = #state{sync_size=SyncSize, sync_interval = SyncInterval}) ->
+ {"Internal rotation and delayed write",
+ fun() ->
+ CheckInterval = 3000, % 3 sec
+ RotationSize = 15,
+ PreviousCheck = os:timestamp(),
+
+ {ok, {FD, Inode, _}} = lager_util:open_logfile("test.log", {SyncSize, SyncInterval}),
+ State0 = DefaultState#state{
+ fd=FD, inode=Inode, size=RotationSize,
+ check_interval=CheckInterval, last_check=PreviousCheck},
+
+ %% new message within check interval with sync_on level
+ Msg1Timestamp = add_secs(PreviousCheck, 1),
+ State0 = State1 = write(State0, Msg1Timestamp, ?ERROR, "big big message 1"),
+
+ %% new message within check interval under sync_on level
+ %% not written to disk yet
+ Msg2Timestamp = add_secs(PreviousCheck, 2),
+ State0 = State2 = write(State1, Msg2Timestamp, ?DEBUG, "buffered message 2"),
+
+ %% although file size is big enough...
+ {ok, FInfo} = file:read_file_info("test.log"),
+ ?assert(RotationSize < FInfo#file_info.size),
+ %% ...no rotation yet
+ ?assertEqual(PreviousCheck, State2#state.last_check),
+ ?assertNot(filelib:is_regular("test.log.0")),
+
+ %% new message after check interval
+ Msg3Timestamp = add_secs(PreviousCheck, 4),
+ _State3 = write(State2, Msg3Timestamp, ?DEBUG, "message 3"),
+
+ %% rotation happened
+ ?assert(filelib:is_regular("test.log.0")),
+
+ {ok, Bin1} = file:read_file("test.log.0"),
+ {ok, Bin2} = file:read_file("test.log"),
+ %% message 1-3 written to file
+ ?assertEqual(<<"big big message 1buffered message 2">>, Bin1),
+ %% message 4 buffered, not yet written to file
+ ?assertEqual(<<"">>, Bin2),
+ ok
+ end}
+ end
+ ]}.
+
+add_secs({Mega, Secs, Micro}, Add) ->
+ NewSecs = Secs + Add,
+ {Mega + NewSecs div 10000000, NewSecs rem 10000000, Micro}.
filesystem_test_() ->
{foreach,
fun() ->
file:write_file("test.log", ""),
+ file:delete("foo.log"),
+ file:delete("foo.log.0"),
+ file:delete("foo.log.1"),
error_logger:tty(false),
application:load(lager),
application:set_env(lager, handlers, [{lager_test_backend, info}]),
application:set_env(lager, error_logger_redirect, false),
- application:start(compiler),
- application:start(syntax_tools),
- application:start(lager)
+ application:set_env(lager, async_threshold, undefined),
+ lager:start()
end,
fun(_) ->
file:delete("test.log"),
+ file:delete("test.log.0"),
+ file:delete("test.log.1"),
application:stop(lager),
+ application:stop(goldrush),
error_logger:tty(true)
end,
[
{"under normal circumstances, file should be opened",
fun() ->
- gen_event:add_handler(lager_event, lager_file_backend, {"test.log", info}),
+ gen_event:add_handler(lager_event, lager_file_backend, [{"test.log", info}, {lager_default_formatter}]),
lager:log(error, self(), "Test message"),
{ok, Bin} = file:read_file("test.log"),
Pid = pid_to_list(self()),
?assertMatch([_, _, "[error]", Pid, "Test message\n"], re:split(Bin, " ", [{return, list}, {parts, 5}]))
end
},
+ {"don't choke on unicode",
+ fun() ->
+ gen_event:add_handler(lager_event, lager_file_backend, [{"test.log", info}, {lager_default_formatter}]),
+ lager:log(error, self(),"~ts", [[20013,25991,27979,35797]]),
+ {ok, Bin} = file:read_file("test.log"),
+ Pid = pid_to_list(self()),
+ ?assertMatch([_, _, "[error]", Pid, [228,184,173,230,150,135,230,181,139,232,175,149, $\n]], re:split(Bin, " ", [{return, list}, {parts, 5}]))
+ end
+ },
+ {"don't choke on latin-1",
+ fun() ->
+ %% XXX if this test fails, check that this file is encoded latin-1, not utf-8!
+ gen_event:add_handler(lager_event, lager_file_backend, [{"test.log", info}, {lager_default_formatter}]),
+ lager:log(error, self(),"~ts", [[76, 198, 221, 206, 78, $-, 239]]),
+ {ok, Bin} = file:read_file("test.log"),
+ Pid = pid_to_list(self()),
+ Res = re:split(Bin, " ", [{return, list}, {parts, 5}]),
+ ?assertMatch([_, _, "[error]", Pid, [76,195,134,195,157,195,142,78,45,195,175,$\n]], Res)
+ end
+ },
{"file can't be opened on startup triggers an error message",
fun() ->
{ok, FInfo} = file:read_file_info("test.log"),
file:write_file_info("test.log", FInfo#file_info{mode = 0}),
- gen_event:add_handler(lager_event, lager_file_backend, {"test.log", info}),
+ gen_event:add_handler(lager_event, lager_file_backend, {"test.log", info, 10*1024*1024, "$D0", 5}),
?assertEqual(1, lager_test_backend:count()),
- {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
+ {_Level, _Time,Message,_Metadata} = lager_test_backend:pop(),
?assertEqual("Failed to open log file test.log with error permission denied", lists:flatten(Message))
end
},
{"file that becomes unavailable at runtime should trigger an error message",
fun() ->
- gen_event:add_handler(lager_event, lager_file_backend, {"test.log", info}),
+ gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {check_interval, 0}]),
?assertEqual(0, lager_test_backend:count()),
lager:log(error, self(), "Test message"),
?assertEqual(1, lager_test_backend:count()),
@@ -277,7 +573,7 @@ filesystem_test_() ->
?assertEqual(3, lager_test_backend:count()),
lager_test_backend:pop(),
lager_test_backend:pop(),
- {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
+ {_Level, _Time, Message,_Metadata} = lager_test_backend:pop(),
?assertEqual("Failed to reopen log file test.log with error permission denied", lists:flatten(Message))
end
},
@@ -286,9 +582,9 @@ filesystem_test_() ->
{ok, FInfo} = file:read_file_info("test.log"),
OldPerms = FInfo#file_info.mode,
file:write_file_info("test.log", FInfo#file_info{mode = 0}),
- gen_event:add_handler(lager_event, lager_file_backend, {"test.log", info}),
+ gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"},{check_interval, 0}]),
?assertEqual(1, lager_test_backend:count()),
- {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
+ {_Level, _Time, Message,_Metadata} = lager_test_backend:pop(),
?assertEqual("Failed to open log file test.log with error permission denied", lists:flatten(Message)),
file:write_file_info("test.log", FInfo#file_info{mode = OldPerms}),
lager:log(error, self(), "Test message"),
@@ -299,7 +595,7 @@ filesystem_test_() ->
},
{"external logfile rotation/deletion should be handled",
fun() ->
- gen_event:add_handler(lager_event, lager_file_backend, {"test.log", info}),
+ gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {check_interval, 0}]),
?assertEqual(0, lager_test_backend:count()),
lager:log(error, self(), "Test message1"),
?assertEqual(1, lager_test_backend:count()),
@@ -315,6 +611,68 @@ filesystem_test_() ->
?assertMatch([_, _, "[error]", Pid, "Test message3\n"], re:split(Bin2, " ", [{return, list}, {parts, 5}]))
end
},
+ {"internal size rotation should work",
+ fun() ->
+ gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {check_interval, 0}, {size, 10}]),
+ lager:log(error, self(), "Test message1"),
+ lager:log(error, self(), "Test message1"),
+ ?assert(filelib:is_regular("test.log.0"))
+ end
+ },
+ {"internal time rotation should work",
+ fun() ->
+ gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {check_interval, 1000}]),
+ lager:log(error, self(), "Test message1"),
+ lager:log(error, self(), "Test message1"),
+ whereis(lager_event) ! {rotate, "test.log"},
+ lager:log(error, self(), "Test message1"),
+ ?assert(filelib:is_regular("test.log.0"))
+ end
+ },
+ {"sync_on option should work",
+ fun() ->
+ gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {sync_on, "=info"}, {check_interval, 5000}, {sync_interval, 5000}]),
+ lager:log(error, self(), "Test message1"),
+ lager:log(error, self(), "Test message1"),
+ ?assertEqual({ok, <<>>}, file:read_file("test.log")),
+ lager:log(info, self(), "Test message1"),
+ {ok, Bin} = file:read_file("test.log"),
+ ?assert(<<>> /= Bin)
+ end
+ },
+ {"sync_on none option should work (also tests sync_interval)",
+ fun() ->
+ gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {sync_on, "none"}, {check_interval, 5000}, {sync_interval, 1000}]),
+ lager:log(error, self(), "Test message1"),
+ lager:log(error, self(), "Test message1"),
+ ?assertEqual({ok, <<>>}, file:read_file("test.log")),
+ lager:log(info, self(), "Test message1"),
+ ?assertEqual({ok, <<>>}, file:read_file("test.log")),
+ timer:sleep(2000),
+ {ok, Bin} = file:read_file("test.log"),
+ ?assert(<<>> /= Bin)
+ end
+ },
+ {"sync_size option should work",
+ fun() ->
+ gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {sync_on, "none"}, {check_interval, 5001}, {sync_size, 640}, {sync_interval, 5001}]),
+ lager:log(error, self(), "Test messageis64bytes"),
+ lager:log(error, self(), "Test messageis64bytes"),
+ lager:log(error, self(), "Test messageis64bytes"),
+ lager:log(error, self(), "Test messageis64bytes"),
+ lager:log(error, self(), "Test messageis64bytes"),
+ ?assertEqual({ok, <<>>}, file:read_file("test.log")),
+ lager:log(error, self(), "Test messageis64bytes"),
+ lager:log(error, self(), "Test messageis64bytes"),
+ lager:log(error, self(), "Test messageis64bytes"),
+ lager:log(error, self(), "Test messageis64bytes"),
+ ?assertEqual({ok, <<>>}, file:read_file("test.log")),
+ %% now we've written enough bytes
+ lager:log(error, self(), "Test messageis64bytes"),
+ {ok, Bin} = file:read_file("test.log"),
+ ?assert(<<>> /= Bin)
+ end
+ },
{"runtime level changes",
fun() ->
gen_event:add_handler(lager_event, {lager_file_backend, "test.log"}, {"test.log", info}),
@@ -334,7 +692,7 @@ filesystem_test_() ->
},
{"invalid runtime level changes",
fun() ->
- gen_event:add_handler(lager_event, lager_file_backend, {"test.log", info}),
+ gen_event:add_handler(lager_event, lager_file_backend, [{"test.log", info, 10*1024*1024, "$D0", 5}, {lager_default_formatter}]),
gen_event:add_handler(lager_event, lager_file_backend, {"test3.log", info}),
?assertEqual({error, bad_module}, lager:set_loglevel(lager_file_backend, "test.log", warning))
end
@@ -345,11 +703,12 @@ filesystem_test_() ->
{"test.log", critical}),
lager:error("Test message"),
?assertEqual({ok, <<>>}, file:read_file("test.log")),
- {Level, _} = lager_mochiglobal:get(loglevel),
- lager_mochiglobal:put(loglevel, {Level, [{[{module,
+ {Level, _} = lager_config:get({lager_event, loglevel}),
+ lager_config:set({lager_event, loglevel}, {Level, [{[{module,
?MODULE}], ?DEBUG,
{lager_file_backend, "test.log"}}]}),
lager:error("Test message"),
+ timer:sleep(1000),
{ok, Bin} = file:read_file("test.log"),
?assertMatch([_, _, "[error]", _, "Test message\n"], re:split(Bin, " ", [{return, list}, {parts, 5}]))
end
@@ -357,13 +716,13 @@ filesystem_test_() ->
{"tracing should not duplicate messages",
fun() ->
gen_event:add_handler(lager_event, lager_file_backend,
- {"test.log", critical}),
+ [{file, "test.log"}, {level, critical}, {check_interval, always}]),
lager:critical("Test message"),
{ok, Bin1} = file:read_file("test.log"),
?assertMatch([_, _, "[critical]", _, "Test message\n"], re:split(Bin1, " ", [{return, list}, {parts, 5}])),
ok = file:delete("test.log"),
- {Level, _} = lager_mochiglobal:get(loglevel),
- lager_mochiglobal:put(loglevel, {Level, [{[{module,
+ {Level, _} = lager_config:get({lager_event, loglevel}),
+ lager_config:set({lager_event, loglevel}, {Level, [{[{module,
?MODULE}], ?DEBUG,
{lager_file_backend, "test.log"}}]}),
lager:critical("Test message"),
@@ -380,13 +739,133 @@ filesystem_test_() ->
file:delete("foo.log"),
{ok, _} = lager:trace_file("foo.log", [{module, ?MODULE}]),
lager:error("Test message"),
+ %% not elegible for trace
+ lager:log(error, self(), "Test message"),
{ok, Bin3} = file:read_file("foo.log"),
?assertMatch([_, _, "[error]", _, "Test message\n"], re:split(Bin3, " ", [{return, list}, {parts, 5}]))
end
+ },
+ {"tracing to a dedicated file should work even if root_log is set",
+ fun() ->
+ {ok, P} = file:get_cwd(),
+ file:delete(P ++ "/test_root_log/foo.log"),
+ application:set_env(lager, log_root, P++"/test_root_log"),
+ {ok, _} = lager:trace_file("foo.log", [{module, ?MODULE}]),
+ lager:error("Test message"),
+ %% not elegible for trace
+ lager:log(error, self(), "Test message"),
+ {ok, Bin3} = file:read_file(P++"/test_root_log/foo.log"),
+ application:unset_env(lager, log_root),
+ ?assertMatch([_, _, "[error]", _, "Test message\n"], re:split(Bin3, " ", [{return, list}, {parts, 5}]))
+ end
+ },
+ {"tracing with options should work",
+ fun() ->
+ file:delete("foo.log"),
+ {ok, _} = lager:trace_file("foo.log", [{module, ?MODULE}], [{size, 20}, {check_interval, 1}]),
+ lager:error("Test message"),
+ ?assertNot(filelib:is_regular("foo.log.0")),
+ %% rotation is sensitive to intervals between
+ %% writes so we sleep to exceed the 1
+ %% millisecond interval specified by
+ %% check_interval above
+ timer:sleep(2),
+ lager:error("Test message"),
+ timer:sleep(10),
+ ?assert(filelib:is_regular("foo.log.0"))
+ end
}
]
}.
+formatting_test_() ->
+ {foreach,
+ fun() ->
+ file:write_file("test.log", ""),
+ file:write_file("test2.log", ""),
+ error_logger:tty(false),
+ application:load(lager),
+ application:set_env(lager, handlers, [{lager_test_backend, info}]),
+ application:set_env(lager, error_logger_redirect, false),
+ lager:start()
+ end,
+ fun(_) ->
+ file:delete("test.log"),
+ file:delete("test2.log"),
+ application:stop(lager),
+ application:stop(goldrush),
+ error_logger:tty(true)
+ end,
+ [{"Should have two log files, the second prefixed with 2>",
+ fun() ->
+ gen_event:add_handler(lager_event, lager_file_backend,[{"test.log", debug},{lager_default_formatter,["[",severity,"] ", message, "\n"]}]),
+ gen_event:add_handler(lager_event, lager_file_backend,[{"test2.log", debug},{lager_default_formatter,["2> [",severity,"] ", message, "\n"]}]),
+ lager:log(error, self(), "Test message"),
+ ?assertMatch({ok, <<"[error] Test message\n">>},file:read_file("test.log")),
+ ?assertMatch({ok, <<"2> [error] Test message\n">>},file:read_file("test2.log"))
+ end
+ }
+ ]}.
+
+config_validation_test_() ->
+ [
+ {"missing file",
+ ?_assertEqual(false,
+ validate_logfile_proplist([{level, info},{size, 10}]))
+ },
+ {"bad level",
+ ?_assertEqual(false,
+ validate_logfile_proplist([{file, "test.log"}, {level, blah},{size, 10}]))
+ },
+ {"bad size",
+ ?_assertEqual(false,
+ validate_logfile_proplist([{file, "test.log"}, {size, infinity}]))
+ },
+ {"bad count",
+ ?_assertEqual(false,
+ validate_logfile_proplist([{file, "test.log"}, {count, infinity}]))
+ },
+ {"bad high water mark",
+ ?_assertEqual(false,
+ validate_logfile_proplist([{file, "test.log"}, {high_water_mark, infinity}]))
+ },
+ {"bad date",
+ ?_assertEqual(false,
+ validate_logfile_proplist([{file, "test.log"}, {date, "midnight"}]))
+ },
+ {"blank date is ok",
+ ?_assertMatch([_|_],
+ validate_logfile_proplist([{file, "test.log"}, {date, ""}]))
+ },
+ {"bad sync_interval",
+ ?_assertEqual(false,
+ validate_logfile_proplist([{file, "test.log"}, {sync_interval, infinity}]))
+ },
+ {"bad sync_size",
+ ?_assertEqual(false,
+ validate_logfile_proplist([{file, "test.log"}, {sync_size, infinity}]))
+ },
+ {"bad check_interval",
+ ?_assertEqual(false,
+ validate_logfile_proplist([{file, "test.log"}, {check_interval, infinity}]))
+ },
+ {"bad sync_on level",
+ ?_assertEqual(false,
+ validate_logfile_proplist([{file, "test.log"}, {sync_on, infinity}]))
+ },
+ {"bad formatter module",
+ ?_assertEqual(false,
+ validate_logfile_proplist([{file, "test.log"}, {formatter, "io:format"}]))
+ },
+ {"bad formatter config",
+ ?_assertEqual(false,
+ validate_logfile_proplist([{file, "test.log"}, {formatter_config, blah}]))
+ },
+ {"unknown option",
+ ?_assertEqual(false,
+ validate_logfile_proplist([{file, "test.log"}, {rhubarb, spicy}]))
+ }
+ ].
--endif.
+-endif.
diff --git a/deps/lager/src/lager_format.erl b/deps/lager/src/lager_format.erl
index 543ac07..d12ce3b 100644
--- a/deps/lager/src/lager_format.erl
+++ b/deps/lager/src/lager_format.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2011. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2011-2012. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -23,24 +23,37 @@
-export([format/3, format/4]).
-record(options, {
- chomp = false
+ chomp = false :: boolean()
}).
format(FmtStr, Args, MaxLen) ->
format(FmtStr, Args, MaxLen, []).
-format(FmtStr, Args, MaxLen, Opts) ->
- Options = make_options(Opts, #options{}),
- Cs = collect(FmtStr, Args),
- {Cs2, MaxLen2} = build(Cs, [], MaxLen, Options),
- %% count how many terms remain
- {Count, StrLen} = lists:foldl(
- fun({_C, _As, _F, _Adj, _P, _Pad, _Enc}, {Terms, Chars}) ->
- {Terms + 1, Chars};
- (_, {Terms, Chars}) ->
- {Terms, Chars + 1}
- end, {0, 0}, Cs2),
- build2(Cs2, Count, MaxLen2 - StrLen).
+format([], [], _, _) ->
+ "";
+format(FmtStr, Args, MaxLen, Opts) when is_atom(FmtStr) ->
+ format(atom_to_list(FmtStr), Args, MaxLen, Opts);
+format(FmtStr, Args, MaxLen, Opts) when is_binary(FmtStr) ->
+ format(binary_to_list(FmtStr), Args, MaxLen, Opts);
+format(FmtStr, Args, MaxLen, Opts) when is_list(FmtStr) ->
+ case lager_stdlib:string_p(FmtStr) of
+ true ->
+ Options = make_options(Opts, #options{}),
+ Cs = collect(FmtStr, Args),
+ {Cs2, MaxLen2} = build(Cs, [], MaxLen, Options),
+ %% count how many terms remain
+ {Count, StrLen} = lists:foldl(
+ fun({_C, _As, _F, _Adj, _P, _Pad, _Enc}, {Terms, Chars}) ->
+ {Terms + 1, Chars};
+ (_, {Terms, Chars}) ->
+ {Terms, Chars + 1}
+ end, {0, 0}, Cs2),
+ build2(Cs2, Count, MaxLen2 - StrLen);
+ false ->
+ erlang:error(badarg)
+ end;
+format(_FmtStr, _Args, _MaxLen, _Opts) ->
+ erlang:error(badarg).
collect([$~|Fmt0], Args0) ->
{C,Fmt1,Args1} = collect_cseq(Fmt0, Args0),
@@ -88,7 +101,7 @@ field_value(Fmt, Args) ->
field_value([C|Fmt], Args, F) when is_integer(C), C >= $0, C =< $9 ->
field_value(Fmt, Args, 10*F + (C - $0));
-field_value(Fmt, Args, F) -> %Default case
+field_value(Fmt, Args, F) -> %Default case
{F,Fmt,Args}.
pad_char([$.,$*|Fmt], [Pad|Args]) -> {Pad,Fmt,Args};
@@ -96,7 +109,7 @@ pad_char([$.,Pad|Fmt], Args) -> {Pad,Fmt,Args};
pad_char(Fmt, Args) -> {$\s,Fmt,Args}.
%% collect_cc([FormatChar], [Argument]) ->
-%% {Control,[ControlArg],[FormatChar],[Arg]}.
+%% {Control,[ControlArg],[FormatChar],[Arg]}.
%% Here we collect the argments for each control character.
%% Be explicit to cause failure early.
@@ -151,8 +164,7 @@ build2([C|Cs], Count, MaxLen) ->
build2([], _, _) -> [].
%% control(FormatChar, [Argument], FieldWidth, Adjust, Precision, PadChar,
-%% Indentation) ->
-%% [Char]
+%% Indentation) -> [Char]
%% This is the main dispatch function for the various formatting commands.
%% Field widths and precisions have already been calculated.
@@ -222,18 +234,16 @@ control2($w, [A], F, Adj, P, Pad, _Enc, L) ->
Term = lager_trunc_io:fprint(A, L, [{lists_as_strings, false}]),
Res = term(Term, F, Adj, P, Pad),
{Res, lists:flatlength(Res)};
-control2($p, [A], F, Adj, P, Pad, _Enc, L) ->
+control2($p, [A], _F, _Adj, _P, _Pad, _Enc, L) ->
Term = lager_trunc_io:fprint(A, L, [{lists_as_strings, true}]),
- Res = term(Term, F, Adj, P, Pad),
- {Res, lists:flatlength(Res)};
+ {Term, lists:flatlength(Term)};
control2($W, [A,Depth], F, Adj, P, Pad, _Enc, L) when is_integer(Depth) ->
Term = lager_trunc_io:fprint(A, L, [{depth, Depth}, {lists_as_strings, false}]),
Res = term(Term, F, Adj, P, Pad),
{Res, lists:flatlength(Res)};
-control2($P, [A,Depth], F, Adj, P, Pad, _Enc, L) when is_integer(Depth) ->
+control2($P, [A,Depth], _F, _Adj, _P, _Pad, _Enc, L) when is_integer(Depth) ->
Term = lager_trunc_io:fprint(A, L, [{depth, Depth}, {lists_as_strings, true}]),
- Res = term(Term, F, Adj, P, Pad),
- {Res, lists:flatlength(Res)};
+ {Term, lists:flatlength(Term)};
control2($s, [L0], F, Adj, P, Pad, latin1, L) ->
List = lager_trunc_io:fprint(maybe_flatten(L0), L, [{force_strings, true}]),
Res = string(List, F, Adj, P, Pad),
@@ -277,15 +287,15 @@ term(T, F, Adj, P0, Pad) ->
L = lists:flatlength(T),
P = case P0 of none -> erlang:min(L, F); _ -> P0 end,
if
- L > P ->
- adjust(chars($*, P), chars(Pad, F-P), Adj);
- F >= P ->
- adjust(T, chars(Pad, F-L), Adj)
+ L > P ->
+ adjust(chars($*, P), chars(Pad, F-P), Adj);
+ F >= P ->
+ adjust(T, chars(Pad, F-L), Adj)
end.
%% fwrite_e(Float, Field, Adjust, Precision, PadChar)
-fwrite_e(Fl, none, Adj, none, Pad) -> %Default values
+fwrite_e(Fl, none, Adj, none, Pad) -> %Default values
fwrite_e(Fl, none, Adj, 6, Pad);
fwrite_e(Fl, none, _Adj, P, _Pad) when P >= 2 ->
float_e(Fl, float_data(Fl), P);
@@ -294,12 +304,12 @@ fwrite_e(Fl, F, Adj, none, Pad) ->
fwrite_e(Fl, F, Adj, P, Pad) when P >= 2 ->
term(float_e(Fl, float_data(Fl), P), F, Adj, F, Pad).
-float_e(Fl, Fd, P) when Fl < 0.0 -> %Negative numbers
+float_e(Fl, Fd, P) when Fl < 0.0 -> %Negative numbers
[$-|float_e(-Fl, Fd, P)];
float_e(_Fl, {Ds,E}, P) ->
case float_man(Ds, 1, P-1) of
- {[$0|Fs],true} -> [[$1|Fs]|float_exp(E)];
- {Fs,false} -> [Fs|float_exp(E-1)]
+ {[$0|Fs],true} -> [[$1|Fs]|float_exp(E)];
+ {Fs,false} -> [Fs|float_exp(E-1)]
end.
%% float_man([Digit], Icount, Dcount) -> {[Chars],CarryFlag}.
@@ -312,22 +322,22 @@ float_man(Ds, 0, Dc) ->
{[$.|Cs],C};
float_man([D|Ds], I, Dc) ->
case float_man(Ds, I-1, Dc) of
- {Cs,true} when D =:= $9 -> {[$0|Cs],true};
- {Cs,true} -> {[D+1|Cs],false};
- {Cs,false} -> {[D|Cs],false}
+ {Cs,true} when D =:= $9 -> {[$0|Cs],true};
+ {Cs,true} -> {[D+1|Cs],false};
+ {Cs,false} -> {[D|Cs],false}
end;
-float_man([], I, Dc) -> %Pad with 0's
+float_man([], I, Dc) -> %Pad with 0's
{string:chars($0, I, [$.|string:chars($0, Dc)]),false}.
float_man([D|_], 0) when D >= $5 -> {[],true};
float_man([_|_], 0) -> {[],false};
float_man([D|Ds], Dc) ->
case float_man(Ds, Dc-1) of
- {Cs,true} when D =:= $9 -> {[$0|Cs],true};
- {Cs,true} -> {[D+1|Cs],false};
- {Cs,false} -> {[D|Cs],false}
+ {Cs,true} when D =:= $9 -> {[$0|Cs],true};
+ {Cs,true} -> {[D+1|Cs],false};
+ {Cs,false} -> {[D|Cs],false}
end;
-float_man([], Dc) -> {string:chars($0, Dc),false}. %Pad with 0's
+float_man([], Dc) -> {string:chars($0, Dc),false}. %Pad with 0's
%% float_exp(Exponent) -> [Char].
%% Generate the exponent of a floating point number. Always include sign.
@@ -339,7 +349,7 @@ float_exp(E) ->
%% fwrite_f(FloatData, Field, Adjust, Precision, PadChar)
-fwrite_f(Fl, none, Adj, none, Pad) -> %Default values
+fwrite_f(Fl, none, Adj, none, Pad) -> %Default values
fwrite_f(Fl, none, Adj, 6, Pad);
fwrite_f(Fl, none, _Adj, P, _Pad) when P >= 1 ->
float_f(Fl, float_data(Fl), P);
@@ -351,11 +361,11 @@ fwrite_f(Fl, F, Adj, P, Pad) when P >= 1 ->
float_f(Fl, Fd, P) when Fl < 0.0 ->
[$-|float_f(-Fl, Fd, P)];
float_f(Fl, {Ds,E}, P) when E =< 0 ->
- float_f(Fl, {string:chars($0, -E+1, Ds),1}, P); %Prepend enough 0's
+ float_f(Fl, {string:chars($0, -E+1, Ds),1}, P); %Prepend enough 0's
float_f(_Fl, {Ds,E}, P) ->
case float_man(Ds, E, P) of
- {Fs,true} -> "1" ++ Fs; %Handle carry
- {Fs,false} -> Fs
+ {Fs,true} -> "1" ++ Fs; %Handle carry
+ {Fs,false} -> Fs
end.
%% float_data([FloatChar]) -> {[Digit],Exponent}
@@ -380,20 +390,20 @@ fwrite_g(Fl, F, Adj, none, Pad) ->
fwrite_g(Fl, F, Adj, P, Pad) when P >= 1 ->
A = abs(Fl),
E = if A < 1.0e-1 -> -2;
- A < 1.0e0 -> -1;
- A < 1.0e1 -> 0;
- A < 1.0e2 -> 1;
- A < 1.0e3 -> 2;
- A < 1.0e4 -> 3;
- true -> fwrite_f
- end,
+ A < 1.0e0 -> -1;
+ A < 1.0e1 -> 0;
+ A < 1.0e2 -> 1;
+ A < 1.0e3 -> 2;
+ A < 1.0e4 -> 3;
+ true -> fwrite_f
+ end,
if P =< 1, E =:= -1;
- P-1 > E, E >= -1 ->
- fwrite_f(Fl, F, Adj, P-1-E, Pad);
- P =< 1 ->
- fwrite_e(Fl, F, Adj, 2, Pad);
- true ->
- fwrite_e(Fl, F, Adj, P, Pad)
+ P-1 > E, E >= -1 ->
+ fwrite_f(Fl, F, Adj, P-1-E, Pad);
+ P =< 1 ->
+ fwrite_e(Fl, F, Adj, 2, Pad);
+ true ->
+ fwrite_e(Fl, F, Adj, P, Pad)
end.
@@ -407,15 +417,15 @@ string(S, none, _Adj, P, Pad) ->
string(S, F, Adj, P, Pad) when F >= P ->
N = lists:flatlength(S),
if F > P ->
- if N > P ->
- adjust(flat_trunc(S, P), chars(Pad, F-P), Adj);
- N < P ->
- adjust([S|chars(Pad, P-N)], chars(Pad, F-P), Adj);
- true -> % N == P
- adjust(S, chars(Pad, F-P), Adj)
- end;
+ if N > P ->
+ adjust(flat_trunc(S, P), chars(Pad, F-P), Adj);
+ N < P ->
+ adjust([S|chars(Pad, P-N)], chars(Pad, F-P), Adj);
+ true -> % N == P
+ adjust(S, chars(Pad, F-P), Adj)
+ end;
true -> % F == P
- string_field(S, F, Adj, N, Pad)
+ string_field(S, F, Adj, N, Pad)
end.
string_field(S, F, _Adj, N, _Pad) when N > F ->
@@ -431,11 +441,11 @@ string_field(S, _, _, _, _) -> % N == F
unprefixed_integer(Int, F, Adj, Base, Pad, Lowercase)
when Base >= 2, Base =< 1+$Z-$A+10 ->
if Int < 0 ->
- S = cond_lowercase(erlang:integer_to_list(-Int, Base), Lowercase),
- term([$-|S], F, Adj, none, Pad);
+ S = cond_lowercase(erlang:integer_to_list(-Int, Base), Lowercase),
+ term([$-|S], F, Adj, none, Pad);
true ->
- S = cond_lowercase(erlang:integer_to_list(Int, Base), Lowercase),
- term(S, F, Adj, none, Pad)
+ S = cond_lowercase(erlang:integer_to_list(Int, Base), Lowercase),
+ term(S, F, Adj, none, Pad)
end.
%% prefixed_integer(Int, Field, Adjust, Base, PadChar, Prefix, Lowercase)
@@ -444,11 +454,11 @@ unprefixed_integer(Int, F, Adj, Base, Pad, Lowercase)
prefixed_integer(Int, F, Adj, Base, Pad, Prefix, Lowercase)
when Base >= 2, Base =< 1+$Z-$A+10 ->
if Int < 0 ->
- S = cond_lowercase(erlang:integer_to_list(-Int, Base), Lowercase),
- term([$-,Prefix|S], F, Adj, none, Pad);
+ S = cond_lowercase(erlang:integer_to_list(-Int, Base), Lowercase),
+ term([$-,Prefix|S], F, Adj, none, Pad);
true ->
- S = cond_lowercase(erlang:integer_to_list(Int, Base), Lowercase),
- term([Prefix|S], F, Adj, none, Pad)
+ S = cond_lowercase(erlang:integer_to_list(Int, Base), Lowercase),
+ term([Prefix|S], F, Adj, none, Pad)
end.
%% char(Char, Field, Adjust, Precision, PadChar) -> [Char].
@@ -473,19 +483,14 @@ adjust(Data, Pad, left) -> [Data|Pad];
adjust(Data, Pad, right) -> [Pad|Data].
%% Flatten and truncate a deep list to at most N elements.
-
flat_trunc(List, N) when is_integer(N), N >= 0 ->
- flat_trunc(List, N, [], []).
+ flat_trunc(List, N, []).
-flat_trunc(L, 0, _, R) when is_list(L) ->
+flat_trunc(L, 0, R) when is_list(L) ->
lists:reverse(R);
-flat_trunc([H|T], N, S, R) when is_list(H) ->
- flat_trunc(H, N, [T|S], R);
-flat_trunc([H|T], N, S, R) ->
- flat_trunc(T, N-1, S, [H|R]);
-flat_trunc([], N, [H|S], R) ->
- flat_trunc(H, N, S, R);
-flat_trunc([], _, [], R) ->
+flat_trunc([H|T], N, R) ->
+ flat_trunc(T, N-1, [H|R]);
+flat_trunc([], _, R) ->
lists:reverse(R).
%% A deep version of string:chars/2,3
diff --git a/deps/lager/src/lager_handler_watcher.erl b/deps/lager/src/lager_handler_watcher.erl
index 32a7185..4251db5 100644
--- a/deps/lager/src/lager_handler_watcher.erl
+++ b/deps/lager/src/lager_handler_watcher.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
@@ -23,6 +23,8 @@
-behaviour(gen_server).
+-include("lager.hrl").
+
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-endif.
@@ -34,20 +36,20 @@
-export([start_link/3, start/3]).
-record(state, {
- module,
- config,
- event
+ module :: atom(),
+ config :: any(),
+ sink :: pid() | atom()
}).
-start_link(Event, Module, Config) ->
- gen_server:start_link(?MODULE, [Event, Module, Config], []).
+start_link(Sink, Module, Config) ->
+ gen_server:start_link(?MODULE, [Sink, Module, Config], []).
-start(Event, Module, Config) ->
- gen_server:start(?MODULE, [Event, Module, Config], []).
+start(Sink, Module, Config) ->
+ gen_server:start(?MODULE, [Sink, Module, Config], []).
-init([Event, Module, Config]) ->
- install_handler(Event, Module, Config),
- {ok, #state{event=Event, module=Module, config=Config}}.
+init([Sink, Module, Config]) ->
+ install_handler(Sink, Module, Config),
+ {ok, #state{sink=Sink, module=Module, config=Config}}.
handle_call(_Call, _From, State) ->
{reply, ok, State}.
@@ -60,14 +62,21 @@ handle_info({gen_event_EXIT, Module, normal}, #state{module=Module} = State) ->
handle_info({gen_event_EXIT, Module, shutdown}, #state{module=Module} = State) ->
{stop, normal, State};
handle_info({gen_event_EXIT, Module, Reason}, #state{module=Module,
- config=Config, event=Event} = State) ->
- lager:log(error, self(), "Lager event handler ~p exited with reason ~s",
- [Module, error_logger_lager_h:format_reason(Reason)]),
- install_handler(Event, Module, Config),
+ config=Config, sink=Sink} = State) ->
+ case lager:log(error, self(), "Lager event handler ~p exited with reason ~s",
+ [Module, error_logger_lager_h:format_reason(Reason)]) of
+ ok ->
+ install_handler(Sink, Module, Config);
+ {error, _} ->
+ %% lager is not working, so installing a handler won't work
+ ok
+ end,
{noreply, State};
-handle_info(reinstall_handler, #state{module=Module, config=Config, event=Event} = State) ->
- install_handler(Event, Module, Config),
+handle_info(reinstall_handler, #state{module=Module, config=Config, sink=Sink} = State) ->
+ install_handler(Sink, Module, Config),
{noreply, State};
+handle_info(stop, State) ->
+ {stop, normal, State};
handle_info(_Info, State) ->
{noreply, State}.
@@ -78,17 +87,35 @@ code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% internal
-
-install_handler(Event, Module, Config) ->
- case gen_event:add_sup_handler(Event, Module, Config) of
+install_handler(Sink, lager_backend_throttle, Config) ->
+ %% The lager_backend_throttle needs to know to which sink it is
+ %% attached, hence this admittedly ugly workaround. Handlers are
+ %% sensitive to the structure of the configuration sent to `init',
+ %% sadly, so it's not trivial to add a configuration item to be
+ %% ignored to backends without breaking 3rd party handlers.
+ install_handler2(Sink, lager_backend_throttle, [{sink, Sink}|Config]);
+install_handler(Sink, Module, Config) ->
+ install_handler2(Sink, Module, Config).
+
+%% private
+install_handler2(Sink, Module, Config) ->
+ case gen_event:add_sup_handler(Sink, Module, Config) of
ok ->
- lager:log(debug, self(), "Lager installed handler ~p into ~p", [Module, Event]),
+ ?INT_LOG(debug, "Lager installed handler ~p into ~p", [Module, Sink]),
+ lager:update_loglevel_config(Sink),
+ ok;
+ {error, {fatal, Reason}} ->
+ ?INT_LOG(error, "Lager fatally failed to install handler ~p into"
+ " ~p, NOT retrying: ~p", [Module, Sink, Reason]),
+ %% tell ourselves to stop
+ self() ! stop,
ok;
Error ->
%% try to reinstall it later
- lager:log(error, self(), "Lager failed to install handler ~p into"
- " ~p, retrying later : ~p", [Module, Event, Error]),
- erlang:send_after(5000, self(), reinstall_handler)
+ ?INT_LOG(error, "Lager failed to install handler ~p into"
+ " ~p, retrying later : ~p", [Module, Sink, Error]),
+ erlang:send_after(5000, self(), reinstall_handler),
+ ok
end.
-ifdef(TEST).
@@ -106,12 +133,10 @@ reinstall_on_initial_failure_test_() ->
application:set_env(lager, handlers, [{lager_test_backend, info}, {lager_crash_backend, [from_now(2), undefined]}]),
application:set_env(lager, error_logger_redirect, false),
application:unset_env(lager, crash_log),
- application:start(compiler),
- application:start(syntax_tools),
- application:start(lager),
+ lager:start(),
try
?assertEqual(1, lager_test_backend:count()),
- {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
+ {_Level, _Time, Message, _Metadata} = lager_test_backend:pop(),
?assertMatch("Lager failed to install handler lager_crash_backend into lager_event, retrying later :"++_, lists:flatten(Message)),
?assertEqual(0, lager_test_backend:count()),
timer:sleep(6000),
@@ -119,6 +144,7 @@ reinstall_on_initial_failure_test_() ->
?assert(lists:member(lager_crash_backend, gen_event:which_handlers(lager_event)))
after
application:stop(lager),
+ application:stop(goldrush),
error_logger:tty(true)
end
end
@@ -134,21 +160,20 @@ reinstall_on_runtime_failure_test_() ->
application:set_env(lager, handlers, [{lager_test_backend, info}, {lager_crash_backend, [undefined, from_now(5)]}]),
application:set_env(lager, error_logger_redirect, false),
application:unset_env(lager, crash_log),
- application:start(compiler),
- application:start(syntax_tools),
- application:start(lager),
+ lager:start(),
try
?assertEqual(0, lager_test_backend:count()),
?assert(lists:member(lager_crash_backend, gen_event:which_handlers(lager_event))),
timer:sleep(6000),
?assertEqual(2, lager_test_backend:count()),
- {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
- ?assertEqual("Lager event handler lager_crash_backend exited with reason crash", lists:flatten(Message)),
- {_Level2, _Time2, [_, _, Message2]} = lager_test_backend:pop(),
- ?assertMatch("Lager failed to install handler lager_crash_backend into lager_event, retrying later :"++_, lists:flatten(Message2)),
+ {_Severity, _Date, Msg, _Metadata} = lager_test_backend:pop(),
+ ?assertEqual("Lager event handler lager_crash_backend exited with reason crash", lists:flatten(Msg)),
+ {_Severity2, _Date2, Msg2, _Metadata2} = lager_test_backend:pop(),
+ ?assertMatch("Lager failed to install handler lager_crash_backend into lager_event, retrying later :"++_, lists:flatten(Msg2)),
?assertEqual(false, lists:member(lager_crash_backend, gen_event:which_handlers(lager_event)))
after
application:stop(lager),
+ application:stop(goldrush),
error_logger:tty(true)
end
end
diff --git a/deps/lager/src/lager_handler_watcher_sup.erl b/deps/lager/src/lager_handler_watcher_sup.erl
index bea8a63..f3763aa 100644
--- a/deps/lager/src/lager_handler_watcher_sup.erl
+++ b/deps/lager/src/lager_handler_watcher_sup.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
@@ -35,5 +35,5 @@ init([]) ->
{ok, {{simple_one_for_one, 10, 60},
[
{lager_handler_watcher, {lager_handler_watcher, start_link, []},
- transient, 5000, worker, [lager_handler_watcher]}
+ temporary, 5000, worker, [lager_handler_watcher]}
]}}.
diff --git a/deps/lager/src/lager_mochiglobal.erl b/deps/lager/src/lager_mochiglobal.erl
deleted file mode 100644
index 5a92ce8..0000000
--- a/deps/lager/src/lager_mochiglobal.erl
+++ /dev/null
@@ -1,107 +0,0 @@
-%% @author Bob Ippolito <bob@mochimedia.com>
-%% @copyright 2010 Mochi Media, Inc.
-%% @doc Abuse module constant pools as a "read-only shared heap" (since erts 5.6)
-%% <a href="http://www.erlang.org/pipermail/erlang-questions/2009-March/042503.html">[1]</a>.
--module(lager_mochiglobal).
--author("Bob Ippolito <bob@mochimedia.com>").
--export([get/1, get/2, put/2, delete/1]).
-
--spec get(atom()) -> any() | undefined.
-%% @equiv get(K, undefined)
-get(K) ->
- get(K, undefined).
-
--spec get(atom(), T) -> any() | T.
-%% @doc Get the term for K or return Default.
-get(K, Default) ->
- get(K, Default, key_to_module(K)).
-
-get(_K, Default, Mod) ->
- try Mod:term()
- catch error:undef ->
- Default
- end.
-
--spec put(atom(), any()) -> ok.
-%% @doc Store term V at K, replaces an existing term if present.
-put(K, V) ->
- put(K, V, key_to_module(K)).
-
-put(_K, V, Mod) ->
- Bin = compile(Mod, V),
- code:purge(Mod),
- {module, Mod} = code:load_binary(Mod, atom_to_list(Mod) ++ ".erl", Bin),
- ok.
-
--spec delete(atom()) -> boolean().
-%% @doc Delete term stored at K, no-op if non-existent.
-delete(K) ->
- delete(K, key_to_module(K)).
-
-delete(_K, Mod) ->
- code:purge(Mod),
- code:delete(Mod).
-
--spec key_to_module(atom()) -> atom().
-key_to_module(K) ->
- list_to_atom("lager_mochiglobal:" ++ atom_to_list(K)).
-
--spec compile(atom(), any()) -> binary().
-compile(Module, T) ->
- {ok, Module, Bin} = compile:forms(forms(Module, T),
- [verbose, report_errors]),
- Bin.
-
--spec forms(atom(), any()) -> [erl_syntax:syntaxTree()].
-forms(Module, T) ->
- [erl_syntax:revert(X) || X <- term_to_abstract(Module, term, T)].
-
--spec term_to_abstract(atom(), atom(), any()) -> [erl_syntax:syntaxTree()].
-term_to_abstract(Module, Getter, T) ->
- [%% -module(Module).
- erl_syntax:attribute(
- erl_syntax:atom(module),
- [erl_syntax:atom(Module)]),
- %% -export([Getter/0]).
- erl_syntax:attribute(
- erl_syntax:atom(export),
- [erl_syntax:list(
- [erl_syntax:arity_qualifier(
- erl_syntax:atom(Getter),
- erl_syntax:integer(0))])]),
- %% Getter() -> T.
- erl_syntax:function(
- erl_syntax:atom(Getter),
- [erl_syntax:clause([], none, [erl_syntax:abstract(T)])])].
-
-%%
-%% Tests
-%%
--ifdef(TEST).
--include_lib("eunit/include/eunit.hrl").
-get_put_delete_test() ->
- K = '$$test$$mochiglobal',
- delete(K),
- ?assertEqual(
- bar,
- get(K, bar)),
- try
- ?MODULE:put(K, baz),
- ?assertEqual(
- baz,
- get(K, bar)),
- ?MODULE:put(K, wibble),
- ?assertEqual(
- wibble,
- ?MODULE:get(K))
- after
- delete(K)
- end,
- ?assertEqual(
- bar,
- get(K, bar)),
- ?assertEqual(
- undefined,
- ?MODULE:get(K)),
- ok.
--endif.
diff --git a/deps/lager/src/lager_msg.erl b/deps/lager/src/lager_msg.erl
new file mode 100644
index 0000000..6e69a8d
--- /dev/null
+++ b/deps/lager/src/lager_msg.erl
@@ -0,0 +1,64 @@
+-module(lager_msg).
+
+-export([new/4, new/5]).
+-export([message/1]).
+-export([timestamp/1]).
+-export([datetime/1]).
+-export([severity/1]).
+-export([severity_as_int/1]).
+-export([metadata/1]).
+-export([destinations/1]).
+
+-record(lager_msg,{
+ destinations :: list(),
+ metadata :: [tuple()],
+ severity :: lager:log_level(),
+ datetime :: {string(), string()},
+ timestamp :: erlang:timestamp(),
+ message :: list()
+ }).
+
+-opaque lager_msg() :: #lager_msg{}.
+-export_type([lager_msg/0]).
+
+%% create with provided timestamp, handy for testing mostly
+-spec new(list(), erlang:timestamp(), lager:log_level(), [tuple()], list()) -> lager_msg().
+new(Msg, Timestamp, Severity, Metadata, Destinations) ->
+ {Date, Time} = lager_util:format_time(lager_util:maybe_utc(lager_util:localtime_ms(Timestamp))),
+ #lager_msg{message=Msg, datetime={Date, Time}, timestamp=Timestamp, severity=Severity,
+ metadata=Metadata, destinations=Destinations}.
+
+-spec new(list(), lager:log_level(), [tuple()], list()) -> lager_msg().
+new(Msg, Severity, Metadata, Destinations) ->
+ Now = os:timestamp(),
+ new(Msg, Now, Severity, Metadata, Destinations).
+
+-spec message(lager_msg()) -> list().
+message(Msg) ->
+ Msg#lager_msg.message.
+
+-spec timestamp(lager_msg()) -> erlang:timestamp().
+timestamp(Msg) ->
+ Msg#lager_msg.timestamp.
+
+-spec datetime(lager_msg()) -> {string(), string()}.
+datetime(Msg) ->
+ Msg#lager_msg.datetime.
+
+-spec severity(lager_msg()) -> lager:log_level().
+severity(Msg) ->
+ Msg#lager_msg.severity.
+
+-spec severity_as_int(lager_msg()) -> lager:log_level_number().
+severity_as_int(Msg) ->
+ lager_util:level_to_num(Msg#lager_msg.severity).
+
+-spec metadata(lager_msg()) -> [tuple()].
+metadata(Msg) ->
+ Msg#lager_msg.metadata.
+
+-spec destinations(lager_msg()) -> list().
+destinations(Msg) ->
+ Msg#lager_msg.destinations.
+
+
diff --git a/deps/lager/src/lager_sup.erl b/deps/lager/src/lager_sup.erl
index 7c2b29b..b52fae6 100644
--- a/deps/lager/src/lager_sup.erl
+++ b/deps/lager/src/lager_sup.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
@@ -32,9 +32,16 @@ start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
+ %% set up the config, is safe even during relups
+ lager_config:new(),
+ %% TODO:
+ %% Always start lager_event as the default and make sure that
+ %% other gen_event stuff can start up as needed
+ %%
+ %% Maybe a new API to handle the sink and its policy?
Children = [
{lager, {gen_event, start_link, [{local, lager_event}]},
- permanent, 5000, worker, [dynamic]},
+ permanent, 5000, worker, dynamic},
{lager_handler_watcher_sup, {lager_handler_watcher_sup, start_link, []},
permanent, 5000, supervisor, [lager_handler_watcher_sup]}],
@@ -42,6 +49,8 @@ init([]) ->
Crash = case application:get_env(lager, crash_log) of
{ok, undefined} ->
[];
+ {ok, false} ->
+ [];
{ok, File} ->
MaxBytes = case application:get_env(lager, crash_log_msg_size) of
{ok, Val} when is_integer(Val) andalso Val > 0 -> Val;
diff --git a/deps/lager/src/lager_transform.erl b/deps/lager/src/lager_transform.erl
index 7cbe499..aa5e6d7 100644
--- a/deps/lager/src/lager_transform.erl
+++ b/deps/lager/src/lager_transform.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
@@ -18,7 +18,7 @@
%% This parse transform rewrites functions calls to lager:Severity/1,2 into
%% a more complicated function that captures module, function, line, pid and
%% time as well. The entire function call is then wrapped in a case that
-%% checks the mochiglobal 'loglevel' value, so the code isn't executed if
+%% checks the lager_config 'loglevel' value, so the code isn't executed if
%% nothing wishes to consume the message.
-module(lager_transform).
@@ -26,20 +26,27 @@
-include("lager.hrl").
-export([parse_transform/2]).
--export([format_error/1]).
%% @private
-parse_transform(AST, _Options) ->
- try walk_ast([], AST) of
- Forms -> Forms
- catch
- throw:E ->
- E
- end.
-
+parse_transform(AST, Options) ->
+ TruncSize = proplists:get_value(lager_truncation_size, Options, ?DEFAULT_TRUNCATION),
+ Enable = proplists:get_value(lager_print_records_flag, Options, true),
+ Sinks = [lager] ++ proplists:get_value(lager_extra_sinks, Options, []),
+ put(print_records_flag, Enable),
+ put(truncation_size, TruncSize),
+ put(sinks, Sinks),
+ erlang:put(records, []),
+ %% .app file should either be in the outdir, or the same dir as the source file
+ guess_application(proplists:get_value(outdir, Options), hd(AST)),
+ walk_ast([], AST).
walk_ast(Acc, []) ->
- lists:reverse(Acc);
+ case get(print_records_flag) of
+ true ->
+ insert_record_attribute(Acc);
+ false ->
+ lists:reverse(Acc)
+ end;
walk_ast(Acc, [{attribute, _, module, {Module, _PmodArgs}}=H|T]) ->
%% A wild parameterized module appears!
put(module, Module),
@@ -51,6 +58,14 @@ walk_ast(Acc, [{function, Line, Name, Arity, Clauses}|T]) ->
put(function, Name),
walk_ast([{function, Line, Name, Arity,
walk_clauses([], Clauses)}|Acc], T);
+walk_ast(Acc, [{attribute, _, record, {Name, Fields}}=H|T]) ->
+ FieldNames = lists:map(fun({record_field, _, {atom, _, FieldName}}) ->
+ FieldName;
+ ({record_field, _, {atom, _, FieldName}, _Default}) ->
+ FieldName
+ end, Fields),
+ stash_record({Name, FieldNames}),
+ walk_ast([H|Acc], T);
walk_ast(Acc, [H|T]) ->
walk_ast([H|Acc], T).
@@ -62,203 +77,230 @@ walk_clauses(Acc, [{clause, Line, Arguments, Guards, Body}|T]) ->
walk_body(Acc, []) ->
lists:reverse(Acc);
walk_body(Acc, [H|T]) ->
- walk_body([transform_statement(H)|Acc], T).
-%%
-%% lager:debug([{a,1},{b,2}], "Hello ~w", [world])
-%%
-%% generate:
-%% case lager_mochiglobal:get(loglevel, {- 1,[]}) of
-%% {_lager_transform_level3,[]}
-%% when _lager_transform_level3 < 7 ->
-%% ok;
-%% {_,_lager_transform_match4} ->
-%% lager:dispatch_log1(_lager_transform_match4,
-%% debug,
-%% ?MODULE,
-%% ?FUNCTION,
-%% ?LINE,
-%% self(),
-%% fun({a,1}) -> true;
-%% ({a,'*'}) -> true;
-%% ({b,2}) -> true;
-%% ({b,'*'}) -> true;
-%% ({module,?MODULE}) -> true;
-%% ({module,'*'}) -> true;
-%% ({function,?FUNCTION}) -> true;
-%% ({function,'*'}) -> true;
-%% ({line,?LINE}) -> true;
-%% ({line,'*'}) -> true;
-%% ({pid,'*'}) -> true;
-%% ({pid,PID}) ->
-%% PID =:= pid_to_list(self());
-%% (_) -> false
-%% end,
-%% "Hello ~w",
-%% [world])
-%% end
-%%
-transform_statement({call, L, {remote, L1, {atom, L2, lager},
- {atom, L3, Severity}}, Arguments0} = Stmt) ->
- case lists:member(Severity, ?LEVELS) of
+ walk_body([transform_statement(H, get(sinks))|Acc], T).
+
+transform_statement({call, Line, {remote, _Line1, {atom, _Line2, Module},
+ {atom, _Line3, Function}}, Arguments0} = Stmt,
+ Sinks) ->
+ case lists:member(Module, Sinks) of
true ->
- Trace0 = [trace_clause({atom,L,module},{atom,L,'*'}),
- trace_clause({atom,L,module},{atom,L,get(module)}),
- trace_clause({atom,L,function},{atom,L,'*'}),
- trace_clause({atom,L,function},{atom,L,get(function)}),
- trace_clause({atom,L,line},{atom,L,'*'}),
- trace_clause({atom,L,line},{integer,L,L}),
- trace_clause({atom,L,pid}, {atom,L,'*'}),
- trace_clause({atom,L,pid}, {atom,L,pid})
- ],
- {Trace1, Message, Arguments} =
- case Arguments0 of
- [Format] ->
- {Trace0, Format, {nil, L}};
- [Arg1, Arg2] ->
- %% some ambiguity here, figure out if these arguments are
- %% [Format, Args] or [Attr, Format].
- %% The trace attributes will be a list of tuples, so check
- %% for that.
- case Arg1 of
- {cons, _, {tuple, _, _}, _} ->
- {concat_clauses(L,Arg1,Trace0),
- Arg2, {nil,L}};
- _ ->
- {Trace0, Arg1, Arg2}
- end;
- [Attrs, Format, Args] ->
- {concat_clauses(L,Attrs,Trace0), Format, Args}
- end,
- Trace2 = lists:ukeysort(1, Trace1),
- Clauses = generate_clauses(L,Trace2) ++
- [{clause,L,[{var,L,'_'}],[],
- [{atom,L,false}]}],
- Traces = {'fun',L,{clauses,Clauses}},
- SeverityNum =
- case Severity of
- debug -> ?DEBUG;
- info -> ?INFO;
- notice -> ?NOTICE;
- warning -> ?WARNING;
- error -> ?ERROR;
- critical -> ?CRITICAL;
- alert -> ?ALERT;
- emergency -> ?EMERGENCY;
- none -> ?LOG_NONE
- end,
- LevelThreshold = new_var("level",L),
- Match = new_var("match",L),
- {block, L,
- [
- {'case',L,
- {call,L,
- {remote,L,{atom,L,lager_mochiglobal},{atom,L,get}},
- [{atom,L,loglevel},
- {tuple,L,[{op,L,'-',{integer,L,1}},{nil,L}]}]},
- [{clause,L,
- [{tuple,L,[LevelThreshold,{nil,L}]}],
- [[{op,L,'<',LevelThreshold,{integer,L,SeverityNum}}]],
- [{atom,L,ok}]},
- {clause,L,
- [{tuple,L,[{var,L,'_'},Match]}],
- [],
- [{call,L,
- {remote,L1,{atom,L,lager},
- {atom,L2,dispatch_log1}},
- [Match,
- {atom,L3,Severity},
- {atom,L3,get(module)},
- {atom,L3,get(function)},
- {integer,L3,L},
- {call, L3, {atom, L3 ,self}, []},
- Traces,
- Message,
- Arguments
- ]}]}]}
- ]};
- false ->
- Stmt
+ case lists:member(Function, ?LEVELS) of
+ true ->
+ SinkName = lager_util:make_internal_sink_name(Module),
+ do_transform(Line, SinkName, Function, Arguments0);
+ false ->
+ case lists:keyfind(Function, 1, ?LEVELS_UNSAFE) of
+ {Function, Severity} ->
+ SinkName = lager_util:make_internal_sink_name(Module),
+ do_transform(Line, SinkName, Severity, Arguments0, unsafe);
+ false ->
+ Stmt
+ end
+ end;
+ false ->
+ list_to_tuple(transform_statement(tuple_to_list(Stmt), Sinks))
end;
-transform_statement({call, L, {remote, L1, {atom, L2, boston_lager},
- {atom, L3, Severity}}, Arguments}) ->
- NewArgs =
- case Arguments of
- [{string, L, Msg}] ->
- [{string, L, re:replace(Msg, "r", "h",
- [{return, list}, global])}];
- [{string, L, Format}, Args] ->
- [{string, L, re:replace(Format, "r", "h",
- [{return, list}, global])}, Args];
- Other -> Other
- end,
- transform_statement({call, L, {remote, L1, {atom, L2, lager},
- {atom, L3, Severity}}, NewArgs});
-transform_statement(Stmt) when is_tuple(Stmt) ->
- list_to_tuple(transform_statement(tuple_to_list(Stmt)));
-transform_statement(Stmt) when is_list(Stmt) ->
- [transform_statement(S) || S <- Stmt];
-transform_statement(Stmt) ->
+transform_statement(Stmt, Sinks) when is_tuple(Stmt) ->
+ list_to_tuple(transform_statement(tuple_to_list(Stmt), Sinks));
+transform_statement(Stmt, Sinks) when is_list(Stmt) ->
+ [transform_statement(S, Sinks) || S <- Stmt];
+transform_statement(Stmt, _Sinks) ->
Stmt.
-%%
-%% Generate from key value pairs
-%% remove duplicates!
-%%
+do_transform(Line, SinkName, Severity, Arguments0) ->
+ do_transform(Line, SinkName, Severity, Arguments0, safe).
-generate_clauses(L,[{{pid,pid},{_K,_V}}|Cs]) ->
- make_pid_clause(L) ++ generate_clauses(L, Cs);
-generate_clauses(L,[{{_,_},{K,V}}|Cs]) ->
- make_clause(L,K,V) ++ generate_clauses(L, Cs);
-generate_clauses(_L, []) ->
- [].
+do_transform(Line, SinkName, Severity, Arguments0, Safety) ->
+ SeverityAsInt=lager_util:level_to_num(Severity),
+ DefaultAttrs0 = {cons, Line, {tuple, Line, [
+ {atom, Line, module}, {atom, Line, get(module)}]},
+ {cons, Line, {tuple, Line, [
+ {atom, Line, function}, {atom, Line, get(function)}]},
+ {cons, Line, {tuple, Line, [
+ {atom, Line, line},
+ {integer, Line, Line}]},
+ {cons, Line, {tuple, Line, [
+ {atom, Line, pid},
+ {call, Line, {atom, Line, pid_to_list}, [
+ {call, Line, {atom, Line ,self}, []}]}]},
+ {cons, Line, {tuple, Line, [
+ {atom, Line, node},
+ {call, Line, {atom, Line, node}, []}]},
+ %% get the metadata with lager:md(), this will always return a list so we can use it as the tail here
+ {call, Line, {remote, Line, {atom, Line, lager}, {atom, Line, md}}, []}}}}}},
+ %{nil, Line}}}}}}},
+ DefaultAttrs = case erlang:get(application) of
+ undefined ->
+ DefaultAttrs0;
+ App ->
+ %% stick the application in the attribute list
+ concat_lists({cons, Line, {tuple, Line, [
+ {atom, Line, application},
+ {atom, Line, App}]},
+ {nil, Line}}, DefaultAttrs0)
+ end,
+ {Meta, Message, Arguments} = case Arguments0 of
+ [Format] ->
+ {DefaultAttrs, Format, {atom, Line, none}};
+ [Arg1, Arg2] ->
+ %% some ambiguity here, figure out if these arguments are
+ %% [Format, Args] or [Attr, Format].
+ %% The trace attributes will be a list of tuples, so check
+ %% for that.
+ case {element(1, Arg1), Arg1} of
+ {_, {cons, _, {tuple, _, _}, _}} ->
+ {concat_lists(Arg1, DefaultAttrs),
+ Arg2, {atom, Line, none}};
+ {Type, _} when Type == var;
+ Type == lc;
+ Type == call;
+ Type == record_field ->
+ %% crap, its not a literal. look at the second
+ %% argument to see if it is a string
+ case Arg2 of
+ {string, _, _} ->
+ {concat_lists(Arg1, DefaultAttrs),
+ Arg2, {atom, Line, none}};
+ _ ->
+ %% not a string, going to have to guess
+ %% it's the argument list
+ {DefaultAttrs, Arg1, Arg2}
+ end;
+ _ ->
+ {DefaultAttrs, Arg1, Arg2}
+ end;
+ [Attrs, Format, Args] ->
+ {concat_lists(Attrs, DefaultAttrs), Format, Args}
+ end,
+ %% Generate some unique variable names so we don't accidentaly export from case clauses.
+ %% Note that these are not actual atoms, but the AST treats variable names as atoms.
+ LevelVar = make_varname("__Level", Line),
+ TracesVar = make_varname("__Traces", Line),
+ PidVar = make_varname("__Pid", Line),
+ LogFun = case Safety of
+ safe ->
+ do_log;
+ unsafe ->
+ do_log_unsafe
+ end,
+ %% Wrap the call to lager:dispatch_log/6 in case that will avoid doing any work if this message is not elegible for logging
+ %% See lager.erl (lines 89-100) for lager:dispatch_log/6
+ %% case {whereis(Sink), whereis(?DEFAULT_SINK), lager_config:get({Sink, loglevel}, {?LOG_NONE, []})} of
+ {'case',Line,
+ {tuple,Line,
+ [{call,Line,{atom,Line,whereis},[{atom,Line,SinkName}]},
+ {call,Line,{atom,Line,whereis},[{atom,Line,?DEFAULT_SINK}]},
+ {call,Line,
+ {remote,Line,{atom,Line,lager_config},{atom,Line,get}},
+ [{tuple,Line,[{atom,Line,SinkName},{atom,Line,loglevel}]},
+ {tuple,Line,[{integer,Line,0},{nil,Line}]}]}]},
+ %% {undefined, undefined, _} -> {error, lager_not_running};
+ [{clause,Line,
+ [{tuple,Line,
+ [{atom,Line,undefined},{atom,Line,undefined},{var,Line,'_'}]}],
+ [],
+ %% trick the linter into avoiding a 'term constructed but not used' error:
+ %% (fun() -> {error, lager_not_running} end)()
+ [{call, Line, {'fun', Line, {clauses, [{clause, Line, [],[], [{tuple, Line, [{atom, Line, error},{atom, Line, lager_not_running}]}]}]}}, []}]
+ },
+ %% {undefined, _, _} -> {error, {sink_not_configured, Sink}};
+ {clause,Line,
+ [{tuple,Line,
+ [{atom,Line,undefined},{var,Line,'_'},{var,Line,'_'}]}],
+ [],
+ %% same trick as above to avoid linter error
+ [{call, Line, {'fun', Line, {clauses, [{clause, Line, [],[], [{tuple,Line, [{atom,Line,error}, {tuple,Line,[{atom,Line,sink_not_configured},{atom,Line,SinkName}]}]}]}]}}, []}]
+ },
+ %% {SinkPid, _, {Level, Traces}} when ... -> lager:do_log/9;
+ {clause,Line,
+ [{tuple,Line,
+ [{var,Line,PidVar},
+ {var,Line,'_'},
+ {tuple,Line,[{var,Line,LevelVar},{var,Line,TracesVar}]}]}],
+ [[{op, Line, 'orelse',
+ {op, Line, '/=', {op, Line, 'band', {var, Line, LevelVar}, {integer, Line, SeverityAsInt}}, {integer, Line, 0}},
+ {op, Line, '/=', {var, Line, TracesVar}, {nil, Line}}}]],
+ [{call,Line,{remote, Line, {atom, Line, lager}, {atom, Line, LogFun}},
+ [{atom,Line,Severity},
+ Meta,
+ Message,
+ Arguments,
+ {integer, Line, get(truncation_size)},
+ {integer, Line, SeverityAsInt},
+ {var, Line, LevelVar},
+ {var, Line, TracesVar},
+ {atom, Line, SinkName},
+ {var, Line, PidVar}]}]},
+ %% _ -> ok
+ {clause,Line,[{var,Line,'_'}],[],[{atom,Line,ok}]}]}.
-%%
-%% create trace clause
-%% ({key, Value}) -> true;
-%%
-make_clause(L,K,V) ->
- [{clause,L,[{tuple,L,[K,V]}],[],[{atom,L,true}]}].
-
-%%
-%% create special pid clauses
-%% ({pid, PID}) -> PID =:= pid_to_list(self())
-%%
-make_pid_clause(L) ->
- PID = new_var("pid", L),
- K = {atom,L,pid},
- [{clause,L,[{tuple,L,[K,PID]}],[],
- [{op,L,'=:=', PID, {call,L,{atom,L,pid_to_list},
- [{call,L,{atom,L,self},[]}]}}]}].
+make_varname(Prefix, Line) ->
+ list_to_atom(Prefix ++ atom_to_list(get(module)) ++ integer_to_list(Line)).
-%%
-%% Concat clause from input
-%%
-concat_clauses(_L,{nil, _Line}, B) ->
+%% concat 2 list ASTs by replacing the terminating [] in A with the contents of B
+concat_lists({var, Line, _Name}=Var, B) ->
+ %% concatenating a var with a cons
+ {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
+ [{cons, Line, Var, B}]};
+concat_lists({lc, Line, _Body, _Generator} = LC, B) ->
+ %% concatenating a LC with a cons
+ {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
+ [{cons, Line, LC, B}]};
+concat_lists({call, Line, _Function, _Args} = Call, B) ->
+ %% concatenating a call with a cons
+ {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
+ [{cons, Line, Call, B}]};
+concat_lists({record_field, Line, _Var, _Record, _Field} = Rec, B) ->
+ %% concatenating a record_field with a cons
+ {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
+ [{cons, Line, Rec, B}]};
+concat_lists({nil, _Line}, B) ->
B;
-concat_clauses(L,{cons, _Line, {tuple,_L,[K,V]}, Tail}, B) ->
- [ trace_clause(K, {atom,L,'*'}),
- trace_clause(K,V) | concat_clauses(L,Tail, B)];
-concat_clauses(L, _, _) ->
- Err = {"*current*",[{L,?MODULE,"traces must be key value pairs"}]},
- throw({error,[Err],[]}).
-
-format_error(Str) ->
- io_lib:format("~s", [Str]).
+concat_lists({cons, Line, Element, Tail}, B) ->
+ {cons, Line, Element, concat_lists(Tail, B)}.
-trace_clause(K,V) ->
- Kn = erl_parse:normalise(K),
- Vn = erl_parse:normalise(V),
- {{Kn,Vn},{K,V}}.
+stash_record(Record) ->
+ Records = case erlang:get(records) of
+ undefined ->
+ [];
+ R ->
+ R
+ end,
+ erlang:put(records, [Record|Records]).
+insert_record_attribute(AST) ->
+ lists:foldl(fun({attribute, Line, module, _}=E, Acc) ->
+ [E, {attribute, Line, lager_records, erlang:get(records)}|Acc];
+ (E, Acc) ->
+ [E|Acc]
+ end, [], AST).
-new_var(Base,L) ->
- N = case get(lager_transform_var) of
- undefined ->
- 1;
- N0 ->
- N0
- end,
- put(lager_transform_var, N+1),
- Name = list_to_atom("_lager_transform_"++Base++integer_to_list(N)),
- {var, L, Name}.
+guess_application(Dirname, Attr) when Dirname /= undefined ->
+ case find_app_file(Dirname) of
+ no_idea ->
+ %% try it based on source file directory (app.src most likely)
+ guess_application(undefined, Attr);
+ _ ->
+ ok
+ end;
+guess_application(undefined, {attribute, _, file, {Filename, _}}) ->
+ Dir = filename:dirname(Filename),
+ find_app_file(Dir);
+guess_application(_, _) ->
+ ok.
+find_app_file(Dir) ->
+ case filelib:wildcard(Dir++"/*.{app,app.src}") of
+ [] ->
+ no_idea;
+ [File] ->
+ case file:consult(File) of
+ {ok, [{application, Appname, _Attributes}|_]} ->
+ erlang:put(application, Appname);
+ _ ->
+ no_idea
+ end;
+ _ ->
+ %% multiple files, uh oh
+ no_idea
+ end.
diff --git a/deps/lager/src/lager_trunc_io.erl b/deps/lager/src/lager_trunc_io.erl
index a9e8819..8763d49 100644
--- a/deps/lager/src/lager_trunc_io.erl
+++ b/deps/lager/src/lager_trunc_io.erl
@@ -3,12 +3,12 @@
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with your Erlang distribution. If not, it can be
%% retrieved via the world wide web at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% The Initial Developer of the Original Code is Corelatus AB.
%% Portions created by Corelatus are Copyright 2003, Corelatus
%% AB. All Rights Reserved.''
@@ -32,7 +32,7 @@
-module(lager_trunc_io).
-author('matthias@corelatus.se').
-%% And thanks to Chris Newcombe for a bug fix
+%% And thanks to Chris Newcombe for a bug fix
-export([format/3, format/4, print/2, print/3, fprint/2, fprint/3, safe/2]). % interface functions
-version("$Id: trunc_io.erl,v 1.11 2009-02-23 12:01:06 matthias Exp $").
@@ -41,6 +41,11 @@
-include_lib("eunit/include/eunit.hrl").
-endif.
+-type option() :: {'depth', integer()}
+ | {'lists_as_strings', boolean()}
+ | {'force_strings', boolean()}.
+-type options() :: [option()].
+
-record(print_options, {
%% negative depth means no depth limiting
depth = -1 :: integer(),
@@ -55,10 +60,9 @@ format(Fmt, Args, Max) ->
format(Fmt, Args, Max, []).
format(Fmt, Args, Max, Options) ->
- try lager_format:format(Fmt, Args, Max, Options) of
- Result -> Result
+ try lager_format:format(Fmt, Args, Max, Options)
catch
- _:_ ->
+ _What:_Why ->
erlang:error(badarg, [Fmt, Args])
end.
@@ -71,12 +75,12 @@ fprint(Term, Max) ->
%% @doc Returns an flattened list containing the ASCII representation of the given
%% term.
--spec fprint(term(), pos_integer(), #print_options{}) -> string().
-fprint(T, Max, Options) ->
+-spec fprint(term(), pos_integer(), options()) -> string().
+fprint(T, Max, Options) ->
{L, _} = print(T, Max, prepare_options(Options, #print_options{})),
lists:flatten(L).
-%% @doc Same as print, but never crashes.
+%% @doc Same as print, but never crashes.
%%
%% This is a tradeoff. Print might conceivably crash if it's asked to
%% print something it doesn't understand, for example some new data
@@ -84,13 +88,13 @@ fprint(T, Max, Options) ->
%% to io_lib to format the term, but then the formatting is
%% depth-limited instead of length limited, so you might run out
%% memory printing it. Out of the frying pan and into the fire.
-%%
+%%
-spec safe(term(), pos_integer()) -> {string(), pos_integer()} | {string()}.
safe(What, Len) ->
case catch print(What, Len) of
- {L, Used} when is_list(L) -> {L, Used};
- _ -> {"unable to print" ++ io_lib:write(What, 99)}
- end.
+ {L, Used} when is_list(L) -> {L, Used};
+ _ -> {"unable to print" ++ io_lib:write(What, 99)}
+ end.
%% @doc Returns {List, Length}
-spec print(term(), pos_integer()) -> {iolist(), pos_integer()}.
@@ -98,7 +102,7 @@ print(Term, Max) ->
print(Term, Max, []).
%% @doc Returns {List, Length}
--spec print(term(), pos_integer(), #print_options{}) -> {iolist(), pos_integer()}.
+-spec print(term(), pos_integer(), options() | #print_options{}) -> {iolist(), pos_integer()}.
print(Term, Max, Options) when is_list(Options) ->
%% need to convert the proplist to a record
print(Term, Max, prepare_options(Options, #print_options{}));
@@ -109,12 +113,9 @@ print(Term, _Max, #print_options{force_strings=true}) when not is_list(Term), no
print(_, Max, _Options) when Max < 0 -> {"...", 3};
print(_, _, #print_options{depth=0}) -> {"...", 3};
-print(Tuple, Max, Options) when is_tuple(Tuple) ->
- {TC, Len} = tuple_contents(Tuple, Max-2, Options),
- {[${, TC, $}], Len + 2};
-%% @doc We assume atoms, floats, funs, integers, PIDs, ports and refs never need
-%% to be truncated. This isn't strictly true, someone could make an
+%% @doc We assume atoms, floats, funs, integers, PIDs, ports and refs never need
+%% to be truncated. This isn't strictly true, someone could make an
%% arbitrarily long bignum. Let's assume that won't happen unless someone
%% is being malicious.
%%
@@ -126,26 +127,73 @@ print(Atom, _Max, #print_options{force_strings=NoQuote}) when is_atom(Atom) ->
end,
{R, length(R)};
-print(<<>>, _Max, _Options) ->
+print(<<>>, _Max, #print_options{depth=1}) ->
{"<<>>", 4};
+print(Bin, _Max, #print_options{depth=1}) when is_binary(Bin) ->
+ {"<<...>>", 7};
+print(<<>>, _Max, Options) ->
+ case Options#print_options.force_strings of
+ true ->
+ {"", 0};
+ false ->
+ {"<<>>", 4}
+ end;
print(Binary, 0, _Options) when is_bitstring(Binary) ->
{"<<..>>", 6};
+print(Bin, Max, _Options) when is_binary(Bin), Max < 2 ->
+ {"<<...>>", 7};
print(Binary, Max, Options) when is_binary(Binary) ->
B = binary_to_list(Binary, 1, lists:min([Max, byte_size(Binary)])),
- {L, Len} = case Options#print_options.lists_as_strings orelse
+ {Res, Length} = case Options#print_options.lists_as_strings orelse
Options#print_options.force_strings of
true ->
- alist_start(B, Max-4, Options);
+ Depth = Options#print_options.depth,
+ MaxSize = (Depth - 1) * 4,
+ %% check if we need to truncate based on depth
+ In = case Depth > -1 andalso MaxSize < length(B) andalso
+ not Options#print_options.force_strings of
+ true ->
+ string:substr(B, 1, MaxSize);
+ false -> B
+ end,
+ MaxLen = case Options#print_options.force_strings of
+ true ->
+ Max;
+ false ->
+ %% make room for the leading doublequote
+ Max - 1
+ end,
+ try alist(In, MaxLen, Options) of
+ {L0, Len0} ->
+ case Options#print_options.force_strings of
+ false ->
+ case B /= In of
+ true ->
+ {[$", L0, "..."], Len0+4};
+ false ->
+ {[$"|L0], Len0+1}
+ end;
+ true ->
+ {L0, Len0}
+ end
+ catch
+ throw:{unprintable, C} ->
+ Index = string:chr(In, C),
+ case Index > 1 andalso Options#print_options.depth =< Index andalso
+ Options#print_options.depth > -1 andalso
+ not Options#print_options.force_strings of
+ true ->
+ %% print first Index-1 characters followed by ...
+ {L0, Len0} = alist_start(string:substr(In, 1, Index - 1), Max - 1, Options),
+ {L0++"...", Len0+3};
+ false ->
+ list_body(In, Max-4, dec_depth(Options), true)
+ end
+ end;
_ ->
- list_body(B, Max-4, Options, false)
- end,
- {Res, Length} = case L of
- [91, X, 93] ->
- {X, Len - 2};
- X ->
- {X, Len}
+ list_body(B, Max-4, dec_depth(Options), true)
end,
case Options#print_options.force_strings of
true ->
@@ -159,14 +207,24 @@ print(Binary, Max, Options) when is_binary(Binary) ->
%% some magic for dealing with the output of bitstring_to_list, which returns
%% a list of integers (as expected) but with a trailing binary that represents
%% the remaining bits.
+print({inline_bitstring, B}, _Max, _Options) when is_bitstring(B) ->
+ Size = bit_size(B),
+ <<Value:Size>> = B,
+ ValueStr = integer_to_list(Value),
+ SizeStr = integer_to_list(Size),
+ {[ValueStr, $:, SizeStr], length(ValueStr) + length(SizeStr) +1};
print(BitString, Max, Options) when is_bitstring(BitString) ->
- case byte_size(BitString) > Max of
+ BL = case byte_size(BitString) > Max of
true ->
- BL = binary_to_list(BitString, 1, Max);
+ binary_to_list(BitString, 1, Max);
_ ->
- BL = erlang:bitstring_to_list(BitString)
+ R = erlang:bitstring_to_list(BitString),
+ {Bytes, [Bits]} = lists:splitwith(fun erlang:is_integer/1, R),
+ %% tag the trailing bits with a special tuple we catch when
+ %% list_body calls print again
+ Bytes ++ [{inline_bitstring, Bits}]
end,
- {X, Len0} = list_body(BL, Max - 4, Options, false),
+ {X, Len0} = list_body(BL, Max - 4, dec_depth(Options), true),
{["<<", X, ">>"], Len0 + 4};
print(Float, _Max, _Options) when is_float(Float) ->
@@ -202,6 +260,15 @@ print(Port, _Max, _Options) when is_port(Port) ->
L = erlang:port_to_list(Port),
{L, length(L)};
+print({'$lager_record', Name, Fields}, Max, Options) ->
+ Leader = "#" ++ atom_to_list(Name) ++ "{",
+ {RC, Len} = record_fields(Fields, Max - length(Leader) + 1, dec_depth(Options)),
+ {[Leader, RC, "}"], Len + length(Leader) + 1};
+
+print(Tuple, Max, Options) when is_tuple(Tuple) ->
+ {TC, Len} = tuple_contents(Tuple, Max-2, Options),
+ {[${, TC, $}], Len + 2};
+
print(List, Max, Options) when is_list(List) ->
case Options#print_options.lists_as_strings orelse
Options#print_options.force_strings of
@@ -210,6 +277,15 @@ print(List, Max, Options) when is_list(List) ->
_ ->
{R, Len} = list_body(List, Max - 2, dec_depth(Options), false),
{[$[, R, $]], Len + 2}
+ end;
+
+print(Map, Max, Options) ->
+ case erlang:is_builtin(erlang, is_map, 1) andalso erlang:is_map(Map) of
+ true ->
+ {MapBody, Len} = map_body(Map, Max - 3, dec_depth(Options)),
+ {[$#, ${, MapBody, $}], Len + 3};
+ false ->
+ error(badarg, [Map, Max, Options])
end.
%% Returns {List, Length}
@@ -222,13 +298,16 @@ tuple_contents(Tuple, Max, Options) ->
list_body([], _Max, _Options, _Tuple) -> {[], 0};
list_body(_, Max, _Options, _Tuple) when Max < 4 -> {"...", 3};
list_body(_, _Max, #print_options{depth=0}, _Tuple) -> {"...", 3};
-list_body([B], _Max, _Options, _Tuple) when is_bitstring(B), not is_binary(B) ->
- Size = bit_size(B),
- <<Value:Size>> = B,
- ValueStr = integer_to_list(Value),
- SizeStr = integer_to_list(Size),
- {[ValueStr, $:, SizeStr], length(ValueStr) + length(SizeStr) +1};
-list_body([H|T], Max, Options, Tuple) ->
+list_body([H], Max, Options=#print_options{depth=1}, _Tuple) ->
+ print(H, Max, Options);
+list_body([H|_], Max, Options=#print_options{depth=1}, Tuple) ->
+ {List, Len} = print(H, Max-4, Options),
+ Sep = case Tuple of
+ true -> $,;
+ false -> $|
+ end,
+ {[List ++ [Sep | "..."]], Len + 4};
+list_body([H|T], Max, Options, Tuple) ->
{List, Len} = print(H, Max, Options),
{Final, FLen} = list_bodyc(T, Max - Len, Options, Tuple),
{[List|Final], FLen + Len};
@@ -238,15 +317,11 @@ list_body(X, Max, Options, _Tuple) -> %% improper list
list_bodyc([], _Max, _Options, _Tuple) -> {[], 0};
list_bodyc(_, Max, _Options, _Tuple) when Max < 5 -> {",...", 4};
-list_bodyc([B], _Max, _Options, _Tuple) when is_bitstring(B), not is_binary(B) ->
- Size = bit_size(B),
- <<Value:Size>> = B,
- ValueStr = integer_to_list(Value),
- SizeStr = integer_to_list(Size),
- {[$, , ValueStr, $:, SizeStr], length(ValueStr) + length(SizeStr) +2};
-list_bodyc([H|T], Max, #print_options{depth=Depth} = Options, Tuple) ->
+list_bodyc(_, _Max, #print_options{depth=1}, true) -> {",...", 4};
+list_bodyc(_, _Max, #print_options{depth=1}, false) -> {"|...", 4};
+list_bodyc([H|T], Max, #print_options{depth=Depth} = Options, Tuple) ->
{List, Len} = print(H, Max, dec_depth(Options)),
- {Final, FLen} = list_bodyc(T, Max - Len - 1, Options, Tuple),
+ {Final, FLen} = list_bodyc(T, Max - Len - 1, dec_depth(Options), Tuple),
Sep = case Depth == 1 andalso not Tuple of
true -> $|;
_ -> $,
@@ -256,6 +331,36 @@ list_bodyc(X, Max, Options, _Tuple) -> %% improper list
{List, Len} = print(X, Max - 1, Options),
{[$|,List], Len + 1}.
+map_body(Map, Max, #print_options{depth=Depth}) when Max < 4; Depth =:= 0 ->
+ case erlang:map_size(Map) of
+ 0 -> {[], 0};
+ _ -> {"...", 3}
+ end;
+map_body(Map, Max, Options) ->
+ case maps:to_list(Map) of
+ [] ->
+ {[], 0};
+ [{Key, Value} | Rest] ->
+ {KeyStr, KeyLen} = print(Key, Max - 4, Options),
+ DiffLen = KeyLen + 4,
+ {ValueStr, ValueLen} = print(Value, Max - DiffLen, Options),
+ DiffLen2 = DiffLen + ValueLen,
+ {Final, FLen} = map_bodyc(Rest, Max - DiffLen2, dec_depth(Options)),
+ {[KeyStr, " => ", ValueStr | Final], DiffLen2 + FLen}
+ end.
+
+map_bodyc([], _Max, _Options) ->
+ {[], 0};
+map_bodyc(_Rest, Max,#print_options{depth=Depth}) when Max < 5; Depth =:= 0 ->
+ {",...", 4};
+map_bodyc([{Key, Value} | Rest], Max, Options) ->
+ {KeyStr, KeyLen} = print(Key, Max - 5, Options),
+ DiffLen = KeyLen + 5,
+ {ValueStr, ValueLen} = print(Value, Max - DiffLen, Options),
+ DiffLen2 = DiffLen + ValueLen,
+ {Final, FLen} = map_bodyc(Rest, Max - DiffLen2, dec_depth(Options)),
+ {[$,, KeyStr, " => ", ValueStr | Final], DiffLen2 + FLen}.
+
%% The head of a list we hope is ascii. Examples:
%%
%% [65,66,67] -> "ABC"
@@ -269,21 +374,31 @@ alist_start(_, Max, _Options) when Max < 4 -> {"...", 3};
alist_start(_, _Max, #print_options{depth=0}) -> {"[...]", 5};
alist_start(L, Max, #print_options{force_strings=true} = Options) ->
alist(L, Max, Options);
+%alist_start([H|_T], _Max, #print_options{depth=1}) when is_integer(H) -> {[$[, H, $|, $., $., $., $]], 7};
alist_start([H|T], Max, Options) when is_integer(H), H >= 16#20, H =< 16#7e -> % definitely printable
try alist([H|T], Max -1, Options) of
{L, Len} ->
{[$"|L], Len + 1}
catch
- throw:unprintable ->
+ throw:{unprintable, _} ->
{R, Len} = list_body([H|T], Max-2, Options, false),
{[$[, R, $]], Len + 2}
end;
-alist_start([H|T], Max, Options) when H =:= 9; H =:= 10; H =:= 13 ->
+alist_start([H|T], Max, Options) when is_integer(H), H >= 16#a0, H =< 16#ff -> % definitely printable
try alist([H|T], Max -1, Options) of
{L, Len} ->
{[$"|L], Len + 1}
catch
- throw:unprintable ->
+ throw:{unprintable, _} ->
+ {R, Len} = list_body([H|T], Max-2, Options, false),
+ {[$[, R, $]], Len + 2}
+ end;
+alist_start([H|T], Max, Options) when H =:= $\t; H =:= $\n; H =:= $\r; H =:= $\v; H =:= $\e; H=:= $\f; H=:= $\b ->
+ try alist([H|T], Max -1, Options) of
+ {L, Len} ->
+ {[$"|L], Len + 1}
+ catch
+ throw:{unprintable, _} ->
{R, Len} = list_body([H|T], Max-2, Options, false),
{[$[, R, $]], Len + 2}
end;
@@ -295,10 +410,17 @@ alist([], _Max, #print_options{force_strings=true}) -> {"", 0};
alist([], _Max, _Options) -> {"\"", 1};
alist(_, Max, #print_options{force_strings=true}) when Max < 4 -> {"...", 3};
alist(_, Max, #print_options{force_strings=false}) when Max < 5 -> {"...\"", 4};
+alist([H|T], Max, Options = #print_options{force_strings=false,lists_as_strings=true}) when H =:= $"; H =:= $\\ ->
+ %% preserve escaping around quotes
+ {L, Len} = alist(T, Max-1, Options),
+ {[$\\,H|L], Len + 2};
alist([H|T], Max, Options) when is_integer(H), H >= 16#20, H =< 16#7e -> % definitely printable
{L, Len} = alist(T, Max-1, Options),
{[H|L], Len + 1};
-alist([H|T], Max, Options) when H =:= 9; H =:= 10; H =:= 13 ->
+alist([H|T], Max, Options) when is_integer(H), H >= 16#a0, H =< 16#ff -> % definitely printable
+ {L, Len} = alist(T, Max-1, Options),
+ {[H|L], Len + 1};
+alist([H|T], Max, Options) when H =:= $\t; H =:= $\n; H =:= $\r; H =:= $\v; H =:= $\e; H=:= $\f; H=:= $\b ->
{L, Len} = alist(T, Max-1, Options),
case Options#print_options.force_strings of
true ->
@@ -309,10 +431,24 @@ alist([H|T], Max, Options) when H =:= 9; H =:= 10; H =:= 13 ->
alist([H|T], Max, #print_options{force_strings=true} = Options) when is_integer(H) ->
{L, Len} = alist(T, Max-1, Options),
{[H|L], Len + 1};
+alist([H|T], Max, Options = #print_options{force_strings=true}) when is_binary(H); is_list(H) ->
+ {List, Len} = print(H, Max, Options),
+ case (Max - Len) =< 0 of
+ true ->
+ %% no more room to print anything
+ {List, Len};
+ false ->
+ %% no need to decrement depth, as we're in printable string mode
+ {Final, FLen} = alist(T, Max - Len, Options),
+ {[List|Final], FLen+Len}
+ end;
alist(_, _, #print_options{force_strings=true}) ->
erlang:error(badarg);
-alist(_L, _Max, _Options) ->
- throw(unprintable).
+alist([H|_L], _Max, _Options) ->
+ throw({unprintable, H});
+alist(H, _Max, _Options) ->
+ %% improper list
+ throw({unprintable, H}).
%% is the first character in the atom alphabetic & lowercase?
atom_needs_quoting_start([H|T]) when H >= $a, H =< $z ->
@@ -330,6 +466,7 @@ atom_needs_quoting([H|T]) when (H >= $a andalso H =< $z);
atom_needs_quoting(_) ->
true.
+-spec prepare_options(options(), #print_options{}) -> #print_options{}.
prepare_options([], Options) ->
Options;
prepare_options([{depth, Depth}|T], Options) when is_integer(Depth) ->
@@ -344,9 +481,30 @@ dec_depth(#print_options{depth=Depth} = Options) when Depth > 0 ->
dec_depth(Options) ->
Options.
-escape(9) -> "\\t";
-escape(10) -> "\\n";
-escape(13) -> "\\r".
+escape($\t) -> "\\t";
+escape($\n) -> "\\n";
+escape($\r) -> "\\r";
+escape($\e) -> "\\e";
+escape($\f) -> "\\f";
+escape($\b) -> "\\b";
+escape($\v) -> "\\v".
+
+record_fields([], _, _) ->
+ {"", 0};
+record_fields(_, Max, #print_options{depth=D}) when Max < 4; D == 0 ->
+ {"...", 3};
+record_fields([{Field, Value}|T], Max, Options) ->
+ {ExtraChars, Terminator} = case T of
+ [] ->
+ {1, []};
+ _ ->
+ {2, ","}
+ end,
+ {FieldStr, FieldLen} = print(Field, Max - ExtraChars, Options),
+ {ValueStr, ValueLen} = print(Value, Max - (FieldLen + ExtraChars), Options),
+ {Final, FLen} = record_fields(T, Max - (FieldLen + ValueLen + ExtraChars), dec_depth(Options)),
+ {[FieldStr++"="++ValueStr++Terminator|Final], FLen + FieldLen + ValueLen + ExtraChars}.
+
-ifdef(TEST).
%%--------------------
@@ -358,29 +516,29 @@ test() ->
-spec test(atom(), atom()) -> ok.
test(Mod, Func) ->
Simple_items = [atom, 1234, 1234.0, {tuple}, [], [list], "string", self(),
- <<1,2,3>>, make_ref(), fun() -> ok end],
+ <<1,2,3>>, make_ref(), fun() -> ok end],
F = fun(A) ->
- Mod:Func(A, 100),
- Mod:Func(A, 2),
- Mod:Func(A, 20)
- end,
+ Mod:Func(A, 100),
+ Mod:Func(A, 2),
+ Mod:Func(A, 20)
+ end,
G = fun(A) ->
- case catch F(A) of
- {'EXIT', _} -> exit({failed, A});
- _ -> ok
- end
- end,
-
+ case catch F(A) of
+ {'EXIT', _} -> exit({failed, A});
+ _ -> ok
+ end
+ end,
+
lists:foreach(G, Simple_items),
-
+
Tuples = [ {1,2,3,a,b,c}, {"abc", def, 1234},
- {{{{a},b,c,{d},e}},f}],
-
+ {{{{a},b,c,{d},e}},f}],
+
Lists = [ [1,2,3,4,5,6,7], lists:seq(1,1000),
- [{a}, {a,b}, {a, [b,c]}, "def"], [a|b], [$a|$b] ],
-
-
+ [{a}, {a,b}, {a, [b,c]}, "def"], [a|b], [$a|$b] ],
+
+
lists:foreach(G, Tuples),
lists:foreach(G, Lists).
@@ -395,7 +553,7 @@ perf(M, F, Reps) when Reps > 0 ->
test(M,F),
perf(M,F,Reps-1);
perf(_,_,_) ->
- done.
+ done.
%% Performance test. Needs a particularly large term I saved as a binary...
-spec perf1() -> {non_neg_integer(), non_neg_integer()}.
@@ -412,13 +570,18 @@ format_test() ->
?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~p", [["foo", $b, $a, $r]], 50))),
?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~P", [["foo", $b, $a, $r], 10], 50))),
?assertEqual("[[102,111,111],98,97,114]", lists:flatten(format("~w", [["foo", $b, $a, $r]], 50))),
-
+
%% complex ones
?assertEqual(" foobar", lists:flatten(format("~10s", [["foo", $b, $a, $r]], 50))),
- ?assertEqual(" [\"foo\",98,97,114]", lists:flatten(format("~22p", [["foo", $b, $a, $r]], 50))),
- ?assertEqual(" [\"foo\",98,97,114]", lists:flatten(format("~22P", [["foo", $b, $a, $r], 10], 50))),
+ ?assertEqual("f", lists:flatten(format("~1s", [["foo", $b, $a, $r]], 50))),
+ ?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~22p", [["foo", $b, $a, $r]], 50))),
+ ?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~22P", [["foo", $b, $a, $r], 10], 50))),
?assertEqual("**********", lists:flatten(format("~10W", [["foo", $b, $a, $r], 10], 50))),
?assertEqual("[[102,111,111],98,97,114]", lists:flatten(format("~25W", [["foo", $b, $a, $r], 10], 50))),
+ % Note these next two diverge from io_lib:format; the field width is
+ % ignored, when it should be used as max line length.
+ ?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~10p", [["foo", $b, $a, $r]], 50))),
+ ?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~10P", [["foo", $b, $a, $r], 10], 50))),
ok.
atom_quoting_test() ->
@@ -455,6 +618,7 @@ quote_strip_test() ->
binary_printing_test() ->
?assertEqual("<<>>", lists:flatten(format("~p", [<<>>], 50))),
+ ?assertEqual("", lists:flatten(format("~s", [<<>>], 50))),
?assertEqual("<<..>>", lists:flatten(format("~p", [<<"hi">>], 0))),
?assertEqual("<<...>>", lists:flatten(format("~p", [<<"hi">>], 1))),
?assertEqual("<<\"hello\">>", lists:flatten(format("~p", [<<$h, $e, $l, $l, $o>>], 50))),
@@ -465,7 +629,21 @@ binary_printing_test() ->
?assertEqual("hello", lists:flatten(format("~s", [<<"hello">>], 50))),
?assertEqual("hello\nworld", lists:flatten(format("~s", [<<"hello\nworld">>], 50))),
?assertEqual("<<\"hello\\nworld\">>", lists:flatten(format("~p", [<<"hello\nworld">>], 50))),
+ ?assertEqual("<<\"\\\"hello world\\\"\">>", lists:flatten(format("~p", [<<"\"hello world\"">>], 50))),
+ ?assertEqual("<<\"hello\\\\world\">>", lists:flatten(format("~p", [<<"hello\\world">>], 50))),
+ ?assertEqual("<<\"hello\\\\\world\">>", lists:flatten(format("~p", [<<"hello\\\world">>], 50))),
+ ?assertEqual("<<\"hello\\\\\\\\world\">>", lists:flatten(format("~p", [<<"hello\\\\world">>], 50))),
+ ?assertEqual("<<\"hello\\bworld\">>", lists:flatten(format("~p", [<<"hello\bworld">>], 50))),
+ ?assertEqual("<<\"hello\\tworld\">>", lists:flatten(format("~p", [<<"hello\tworld">>], 50))),
+ ?assertEqual("<<\"hello\\nworld\">>", lists:flatten(format("~p", [<<"hello\nworld">>], 50))),
+ ?assertEqual("<<\"hello\\rworld\">>", lists:flatten(format("~p", [<<"hello\rworld">>], 50))),
+ ?assertEqual("<<\"hello\\eworld\">>", lists:flatten(format("~p", [<<"hello\eworld">>], 50))),
+ ?assertEqual("<<\"hello\\fworld\">>", lists:flatten(format("~p", [<<"hello\fworld">>], 50))),
+ ?assertEqual("<<\"hello\\vworld\">>", lists:flatten(format("~p", [<<"hello\vworld">>], 50))),
?assertEqual(" hello", lists:flatten(format("~10s", [<<"hello">>], 50))),
+ ?assertEqual("[a]", lists:flatten(format("~s", [<<"[a]">>], 50))),
+ ?assertEqual("[a]", lists:flatten(format("~s", [[<<"[a]">>]], 50))),
+
ok.
bitstring_printing_test() ->
@@ -483,6 +661,7 @@ bitstring_printing_test() ->
?assertEqual("<<...>>", lists:flatten(format("~p", [<<1:7>>], 1))),
?assertEqual("[<<1>>,<<2>>]", lists:flatten(format("~p", [[<<1>>, <<2>>]],
100))),
+ ?assertEqual("{<<1:7>>}", lists:flatten(format("~p", [{<<1:7>>}], 50))),
ok.
list_printing_test() ->
@@ -516,6 +695,24 @@ list_printing_test() ->
lists:flatten(format("~p", [
[22835963083295358096932575511191922182123945984,
22835963083295358096932575511191922182123945984]], 53))),
+ %%improper list
+ ?assertEqual("[1,2,3|4]", lists:flatten(format("~P", [[1|[2|[3|4]]], 5], 50))),
+ ?assertEqual("[1|1]", lists:flatten(format("~P", [[1|1], 5], 50))),
+ ?assertEqual("[9|9]", lists:flatten(format("~p", [[9|9]], 50))),
+ ok.
+
+iolist_printing_test() ->
+ ?assertEqual("iolist: HelloIamaniolist",
+ lists:flatten(format("iolist: ~s", [[$H, $e, $l, $l, $o, "I", ["am", [<<"an">>], [$i, $o, $l, $i, $s, $t]]]], 1000))),
+ ?assertEqual("123...",
+ lists:flatten(format("~s", [[<<"123456789">>, "HellIamaniolist"]], 6))),
+ ?assertEqual("123456...",
+ lists:flatten(format("~s", [[<<"123456789">>, "HellIamaniolist"]], 9))),
+ ?assertEqual("123456789H...",
+ lists:flatten(format("~s", [[<<"123456789">>, "HellIamaniolist"]], 13))),
+ ?assertEqual("123456789HellIamaniolist",
+ lists:flatten(format("~s", [[<<"123456789">>, "HellIamaniolist"]], 30))),
+
ok.
tuple_printing_test() ->
@@ -545,6 +742,46 @@ tuple_printing_test() ->
22835963083295358096932575511191922182123945984}], 53))),
ok.
+map_printing_test() ->
+ case erlang:is_builtin(erlang, is_map, 1) of
+ true ->
+ ?assertEqual("#{}", lists:flatten(format("~p", [maps:new()], 50))),
+ ?assertEqual("#{}", lists:flatten(format("~p", [maps:new()], 3))),
+ ?assertEqual("#{}", lists:flatten(format("~w", [maps:new()], 50))),
+ ?assertError(badarg, lists:flatten(format("~s", [maps:new()], 50))),
+ ?assertEqual("#{...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}])], 1))),
+ ?assertEqual("#{...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}])], 6))),
+ ?assertEqual("#{bar => ...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}])], 7))),
+ ?assertEqual("#{bar => ...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}])], 9))),
+ ?assertEqual("#{bar => foo}", lists:flatten(format("~p", [maps:from_list([{bar, foo}])], 10))),
+ ?assertEqual("#{bar => ...,...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 9))),
+ ?assertEqual("#{bar => foo,...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 10))),
+ ?assertEqual("#{bar => foo,...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 17))),
+ ?assertEqual("#{bar => foo,foo => ...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 18))),
+ ?assertEqual("#{bar => foo,foo => ...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 19))),
+ ?assertEqual("#{bar => foo,foo => ...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 20))),
+ ?assertEqual("#{bar => foo,foo => bar}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 21))),
+ ?assertEqual("#{22835963083295358096932575511191922182123945984 => ...}",
+ lists:flatten(format("~w", [
+ maps:from_list([{22835963083295358096932575511191922182123945984,
+ 22835963083295358096932575511191922182123945984}])], 10))),
+ ?assertEqual("#{22835963083295358096932575511191922182123945984 => ...}",
+ lists:flatten(format("~w", [
+ maps:from_list([{22835963083295358096932575511191922182123945984,
+ bar}])], 10))),
+ ?assertEqual("#{22835963083295358096932575511191922182123945984 => ...}",
+ lists:flatten(format("~w", [
+ maps:from_list([{22835963083295358096932575511191922182123945984,
+ bar}])], 53))),
+ ?assertEqual("#{22835963083295358096932575511191922182123945984 => bar}",
+ lists:flatten(format("~w", [
+ maps:from_list([{22835963083295358096932575511191922182123945984,
+ bar}])], 54))),
+ ok;
+ false ->
+ ok
+ end.
+
unicode_test() ->
?assertEqual([231,167,129], lists:flatten(format("~s", [<<231,167,129>>], 50))),
?assertEqual([31169], lists:flatten(format("~ts", [<<231,167,129>>], 50))),
@@ -568,9 +805,81 @@ depth_limit_test() ->
?assertEqual("{a,{b,{c,{...}}}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 7], 50))),
?assertEqual("{a,{b,{c,{d}}}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 8], 50))),
+ case erlang:is_builtin(erlang, is_map, 1) of
+ true ->
+ ?assertEqual("#{a => #{...}}",
+ lists:flatten(format("~P",
+ [maps:from_list([{a, maps:from_list([{b, maps:from_list([{c, d}])}])}]), 2], 50))),
+ ?assertEqual("#{a => #{b => #{...}}}",
+ lists:flatten(format("~P",
+ [maps:from_list([{a, maps:from_list([{b, maps:from_list([{c, d}])}])}]), 3], 50))),
+ ?assertEqual("#{a => #{b => #{c => d}}}",
+ lists:flatten(format("~P",
+ [maps:from_list([{a, maps:from_list([{b, maps:from_list([{c, d}])}])}]), 4], 50))),
+
+ ?assertEqual("#{}", lists:flatten(format("~P", [maps:new(), 1], 50))),
+ ?assertEqual("#{...}", lists:flatten(format("~P", [maps:from_list([{1,1}, {2,2}, {3,3}]), 1], 50))),
+ ?assertEqual("#{1 => 1,...}", lists:flatten(format("~P", [maps:from_list([{1,1}, {2,2}, {3,3}]), 2], 50))),
+ ?assertEqual("#{1 => 1,2 => 2,...}", lists:flatten(format("~P", [maps:from_list([{1,1}, {2,2}, {3,3}]), 3], 50))),
+ ?assertEqual("#{1 => 1,2 => 2,3 => 3}", lists:flatten(format("~P", [maps:from_list([{1,1}, {2,2}, {3,3}]), 4], 50))),
+
+ ok;
+ false ->
+ ok
+ end,
+
?assertEqual("{\"a\",[...]}", lists:flatten(format("~P", [{"a", ["b", ["c", ["d"]]]}, 3], 50))),
?assertEqual("{\"a\",[\"b\",[[...]|...]]}", lists:flatten(format("~P", [{"a", ["b", ["c", ["d"]]]}, 6], 50))),
?assertEqual("{\"a\",[\"b\",[\"c\",[\"d\"]]]}", lists:flatten(format("~P", [{"a", ["b", ["c", ["d"]]]}, 9], 50))),
+
+ ?assertEqual("[...]", lists:flatten(format("~P", [[1, 2, 3], 1], 50))),
+ ?assertEqual("[1|...]", lists:flatten(format("~P", [[1, 2, 3], 2], 50))),
+ ?assertEqual("[1,2|...]", lists:flatten(format("~P", [[1, 2, 3], 3], 50))),
+ ?assertEqual("[1,2,3]", lists:flatten(format("~P", [[1, 2, 3], 4], 50))),
+
+ ?assertEqual("{1,...}", lists:flatten(format("~P", [{1, 2, 3}, 2], 50))),
+ ?assertEqual("{1,2,...}", lists:flatten(format("~P", [{1, 2, 3}, 3], 50))),
+ ?assertEqual("{1,2,3}", lists:flatten(format("~P", [{1, 2, 3}, 4], 50))),
+
+ ?assertEqual("{1,...}", lists:flatten(format("~P", [{1, 2, 3}, 2], 50))),
+ ?assertEqual("[1,2|...]", lists:flatten(format("~P", [[1, 2, <<3>>], 3], 50))),
+ ?assertEqual("[1,2,<<...>>]", lists:flatten(format("~P", [[1, 2, <<3>>], 4], 50))),
+ ?assertEqual("[1,2,<<3>>]", lists:flatten(format("~P", [[1, 2, <<3>>], 5], 50))),
+
+ ?assertEqual("<<...>>", lists:flatten(format("~P", [<<0, 0, 0, 0>>, 1], 50))),
+ ?assertEqual("<<0,...>>", lists:flatten(format("~P", [<<0, 0, 0, 0>>, 2], 50))),
+ ?assertEqual("<<0,0,...>>", lists:flatten(format("~P", [<<0, 0, 0, 0>>, 3], 50))),
+ ?assertEqual("<<0,0,0,...>>", lists:flatten(format("~P", [<<0, 0, 0, 0>>, 4], 50))),
+ ?assertEqual("<<0,0,0,0>>", lists:flatten(format("~P", [<<0, 0, 0, 0>>, 5], 50))),
+
+ %% this is a seriously weird edge case
+ ?assertEqual("<<\" \"...>>", lists:flatten(format("~P", [<<32, 32, 32, 0>>, 2], 50))),
+ ?assertEqual("<<\" \"...>>", lists:flatten(format("~P", [<<32, 32, 32, 0>>, 3], 50))),
+ ?assertEqual("<<\" \"...>>", lists:flatten(format("~P", [<<32, 32, 32, 0>>, 4], 50))),
+ ?assertEqual("<<32,32,32,0>>", lists:flatten(format("~P", [<<32, 32, 32, 0>>, 5], 50))),
+ ?assertEqual("<<32,32,32,0>>", lists:flatten(format("~p", [<<32, 32, 32, 0>>], 50))),
+
+ %% depth limiting for some reason works in 4 byte chunks on printable binaries?
+ ?assertEqual("<<\"hell\"...>>", lists:flatten(format("~P", [<<"hello world">>, 2], 50))),
+ ?assertEqual("<<\"abcd\"...>>", lists:flatten(format("~P", [<<$a, $b, $c, $d, $e, 0>>, 2], 50))),
+
+ %% I don't even know...
+ ?assertEqual("<<>>", lists:flatten(format("~P", [<<>>, 1], 50))),
+ ?assertEqual("<<>>", lists:flatten(format("~W", [<<>>, 1], 50))),
+
+ ?assertEqual("{abc,<<\"abc\\\"\">>}", lists:flatten(format("~P", [{abc,<<"abc\"">>}, 4], 50))),
+
+ ok.
+
+print_terms_without_format_string_test() ->
+ ?assertError(badarg, format({hello, world}, [], 50)),
+ ?assertError(badarg, format([{google, bomb}], [], 50)),
+ ?assertError(badarg, format([$h,$e,$l,$l,$o, 3594], [], 50)),
+ ?assertEqual("helloworld", lists:flatten(format([$h,$e,$l,$l,$o, "world"], [], 50))),
+ ?assertEqual("hello", lists:flatten(format(<<"hello">>, [], 50))),
+ ?assertEqual("hello", lists:flatten(format('hello', [], 50))),
+ ?assertError(badarg, format(<<1, 2, 3, 1:7>>, [], 100)),
+ ?assertError(badarg, format(65535, [], 50)),
ok.
-endif.
diff --git a/deps/lager/src/lager_util.erl b/deps/lager/src/lager_util.erl
index f05d0e6..76fdda6 100644
--- a/deps/lager/src/lager_util.erl
+++ b/deps/lager/src/lager_util.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
@@ -18,45 +18,128 @@
-include_lib("kernel/include/file.hrl").
--export([levels/0, level_to_num/1, num_to_level/1, open_logfile/2,
- ensure_logfile/4, rotate_logfile/2, format_time/0, format_time/1,
- localtime_ms/0, maybe_utc/1, parse_rotation_date_spec/1,
- calculate_next_rotation/1, validate_trace/1, check_traces/4]).
--export([check_f_traces/4]).
+-export([levels/0, level_to_num/1, level_to_chr/1,
+ num_to_level/1, config_to_mask/1, config_to_levels/1, mask_to_levels/1,
+ open_logfile/2, ensure_logfile/4, rotate_logfile/2, format_time/0, format_time/1,
+ localtime_ms/0, localtime_ms/1, maybe_utc/1, parse_rotation_date_spec/1,
+ calculate_next_rotation/1, validate_trace/1, check_traces/4, is_loggable/3,
+ trace_filter/1, trace_filter/2, expand_path/1, check_hwm/1, make_internal_sink_name/1]).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-endif.
+-include("lager.hrl").
+
levels() ->
- [debug, info, notice, warning, error, critical, alert, emergency].
-
-level_to_num(debug) -> 7;
-level_to_num(info) -> 6;
-level_to_num(notice) -> 5;
-level_to_num(warning) -> 4;
-level_to_num(error) -> 3;
-level_to_num(critical) -> 2;
-level_to_num(alert) -> 1;
-level_to_num(emergency) -> 0;
-level_to_num(none) -> -1.
-
-num_to_level(7) -> debug;
-num_to_level(6) -> info;
-num_to_level(5) -> notice;
-num_to_level(4) -> warning;
-num_to_level(3) -> error;
-num_to_level(2) -> critical;
-num_to_level(1) -> alert;
-num_to_level(0) -> emergency;
-num_to_level(-1) -> none.
+ [debug, info, notice, warning, error, critical, alert, emergency, none].
+
+level_to_num(debug) -> ?DEBUG;
+level_to_num(info) -> ?INFO;
+level_to_num(notice) -> ?NOTICE;
+level_to_num(warning) -> ?WARNING;
+level_to_num(error) -> ?ERROR;
+level_to_num(critical) -> ?CRITICAL;
+level_to_num(alert) -> ?ALERT;
+level_to_num(emergency) -> ?EMERGENCY;
+level_to_num(none) -> ?LOG_NONE.
+
+level_to_chr(debug) -> $D;
+level_to_chr(info) -> $I;
+level_to_chr(notice) -> $N;
+level_to_chr(warning) -> $W;
+level_to_chr(error) -> $E;
+level_to_chr(critical) -> $C;
+level_to_chr(alert) -> $A;
+level_to_chr(emergency) -> $M;
+level_to_chr(none) -> $ .
+
+num_to_level(?DEBUG) -> debug;
+num_to_level(?INFO) -> info;
+num_to_level(?NOTICE) -> notice;
+num_to_level(?WARNING) -> warning;
+num_to_level(?ERROR) -> error;
+num_to_level(?CRITICAL) -> critical;
+num_to_level(?ALERT) -> alert;
+num_to_level(?EMERGENCY) -> emergency;
+num_to_level(?LOG_NONE) -> none.
+
+-spec config_to_mask(atom()|string()) -> {'mask', integer()}.
+config_to_mask(Conf) ->
+ Levels = config_to_levels(Conf),
+ {mask, lists:foldl(fun(Level, Acc) ->
+ level_to_num(Level) bor Acc
+ end, 0, Levels)}.
+
+-spec mask_to_levels(non_neg_integer()) -> [lager:log_level()].
+mask_to_levels(Mask) ->
+ mask_to_levels(Mask, levels(), []).
+
+mask_to_levels(_Mask, [], Acc) ->
+ lists:reverse(Acc);
+mask_to_levels(Mask, [Level|Levels], Acc) ->
+ NewAcc = case (level_to_num(Level) band Mask) /= 0 of
+ true ->
+ [Level|Acc];
+ false ->
+ Acc
+ end,
+ mask_to_levels(Mask, Levels, NewAcc).
+
+-spec config_to_levels(atom()|string()) -> [lager:log_level()].
+config_to_levels(Conf) when is_atom(Conf) ->
+ config_to_levels(atom_to_list(Conf));
+config_to_levels([$! | Rest]) ->
+ levels() -- config_to_levels(Rest);
+config_to_levels([$=, $< | Rest]) ->
+ [_|Levels] = config_to_levels_int(Rest),
+ lists:filter(fun(E) -> not lists:member(E, Levels) end, levels());
+config_to_levels([$<, $= | Rest]) ->
+ [_|Levels] = config_to_levels_int(Rest),
+ lists:filter(fun(E) -> not lists:member(E, Levels) end, levels());
+config_to_levels([$>, $= | Rest]) ->
+ config_to_levels_int(Rest);
+config_to_levels([$=, $> | Rest]) ->
+ config_to_levels_int(Rest);
+config_to_levels([$= | Rest]) ->
+ [level_to_atom(Rest)];
+config_to_levels([$< | Rest]) ->
+ Levels = config_to_levels_int(Rest),
+ lists:filter(fun(E) -> not lists:member(E, Levels) end, levels());
+config_to_levels([$> | Rest]) ->
+ [_|Levels] = config_to_levels_int(Rest),
+ lists:filter(fun(E) -> lists:member(E, Levels) end, levels());
+config_to_levels(Conf) ->
+ config_to_levels_int(Conf).
+
+%% internal function to break the recursion loop
+config_to_levels_int(Conf) ->
+ Level = level_to_atom(Conf),
+ lists:dropwhile(fun(E) -> E /= Level end, levels()).
+
+level_to_atom(String) ->
+ Levels = levels(),
+ try list_to_existing_atom(String) of
+ Atom ->
+ case lists:member(Atom, Levels) of
+ true ->
+ Atom;
+ false ->
+ erlang:error(badarg)
+ end
+ catch
+ _:_ ->
+ erlang:error(badarg)
+ end.
open_logfile(Name, Buffer) ->
case filelib:ensure_dir(Name) of
ok ->
Options = [append, raw] ++
- if Buffer == true -> [delayed_write];
- true -> []
+ case Buffer of
+ {Size, Interval} when is_integer(Interval), Interval >= 0, is_integer(Size), Size >= 0 ->
+ [{delayed_write, Size, Interval}];
+ _ -> []
end,
case file:open(Name, Options) of
{ok, FD} ->
@@ -80,8 +163,8 @@ ensure_logfile(Name, FD, Inode, Buffer) ->
{ok, {FD, Inode, FInfo#file_info.size}};
false ->
%% delayed write can cause file:close not to do a close
- file:close(FD),
- file:close(FD),
+ _ = file:close(FD),
+ _ = file:close(FD),
case open_logfile(Name, Buffer) of
{ok, {FD2, Inode3, Size}} ->
%% inode changed, file was probably moved and
@@ -93,8 +176,8 @@ ensure_logfile(Name, FD, Inode, Buffer) ->
end;
_ ->
%% delayed write can cause file:close not to do a close
- file:close(FD),
- file:close(FD),
+ _ = file:close(FD),
+ _ = file:close(FD),
case open_logfile(Name, Buffer) of
{ok, {FD2, Inode3, Size}} ->
%% file was removed
@@ -106,10 +189,15 @@ ensure_logfile(Name, FD, Inode, Buffer) ->
%% returns localtime with milliseconds included
localtime_ms() ->
- {_, _, Micro} = Now = os:timestamp(),
+ Now = os:timestamp(),
+ localtime_ms(Now).
+
+localtime_ms(Now) ->
+ {_, _, Micro} = Now,
{Date, {Hours, Minutes, Seconds}} = calendar:now_to_local_time(Now),
{Date, {Hours, Minutes, Seconds, Micro div 1000 rem 1000}}.
+
maybe_utc({Date, {H, M, S, Ms}}) ->
case lager_stdlib:maybe_utc({Date, {H, M, S}}) of
{utc, {Date1, {H1, M1, S1}}} ->
@@ -118,31 +206,35 @@ maybe_utc({Date, {H, M, S, Ms}}) ->
{Date1, {H1, M1, S1, Ms}}
end.
+%% renames failing are OK
rotate_logfile(File, 0) ->
file:delete(File);
rotate_logfile(File, 1) ->
- file:rename(File, File++".0"),
- rotate_logfile(File, 0);
+ case file:rename(File, File++".0") of
+ ok ->
+ ok;
+ _ ->
+ rotate_logfile(File, 0)
+ end;
rotate_logfile(File, Count) ->
- file:rename(File ++ "." ++ integer_to_list(Count - 2), File ++ "." ++
- integer_to_list(Count - 1)),
+ _ = file:rename(File ++ "." ++ integer_to_list(Count - 2), File ++ "." ++ integer_to_list(Count - 1)),
rotate_logfile(File, Count - 1).
format_time() ->
format_time(maybe_utc(localtime_ms())).
format_time({utc, {{Y, M, D}, {H, Mi, S, Ms}}}) ->
- {io_lib:format("~b-~2..0b-~2..0b", [Y, M, D]),
- io_lib:format("~2..0b:~2..0b:~2..0b.~3..0b UTC", [H, Mi, S, Ms])};
+ {[integer_to_list(Y), $-, i2l(M), $-, i2l(D)],
+ [i2l(H), $:, i2l(Mi), $:, i2l(S), $., i3l(Ms), $ , $U, $T, $C]};
format_time({{Y, M, D}, {H, Mi, S, Ms}}) ->
- {io_lib:format("~b-~2..0b-~2..0b", [Y, M, D]),
- io_lib:format("~2..0b:~2..0b:~2..0b.~3..0b", [H, Mi, S, Ms])};
+ {[integer_to_list(Y), $-, i2l(M), $-, i2l(D)],
+ [i2l(H), $:, i2l(Mi), $:, i2l(S), $., i3l(Ms)]};
format_time({utc, {{Y, M, D}, {H, Mi, S}}}) ->
- {io_lib:format("~b-~2..0b-~2..0b", [Y, M, D]),
- io_lib:format("~2..0b:~2..0b:~2..0b UTC", [H, Mi, S])};
+ {[integer_to_list(Y), $-, i2l(M), $-, i2l(D)],
+ [i2l(H), $:, i2l(Mi), $:, i2l(S), $ , $U, $T, $C]};
format_time({{Y, M, D}, {H, Mi, S}}) ->
- {io_lib:format("~b-~2..0b-~2..0b", [Y, M, D]),
- io_lib:format("~2..0b:~2..0b:~2..0b", [H, Mi, S])}.
+ {[integer_to_list(Y), $-, i2l(M), $-, i2l(D)],
+ [i2l(H), $:, i2l(Mi), $:, i2l(S)]}.
parse_rotation_day_spec([], Res) ->
{ok, Res ++ [{hour, 0}]};
@@ -272,23 +364,33 @@ calculate_next_rotation([{date, Date}|T], {{Year, Month, Day}, _} = Now) ->
NewNow = calendar:gregorian_seconds_to_datetime(Seconds),
calculate_next_rotation(T, NewNow).
-validate_trace({Filter, Level, {Destination, ID}}) when is_list(Filter), is_atom(Level), is_atom(Destination) ->
+-spec trace_filter(Query :: 'none' | [tuple()]) -> {ok, any()}.
+trace_filter(Query) ->
+ trace_filter(?DEFAULT_TRACER, Query).
+
+%% TODO: Support multiple trace modules
+%-spec trace_filter(Module :: atom(), Query :: 'none' | [tuple()]) -> {ok, any()}.
+trace_filter(Module, Query) when Query == none; Query == [] ->
+ {ok, _} = glc:compile(Module, glc:null(false));
+trace_filter(Module, Query) when is_list(Query) ->
+ {ok, _} = glc:compile(Module, glc_lib:reduce(trace_any(Query))).
+
+validate_trace({Filter, Level, {Destination, ID}}) when is_tuple(Filter); is_list(Filter), is_atom(Level), is_atom(Destination) ->
case validate_trace({Filter, Level, Destination}) of
{ok, {F, L, D}} ->
{ok, {F, L, {D, ID}}};
Error ->
Error
end;
-validate_trace({Filter, Level, Destination}) when is_list(Filter), is_atom(Level), is_atom(Destination) ->
- try level_to_num(Level) of
+validate_trace({Filter, Level, Destination}) when is_tuple(Filter); is_list(Filter), is_atom(Level), is_atom(Destination) ->
+ ValidFilter = validate_trace_filter(Filter),
+ try config_to_mask(Level) of
+ _ when not ValidFilter ->
+ {error, invalid_trace};
+ L when is_list(Filter) ->
+ {ok, {trace_all(Filter), L, Destination}};
L ->
- case lists:all(fun({Key, _Value}) when is_atom(Key) -> true; (_) ->
- false end, Filter) of
- true ->
- {ok, {Filter, L, Destination}};
- _ ->
- {error, invalid_filter}
- end
+ {ok, {Filter, L, Destination}}
catch
_:_ ->
{error, invalid_level}
@@ -296,51 +398,140 @@ validate_trace({Filter, Level, Destination}) when is_list(Filter), is_atom(Level
validate_trace(_) ->
{error, invalid_trace}.
-
-check_f_traces(_, _, [], Acc) ->
- lists:flatten(Acc);
-check_f_traces(AttrFun, Level, [{_, FilterLevel, _}|Flows], Acc)
- when Level > FilterLevel ->
- check_f_traces(AttrFun, Level, Flows, Acc);
-check_f_traces(AttrFun, Level, [Flow|Flows], Acc) ->
- check_f_traces(AttrFun, Level, Flows, [check_f_trace(AttrFun, Flow)|Acc]).
-
-check_f_trace(AttrFun, {Filter, _Level, Dest}) ->
- case lists:all(AttrFun, Filter) of
- true -> Dest;
- false -> []
- end.
-
+validate_trace_filter(Filter) when is_tuple(Filter), is_atom(element(1, Filter)) =:= false ->
+ false;
+validate_trace_filter(Filter) ->
+ case lists:all(fun({Key, '*'}) when is_atom(Key) -> true;
+ ({Key, '!'}) when is_atom(Key) -> true;
+ ({Key, _Value}) when is_atom(Key) -> true;
+ ({Key, '=', _Value}) when is_atom(Key) -> true;
+ ({Key, '<', _Value}) when is_atom(Key) -> true;
+ ({Key, '>', _Value}) when is_atom(Key) -> true;
+ (_) -> false end, Filter) of
+ true ->
+ true;
+ _ ->
+ false
+ end.
+
+trace_all(Query) ->
+ glc:all(trace_acc(Query)).
+
+trace_any(Query) ->
+ glc:any(Query).
+
+trace_acc(Query) ->
+ trace_acc(Query, []).
+
+trace_acc([], Acc) ->
+ lists:reverse(Acc);
+trace_acc([{Key, '*'}|T], Acc) ->
+ trace_acc(T, [glc:wc(Key)|Acc]);
+trace_acc([{Key, '!'}|T], Acc) ->
+ trace_acc(T, [glc:nf(Key)|Acc]);
+trace_acc([{Key, Val}|T], Acc) ->
+ trace_acc(T, [glc:eq(Key, Val)|Acc]);
+trace_acc([{Key, '=', Val}|T], Acc) ->
+ trace_acc(T, [glc:eq(Key, Val)|Acc]);
+trace_acc([{Key, '>', Val}|T], Acc) ->
+ trace_acc(T, [glc:gt(Key, Val)|Acc]);
+trace_acc([{Key, '<', Val}|T], Acc) ->
+ trace_acc(T, [glc:lt(Key, Val)|Acc]).
+
check_traces(_, _, [], Acc) ->
lists:flatten(Acc);
-check_traces(Attrs, Level, [{_, FilterLevel, _}|Flows], Acc) when Level > FilterLevel ->
+check_traces(Attrs, Level, [{_, {mask, FilterLevel}, _}|Flows], Acc) when (Level band FilterLevel) == 0 ->
check_traces(Attrs, Level, Flows, Acc);
check_traces(Attrs, Level, [{Filter, _, _}|Flows], Acc) when length(Attrs) < length(Filter) ->
check_traces(Attrs, Level, Flows, Acc);
check_traces(Attrs, Level, [Flow|Flows], Acc) ->
check_traces(Attrs, Level, Flows, [check_trace(Attrs, Flow)|Acc]).
-check_trace(Attrs, {Filter, _Level, Dest}) ->
- case check_trace_iter(Attrs, Filter) of
- true ->
- Dest;
- false ->
- []
+check_trace(Attrs, {Filter, _Level, Dest}) when is_list(Filter) ->
+ check_trace(Attrs, {trace_all(Filter), _Level, Dest});
+
+check_trace(Attrs, {Filter, _Level, Dest}) when is_tuple(Filter) ->
+ Made = gre:make(Attrs, [list]),
+ glc:handle(?DEFAULT_TRACER, Made),
+ Match = glc_lib:matches(Filter, Made),
+ case Match of
+ true ->
+ Dest;
+ false ->
+ []
+ end.
+
+-spec is_loggable(lager_msg:lager_msg(), non_neg_integer()|{'mask', non_neg_integer()}, term()) -> boolean().
+is_loggable(Msg, {mask, Mask}, MyName) ->
+ %% using syslog style comparison flags
+ %S = lager_msg:severity_as_int(Msg),
+ %?debugFmt("comparing masks ~.2B and ~.2B -> ~p~n", [S, Mask, S band Mask]),
+ (lager_msg:severity_as_int(Msg) band Mask) /= 0 orelse
+ lists:member(MyName, lager_msg:destinations(Msg));
+is_loggable(Msg ,SeverityThreshold,MyName) ->
+ lager_msg:severity_as_int(Msg) =< SeverityThreshold orelse
+ lists:member(MyName, lager_msg:destinations(Msg)).
+
+i2l(I) when I < 10 -> [$0, $0+I];
+i2l(I) -> integer_to_list(I).
+i3l(I) when I < 100 -> [$0 | i2l(I)];
+i3l(I) -> integer_to_list(I).
+
+%% When log_root option is provided, get the real path to a file
+expand_path(RelPath) ->
+ case application:get_env(lager, log_root) of
+ {ok, LogRoot} when is_list(LogRoot) -> % Join relative path
+ filename:join(LogRoot, RelPath);
+ undefined -> % No log_root given, keep relative path
+ RelPath
+ end.
+
+%% Log rate limit, i.e. high water mark for incoming messages
+
+check_hwm(Shaper = #lager_shaper{hwm = undefined}) ->
+ {true, 0, Shaper};
+check_hwm(Shaper = #lager_shaper{mps = Mps, hwm = Hwm}) when Mps < Hwm ->
+ %% haven't hit high water mark yet, just log it
+ {true, 0, Shaper#lager_shaper{mps=Mps+1}};
+check_hwm(Shaper = #lager_shaper{lasttime = Last, dropped = Drop}) ->
+ %% are we still in the same second?
+ {M, S, _} = Now = os:timestamp(),
+ case Last of
+ {M, S, _} ->
+ %% still in same second, but have exceeded the high water mark
+ NewDrops = discard_messages(Now, 0),
+ {false, 0, Shaper#lager_shaper{dropped=Drop+NewDrops}};
+ _ ->
+ %% different second, reset all counters and allow it
+ {true, Drop, Shaper#lager_shaper{dropped = 0, mps=1, lasttime = Now}}
end.
-check_trace_iter(_, []) ->
- true;
-check_trace_iter(Attrs, [{Key, Match}|T]) ->
- case lists:keyfind(Key, 1, Attrs) of
- {Key, _} when Match == '*' ->
- check_trace_iter(Attrs, T);
- {Key, Match} ->
- check_trace_iter(Attrs, T);
+discard_messages(Second, Count) ->
+ {M, S, _} = os:timestamp(),
+ case Second of
+ {M, S, _} ->
+ receive
+ %% we only discard gen_event notifications, because
+ %% otherwise we might discard gen_event internal
+ %% messages, such as trapped EXITs
+ {notify, _Event} ->
+ discard_messages(Second, Count+1)
+ after 0 ->
+ Count
+ end;
_ ->
- false
+ Count
end.
+%% @private Build an atom for the gen_event process based on a sink name.
+%% For historical reasons, the default gen_event process for lager itself is named
+%% `lager_event'. For all other sinks, it is SinkName++`_lager_event'
+make_internal_sink_name(lager) ->
+ ?DEFAULT_SINK;
+make_internal_sink_name(Sink) ->
+ list_to_atom(atom_to_list(Sink) ++ "_lager_event").
+
-ifdef(TEST).
parse_test() ->
@@ -441,31 +632,167 @@ rotate_file_test() ->
rotate_logfile("rotation.log", 10)
end || N <- lists:seq(0, 20)].
+rotate_file_fail_test() ->
+ %% make sure the directory exists
+ ?assertEqual(ok, filelib:ensure_dir("rotation/rotation.log")),
+ %% fix the permissions on it
+ os:cmd("chown -R u+rwx rotation"),
+ %% delete any old files
+ [ok = file:delete(F) || F <- filelib:wildcard("rotation/*")],
+ %% write a file
+ file:write_file("rotation/rotation.log", "hello"),
+ %% hose up the permissions
+ os:cmd("chown u-w rotation"),
+ ?assertMatch({error, _}, rotate_logfile("rotation.log", 10)),
+ ?assert(filelib:is_regular("rotation/rotation.log")),
+ os:cmd("chown u+w rotation"),
+ ?assertMatch(ok, rotate_logfile("rotation/rotation.log", 10)),
+ ?assert(filelib:is_regular("rotation/rotation.log.0")),
+ ?assertEqual(false, filelib:is_regular("rotation/rotation.log")),
+ ok.
+
check_trace_test() ->
- ?assertEqual([foo], check_traces([{module, ?MODULE}], 0, [{[{module, ?MODULE}],
- 0, foo},
- {[{module, test}], 0, bar}], [])),
- ?assertEqual([], check_traces([{module, ?MODULE}], 0, [{[{module, ?MODULE},
- {foo, bar}], 0, foo},
- {[{module, test}], 0, bar}], [])),
- ?assertEqual([bar], check_traces([{module, ?MODULE}], 0, [{[{module, ?MODULE},
- {foo, bar}], 0, foo},
- {[{module, '*'}], 0, bar}], [])),
- ?assertEqual([bar], check_traces([{module, ?MODULE}], 0, [{[{module, '*'},
- {foo, bar}], 0, foo},
- {[{module, '*'}], 0, bar}], [])),
- ?assertEqual([bar], check_traces([{module, ?MODULE}], 0, [{[{module, '*'},
- {foo, '*'}], 0, foo},
- {[{module, '*'}], 0, bar}], [])),
- ?assertEqual([bar, foo], check_traces([{module, ?MODULE}, {foo, bar}], 0, [{[{module, '*'},
- {foo, '*'}], 0, foo},
- {[{module, '*'}], 0, bar}], [])),
- ?assertEqual([], check_traces([{module, ?MODULE}, {foo, bar}], 6, [{[{module, '*'},
- {foo, '*'}], 0, foo},
- {[{module, '*'}], 0, bar}], [])),
- ?assertEqual([foo], check_traces([{module, ?MODULE}, {foo, bar}], 6, [{[{module, '*'},
- {foo, '*'}], 7, foo},
- {[{module, '*'}], 0, bar}], [])),
+ lager:start(),
+ trace_filter(none),
+ %% match by module
+ ?assertEqual([foo], check_traces([{module, ?MODULE}], ?EMERGENCY, [
+ {[{module, ?MODULE}], config_to_mask(emergency), foo},
+ {[{module, test}], config_to_mask(emergency), bar}], [])),
+ %% match by module, but other unsatisfyable attribute
+ ?assertEqual([], check_traces([{module, ?MODULE}], ?EMERGENCY, [
+ {[{module, ?MODULE}, {foo, bar}], config_to_mask(emergency), foo},
+ {[{module, test}], config_to_mask(emergency), bar}], [])),
+ %% match by wildcard module
+ ?assertEqual([bar], check_traces([{module, ?MODULE}], ?EMERGENCY, [
+ {[{module, ?MODULE}, {foo, bar}], config_to_mask(emergency), foo},
+ {[{module, '*'}], config_to_mask(emergency), bar}], [])),
+ %% wildcard module, one trace with unsatisfyable attribute
+ ?assertEqual([bar], check_traces([{module, ?MODULE}], ?EMERGENCY, [
+ {[{module, '*'}, {foo, bar}], config_to_mask(emergency), foo},
+ {[{module, '*'}], config_to_mask(emergency), bar}], [])),
+ %% wildcard but not present custom trace attribute
+ ?assertEqual([bar], check_traces([{module, ?MODULE}], ?EMERGENCY, [
+ {[{module, '*'}, {foo, '*'}], config_to_mask(emergency), foo},
+ {[{module, '*'}], config_to_mask(emergency), bar}], [])),
+ %% wildcarding a custom attribute works when it is present
+ ?assertEqual([bar, foo], check_traces([{module, ?MODULE}, {foo, bar}], ?EMERGENCY, [
+ {[{module, '*'}, {foo, '*'}], config_to_mask(emergency), foo},
+ {[{module, '*'}], config_to_mask(emergency), bar}], [])),
+ %% denied by level
+ ?assertEqual([], check_traces([{module, ?MODULE}, {foo, bar}], ?INFO, [
+ {[{module, '*'}, {foo, '*'}], config_to_mask(emergency), foo},
+ {[{module, '*'}], config_to_mask(emergency), bar}], [])),
+ %% allowed by level
+ ?assertEqual([foo], check_traces([{module, ?MODULE}, {foo, bar}], ?INFO, [
+ {[{module, '*'}, {foo, '*'}], config_to_mask(debug), foo},
+ {[{module, '*'}], config_to_mask(emergency), bar}], [])),
+ ?assertEqual([anythingbutnotice, infoandbelow, infoonly], check_traces([{module, ?MODULE}], ?INFO, [
+ {[{module, '*'}], config_to_mask('=debug'), debugonly},
+ {[{module, '*'}], config_to_mask('=info'), infoonly},
+ {[{module, '*'}], config_to_mask('<=info'), infoandbelow},
+ {[{module, '*'}], config_to_mask('!=info'), anythingbutinfo},
+ {[{module, '*'}], config_to_mask('!=notice'), anythingbutnotice}
+ ], [])),
+ application:stop(lager),
+ application:stop(goldrush),
+ ok.
+
+is_loggable_test_() ->
+ [
+ {"Loggable by severity only", ?_assert(is_loggable(lager_msg:new("", alert, [], []),2,me))},
+ {"Not loggable by severity only", ?_assertNot(is_loggable(lager_msg:new("", critical, [], []),1,me))},
+ {"Loggable by severity with destination", ?_assert(is_loggable(lager_msg:new("", alert, [], [you]),2,me))},
+ {"Not loggable by severity with destination", ?_assertNot(is_loggable(lager_msg:new("", critical, [], [you]),1,me))},
+ {"Loggable by destination overriding severity", ?_assert(is_loggable(lager_msg:new("", critical, [], [me]),1,me))}
+ ].
+
+format_time_test_() ->
+ [
+ ?_assertEqual("2012-10-04 11:16:23.002",
+ begin
+ {D, T} = format_time({{2012,10,04},{11,16,23,2}}),
+ lists:flatten([D,$ ,T])
+ end),
+ ?_assertEqual("2012-10-04 11:16:23.999",
+ begin
+ {D, T} = format_time({{2012,10,04},{11,16,23,999}}),
+ lists:flatten([D,$ ,T])
+ end),
+ ?_assertEqual("2012-10-04 11:16:23",
+ begin
+ {D, T} = format_time({{2012,10,04},{11,16,23}}),
+ lists:flatten([D,$ ,T])
+ end),
+ ?_assertEqual("2012-10-04 00:16:23.092 UTC",
+ begin
+ {D, T} = format_time({utc, {{2012,10,04},{0,16,23,92}}}),
+ lists:flatten([D,$ ,T])
+ end),
+ ?_assertEqual("2012-10-04 11:16:23 UTC",
+ begin
+ {D, T} = format_time({utc, {{2012,10,04},{11,16,23}}}),
+ lists:flatten([D,$ ,T])
+ end)
+ ].
+
+config_to_levels_test() ->
+ ?assertEqual([none], config_to_levels('none')),
+ ?assertEqual({mask, 0}, config_to_mask('none')),
+ ?assertEqual([debug], config_to_levels('=debug')),
+ ?assertEqual([debug], config_to_levels('<info')),
+ ?assertEqual(levels() -- [debug], config_to_levels('!=debug')),
+ ?assertEqual(levels() -- [debug], config_to_levels('>debug')),
+ ?assertEqual(levels() -- [debug], config_to_levels('>=info')),
+ ?assertEqual(levels() -- [debug], config_to_levels('=>info')),
+ ?assertEqual([debug, info, notice], config_to_levels('<=notice')),
+ ?assertEqual([debug, info, notice], config_to_levels('=<notice')),
+ ?assertEqual([debug], config_to_levels('<info')),
+ ?assertEqual([debug], config_to_levels('!info')),
+ ?assertError(badarg, config_to_levels(ok)),
+ ?assertError(badarg, config_to_levels('<=>info')),
+ ?assertError(badarg, config_to_levels('=<=info')),
+ ?assertError(badarg, config_to_levels('<==>=<=>info')),
+ %% double negatives DO work, however
+ ?assertEqual([debug], config_to_levels('!!=debug')),
+ ?assertEqual(levels() -- [debug], config_to_levels('!!!=debug')),
ok.
+config_to_mask_test() ->
+ ?assertEqual({mask, 0}, config_to_mask('none')),
+ ?assertEqual({mask, ?DEBUG bor ?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY}, config_to_mask('debug')),
+ ?assertEqual({mask, ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY}, config_to_mask('warning')),
+ ?assertEqual({mask, ?DEBUG bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY}, config_to_mask('!=info')),
+ ok.
+
+mask_to_levels_test() ->
+ ?assertEqual([], mask_to_levels(0)),
+ ?assertEqual([debug], mask_to_levels(2#10000000)),
+ ?assertEqual([debug, info], mask_to_levels(2#11000000)),
+ ?assertEqual([debug, info, emergency], mask_to_levels(2#11000001)),
+ ?assertEqual([debug, notice, error], mask_to_levels(?DEBUG bor ?NOTICE bor ?ERROR)),
+ ok.
+
+expand_path_test() ->
+ OldRootVal = application:get_env(lager, log_root),
+
+ ok = application:unset_env(lager, log_root),
+ ?assertEqual("/foo/bar", expand_path("/foo/bar")),
+ ?assertEqual("foo/bar", expand_path("foo/bar")),
+
+ ok = application:set_env(lager, log_root, "log/dir"),
+ ?assertEqual("/foo/bar", expand_path("/foo/bar")), % Absolute path should not be changed
+ ?assertEqual("log/dir/foo/bar", expand_path("foo/bar")),
+
+ case OldRootVal of
+ undefined -> application:unset_env(lager, log_root);
+ {ok, Root} -> application:set_env(lager, log_root, Root)
+ end,
+ ok.
+
+sink_name_test_() ->
+ [
+ ?_assertEqual(lager_event, make_internal_sink_name(lager)),
+ ?_assertEqual(audit_lager_event, make_internal_sink_name(audit))
+ ].
+
-endif.
diff --git a/deps/lager/test/compress_pr_record_test.erl b/deps/lager/test/compress_pr_record_test.erl
new file mode 100644
index 0000000..e3de673
--- /dev/null
+++ b/deps/lager/test/compress_pr_record_test.erl
@@ -0,0 +1,16 @@
+-module(compress_pr_record_test).
+
+-compile([{parse_transform, lager_transform}]).
+
+-record(a, {field1, field2, foo, bar, baz, zyu, zix}).
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-endif.
+
+nested_record_test() ->
+ A = #a{field1 = "Notice me senpai"},
+ Pr_A = lager:pr(A, ?MODULE),
+ Pr_A_Comp = lager:pr(A, ?MODULE, [compress]),
+ ?assertMatch({'$lager_record', a, [{field1, "Notice me senpai"}, {field2, undefined} | _]}, Pr_A),
+ ?assertEqual({'$lager_record', a, [{field1, "Notice me senpai"}]}, Pr_A_Comp).
diff --git a/deps/lager/test/crash.erl b/deps/lager/test/crash.erl
index 7037379..bb871c9 100644
--- a/deps/lager/test/crash.erl
+++ b/deps/lager/test/crash.erl
@@ -9,88 +9,98 @@
-export([start/0]).
+-record(state, {
+ host :: term(),
+ port :: term()
+ }).
+
start() ->
- gen_server:start({local, ?MODULE}, ?MODULE, [], []).
+ gen_server:start({local, ?MODULE}, ?MODULE, [], []).
init(_) ->
- {ok, {}}.
+ {ok, {}}.
handle_call(undef, _, State) ->
- {reply, ?MODULE:booger(), State};
+ {reply, ?MODULE:booger(), State};
handle_call(badfun, _, State) ->
- M = booger,
- {reply, M(), State};
+ M = booger,
+ {reply, M(), State};
handle_call(bad_return, _, _) ->
- bleh;
+ bleh;
handle_call(bad_return_string, _, _) ->
- {tuple, {tuple, "string"}};
+ {tuple, {tuple, "string"}};
handle_call(case_clause, _, State) ->
- case State of
- goober ->
- {reply, ok, State}
- end;
+ case State of
+ goober ->
+ {reply, ok, State}
+ end;
handle_call(case_clause_string, _, State) ->
- Foo = atom_to_list(?MODULE),
- case Foo of
- State ->
- {reply, ok, State}
- end;
+ Foo = atom_to_list(?MODULE),
+ case Foo of
+ State ->
+ {reply, ok, State}
+ end;
handle_call(if_clause, _, State) ->
- if State == 1 ->
- {reply, ok, State}
- end;
+ if State == 1 ->
+ {reply, ok, State}
+ end;
handle_call(try_clause, _, State) ->
- Res = try tuple_to_list(State) of
- [_A, _B] -> ok
- catch
- _:_ -> ok
- end,
- {reply, Res, State};
+ Res = try tuple_to_list(State) of
+ [_A, _B] -> ok
+ catch
+ _:_ -> ok
+ end,
+ {reply, Res, State};
handle_call(badmatch, _, State) ->
- {A, B, C} = State,
- {reply, [A, B, C], State};
+ {A, B, C} = State,
+ {reply, [A, B, C], State};
+handle_call(badrecord, _, State) ->
+ Host = State#state.host,
+ {reply, Host, State};
handle_call(function_clause, _, State) ->
- {reply, function(State), State};
+ {reply, function(State), State};
handle_call(badarith, _, State) ->
- Res = 1 / length(tuple_to_list(State)),
- {reply, Res, State};
+ Res = 1 / length(tuple_to_list(State)),
+ {reply, Res, State};
handle_call(badarg1, _, State) ->
- Res = list_to_binary(["foo", bar]),
- {reply, Res, State};
+ Res = list_to_binary(["foo", bar]),
+ {reply, Res, State};
handle_call(badarg2, _, State) ->
- Res = erlang:iolist_to_binary(["foo", bar]),
- {reply, Res, State};
+ Res = erlang:iolist_to_binary(["foo", bar]),
+ {reply, Res, State};
handle_call(system_limit, _, State) ->
- Res = list_to_atom(lists:flatten(lists:duplicate(256, "a"))),
- {reply, Res, State};
+ Res = list_to_atom(lists:flatten(lists:duplicate(256, "a"))),
+ {reply, Res, State};
handle_call(process_limit, _, State) ->
- %% run with +P 300 to make this crash
- [erlang:spawn(fun() -> timer:sleep(5000) end) || _ <- lists:seq(0, 500)],
- {reply, ok, State};
+ %% run with +P 300 to make this crash
+ [erlang:spawn(fun() -> timer:sleep(5000) end) || _ <- lists:seq(0, 500)],
+ {reply, ok, State};
handle_call(port_limit, _, State) ->
- [erlang:open_port({spawn, "ls"}, []) || _ <- lists:seq(0, 1024)],
- {reply, ok, State};
+ [erlang:open_port({spawn, "ls"}, []) || _ <- lists:seq(0, 1024)],
+ {reply, ok, State};
handle_call(noproc, _, State) ->
- Res = gen_event:call(foo, bar, baz),
- {reply, Res, State};
+ Res = gen_event:call(foo, bar, baz),
+ {reply, Res, State};
handle_call(badarity, _, State) ->
- F = fun(A, B, C) -> A + B + C end,
- Res = F(State),
- {reply, Res, State};
+ F = fun(A, B, C) -> A + B + C end,
+ Res = F(State),
+ {reply, Res, State};
+handle_call(throw, _, _State) ->
+ throw(a_ball);
handle_call(_Call, _From, State) ->
- {reply, ok, State}.
+ {reply, ok, State}.
handle_cast(_Cast, State) ->
- {noreply, State}.
+ {noreply, State}.
handle_info(_Info, State) ->
- {noreply, State}.
+ {noreply, State}.
terminate(_, _) ->
- ok.
+ ok.
code_change(_, State, _) ->
- {ok, State}.
+ {ok, State}.
function(X) when is_list(X) ->
- ok.
+ ok.
diff --git a/deps/lager/test/lager_crash_backend.erl b/deps/lager/test/lager_crash_backend.erl
index b5981cb..b573a73 100644
--- a/deps/lager/test/lager_crash_backend.erl
+++ b/deps/lager/test/lager_crash_backend.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
diff --git a/deps/lager/test/lager_test_backend.erl b/deps/lager/test/lager_test_backend.erl
index b6bc71c..3b41ca5 100644
--- a/deps/lager/test/lager_test_backend.erl
+++ b/deps/lager/test/lager_test_backend.erl
@@ -1,4 +1,6 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% -------------------------------------------------------------------
+%%
+%% Copyright (c) 2011-2015 Basho Technologies, Inc.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
@@ -13,6 +15,8 @@
%% KIND, either express or implied. See the License for the
%% specific language governing permissions and limitations
%% under the License.
+%%
+%% -------------------------------------------------------------------
-module(lager_test_backend).
@@ -23,16 +27,20 @@
-export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2,
code_change/3]).
--record(state, {level, buffer, ignored}).
--compile([{parse_transform, lager_transform}]).
+-define(TEST_SINK_NAME, '__lager_test_sink'). %% <-- used by parse transform
+-define(TEST_SINK_EVENT, '__lager_test_sink_lager_event'). %% <-- used by lager API calls and internals for gen_event
+
+-record(state, {level :: list(), buffer :: list(), ignored :: term()}).
+-compile({parse_transform, lager_transform}).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
--export([pop/0, count/0, count_ignored/0, flush/0]).
+-record(test, {attrs :: list(), format :: list(), args :: list()}).
+-export([pop/0, count/0, count_ignored/0, flush/0, print_state/0]).
-endif.
init(Level) ->
- {ok, #state{level=lager_util:level_to_num(Level), buffer=[], ignored=[]}}.
+ {ok, #state{level=lager_util:config_to_mask(Level), buffer=[], ignored=[]}}.
handle_call(count, #state{buffer=Buffer} = State) ->
{ok, length(Buffer), State};
@@ -50,17 +58,29 @@ handle_call(pop, #state{buffer=Buffer} = State) ->
handle_call(get_loglevel, #state{level=Level} = State) ->
{ok, Level, State};
handle_call({set_loglevel, Level}, State) ->
- {ok, ok, State#state{level=lager_util:level_to_num(Level)}};
+ {ok, ok, State#state{level=lager_util:config_to_mask(Level)}};
+handle_call(print_state, State) ->
+ spawn(fun() -> lager:info("State ~p", [lager:pr(State, ?MODULE)]) end),
+ timer:sleep(100),
+ {ok, ok, State};
+handle_call(print_bad_state, State) ->
+ spawn(fun() -> lager:info("State ~p", [lager:pr({state, 1}, ?MODULE)]) end),
+ timer:sleep(100),
+ {ok, ok, State};
handle_call(_Request, State) ->
{ok, ok, State}.
-handle_event({log, [?MODULE], Level, Time, Message}, #state{buffer=Buffer} = State) ->
- {ok, State#state{buffer=Buffer ++ [{Level, Time, Message}]}};
-handle_event({log, Level, Time, Message}, #state{level=LogLevel,
- buffer=Buffer} = State) when Level =< LogLevel ->
- {ok, State#state{buffer=Buffer ++ [{Level, Time, Message}]}};
-handle_event({log, _Level, _Time, _Message}, #state{ignored=Ignored} = State) ->
- {ok, State#state{ignored=Ignored ++ [ignored]}};
+handle_event({log, Msg},
+ #state{level=LogLevel,buffer=Buffer,ignored=Ignored} = State) ->
+ case lager_util:is_loggable(Msg, LogLevel, ?MODULE) of
+ true ->
+ {ok, State#state{buffer=Buffer ++
+ [{lager_msg:severity_as_int(Msg),
+ lager_msg:datetime(Msg),
+ lager_msg:message(Msg), lager_msg:metadata(Msg)}]}};
+ _ ->
+ {ok, State#state{ignored=Ignored ++ [ignored]}}
+ end;
handle_event(_Event, State) ->
{ok, State}.
@@ -76,16 +96,63 @@ code_change(_OldVsn, State, _Extra) ->
-ifdef(TEST).
pop() ->
- gen_event:call(lager_event, ?MODULE, pop).
+ pop(lager_event).
count() ->
- gen_event:call(lager_event, ?MODULE, count).
+ count(lager_event).
count_ignored() ->
- gen_event:call(lager_event, ?MODULE, count_ignored).
+ count_ignored(lager_event).
flush() ->
- gen_event:call(lager_event, ?MODULE, flush).
+ flush(lager_event).
+
+print_state() ->
+ print_state(lager_event).
+
+print_bad_state() ->
+ print_bad_state(lager_event).
+
+pop(Sink) ->
+ gen_event:call(Sink, ?MODULE, pop).
+
+count(Sink) ->
+ gen_event:call(Sink, ?MODULE, count).
+
+count_ignored(Sink) ->
+ gen_event:call(Sink, ?MODULE, count_ignored).
+
+flush(Sink) ->
+ gen_event:call(Sink, ?MODULE, flush).
+
+print_state(Sink) ->
+ gen_event:call(Sink, ?MODULE, print_state).
+
+print_bad_state(Sink) ->
+ gen_event:call(Sink, ?MODULE, print_bad_state).
+
+
+has_line_numbers() ->
+ %% are we R15 or greater
+ % this gets called a LOT - cache the answer
+ case erlang:get({?MODULE, has_line_numbers}) of
+ undefined ->
+ R = otp_version() >= 15,
+ erlang:put({?MODULE, has_line_numbers}, R),
+ R;
+ Bool ->
+ Bool
+ end.
+
+otp_version() ->
+ otp_version(erlang:system_info(otp_release)).
+
+otp_version([$R | Rel]) ->
+ {Ver, _} = string:to_integer(Rel),
+ Ver;
+otp_version(Rel) ->
+ {Ver, _} = string:to_integer(Rel),
+ Ver.
not_running_test() ->
?assertEqual({error, lager_not_running}, lager:log(info, self(), "not running")).
@@ -101,15 +168,28 @@ lager_test_() ->
?assertEqual(0, count())
end
},
+ {"test sink not running",
+ fun() ->
+ ?assertEqual({error, {sink_not_configured, test}}, lager:log(test, info, self(), "~p", "not running"))
+ end
+ },
{"logging works",
fun() ->
lager:warning("test message"),
?assertEqual(1, count()),
- {Level, _Time, Message} = pop(),
+ {Level, _Time, Message, _Metadata} = pop(),
+ ?assertMatch(Level, lager_util:level_to_num(warning)),
+ ?assertEqual("test message", Message),
+ ok
+ end
+ },
+ {"unsafe logging works",
+ fun() ->
+ lager:warning_unsafe("test message"),
+ ?assertEqual(1, count()),
+ {Level, _Time, Message, _Metadata} = pop(),
?assertMatch(Level, lager_util:level_to_num(warning)),
- [LevelStr, _LocStr, MsgStr] = re:split(Message, " ", [{return, list}, {parts, 3}]),
- ?assertEqual("[warning]", LevelStr),
- ?assertEqual("test message", MsgStr),
+ ?assertEqual("test message", Message),
ok
end
},
@@ -117,11 +197,19 @@ lager_test_() ->
fun() ->
lager:warning("test message ~p", [self()]),
?assertEqual(1, count()),
- {Level, _Time, Message} = pop(),
+ {Level, _Time, Message,_Metadata} = pop(),
+ ?assertMatch(Level, lager_util:level_to_num(warning)),
+ ?assertEqual(lists:flatten(io_lib:format("test message ~p", [self()])), lists:flatten(Message)),
+ ok
+ end
+ },
+ {"unsafe logging with args works",
+ fun() ->
+ lager:warning("test message ~p", [self()]),
+ ?assertEqual(1, count()),
+ {Level, _Time, Message,_Metadata} = pop(),
?assertMatch(Level, lager_util:level_to_num(warning)),
- [LevelStr, _LocStr, MsgStr] = re:split(Message, " ", [{return, list}, {parts, 3}]),
- ?assertEqual("[warning]", LevelStr),
- ?assertEqual(lists:flatten(io_lib:format("test message ~p", [self()])), MsgStr),
+ ?assertEqual(lists:flatten(io_lib:format("test message ~p", [self()])), lists:flatten(Message)),
ok
end
},
@@ -160,17 +248,137 @@ lager_test_() ->
ok
end
},
+ {"variables inplace of literals in logging statements work",
+ fun() ->
+ ?assertEqual(0, count()),
+ Attr = [{a, alpha}, {b, beta}],
+ Fmt = "format ~p",
+ Args = [world],
+ lager:info(Attr, "hello"),
+ lager:info(Attr, "hello ~p", [world]),
+ lager:info(Fmt, [world]),
+ lager:info("hello ~p", Args),
+ lager:info(Attr, "hello ~p", Args),
+ lager:info([{d, delta}, {g, gamma}], Fmt, Args),
+ ?assertEqual(6, count()),
+ {_Level, _Time, Message, Metadata} = pop(),
+ ?assertMatch([{a, alpha}, {b, beta}|_], Metadata),
+ ?assertEqual("hello", lists:flatten(Message)),
+ {_Level, _Time2, Message2, _Metadata2} = pop(),
+ ?assertEqual("hello world", lists:flatten(Message2)),
+ {_Level, _Time3, Message3, _Metadata3} = pop(),
+ ?assertEqual("format world", lists:flatten(Message3)),
+ {_Level, _Time4, Message4, _Metadata4} = pop(),
+ ?assertEqual("hello world", lists:flatten(Message4)),
+ {_Level, _Time5, Message5, _Metadata5} = pop(),
+ ?assertEqual("hello world", lists:flatten(Message5)),
+ {_Level, _Time6, Message6, Metadata6} = pop(),
+ ?assertMatch([{d, delta}, {g, gamma}|_], Metadata6),
+ ?assertEqual("format world", lists:flatten(Message6)),
+ ok
+ end
+ },
+ {"list comprehension inplace of literals in logging statements work",
+ fun() ->
+ ?assertEqual(0, count()),
+ Attr = [{a, alpha}, {b, beta}],
+ Fmt = "format ~p",
+ Args = [world],
+ lager:info([{K, atom_to_list(V)} || {K, V} <- Attr], "hello"),
+ lager:info([{K, atom_to_list(V)} || {K, V} <- Attr], "hello ~p", [{atom, X} || X <- Args]),
+ lager:info([X || X <- Fmt], [world]),
+ lager:info("hello ~p", [{atom, X} || X <- Args]),
+ lager:info([{K, atom_to_list(V)} || {K, V} <- Attr], "hello ~p", [{atom, X} || X <- Args]),
+ lager:info([{d, delta}, {g, gamma}], Fmt, [{atom, X} || X <- Args]),
+ ?assertEqual(6, count()),
+ {_Level, _Time, Message, Metadata} = pop(),
+ ?assertMatch([{a, "alpha"}, {b, "beta"}|_], Metadata),
+ ?assertEqual("hello", lists:flatten(Message)),
+ {_Level, _Time2, Message2, _Metadata2} = pop(),
+ ?assertEqual("hello {atom,world}", lists:flatten(Message2)),
+ {_Level, _Time3, Message3, _Metadata3} = pop(),
+ ?assertEqual("format world", lists:flatten(Message3)),
+ {_Level, _Time4, Message4, _Metadata4} = pop(),
+ ?assertEqual("hello {atom,world}", lists:flatten(Message4)),
+ {_Level, _Time5, Message5, _Metadata5} = pop(),
+ ?assertEqual("hello {atom,world}", lists:flatten(Message5)),
+ {_Level, _Time6, Message6, Metadata6} = pop(),
+ ?assertMatch([{d, delta}, {g, gamma}|_], Metadata6),
+ ?assertEqual("format {atom,world}", lists:flatten(Message6)),
+ ok
+ end
+ },
+ {"function calls inplace of literals in logging statements work",
+ fun() ->
+ ?assertEqual(0, count()),
+ put(attrs, [{a, alpha}, {b, beta}]),
+ put(format, "format ~p"),
+ put(args, [world]),
+ lager:info(get(attrs), "hello"),
+ lager:info(get(attrs), "hello ~p", get(args)),
+ lager:info(get(format), [world]),
+ lager:info("hello ~p", erlang:get(args)),
+ lager:info(fun() -> get(attrs) end(), "hello ~p", get(args)),
+ lager:info([{d, delta}, {g, gamma}], get(format), get(args)),
+ ?assertEqual(6, count()),
+ {_Level, _Time, Message, Metadata} = pop(),
+ ?assertMatch([{a, alpha}, {b, beta}|_], Metadata),
+ ?assertEqual("hello", lists:flatten(Message)),
+ {_Level, _Time2, Message2, _Metadata2} = pop(),
+ ?assertEqual("hello world", lists:flatten(Message2)),
+ {_Level, _Time3, Message3, _Metadata3} = pop(),
+ ?assertEqual("format world", lists:flatten(Message3)),
+ {_Level, _Time4, Message4, _Metadata4} = pop(),
+ ?assertEqual("hello world", lists:flatten(Message4)),
+ {_Level, _Time5, Message5, _Metadata5} = pop(),
+ ?assertEqual("hello world", lists:flatten(Message5)),
+ {_Level, _Time6, Message6, Metadata6} = pop(),
+ ?assertMatch([{d, delta}, {g, gamma}|_], Metadata6),
+ ?assertEqual("format world", lists:flatten(Message6)),
+ ok
+ end
+ },
+ {"record fields inplace of literals in logging statements work",
+ fun() ->
+ ?assertEqual(0, count()),
+ Test = #test{attrs=[{a, alpha}, {b, beta}], format="format ~p", args=[world]},
+ lager:info(Test#test.attrs, "hello"),
+ lager:info(Test#test.attrs, "hello ~p", Test#test.args),
+ lager:info(Test#test.format, [world]),
+ lager:info("hello ~p", Test#test.args),
+ lager:info(Test#test.attrs, "hello ~p", Test#test.args),
+ lager:info([{d, delta}, {g, gamma}], Test#test.format, Test#test.args),
+ ?assertEqual(6, count()),
+ {_Level, _Time, Message, Metadata} = pop(),
+ ?assertMatch([{a, alpha}, {b, beta}|_], Metadata),
+ ?assertEqual("hello", lists:flatten(Message)),
+ {_Level, _Time2, Message2, _Metadata2} = pop(),
+ ?assertEqual("hello world", lists:flatten(Message2)),
+ {_Level, _Time3, Message3, _Metadata3} = pop(),
+ ?assertEqual("format world", lists:flatten(Message3)),
+ {_Level, _Time4, Message4, _Metadata4} = pop(),
+ ?assertEqual("hello world", lists:flatten(Message4)),
+ {_Level, _Time5, Message5, _Metadata5} = pop(),
+ ?assertEqual("hello world", lists:flatten(Message5)),
+ {_Level, _Time6, Message6, Metadata6} = pop(),
+ ?assertMatch([{d, delta}, {g, gamma}|_], Metadata6),
+ ?assertEqual("format world", lists:flatten(Message6)),
+ ok
+ end
+ },
+
{"log messages below the threshold are ignored",
fun() ->
?assertEqual(0, count()),
lager:debug("this message will be ignored"),
?assertEqual(0, count()),
?assertEqual(0, count_ignored()),
- lager_mochiglobal:put(loglevel, {?DEBUG, []}),
+ lager_config:set(loglevel, {element(2, lager_util:config_to_mask(debug)), []}),
lager:debug("this message should be ignored"),
?assertEqual(0, count()),
?assertEqual(1, count_ignored()),
lager:set_loglevel(?MODULE, debug),
+ ?assertEqual({?DEBUG bor ?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get(loglevel)),
lager:debug("this message should be logged"),
?assertEqual(1, count()),
?assertEqual(1, count_ignored()),
@@ -180,58 +388,352 @@ lager_test_() ->
},
{"tracing works",
fun() ->
- lager_mochiglobal:put(loglevel, {?ERROR, []}),
+ lager_config:set(loglevel, {element(2, lager_util:config_to_mask(error)), []}),
ok = lager:info("hello world"),
?assertEqual(0, count()),
- lager_mochiglobal:put(loglevel, {?ERROR, [{[{module,
- ?MODULE}], ?DEBUG, ?MODULE}]}),
+ lager:trace(?MODULE, [{module, ?MODULE}], debug),
+ ?assertMatch({?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, _}, lager_config:get(loglevel)),
+ %% elegible for tracing
ok = lager:info("hello world"),
+ %% NOT elegible for tracing
+ ok = lager:log(info, [{pid, self()}], "hello world"),
?assertEqual(1, count()),
ok
end
},
{"tracing works with custom attributes",
fun() ->
- lager_mochiglobal:put(loglevel, {?ERROR, []}),
+ lager:set_loglevel(?MODULE, error),
+ ?assertEqual({?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get(loglevel)),
+ lager_config:set(loglevel, {element(2, lager_util:config_to_mask(error)), []}),
lager:info([{requestid, 6}], "hello world"),
?assertEqual(0, count()),
- lager_mochiglobal:put(loglevel, {?ERROR,
- [{[{requestid, 6}], ?DEBUG, ?MODULE}]}),
+ lager:trace(?MODULE, [{requestid, 6}, {foo, bar}], debug),
lager:info([{requestid, 6}, {foo, bar}], "hello world"),
?assertEqual(1, count()),
- lager_mochiglobal:put(loglevel, {?ERROR,
- [{[{requestid, '*'}], ?DEBUG, ?MODULE}]}),
+ lager:trace(?MODULE, [{requestid, '*'}], debug),
lager:info([{requestid, 6}], "hello world"),
?assertEqual(2, count()),
+ lager:clear_all_traces(),
+ lager:info([{requestid, 6}], "hello world"),
+ ?assertEqual(2, count()),
+ ok
+ end
+ },
+ {"tracing works with custom attributes and event stream processing",
+ fun() ->
+ lager:set_loglevel(?MODULE, error),
+ ?assertEqual({?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get(loglevel)),
+ lager_config:set(loglevel, {element(2, lager_util:config_to_mask(error)), []}),
+ lager:info([{requestid, 6}], "hello world"),
+ ?assertEqual(0, count()),
+ lager:trace(?MODULE, [{requestid, '>', 5}, {requestid, '<', 7}, {foo, bar}], debug),
+ lager:info([{requestid, 5}, {foo, bar}], "hello world"),
+ lager:info([{requestid, 6}, {foo, bar}], "hello world"),
+ ?assertEqual(1, count()),
+ lager:clear_all_traces(),
+ lager:trace(?MODULE, [{requestid, '>', 8}, {foo, bar}]),
+ lager:info([{foo, bar}], "hello world"),
+ lager:info([{requestid, 6}], "hello world"),
+ lager:info([{requestid, 7}], "hello world"),
+ lager:info([{requestid, 8}], "hello world"),
+ lager:info([{requestid, 9}, {foo, bar}], "hello world"),
+ lager:info([{requestid, 10}], "hello world"),
+ ?assertEqual(2, count()),
+ lager:trace(?MODULE, [{requestid, '>', 8}]),
+ lager:info([{foo, bar}], "hello world"),
+ lager:info([{requestid, 6}], "hello world"),
+ lager:info([{requestid, 7}], "hello world"),
+ lager:info([{requestid, 8}], "hello world"),
+ lager:info([{requestid, 9}, {foo, bar}], "hello world"),
+ lager:info([{requestid, 10}], "hello world"),
+ ?assertEqual(4, count()),
+ lager:trace(?MODULE, [{foo, '=', bar}]),
+ lager:info([{foo, bar}], "hello world"),
+ lager:info([{requestid, 6}], "hello world"),
+ lager:info([{requestid, 7}], "hello world"),
+ lager:info([{requestid, 8}], "hello world"),
+ lager:info([{requestid, 9}, {foo, bar}], "hello world"),
+ lager:info([{requestid, 10}], "hello world"),
+ lager:trace(?MODULE, [{fu, '!'}]),
+ lager:info([{foo, bar}], "hello world"),
+ lager:info([{ooh, car}], "hello world"),
+ lager:info([{fu, bar}], "hello world"),
+ lager:trace(?MODULE, [{fu, '*'}]),
+ lager:info([{fu, bar}], "hello world"),
+ ?assertEqual(10, count()),
+ lager:clear_all_traces(),
+ lager:info([{requestid, 6}], "hello world"),
+ ?assertEqual(10, count()),
+ ok
+ end
+ },
+ {"tracing custom attributes works with event stream processing statistics and reductions",
+ fun() ->
+ lager:set_loglevel(?MODULE, error),
+ ?assertEqual({?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get(loglevel)),
+ lager_config:set(loglevel, {element(2, lager_util:config_to_mask(error)), []}),
+ lager:info([{requestid, 6}], "hello world"),
+ ?assertEqual(0, count()),
+ lager:trace(?MODULE, [{beta, '*'}]),
+ lager:trace(?MODULE, [{meta, "data"}]),
+ lager:info([{meta, "data"}], "hello world"),
+ lager:info([{beta, 2}], "hello world"),
+ lager:info([{beta, 2.1}, {foo, bar}], "hello world"),
+ lager:info([{meta, <<"data">>}], "hello world"),
+ ?assertEqual(8, ?DEFAULT_TRACER:info(input)),
+ ?assertEqual(6, ?DEFAULT_TRACER:info(output)),
+ ?assertEqual(2, ?DEFAULT_TRACER:info(filter)),
+ lager:clear_all_traces(),
+ lager:trace(?MODULE, [{meta, "data"}]),
+ lager:trace(?MODULE, [{beta, '>', 2}, {beta, '<', 2.12}]),
+ lager:info([{meta, "data"}], "hello world"),
+ lager:info([{beta, 2}], "hello world"),
+ lager:info([{beta, 2.1}, {foo, bar}], "hello world"),
+ lager:info([{meta, <<"data">>}], "hello world"),
+ ?assertEqual(8, ?DEFAULT_TRACER:info(input)),
+ ?assertEqual(4, ?DEFAULT_TRACER:info(output)),
+ ?assertEqual(4, ?DEFAULT_TRACER:info(filter)),
+ lager:clear_all_traces(),
+ lager:trace_console([{beta, '>', 2}, {meta, "data"}]),
+ lager:trace_console([{beta, '>', 2}, {beta, '<', 2.12}]),
+ Reduced = {all,[{any,[{beta,'<',2.12},{meta,'=',"data"}]},
+ {beta,'>',2}]},
+ ?assertEqual(Reduced, ?DEFAULT_TRACER:info('query')),
+
+ lager:clear_all_traces(),
+ lager:info([{requestid, 6}], "hello world"),
+ ?assertEqual(5, count()),
+ ok
+ end
+ },
+ {"persistent traces work",
+ fun() ->
+ ?assertEqual(0, count()),
+ lager:debug([{foo, bar}], "hello world"),
+ ?assertEqual(0, count()),
+ application:stop(lager),
+ application:set_env(lager, traces, [{lager_test_backend, [{foo, bar}], debug}]),
+ lager:start(),
+ lager:debug([{foo, bar}], "hello world"),
+ ?assertEqual(1, count()),
+ application:unset_env(lager, traces),
ok
end
},
{"tracing honors loglevel",
fun() ->
- lager_mochiglobal:put(loglevel, {?ERROR, [{[{module,
- ?MODULE}], ?NOTICE, ?MODULE}]}),
+ lager:set_loglevel(?MODULE, error),
+ ?assertEqual({?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get(loglevel)),
+ {ok, T} = lager:trace(?MODULE, [{module, ?MODULE}], notice),
ok = lager:info("hello world"),
?assertEqual(0, count()),
ok = lager:notice("hello world"),
?assertEqual(1, count()),
+ lager:stop_trace(T),
+ ok = lager:notice("hello world"),
+ ?assertEqual(1, count()),
+ ok
+ end
+ },
+ {"record printing works",
+ fun() ->
+ print_state(),
+ {Level, _Time, Message, _Metadata} = pop(),
+ ?assertMatch(Level, lager_util:level_to_num(info)),
+ {mask, Mask} = lager_util:config_to_mask(info),
+ ?assertEqual("State #state{level={mask,"++integer_to_list(Mask)++"},buffer=[],ignored=[]}", lists:flatten(Message)),
+ ok
+ end
+ },
+ {"record printing fails gracefully",
+ fun() ->
+ print_bad_state(),
+ {Level, _Time, Message, _Metadata} = pop(),
+ ?assertMatch(Level, lager_util:level_to_num(info)),
+ ?assertEqual("State {state,1}", lists:flatten(Message)),
+ ok
+ end
+ },
+ {"record printing fails gracefully when no lager_record attribute",
+ fun() ->
+ spawn(fun() -> lager:info("State ~p", [lager:pr({state, 1}, lager)]) end),
+ timer:sleep(100),
+ {Level, _Time, Message, _Metadata} = pop(),
+ ?assertMatch(Level, lager_util:level_to_num(info)),
+ ?assertEqual("State {state,1}", lists:flatten(Message)),
+ ok
+ end
+ },
+ {"record printing fails gracefully when input is not a tuple",
+ fun() ->
+ spawn(fun() -> lager:info("State ~p", [lager:pr(ok, lager)]) end),
+ timer:sleep(100),
+ {Level, _Time, Message, _Metadata} = pop(),
+ ?assertMatch(Level, lager_util:level_to_num(info)),
+ ?assertEqual("State ok", lists:flatten(Message)),
+ ok
+ end
+ },
+ {"record printing fails gracefully when module is invalid",
+ fun() ->
+ spawn(fun() -> lager:info("State ~p", [lager:pr({state, 1}, not_a_module)]) end),
+ timer:sleep(1000),
+ {Level, _Time, Message, _Metadata} = pop(),
+ ?assertMatch(Level, lager_util:level_to_num(info)),
+ ?assertEqual("State {state,1}", lists:flatten(Message)),
+ ok
+ end
+ },
+ {"installing a new handler adjusts the global loglevel if necessary",
+ fun() ->
+ ?assertEqual({?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get(loglevel)),
+ supervisor:start_child(lager_handler_watcher_sup, [lager_event, {?MODULE, foo}, debug]),
+ ?assertEqual({?DEBUG bor ?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get(loglevel)),
+ ok
+ end
+ },
+ {"metadata in the process dictionary works",
+ fun() ->
+ lager:md([{platypus, gravid}, {sloth, hirsute}, {duck, erroneous}]),
+ lager:info("I sing the animal kingdom electric!"),
+ {_Level, _Time, _Message, Metadata} = pop(),
+ ?assertEqual(gravid, proplists:get_value(platypus, Metadata)),
+ ?assertEqual(hirsute, proplists:get_value(sloth, Metadata)),
+ ?assertEqual(erroneous, proplists:get_value(duck, Metadata)),
+ ?assertEqual(undefined, proplists:get_value(eagle, Metadata)),
+ lager:md([{platypus, gravid}, {sloth, hirsute}, {eagle, superincumbent}]),
+ lager:info("I sing the animal kingdom dielectric!"),
+ {_Level2, _Time2, _Message2, Metadata2} = pop(),
+ ?assertEqual(gravid, proplists:get_value(platypus, Metadata2)),
+ ?assertEqual(hirsute, proplists:get_value(sloth, Metadata2)),
+ ?assertEqual(undefined, proplists:get_value(duck, Metadata2)),
+ ?assertEqual(superincumbent, proplists:get_value(eagle, Metadata2)),
+ ok
+ end
+ },
+ {"unsafe messages really are not truncated",
+ fun() ->
+ lager:info_unsafe("doom, doom has come upon you all ~p", [string:copies("doom", 1500)]),
+ {_, _, Msg,_Metadata} = pop(),
+ ?assert(length(lists:flatten(Msg)) == 6035)
+ end
+ },
+ {"can't store invalid metadata",
+ fun() ->
+ ?assertEqual(ok, lager:md([{platypus, gravid}, {sloth, hirsute}, {duck, erroneous}])),
+ ?assertError(badarg, lager:md({flamboyant, flamingo})),
+ ?assertError(badarg, lager:md("zookeeper zephyr")),
ok
end
}
]
}.
+extra_sinks_test_() ->
+ {foreach,
+ fun setup_sink/0,
+ fun cleanup/1,
+ [
+ {"observe that there is nothing up my sleeve",
+ fun() ->
+ ?assertEqual(undefined, pop(?TEST_SINK_EVENT)),
+ ?assertEqual(0, count(?TEST_SINK_EVENT))
+ end
+ },
+ {"logging works",
+ fun() ->
+ ?TEST_SINK_NAME:warning("test message"),
+ ?assertEqual(1, count(?TEST_SINK_EVENT)),
+ {Level, _Time, Message, _Metadata} = pop(?TEST_SINK_EVENT),
+ ?assertMatch(Level, lager_util:level_to_num(warning)),
+ ?assertEqual("test message", Message),
+ ok
+ end
+ },
+ {"logging with arguments works",
+ fun() ->
+ ?TEST_SINK_NAME:warning("test message ~p", [self()]),
+ ?assertEqual(1, count(?TEST_SINK_EVENT)),
+ {Level, _Time, Message,_Metadata} = pop(?TEST_SINK_EVENT),
+ ?assertMatch(Level, lager_util:level_to_num(warning)),
+ ?assertEqual(lists:flatten(io_lib:format("test message ~p", [self()])), lists:flatten(Message)),
+ ok
+ end
+ },
+ {"variables inplace of literals in logging statements work",
+ fun() ->
+ ?assertEqual(0, count(?TEST_SINK_EVENT)),
+ Attr = [{a, alpha}, {b, beta}],
+ Fmt = "format ~p",
+ Args = [world],
+ ?TEST_SINK_NAME:info(Attr, "hello"),
+ ?TEST_SINK_NAME:info(Attr, "hello ~p", [world]),
+ ?TEST_SINK_NAME:info(Fmt, [world]),
+ ?TEST_SINK_NAME:info("hello ~p", Args),
+ ?TEST_SINK_NAME:info(Attr, "hello ~p", Args),
+ ?TEST_SINK_NAME:info([{d, delta}, {g, gamma}], Fmt, Args),
+ ?assertEqual(6, count(?TEST_SINK_EVENT)),
+ {_Level, _Time, Message, Metadata} = pop(?TEST_SINK_EVENT),
+ ?assertMatch([{a, alpha}, {b, beta}|_], Metadata),
+ ?assertEqual("hello", lists:flatten(Message)),
+ {_Level, _Time2, Message2, _Metadata2} = pop(?TEST_SINK_EVENT),
+ ?assertEqual("hello world", lists:flatten(Message2)),
+ {_Level, _Time3, Message3, _Metadata3} = pop(?TEST_SINK_EVENT),
+ ?assertEqual("format world", lists:flatten(Message3)),
+ {_Level, _Time4, Message4, _Metadata4} = pop(?TEST_SINK_EVENT),
+ ?assertEqual("hello world", lists:flatten(Message4)),
+ {_Level, _Time5, Message5, _Metadata5} = pop(?TEST_SINK_EVENT),
+ ?assertEqual("hello world", lists:flatten(Message5)),
+ {_Level, _Time6, Message6, Metadata6} = pop(?TEST_SINK_EVENT),
+ ?assertMatch([{d, delta}, {g, gamma}|_], Metadata6),
+ ?assertEqual("format world", lists:flatten(Message6)),
+ ok
+ end
+ },
+ {"log messages below the threshold are ignored",
+ fun() ->
+ ?assertEqual(0, count(?TEST_SINK_EVENT)),
+ ?TEST_SINK_NAME:debug("this message will be ignored"),
+ ?assertEqual(0, count(?TEST_SINK_EVENT)),
+ ?assertEqual(0, count_ignored(?TEST_SINK_EVENT)),
+ lager_config:set({?TEST_SINK_EVENT, loglevel}, {element(2, lager_util:config_to_mask(debug)), []}),
+ ?TEST_SINK_NAME:debug("this message should be ignored"),
+ ?assertEqual(0, count(?TEST_SINK_EVENT)),
+ ?assertEqual(1, count_ignored(?TEST_SINK_EVENT)),
+ lager:set_loglevel(?TEST_SINK_EVENT, ?MODULE, undefined, debug),
+ ?assertEqual({?DEBUG bor ?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get({?TEST_SINK_EVENT, loglevel})),
+ ?TEST_SINK_NAME:debug("this message should be logged"),
+ ?assertEqual(1, count(?TEST_SINK_EVENT)),
+ ?assertEqual(1, count_ignored(?TEST_SINK_EVENT)),
+ ?assertEqual(debug, lager:get_loglevel(?TEST_SINK_EVENT, ?MODULE)),
+ ok
+ end
+ }
+ ]
+ }.
+
+setup_sink() ->
+ error_logger:tty(false),
+ application:load(lager),
+ application:set_env(lager, handlers, []),
+ application:set_env(lager, error_logger_redirect, false),
+ application:set_env(lager, extra_sinks, [{?TEST_SINK_EVENT, [{handlers, [{?MODULE, info}]}]}]),
+ lager:start(),
+ gen_event:call(lager_event, ?MODULE, flush),
+ gen_event:call(?TEST_SINK_EVENT, ?MODULE, flush).
+
setup() ->
error_logger:tty(false),
application:load(lager),
application:set_env(lager, handlers, [{?MODULE, info}]),
application:set_env(lager, error_logger_redirect, false),
- application:start(compiler),
- application:start(syntax_tools),
- application:start(lager),
+ lager:start(),
gen_event:call(lager_event, ?MODULE, flush).
cleanup(_) ->
application:stop(lager),
+ application:stop(goldrush),
error_logger:tty(true).
@@ -241,21 +743,74 @@ crash(Type) ->
_ = gen_event:which_handlers(error_logger),
ok.
+test_body(Expected, Actual) ->
+ case has_line_numbers() of
+ true ->
+ ExLen = length(Expected),
+ {Body, Rest} = case length(Actual) > ExLen of
+ true ->
+ {string:substr(Actual, 1, ExLen),
+ string:substr(Actual, (ExLen + 1))};
+ _ ->
+ {Actual, []}
+ end,
+ ?assertEqual(Expected, Body),
+ % OTP-17 (and maybe later releases) may tack on additional info
+ % about the failure, so if Actual starts with Expected (already
+ % confirmed by having gotten past assertEqual above) and ends
+ % with " line NNN" we can ignore what's in-between. By extension,
+ % since there may not be line information appended at all, any
+ % text we DO find is reportable, but not a test failure.
+ case Rest of
+ [] ->
+ ok;
+ _ ->
+ % isolate the extra data and report it if it's not just
+ % a line number indicator
+ case re:run(Rest, "^.*( line \\d+)$", [{capture, [1]}]) of
+ nomatch ->
+ ?debugFmt(
+ "Trailing data \"~s\" following \"~s\"",
+ [Rest, Expected]);
+ {match, [{0, _}]} ->
+ % the whole sting is " line NNN"
+ ok;
+ {match, [{Off, _}]} ->
+ ?debugFmt(
+ "Trailing data \"~s\" following \"~s\"",
+ [string:substr(Rest, 1, Off), Expected])
+ end
+ end;
+ _ ->
+ ?assertEqual(Expected, Actual)
+ end.
+
+
error_logger_redirect_crash_test_() ->
+ TestBody=fun(Name,CrashReason,Expected) -> {Name,
+ fun() ->
+ Pid = whereis(crash),
+ crash(CrashReason),
+ {Level, _, Msg,Metadata} = pop(),
+ test_body(Expected, lists:flatten(Msg)),
+ ?assertEqual(Pid,proplists:get_value(pid,Metadata)),
+ ?assertEqual(lager_util:level_to_num(error),Level)
+ end
+ }
+ end,
{foreach,
fun() ->
error_logger:tty(false),
application:load(lager),
application:set_env(lager, error_logger_redirect, true),
application:set_env(lager, handlers, [{?MODULE, error}]),
- application:start(compiler),
- application:start(syntax_tools),
- application:start(lager),
+ lager:start(),
crash:start()
end,
fun(_) ->
application:stop(lager),
+ application:stop(goldrush),
case whereis(crash) of
undefined -> ok;
Pid -> exit(Pid, kill)
@@ -269,142 +824,24 @@ error_logger_redirect_crash_test_() ->
?assertEqual(0, count())
end
},
- {"bad return value",
- fun() ->
- Pid = whereis(crash),
- crash(bad_return),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: bad return value: bleh", [Pid])),
- ?assertEqual(Expected, lists:flatten(Msg))
- end
- },
- {"bad return value with string",
- fun() ->
- Pid = whereis(crash),
- crash(bad_return_string),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: bad return value: {tuple,{tuple,\"string\"}}", [Pid])),
- ?assertEqual(Expected, lists:flatten(Msg))
- end
- },
- {"case clause",
- fun() ->
- Pid = whereis(crash),
- crash(case_clause),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: no case clause matching {} in crash:handle_call/3", [Pid])),
- ?assertEqual(Expected, lists:flatten(Msg))
- end
- },
- {"case clause string",
- fun() ->
- Pid = whereis(crash),
- crash(case_clause_string),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: no case clause matching \"crash\" in crash:handle_call/3", [Pid])),
- ?assertEqual(Expected, lists:flatten(Msg))
- end
- },
- {"function clause",
- fun() ->
- Pid = whereis(crash),
- crash(function_clause),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: no function clause matching crash:function({})", [Pid])),
- ?assertEqual(Expected, lists:flatten(Msg))
- end
- },
- {"if clause",
- fun() ->
- Pid = whereis(crash),
- crash(if_clause),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: no true branch found while evaluating if expression in crash:handle_call/3", [Pid])),
- ?assertEqual(Expected, lists:flatten(Msg))
- end
- },
- {"try clause",
- fun() ->
- Pid = whereis(crash),
- crash(try_clause),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: no try clause matching [] in crash:handle_call/3", [Pid])),
- ?assertEqual(Expected, lists:flatten(Msg))
- end
- },
- {"undefined function",
- fun() ->
- Pid = whereis(crash),
- crash(undef),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: call to undefined function crash:booger/0 from crash:handle_call/3", [Pid])),
- ?assertEqual(Expected, lists:flatten(Msg))
- end
- },
- {"bad math",
- fun() ->
- Pid = whereis(crash),
- crash(badarith),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: bad arithmetic expression in crash:handle_call/3", [Pid])),
- ?assertEqual(Expected, lists:flatten(Msg))
- end
- },
- {"bad match",
- fun() ->
- Pid = whereis(crash),
- crash(badmatch),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: no match of right hand value {} in crash:handle_call/3", [Pid])),
- ?assertEqual(Expected, lists:flatten(Msg))
- end
- },
- {"bad arity",
- fun() ->
- Pid = whereis(crash),
- crash(badarity),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: fun called with wrong arity of 1 instead of 3 in crash:handle_call/3", [Pid])),
- ?assertEqual(Expected, lists:flatten(Msg))
- end
- },
- {"bad arg1",
- fun() ->
- Pid = whereis(crash),
- crash(badarg1),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: bad argument in crash:handle_call/3", [Pid])),
- ?assertEqual(Expected, lists:flatten(Msg))
- end
- },
- {"bad arg2",
- fun() ->
- Pid = whereis(crash),
- crash(badarg2),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: bad argument in call to erlang:iolist_to_binary([\"foo\",bar]) in crash:handle_call/3", [Pid])),
- ?assertEqual(Expected, lists:flatten(Msg))
- end
- },
- {"noproc",
- fun() ->
- Pid = whereis(crash),
- crash(noproc),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: no such process or port in call to gen_event:call(foo, bar, baz)", [Pid])),
- ?assertEqual(Expected, lists:flatten(Msg))
- end
- },
- {"badfun",
- fun() ->
- Pid = whereis(crash),
- crash(badfun),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: bad function booger in crash:handle_call/3", [Pid])),
- ?assertEqual(Expected, lists:flatten(Msg))
- end
- }
+ TestBody("bad return value",bad_return,"gen_server crash terminated with reason: bad return value: bleh"),
+ TestBody("bad return value with string",bad_return_string,"gen_server crash terminated with reason: bad return value: {tuple,{tuple,\"string\"}}"),
+ TestBody("bad return uncaught throw",throw,"gen_server crash terminated with reason: bad return value: a_ball"),
+ TestBody("case clause",case_clause,"gen_server crash terminated with reason: no case clause matching {} in crash:handle_call/3"),
+ TestBody("case clause string",case_clause_string,"gen_server crash terminated with reason: no case clause matching \"crash\" in crash:handle_call/3"),
+ TestBody("function clause",function_clause,"gen_server crash terminated with reason: no function clause matching crash:function({})"),
+ TestBody("if clause",if_clause,"gen_server crash terminated with reason: no true branch found while evaluating if expression in crash:handle_call/3"),
+ TestBody("try clause",try_clause,"gen_server crash terminated with reason: no try clause matching [] in crash:handle_call/3"),
+ TestBody("undefined function",undef,"gen_server crash terminated with reason: call to undefined function crash:booger/0 from crash:handle_call/3"),
+ TestBody("bad math",badarith,"gen_server crash terminated with reason: bad arithmetic expression in crash:handle_call/3"),
+ TestBody("bad match",badmatch,"gen_server crash terminated with reason: no match of right hand value {} in crash:handle_call/3"),
+ TestBody("bad arity",badarity,"gen_server crash terminated with reason: fun called with wrong arity of 1 instead of 3 in crash:handle_call/3"),
+ TestBody("bad arg1",badarg1,"gen_server crash terminated with reason: bad argument in crash:handle_call/3"),
+ TestBody("bad arg2",badarg2,"gen_server crash terminated with reason: bad argument in call to erlang:iolist_to_binary([\"foo\",bar]) in crash:handle_call/3"),
+ TestBody("bad record",badrecord,"gen_server crash terminated with reason: bad record state in crash:handle_call/3"),
+ TestBody("noproc",noproc,"gen_server crash terminated with reason: no such process or port in call to gen_event:call(foo, bar, baz)"),
+ TestBody("badfun",badfun,"gen_server crash terminated with reason: bad function booger in crash:handle_call/3")
]
}.
@@ -415,7 +852,7 @@ error_logger_redirect_test_() ->
application:load(lager),
application:set_env(lager, error_logger_redirect, true),
application:set_env(lager, handlers, [{?MODULE, info}]),
- application:start(lager),
+ lager:start(),
lager:log(error, self(), "flush flush"),
timer:sleep(100),
gen_event:call(lager_event, ?MODULE, flush)
@@ -423,6 +860,7 @@ error_logger_redirect_test_() ->
fun(_) ->
application:stop(lager),
+ application:stop(goldrush),
error_logger:tty(true)
end,
[
@@ -430,17 +868,22 @@ error_logger_redirect_test_() ->
fun() ->
sync_error_logger:error_report([{this, is}, a, {silly, format}]),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w this: is, a, silly: format", [self()])),
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(error),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ Expected = "this: is, a, silly: format",
?assertEqual(Expected, lists:flatten(Msg))
+
end
},
{"string error reports are printed",
fun() ->
sync_error_logger:error_report("this is less silly"),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w this is less silly", [self()])),
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(error),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ Expected = "this is less silly",
?assertEqual(Expected, lists:flatten(Msg))
end
},
@@ -448,25 +891,40 @@ error_logger_redirect_test_() ->
fun() ->
sync_error_logger:error_msg("doom, doom has come upon you all"),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w doom, doom has come upon you all", [self()])),
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(error),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ Expected = "doom, doom has come upon you all",
?assertEqual(Expected, lists:flatten(Msg))
end
},
+ {"error messages with unicode characters in Args are printed",
+ fun() ->
+ sync_error_logger:error_msg("~ts", ["Привет!"]),
+ _ = gen_event:which_handlers(error_logger),
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(error),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ ?assertEqual("Привет!", lists:flatten(Msg))
+ end
+ },
{"error messages are truncated at 4096 characters",
fun() ->
sync_error_logger:error_msg("doom, doom has come upon you all ~p", [string:copies("doom", 10000)]),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
+ {_, _, Msg,_Metadata} = pop(),
?assert(length(lists:flatten(Msg)) < 5100)
end
},
+
{"info reports are printed",
fun() ->
sync_error_logger:info_report([{this, is}, a, {silly, format}]),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[info] ~w this: is, a, silly: format", [self()])),
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(info),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ Expected = "this: is, a, silly: format",
?assertEqual(Expected, lists:flatten(Msg))
end
},
@@ -474,7 +932,9 @@ error_logger_redirect_test_() ->
fun() ->
sync_error_logger:info_report([[{this, is}, a, {silly, format}] || _ <- lists:seq(0, 600)]),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(info),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
?assert(length(lists:flatten(Msg)) < 5000)
end
},
@@ -482,34 +942,39 @@ error_logger_redirect_test_() ->
fun() ->
sync_error_logger:info_report({foolish, bees}),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[info] ~w {foolish,bees}", [self()])),
- ?assertEqual(Expected, lists:flatten(Msg))
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(info),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ ?assertEqual("{foolish,bees}", lists:flatten(Msg))
end
},
{"single term error reports are printed",
fun() ->
sync_error_logger:error_report({foolish, bees}),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w {foolish,bees}", [self()])),
- ?assertEqual(Expected, lists:flatten(Msg))
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(error),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ ?assertEqual("{foolish,bees}", lists:flatten(Msg))
end
},
{"string info reports are printed",
fun() ->
sync_error_logger:info_report("this is less silly"),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[info] ~w this is less silly", [self()])),
- ?assertEqual(Expected, lists:flatten(Msg))
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(info),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ ?assertEqual("this is less silly", lists:flatten(Msg))
end
},
{"string info reports are truncated at 4096 characters",
fun() ->
sync_error_logger:info_report(string:copies("this is less silly", 1000)),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(info),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
?assert(length(lists:flatten(Msg)) < 5100)
end
},
@@ -517,37 +982,63 @@ error_logger_redirect_test_() ->
fun() ->
sync_error_logger:info_report(["this is less silly", {than, "this"}]),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[info] ~w \"this is less silly\", than: \"this\"", [self()])),
- ?assertEqual(Expected, lists:flatten(Msg))
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(info),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ ?assertEqual("\"this is less silly\", than: \"this\"", lists:flatten(Msg))
end
},
{"info messages are printed",
fun() ->
sync_error_logger:info_msg("doom, doom has come upon you all"),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[info] ~w doom, doom has come upon you all", [self()])),
- ?assertEqual(Expected, lists:flatten(Msg))
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(info),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ ?assertEqual("doom, doom has come upon you all", lists:flatten(Msg))
end
},
{"info messages are truncated at 4096 characters",
fun() ->
sync_error_logger:info_msg("doom, doom has come upon you all ~p", [string:copies("doom", 10000)]),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(info),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
?assert(length(lists:flatten(Msg)) < 5100)
end
},
+ {"info messages with unicode characters in Args are printed",
+ fun() ->
+ sync_error_logger:info_msg("~ts", ["Привет!"]),
+ _ = gen_event:which_handlers(error_logger),
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(info),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ ?assertEqual("Привет!", lists:flatten(Msg))
+ end
+ },
+ {"warning messages with unicode characters in Args are printed",
+ fun() ->
+ sync_error_logger:warning_msg("~ts", ["Привет!"]),
+ Map = error_logger:warning_map(),
+ _ = gen_event:which_handlers(error_logger),
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(Map),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ ?assertEqual("Привет!", lists:flatten(Msg))
+ end
+ },
{"warning messages are printed at the correct level",
fun() ->
sync_error_logger:warning_msg("doom, doom has come upon you all"),
Map = error_logger:warning_map(),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[~w] ~w doom, doom has come upon you all", [Map, self()])),
- ?assertEqual(Expected, lists:flatten(Msg))
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(Map),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ ?assertEqual("doom, doom has come upon you all", lists:flatten(Msg))
end
},
{"warning reports are printed at the correct level",
@@ -555,9 +1046,10 @@ error_logger_redirect_test_() ->
sync_error_logger:warning_report([{i, like}, pie]),
Map = error_logger:warning_map(),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[~w] ~w i: like, pie", [Map, self()])),
- ?assertEqual(Expected, lists:flatten(Msg))
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(Map),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ ?assertEqual("i: like, pie", lists:flatten(Msg))
end
},
{"single term warning reports are printed at the correct level",
@@ -565,36 +1057,51 @@ error_logger_redirect_test_() ->
sync_error_logger:warning_report({foolish, bees}),
Map = error_logger:warning_map(),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[~w] ~w {foolish,bees}", [Map, self()])),
- ?assertEqual(Expected, lists:flatten(Msg))
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(Map),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ ?assertEqual("{foolish,bees}", lists:flatten(Msg))
end
},
{"application stop reports",
fun() ->
sync_error_logger:info_report([{application, foo}, {exited, quittin_time}, {type, lazy}]),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[info] ~w Application foo exited with reason: quittin_time", [self()])),
- ?assertEqual(Expected, lists:flatten(Msg))
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(info),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ ?assertEqual("Application foo exited with reason: quittin_time", lists:flatten(Msg))
end
},
{"supervisor reports",
fun() ->
sync_error_logger:error_report(supervisor_report, [{errorContext, france}, {offender, [{name, mini_steve}, {mfargs, {a, b, [c]}}, {pid, bleh}]}, {reason, fired}, {supervisor, {local, steve}}]),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w Supervisor steve had child mini_steve started with a:b(c) at bleh exit with reason fired in context france", [self()])),
- ?assertEqual(Expected, lists:flatten(Msg))
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(error),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ ?assertEqual("Supervisor steve had child mini_steve started with a:b(c) at bleh exit with reason fired in context france", lists:flatten(Msg))
end
},
{"supervisor reports with real error",
fun() ->
sync_error_logger:error_report(supervisor_report, [{errorContext, france}, {offender, [{name, mini_steve}, {mfargs, {a, b, [c]}}, {pid, bleh}]}, {reason, {function_clause,[{crash,handle_info,[foo]}]}}, {supervisor, {local, steve}}]),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w Supervisor steve had child mini_steve started with a:b(c) at bleh exit with reason no function clause matching crash:handle_info(foo) in context france", [self()])),
- ?assertEqual(Expected, lists:flatten(Msg))
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(error),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ ?assertEqual("Supervisor steve had child mini_steve started with a:b(c) at bleh exit with reason no function clause matching crash:handle_info(foo) in context france", lists:flatten(Msg))
+ end
+ },
+
+ {"supervisor reports with real error and pid",
+ fun() ->
+ sync_error_logger:error_report(supervisor_report, [{errorContext, france}, {offender, [{name, mini_steve}, {mfargs, {a, b, [c]}}, {pid, bleh}]}, {reason, {function_clause,[{crash,handle_info,[foo]}]}}, {supervisor, somepid}]),
+ _ = gen_event:which_handlers(error_logger),
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(error),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ ?assertEqual("Supervisor somepid had child mini_steve started with a:b(c) at bleh exit with reason no function clause matching crash:handle_info(foo) in context france", lists:flatten(Msg))
end
},
@@ -602,120 +1109,259 @@ error_logger_redirect_test_() ->
fun() ->
sync_error_logger:error_report(supervisor_report, [{errorContext, france}, {offender, [{mod, mini_steve}, {pid, bleh}]}, {reason, fired}, {supervisor, {local, steve}}]),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w Supervisor steve had child at module mini_steve at bleh exit with reason fired in context france", [self()])),
- ?assertEqual(Expected, lists:flatten(Msg))
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(error),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ ?assertEqual("Supervisor steve had child at module mini_steve at bleh exit with reason fired in context france", lists:flatten(Msg))
end
},
{"application progress report",
fun() ->
sync_error_logger:info_report(progress, [{application, foo}, {started_at, node()}]),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[info] ~w Application foo started on node ~w", [self(), node()])),
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(info),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ Expected = lists:flatten(io_lib:format("Application foo started on node ~w", [node()])),
?assertEqual(Expected, lists:flatten(Msg))
end
},
{"supervisor progress report",
fun() ->
lager:set_loglevel(?MODULE, debug),
+ ?assertEqual({?DEBUG bor ?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get(loglevel)),
sync_error_logger:info_report(progress, [{supervisor, {local, foo}}, {started, [{mfargs, {foo, bar, 1}}, {pid, baz}]}]),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[debug] ~w Supervisor foo started foo:bar/1 at pid baz", [self()])),
- ?assertEqual(Expected, lists:flatten(Msg))
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(debug),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ ?assertEqual("Supervisor foo started foo:bar/1 at pid baz", lists:flatten(Msg))
+ end
+ },
+ {"supervisor progress report with pid",
+ fun() ->
+ lager:set_loglevel(?MODULE, debug),
+ ?assertEqual({?DEBUG bor ?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get(loglevel)),
+ sync_error_logger:info_report(progress, [{supervisor, somepid}, {started, [{mfargs, {foo, bar, 1}}, {pid, baz}]}]),
+ _ = gen_event:which_handlers(error_logger),
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(debug),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ ?assertEqual("Supervisor somepid started foo:bar/1 at pid baz", lists:flatten(Msg))
end
},
{"crash report for emfile",
fun() ->
- sync_error_logger:error_report(crash_report, [[{pid, self()}, {registered_name, []}, {error_info, {error, {emfile, [{stack, trace, 1}]}, []}}], []]),
+ sync_error_logger:error_report(crash_report, [[{pid, self()}, {registered_name, []}, {error_info, {error, emfile, [{stack, trace, 1}]}}], []]),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w CRASH REPORT Process ~w with 0 neighbours crashed with reason: maximum number of file descriptors exhausted, check ulimit -n", [self(), self()])),
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(error),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~w with 0 neighbours crashed with reason: maximum number of file descriptors exhausted, check ulimit -n", [self()])),
?assertEqual(Expected, lists:flatten(Msg))
end
},
{"crash report for system process limit",
fun() ->
- sync_error_logger:error_report(crash_report, [[{pid, self()}, {registered_name, []}, {error_info, {error, {system_limit, [{erlang, spawn, 1}]}, []}}], []]),
+ sync_error_logger:error_report(crash_report, [[{pid, self()}, {registered_name, []}, {error_info, {error, system_limit, [{erlang, spawn, 1}]}}], []]),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: maximum number of processes exceeded", [self(), self()])),
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(error),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: maximum number of processes exceeded", [self()])),
?assertEqual(Expected, lists:flatten(Msg))
end
},
{"crash report for system process limit2",
fun() ->
- sync_error_logger:error_report(crash_report, [[{pid, self()}, {registered_name, []}, {error_info, {error, {system_limit, [{erlang, spawn_opt, 1}]}, []}}], []]),
+ sync_error_logger:error_report(crash_report, [[{pid, self()}, {registered_name, []}, {error_info, {error, system_limit, [{erlang, spawn_opt, 1}]}}], []]),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: maximum number of processes exceeded", [self(), self()])),
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(error),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: maximum number of processes exceeded", [self()])),
?assertEqual(Expected, lists:flatten(Msg))
end
},
{"crash report for system port limit",
fun() ->
- sync_error_logger:error_report(crash_report, [[{pid, self()}, {registered_name, []}, {error_info, {error, {system_limit, [{erlang, open_port, 1}]}, []}}], []]),
+ sync_error_logger:error_report(crash_report, [[{pid, self()}, {registered_name, []}, {error_info, {error, system_limit, [{erlang, open_port, 1}]}}], []]),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: maximum number of ports exceeded", [self(), self()])),
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(error),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: maximum number of ports exceeded", [self()])),
?assertEqual(Expected, lists:flatten(Msg))
end
},
{"crash report for system port limit",
fun() ->
- sync_error_logger:error_report(crash_report, [[{pid, self()}, {registered_name, []}, {error_info, {error, {system_limit, [{erlang, list_to_atom, 1}]}, []}}], []]),
+ sync_error_logger:error_report(crash_report, [[{pid, self()}, {registered_name, []}, {error_info, {error, system_limit, [{erlang, list_to_atom, 1}]}}], []]),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: tried to create an atom larger than 255, or maximum atom count exceeded", [self(), self()])),
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(error),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: tried to create an atom larger than 255, or maximum atom count exceeded", [self()])),
?assertEqual(Expected, lists:flatten(Msg))
end
},
{"crash report for system ets table limit",
fun() ->
- sync_error_logger:error_report(crash_report, [[{pid, self()}, {registered_name, test}, {error_info, {error, {system_limit,[{ets,new,[segment_offsets,[ordered_set,public]]},{mi_segment,open_write,1},{mi_buffer_converter,handle_cast,2},{gen_server,handle_msg,5},{proc_lib,init_p_do_apply,3}]}, []}}], []]),
+ sync_error_logger:error_report(crash_report, [[{pid, self()}, {registered_name, test}, {error_info, {error, system_limit, [{ets,new,[segment_offsets,[ordered_set,public]]},{mi_segment,open_write,1},{mi_buffer_converter,handle_cast,2},{gen_server,handle_msg,5},{proc_lib,init_p_do_apply,3}]}}], []]),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~w CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: maximum number of ETS tables exceeded", [self(), test])),
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(error),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: maximum number of ETS tables exceeded", [test])),
?assertEqual(Expected, lists:flatten(Msg))
end
},
{"crash report for unknown system limit should be truncated at 500 characters",
fun() ->
- sync_error_logger:error_report(crash_report, [[{pid, self()}, {error_info, {error, {system_limit,[{wtf,boom,[string:copies("aaaa", 4096)]}]}, []}}], []]),
+ sync_error_logger:error_report(crash_report, [[{pid, self()}, {error_info, {error, system_limit, [{wtf,boom,[string:copies("aaaa", 4096)]}]}}], []]),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- ?assert(length(lists:flatten(Msg)) > 600),
- ?assert(length(lists:flatten(Msg)) < 650)
+ {_, _, Msg,_Metadata} = pop(),
+ ?assert(length(lists:flatten(Msg)) > 550),
+ ?assert(length(lists:flatten(Msg)) < 600)
end
},
- {"crash reports for 'special processes' should be handled right",
+ {"crash reports for 'special processes' should be handled right - function_clause",
fun() ->
{ok, Pid} = special_process:start(),
unlink(Pid),
Pid ! function_clause,
timer:sleep(500),
_ = gen_event:which_handlers(error_logger),
- {_, _, Msg} = pop(),
- Expected = lists:flatten(io_lib:format("[error] ~p CRASH REPORT Process ~p with 0 neighbours crashed with reason: no function clause matching special_process:foo(bar)",
- [Pid, Pid])),
- ?assertEqual(Expected, lists:flatten(Msg))
+ {_, _, Msg, _Metadata} = pop(),
+ Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~p with 0 neighbours crashed with reason: no function clause matching special_process:foo(bar)",
+ [Pid])),
+ test_body(Expected, lists:flatten(Msg))
+ end
+ },
+ {"crash reports for 'special processes' should be handled right - case_clause",
+ fun() ->
+ {ok, Pid} = special_process:start(),
+ unlink(Pid),
+ Pid ! {case_clause, wtf},
+ timer:sleep(500),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg, _Metadata} = pop(),
+ Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~p with 0 neighbours crashed with reason: no case clause matching wtf in special_process:loop/0",
+ [Pid])),
+ test_body(Expected, lists:flatten(Msg))
+ end
+ },
+ {"crash reports for 'special processes' should be handled right - exit",
+ fun() ->
+ {ok, Pid} = special_process:start(),
+ unlink(Pid),
+ Pid ! exit,
+ timer:sleep(500),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg, _Metadata} = pop(),
+ Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~p with 0 neighbours exited with reason: byebye in special_process:loop/0",
+ [Pid])),
+ test_body(Expected, lists:flatten(Msg))
+ end
+ },
+ {"crash reports for 'special processes' should be handled right - error",
+ fun() ->
+ {ok, Pid} = special_process:start(),
+ unlink(Pid),
+ Pid ! error,
+ timer:sleep(500),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg, _Metadata} = pop(),
+ Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~p with 0 neighbours crashed with reason: mybad in special_process:loop/0",
+ [Pid])),
+ test_body(Expected, lists:flatten(Msg))
+ end
+ },
+ {"webmachine error reports",
+ fun() ->
+ Path = "/cgi-bin/phpmyadmin",
+ Reason = {error,{error,{badmatch,{error,timeout}},
+ [{myapp,dostuff,2,[{file,"src/myapp.erl"},{line,123}]},
+ {webmachine_resource,resource_call,3,[{file,"src/webmachine_resource.erl"},{line,169}]}]}},
+ sync_error_logger:error_msg("webmachine error: path=~p~n~p~n", [Path, Reason]),
+ _ = gen_event:which_handlers(error_logger),
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(error),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ ?assertEqual("Webmachine error at path \"/cgi-bin/phpmyadmin\" : no match of right hand value {error,timeout} in myapp:dostuff/2 line 123", lists:flatten(Msg))
+
+ end
+ },
+ {"Cowboy error reports, 8 arg version",
+ fun() ->
+ Stack = [{my_handler,init, 3,[{file,"src/my_handler.erl"},{line,123}]},
+ {cowboy_handler,handler_init,4,[{file,"src/cowboy_handler.erl"},{line,169}]}],
+
+ sync_error_logger:error_msg(
+ "** Cowboy handler ~p terminating in ~p/~p~n"
+ " for the reason ~p:~p~n"
+ "** Options were ~p~n"
+ "** Request was ~p~n"
+ "** Stacktrace: ~p~n~n",
+ [my_handler, init, 3, error, {badmatch, {error, timeout}}, [],
+ "Request", Stack]),
+ _ = gen_event:which_handlers(error_logger),
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(error),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ ?assertEqual("Cowboy handler my_handler terminated in my_handler:init/3 with reason: no match of right hand value {error,timeout} in my_handler:init/3 line 123", lists:flatten(Msg))
+ end
+ },
+ {"Cowboy error reports, 10 arg version",
+ fun() ->
+ Stack = [{my_handler,somecallback, 3,[{file,"src/my_handler.erl"},{line,123}]},
+ {cowboy_handler,handler_init,4,[{file,"src/cowboy_handler.erl"},{line,169}]}],
+ sync_error_logger:error_msg(
+ "** Cowboy handler ~p terminating in ~p/~p~n"
+ " for the reason ~p:~p~n** Message was ~p~n"
+ "** Options were ~p~n** Handler state was ~p~n"
+ "** Request was ~p~n** Stacktrace: ~p~n~n",
+ [my_handler, somecallback, 3, error, {badmatch, {error, timeout}}, hello, [],
+ {}, "Request", Stack]),
+
+ _ = gen_event:which_handlers(error_logger),
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(error),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ ?assertEqual("Cowboy handler my_handler terminated in my_handler:somecallback/3 with reason: no match of right hand value {error,timeout} in my_handler:somecallback/3 line 123", lists:flatten(Msg))
+ end
+ },
+ {"Cowboy error reports, 5 arg version",
+ fun() ->
+ sync_error_logger:error_msg(
+ "** Cowboy handler ~p terminating; "
+ "function ~p/~p was not exported~n"
+ "** Request was ~p~n** State was ~p~n~n",
+ [my_handler, to_json, 2, "Request", {}]),
+ _ = gen_event:which_handlers(error_logger),
+ {Level, _, Msg,Metadata} = pop(),
+ ?assertEqual(lager_util:level_to_num(error),Level),
+ ?assertEqual(self(),proplists:get_value(pid,Metadata)),
+ ?assertEqual("Cowboy handler my_handler terminated with reason: call to undefined function my_handler:to_json/2", lists:flatten(Msg))
end
},
{"messages should not be generated if they don't satisfy the threshold",
fun() ->
lager:set_loglevel(?MODULE, error),
+ ?assertEqual({?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get(loglevel)),
sync_error_logger:info_report([hello, world]),
_ = gen_event:which_handlers(error_logger),
?assertEqual(0, count()),
?assertEqual(0, count_ignored()),
lager:set_loglevel(?MODULE, info),
+ ?assertEqual({?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get(loglevel)),
sync_error_logger:info_report([hello, world]),
_ = gen_event:which_handlers(error_logger),
?assertEqual(1, count()),
?assertEqual(0, count_ignored()),
lager:set_loglevel(?MODULE, error),
- lager_mochiglobal:put(loglevel, {?DEBUG, []}),
+ ?assertEqual({?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get(loglevel)),
+ lager_config:set(loglevel, {element(2, lager_util:config_to_mask(debug)), []}),
sync_error_logger:info_report([hello, world]),
_ = gen_event:which_handlers(error_logger),
?assertEqual(1, count()),
@@ -730,6 +1376,136 @@ safe_format_test() ->
?assertEqual("FORMAT ERROR: \"~p ~p ~p\" [foo,bar]", lists:flatten(lager:safe_format("~p ~p ~p", [foo, bar], 1024))),
ok.
--endif.
+unsafe_format_test() ->
+ ?assertEqual("foo bar", lists:flatten(lager:unsafe_format("~p ~p", [foo, bar]))),
+ ?assertEqual("FORMAT ERROR: \"~p ~p ~p\" [foo,bar]", lists:flatten(lager:unsafe_format("~p ~p ~p", [foo, bar]))),
+ ok.
+async_threshold_test_() ->
+ {foreach,
+ fun() ->
+ error_logger:tty(false),
+ ets:new(async_threshold_test, [set, named_table, public]),
+ ets:insert_new(async_threshold_test, {sync_toggled, 0}),
+ ets:insert_new(async_threshold_test, {async_toggled, 0}),
+ application:load(lager),
+ application:set_env(lager, error_logger_redirect, false),
+ application:set_env(lager, async_threshold, 2),
+ application:set_env(lager, async_threshold_window, 1),
+ application:set_env(lager, handlers, [{?MODULE, info}]),
+ lager:start()
+ end,
+ fun(_) ->
+ application:unset_env(lager, async_threshold),
+ application:stop(lager),
+ application:stop(goldrush),
+ ets:delete(async_threshold_test),
+ error_logger:tty(true)
+ end,
+ [
+ {"async threshold works",
+ fun() ->
+ %% we start out async
+ ?assertEqual(true, lager_config:get(async)),
+
+ %% put a ton of things in the queue
+ Workers = [spawn_monitor(fun() -> [lager:info("hello world") || _ <- lists:seq(1, 1000)] end) || _ <- lists:seq(1, 15)],
+
+ %% serialize on mailbox
+ _ = gen_event:which_handlers(lager_event),
+ timer:sleep(500),
+ %% By now the flood of messages will have
+ %% forced the backend throttle to turn off
+ %% async mode, but it's possible all
+ %% outstanding requests have been processed,
+ %% so checking the current status (sync or
+ %% async) is an exercise in race control.
+
+ %% Instead, we'll see whether the backend
+ %% throttle has toggled into sync mode at any
+ %% point in the past
+ ?assertMatch([{sync_toggled, N}] when N > 0,
+ ets:lookup(async_threshold_test, sync_toggled)),
+ %% wait for all the workers to return, meaning that all the messages have been logged (since we're definitely in sync mode at the end of the run)
+ collect_workers(Workers),
+ %% serialize on the mailbox again
+ _ = gen_event:which_handlers(lager_event),
+ %% just in case...
+ timer:sleep(1000),
+ lager:info("hello world"),
+ _ = gen_event:which_handlers(lager_event),
+
+ %% async is true again now that the mailbox has drained
+ ?assertEqual(true, lager_config:get(async)),
+ ok
+ end
+ }
+ ]
+ }.
+
+collect_workers([]) ->
+ ok;
+collect_workers(Workers) ->
+ receive
+ {'DOWN', Ref, _, _, _} ->
+ collect_workers(lists:keydelete(Ref, 2, Workers))
+ end.
+
+produce_n_error_logger_msgs(N) ->
+ lists:foreach(fun (K) ->
+ error_logger:error_msg("Foo ~p!", [K])
+ end,
+ lists:seq(0, N-1)
+ ).
+
+high_watermark_test_() ->
+ {foreach,
+ fun() ->
+ error_logger:tty(false),
+ application:load(lager),
+ application:set_env(lager, error_logger_redirect, true),
+ application:set_env(lager, handlers, [{lager_test_backend, info}]),
+ application:set_env(lager, async_threshold, undefined),
+ lager:start()
+ end,
+ fun(_) ->
+ application:stop(lager),
+ error_logger:tty(true)
+ end,
+ [
+ {"Nothing dropped when error_logger high watermark is undefined",
+ fun () ->
+ ok = error_logger_lager_h:set_high_water(undefined),
+ timer:sleep(100),
+ produce_n_error_logger_msgs(10),
+ timer:sleep(500),
+ ?assert(count() >= 10)
+ end
+ },
+ {"Mostly dropped according to error_logger high watermark",
+ fun () ->
+ ok = error_logger_lager_h:set_high_water(5),
+ timer:sleep(100),
+ produce_n_error_logger_msgs(50),
+ timer:sleep(1000),
+ ?assert(count() < 20)
+ end
+ },
+ {"Non-notifications are not dropped",
+ fun () ->
+ ok = error_logger_lager_h:set_high_water(2),
+ timer:sleep(100),
+ spawn(fun () -> produce_n_error_logger_msgs(300) end),
+ timer:sleep(50),
+ %% if everything were dropped, this call would be dropped
+ %% too, so lets hope it's not
+ ?assert(is_integer(count())),
+ timer:sleep(1000),
+ ?assert(count() < 10)
+ end
+ }
+ ]
+ }.
+
+-endif.
diff --git a/deps/lager/test/pr_nested_record_test.erl b/deps/lager/test/pr_nested_record_test.erl
new file mode 100644
index 0000000..9e2fbf5
--- /dev/null
+++ b/deps/lager/test/pr_nested_record_test.erl
@@ -0,0 +1,19 @@
+-module(pr_nested_record_test).
+
+-compile([{parse_transform, lager_transform}]).
+
+-record(a, {field1 :: term(), field2 :: term()}).
+-record(b, {field1 :: term() , field2 :: term()}).
+
+
+-include_lib("eunit/include/eunit.hrl").
+
+nested_record_test() ->
+ A = #a{field1 = x, field2 = y},
+ B = #b{field1 = A, field2 = {}},
+ Pr_B = lager:pr(B, ?MODULE),
+ ?assertEqual({'$lager_record', b,
+ [{field1, {'$lager_record', a,
+ [{field1, x},{field2, y}]}},
+ {field2, {}}]},
+ Pr_B).
diff --git a/deps/lager/test/special_process.erl b/deps/lager/test/special_process.erl
index 831b950..49d80fa 100644
--- a/deps/lager/test/special_process.erl
+++ b/deps/lager/test/special_process.erl
@@ -19,6 +19,14 @@ loop() ->
error ->
erlang:error(mybad),
loop();
+ {case_clause, X} ->
+ case X of
+ notgonnamatch ->
+ ok;
+ notthiseither ->
+ error
+ end,
+ loop();
_ ->
loop()
end.
diff --git a/deps/lager/test/trunc_io_eqc.erl b/deps/lager/test/trunc_io_eqc.erl
index b363640..9eee35e 100644
--- a/deps/lager/test/trunc_io_eqc.erl
+++ b/deps/lager/test/trunc_io_eqc.erl
@@ -2,7 +2,7 @@
%%
%% trunc_io_eqc: QuickCheck test for trunc_io:format with maxlen
%%
-%% Copyright (c) 2007-2011 Basho Technologies, Inc. All Rights Reserved.
+%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
@@ -23,7 +23,7 @@
-ifdef(TEST).
-ifdef(EQC).
--export([test/0, test/1, check/0, prop_format/0]).
+-export([test/0, test/1, check/0, prop_format/0, prop_equivalence/0]).
-include_lib("eqc/include/eqc.hrl").
-include_lib("eunit/include/eunit.hrl").
@@ -36,9 +36,12 @@
%%====================================================================
eqc_test_() ->
- {timeout, 300,
+ {timeout, 60,
{spawn,
- [?_assertEqual(true, quickcheck(numtests(500, ?QC_OUT(prop_format()))))]
+ [
+ {timeout, 30, ?_assertEqual(true, eqc:quickcheck(eqc:testing_time(14, ?QC_OUT(prop_format()))))},
+ {timeout, 30, ?_assertEqual(true, eqc:quickcheck(eqc:testing_time(14, ?QC_OUT(prop_equivalence()))))}
+ ]
}}.
%%====================================================================
@@ -61,10 +64,10 @@ check() ->
gen_fmt_args() ->
list(oneof([gen_print_str(),
"~~",
- {"~p", gen_any(5)},
+ {"~10000000.p", gen_any(5)},
{"~w", gen_any(5)},
- {"~s", gen_print_str()},
- {"~P", gen_any(5), 4},
+ {"~s", oneof([gen_print_str(), gen_atom(), gen_quoted_atom(), gen_print_bin(), gen_iolist(5)])},
+ {"~1000000.P", gen_any(5), 4},
{"~W", gen_any(5), 4},
{"~i", gen_any(5)},
{"~B", nat()},
@@ -74,7 +77,7 @@ gen_fmt_args() ->
{"~.10#", nat()},
{"~.10+", nat()},
{"~.36B", nat()},
- {"~62P", gen_any(5), 4},
+ {"~1000000.62P", gen_any(5), 4},
{"~c", gen_char()},
{"~tc", gen_char()},
{"~f", real()},
@@ -88,24 +91,40 @@ gen_fmt_args() ->
%% Generates a printable string
gen_print_str() ->
- ?LET(Xs, list(char()), [X || X <- Xs, io_lib:printable_list([X]), X /= $~]).
+ ?LET(Xs, list(char()), [X || X <- Xs, io_lib:printable_list([X]), X /= $~, X < 255]).
+
+gen_print_bin() ->
+ ?LET(Xs, gen_print_str(), list_to_binary(Xs)).
gen_any(MaxDepth) ->
oneof([largeint(),
gen_atom(),
+ gen_quoted_atom(),
nat(),
%real(),
binary(),
+ gen_bitstring(),
gen_pid(),
gen_port(),
gen_ref(),
gen_fun()] ++
[?LAZY(list(gen_any(MaxDepth - 1))) || MaxDepth /= 0] ++
[?LAZY(gen_tuple(gen_any(MaxDepth - 1))) || MaxDepth /= 0]).
-
+
+gen_iolist(0) ->
+ [];
+gen_iolist(Depth) ->
+ list(oneof([gen_char(), gen_print_str(), gen_print_bin(), gen_iolist(Depth-1)])).
+
gen_atom() ->
elements([abc, def, ghi]).
+gen_quoted_atom() ->
+ elements(['abc@bar', '@bar', '10gen']).
+
+gen_bitstring() ->
+ ?LET(XS, binary(), <<XS/binary, 1:7>>).
+
gen_tuple(Gen) ->
?LET(Xs, list(Gen), list_to_tuple(Xs)).
@@ -118,7 +137,7 @@ gen_pid() ->
gen_port() ->
?LAZY(begin
Port = erlang:open_port({spawn, "true"}, []),
- erlang:port_close(Port),
+ catch(erlang:port_close(Port)),
Port
end).
@@ -174,6 +193,23 @@ prop_format() ->
end
end).
+%% Checks for equivalent formatting to io_lib
+prop_equivalence() ->
+ ?FORALL(FmtArgs, gen_fmt_args(),
+ begin
+ {FmtStr, Args} = build_fmt_args(FmtArgs),
+ Expected = lists:flatten(io_lib:format(FmtStr, Args)),
+ Actual = lists:flatten(lager_trunc_io:format(FmtStr, Args, 10485760)),
+ ?WHENFAIL(begin
+ io:format(user, "FmtStr: ~p\n", [FmtStr]),
+ io:format(user, "Args: ~p\n", [Args]),
+ io:format(user, "Expected: ~p\n", [Expected]),
+ io:format(user, "Actual: ~p\n", [Actual])
+ end,
+ Expected == Actual)
+ end).
+
+
%%====================================================================
%% Internal helpers
%%====================================================================
diff --git a/deps/lager/test/zzzz_gh280_crash.erl b/deps/lager/test/zzzz_gh280_crash.erl
new file mode 100644
index 0000000..07d617d
--- /dev/null
+++ b/deps/lager/test/zzzz_gh280_crash.erl
@@ -0,0 +1,33 @@
+%% @doc This test is named zzzz_gh280_crash because it has to be run first and tests are run in
+%% reverse alphabetical order.
+%%
+%% The problem we are attempting to detect here is when log_mf_h is installed as a handler for error_logger
+%% and lager starts up to replace the current handlers with its own. This causes a start up crash because
+%% OTP error logging modules do not have any notion of a lager-style log level.
+-module(zzzz_gh280_crash).
+-compile(export_all).
+
+-include_lib("eunit/include/eunit.hrl").
+
+gh280_crash_test() ->
+ application:stop(lager),
+ application:stop(goldrush),
+
+ error_logger:tty(false),
+ %% see https://github.com/erlang/otp/blob/maint/lib/stdlib/src/log_mf_h.erl#L81
+ %% for an explanation of the init arguments to log_mf_h
+ ok = gen_event:add_sup_handler(error_logger, log_mf_h, log_mf_h:init("/tmp", 10000, 5)),
+ lager:start(),
+ Result = receive
+ {gen_event_EXIT,log_mf_h,normal} ->
+ true;
+ {gen_event_EXIT,Handler,Reason} ->
+ {Handler,Reason};
+ X ->
+ X
+ after 1000 ->
+ timeout
+ end,
+ ?assert(Result),
+ application:stop(lager),
+ application:stop(goldrush).
diff --git a/deps/lager/tools.mk b/deps/lager/tools.mk
new file mode 100644
index 0000000..ab05649
--- /dev/null
+++ b/deps/lager/tools.mk
@@ -0,0 +1,149 @@
+# -------------------------------------------------------------------
+#
+# Copyright (c) 2014 Basho Technologies, Inc.
+#
+# This file is provided to you 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.
+#
+# -------------------------------------------------------------------
+
+# -------------------------------------------------------------------
+# NOTE: This file is is from https://github.com/basho/tools.mk.
+# It should not be edited in a project. It should simply be updated
+# wholesale when a new version of tools.mk is released.
+# -------------------------------------------------------------------
+
+REBAR ?= ./rebar
+REVISION ?= $(shell git rev-parse --short HEAD)
+PROJECT ?= $(shell basename `find src -name "*.app.src"` .app.src)
+
+.PHONY: compile-no-deps test docs xref dialyzer-run dialyzer-quick dialyzer \
+ cleanplt upload-docs
+
+compile-no-deps:
+ ${REBAR} compile skip_deps=true
+
+test: compile
+ ${REBAR} eunit skip_deps=true
+
+upload-docs: docs
+ @if [ -z "${BUCKET}" -o -z "${PROJECT}" -o -z "${REVISION}" ]; then \
+ echo "Set BUCKET, PROJECT, and REVISION env vars to upload docs"; \
+ exit 1; fi
+ @cd doc; s3cmd put -P * "s3://${BUCKET}/${PROJECT}/${REVISION}/" > /dev/null
+ @echo "Docs built at: http://${BUCKET}.s3-website-us-east-1.amazonaws.com/${PROJECT}/${REVISION}"
+
+docs:
+ ${REBAR} doc skip_deps=true
+
+xref: compile
+ ${REBAR} xref skip_deps=true
+
+PLT ?= $(HOME)/.combo_dialyzer_plt
+LOCAL_PLT = .local_dialyzer_plt
+DIALYZER_FLAGS ?= -Wunmatched_returns
+
+${PLT}: compile
+ @if [ -f $(PLT) ]; then \
+ dialyzer --check_plt --plt $(PLT) --apps $(DIALYZER_APPS) && \
+ dialyzer --add_to_plt --plt $(PLT) --output_plt $(PLT) --apps $(DIALYZER_APPS) ; test $$? -ne 1; \
+ else \
+ dialyzer --build_plt --output_plt $(PLT) --apps $(DIALYZER_APPS); test $$? -ne 1; \
+ fi
+
+${LOCAL_PLT}: compile
+ @if [ -d deps ]; then \
+ if [ -f $(LOCAL_PLT) ]; then \
+ dialyzer --check_plt --plt $(LOCAL_PLT) deps/*/ebin && \
+ dialyzer --add_to_plt --plt $(LOCAL_PLT) --output_plt $(LOCAL_PLT) deps/*/ebin ; test $$? -ne 1; \
+ else \
+ dialyzer --build_plt --output_plt $(LOCAL_PLT) deps/*/ebin ; test $$? -ne 1; \
+ fi \
+ fi
+
+dialyzer-run:
+ @echo "==> $(shell basename $(shell pwd)) (dialyzer)"
+# The bulk of the code below deals with the dialyzer.ignore-warnings file
+# which contains strings to ignore if output by dialyzer.
+# Typically the strings include line numbers. Using them exactly is hard
+# to maintain as the code changes. This approach instead ignores the line
+# numbers, but takes into account the number of times a string is listed
+# for a given file. So if one string is listed once, for example, and it
+# appears twice in the warnings, the user is alerted. It is possible but
+# unlikely that this approach could mask a warning if one ignored warning
+# is removed and two warnings of the same kind appear in the file, for
+# example. But it is a trade-off that seems worth it.
+# Details of the cryptic commands:
+# - Remove line numbers from dialyzer.ignore-warnings
+# - Pre-pend duplicate count to each warning with sort | uniq -c
+# - Remove annoying white space around duplicate count
+# - Save in dialyer.ignore-warnings.tmp
+# - Do the same to dialyzer_warnings
+# - Remove matches from dialyzer.ignore-warnings.tmp from output
+# - Remove duplicate count
+# - Escape regex special chars to use lines as regex patterns
+# - Add pattern to match any line number (file.erl:\d+:)
+# - Anchor to match the entire line (^entire line$)
+# - Save in dialyzer_unhandled_warnings
+# - Output matches for those patterns found in the original warnings
+ @if [ -f $(LOCAL_PLT) ]; then \
+ PLTS="$(PLT) $(LOCAL_PLT)"; \
+ else \
+ PLTS=$(PLT); \
+ fi; \
+ if [ -f dialyzer.ignore-warnings ]; then \
+ if [ $$(grep -cvE '[^[:space:]]' dialyzer.ignore-warnings) -ne 0 ]; then \
+ echo "ERROR: dialyzer.ignore-warnings contains a blank/empty line, this will match all messages!"; \
+ exit 1; \
+ fi; \
+ dialyzer $(DIALYZER_FLAGS) --plts $${PLTS} -c ebin > dialyzer_warnings ; \
+ cat dialyzer.ignore-warnings \
+ | sed -E 's/^([^:]+:)[^:]+:/\1/' \
+ | sort \
+ | uniq -c \
+ | sed -E '/.*\.erl: /!s/^[[:space:]]*[0-9]+[[:space:]]*//' \
+ > dialyzer.ignore-warnings.tmp ; \
+ egrep -v "^[[:space:]]*(done|Checking|Proceeding|Compiling)" dialyzer_warnings \
+ | sed -E 's/^([^:]+:)[^:]+:/\1/' \
+ | sort \
+ | uniq -c \
+ | sed -E '/.*\.erl: /!s/^[[:space:]]*[0-9]+[[:space:]]*//' \
+ | grep -F -f dialyzer.ignore-warnings.tmp -v \
+ | sed -E 's/^[[:space:]]*[0-9]+[[:space:]]*//' \
+ | sed -E 's/([]\^:+?|()*.$${}\[])/\\\1/g' \
+ | sed -E 's/(\\\.erl\\\:)/\1\\d+:/g' \
+ | sed -E 's/^(.*)$$/^\1$$/g' \
+ > dialyzer_unhandled_warnings ; \
+ rm dialyzer.ignore-warnings.tmp; \
+ if [ $$(cat dialyzer_unhandled_warnings | wc -l) -gt 0 ]; then \
+ egrep -f dialyzer_unhandled_warnings dialyzer_warnings ; \
+ found_warnings=1; \
+ fi; \
+ [ "$$found_warnings" != 1 ] ; \
+ else \
+ dialyzer $(DIALYZER_FLAGS) --plts $${PLTS} -c ebin; \
+ fi
+
+dialyzer-quick: compile-no-deps dialyzer-run
+
+dialyzer: ${PLT} ${LOCAL_PLT} dialyzer-run
+
+cleanplt:
+ @echo
+ @echo "Are you sure? It takes several minutes to re-build."
+ @echo Deleting $(PLT) and $(LOCAL_PLT) in 5 seconds.
+ @echo
+ sleep 5
+ rm $(PLT)
+ rm $(LOCAL_PLT)