summaryrefslogtreecommitdiff
path: root/deps/lager
diff options
context:
space:
mode:
authorMagnus Feuer <mfeuer@jaguarlandrover.com>2015-04-14 08:26:38 -0700
committerMagnus Feuer <mfeuer@jaguarlandrover.com>2015-04-14 08:26:38 -0700
commit024ab61c898e782dc40e2269ad0f5a23009bcd6b (patch)
tree0fde33ef3c1dbdc54d15c91549a987fc1f47fbf9 /deps/lager
parent7feddc0a5538037acda260ea4dd6b504989bc61c (diff)
downloadrvi_core-024ab61c898e782dc40e2269ad0f5a23009bcd6b.tar.gz
Initial commit of deps
Diffstat (limited to 'deps/lager')
-rw-r--r--deps/lager/LICENSE178
-rw-r--r--deps/lager/Makefile52
-rw-r--r--deps/lager/README.org222
-rw-r--r--deps/lager/TODO3
-rw-r--r--deps/lager/dialyzer.ignore-warnings0
-rw-r--r--deps/lager/include/lager.hrl100
-rw-r--r--deps/lager/include/log.hrl44
-rwxr-xr-xdeps/lager/rebarbin0 -> 108239 bytes
-rw-r--r--deps/lager/rebar.config4
-rw-r--r--deps/lager/src/error_logger_lager_h.erl281
-rw-r--r--deps/lager/src/lager.app.src41
-rw-r--r--deps/lager/src/lager.erl395
-rw-r--r--deps/lager/src/lager_app.erl77
-rw-r--r--deps/lager/src/lager_console_backend.erl273
-rw-r--r--deps/lager/src/lager_crash_log.erl339
-rw-r--r--deps/lager/src/lager_file_backend.erl392
-rw-r--r--deps/lager/src/lager_format.erl523
-rw-r--r--deps/lager/src/lager_handler_watcher.erl159
-rw-r--r--deps/lager/src/lager_handler_watcher_sup.erl39
-rw-r--r--deps/lager/src/lager_mochiglobal.erl107
-rw-r--r--deps/lager/src/lager_stdlib.erl507
-rw-r--r--deps/lager/src/lager_sup.erl80
-rw-r--r--deps/lager/src/lager_transform.erl264
-rw-r--r--deps/lager/src/lager_trunc_io.erl576
-rw-r--r--deps/lager/src/lager_util.erl471
-rw-r--r--deps/lager/test/crash.erl96
-rw-r--r--deps/lager/test/lager_crash_backend.erl68
-rw-r--r--deps/lager/test/lager_test_backend.erl735
-rw-r--r--deps/lager/test/special_process.erl28
-rw-r--r--deps/lager/test/sync_error_logger.erl89
-rw-r--r--deps/lager/test/trunc_io_eqc.erl208
31 files changed, 6351 insertions, 0 deletions
diff --git a/deps/lager/LICENSE b/deps/lager/LICENSE
new file mode 100644
index 0000000..e454a52
--- /dev/null
+++ b/deps/lager/LICENSE
@@ -0,0 +1,178 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/deps/lager/Makefile b/deps/lager/Makefile
new file mode 100644
index 0000000..560bc84
--- /dev/null
+++ b/deps/lager/Makefile
@@ -0,0 +1,52 @@
+.PHONY: rel stagedevrel deps test
+
+all: deps compile
+
+compile:
+ ./rebar compile
+
+deps:
+ ./rebar get-deps
+
+clean:
+ ./rebar 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)
+
diff --git a/deps/lager/README.org b/deps/lager/README.org
new file mode 100644
index 0000000..691dd96
--- /dev/null
+++ b/deps/lager/README.org
@@ -0,0 +1,222 @@
+* 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/TODO b/deps/lager/TODO
new file mode 100644
index 0000000..ebd0998
--- /dev/null
+++ b/deps/lager/TODO
@@ -0,0 +1,3 @@
+Time based log rotation
+Syslog backends (local & remote)
+debug_module & debug_pid
diff --git a/deps/lager/dialyzer.ignore-warnings b/deps/lager/dialyzer.ignore-warnings
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/deps/lager/dialyzer.ignore-warnings
diff --git a/deps/lager/include/lager.hrl b/deps/lager/include/lager.hrl
new file mode 100644
index 0000000..7ea7b8c
--- /dev/null
+++ b/deps/lager/include/lager.hrl
@@ -0,0 +1,100 @@
+%% Copyright (c) 2011 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.
+-ifndef(__LAGER_HRL__).
+-define(__LAGER_HRL__, true).
+
+-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).
+
+-define(LEVEL2NUM(Level),
+ case Level of
+ debug -> ?DEBUG;
+ info -> ?INFO;
+ notice -> ?NOTICE;
+ warning -> ?WARNING;
+ error -> ?ERROR;
+ critical -> ?CRITICAL;
+ alert -> ?ALERT;
+ emergency -> ?EMERGENCY
+ end).
+
+-define(NUM2LEVEL(Num),
+ case Num of
+ ?DEBUG -> debug;
+ ?INFO -> info;
+ ?NOTICE -> notice;
+ ?WARNING -> warning;
+ ?ERROR -> error;
+ ?CRITICAL -> critical;
+ ?ALERT -> alert;
+ ?EMERGENCY -> emergency
+ end).
+
+
+-define(SHOULD_LOG(Level),
+ lager_util:level_to_num(Level) =< element(1, lager_mochiglobal:get(loglevel, {?LOG_NONE, []}))).
+
+-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)]})).
+
+%% FOR INTERNAL USE ONLY
+%% internal non-blocking logging call
+%% there's some special handing for when we try to log (usually errors) while
+%% lager is still starting.
+-ifdef(TEST).
+-define(INT_LOG(Level, Format, Args),
+ case ?SHOULD_LOG(Level) of
+ true ->
+ ?NOTIFY(Level, self(), Format, Args);
+ _ ->
+ ok
+ end).
+-else.
+-define(INT_LOG(Level, Format, Args),
+ Self = self(),
+ %% do this in a spawn so we don't cause a deadlock calling gen_event:which_handlers
+ %% from a gen_event handler
+ spawn(fun() ->
+ case catch(gen_event:which_handlers(lager_event)) of
+ X when X == []; X == {'EXIT', noproc} ->
+ %% there's no handlers yet or lager isn't running, try again
+ %% in half a second.
+ timer:sleep(500),
+ ?NOTIFY(Level, Self, Format, Args);
+ _ ->
+ case ?SHOULD_LOG(Level) of
+ true ->
+ ?NOTIFY(Level, Self, Format, Args);
+ _ ->
+ ok
+ end
+ end
+ end)).
+-endif.
+
+-endif.
diff --git a/deps/lager/include/log.hrl b/deps/lager/include/log.hrl
new file mode 100644
index 0000000..270f06c
--- /dev/null
+++ b/deps/lager/include/log.hrl
@@ -0,0 +1,44 @@
+%%
+%% Log macros
+%%
+-ifndef(__LOG_HRL__).
+-define(__LOG_HRL__, true).
+
+-compile({parse_transform, lager_transform}).
+
+%% Lager logging levels
+%% debug, info, notice, warning, error, critical, alert, emergency, none.
+
+-define(debug(Fmt), lager:debug(Fmt)).
+-define(debug(Fmt, Args), lager:debug(Fmt, Args)).
+-define(debug(Attrs, Fmt, Args), lager:debug(Attrs, Fmt, Args)).
+
+-define(info(Fmt), lager:info(Fmt)).
+-define(info(Fmt, Args), lager:info(Fmt, Args)).
+-define(info(Attrs, Fmt, Args), lager:info(Attrs, Fmt, Args)).
+
+-define(notice(Fmt), lager:notice(Fmt)).
+-define(notice(Fmt, Args), lager:notice(Fmt, Args)).
+-define(notice(Attrs, Fmt, Args), lager:notice(Attrs, Fmt, Args)).
+
+-define(warning(Fmt), lager:warning(Fmt)).
+-define(warning(Fmt, Args), lager:warning(Fmt, Args)).
+-define(warning(Attrs, Fmt, Args), lager:warning(Attrs, Fmt, Args)).
+
+-define(error(Fmt), lager:error(Fmt)).
+-define(error(Fmt, Args), lager:error(Fmt, Args)).
+-define(error(Attrs, Fmt, Args), lager:error(Attrs, Fmt, Args)).
+
+-define(critical(Fmt), lager:critical(Fmt)).
+-define(critical(Fmt, Args), lager:critical(Fmt, Args)).
+-define(critical(Attrs, Fmt, Args), lager:critical(Attrs, Fmt, Args)).
+
+-define(alert(Fmt), lager:alert(Fmt)).
+-define(alert(Fmt, Args), lager:alert(Fmt, Args)).
+-define(alert(Attrs, Fmt, Args), lager:alert(Attrs, Fmt, Args)).
+
+-define(emergency(Fmt), lager:emergency(Fmt)).
+-define(emergency(Fmt, Args), lager:emergency(Fmt, Args)).
+-define(emergency(Attrs, Fmt, Args), lager:emergency(Attrs, Fmt, Args)).
+
+-endif.
diff --git a/deps/lager/rebar b/deps/lager/rebar
new file mode 100755
index 0000000..8645775
--- /dev/null
+++ b/deps/lager/rebar
Binary files differ
diff --git a/deps/lager/rebar.config b/deps/lager/rebar.config
new file mode 100644
index 0000000..bfaa56f
--- /dev/null
+++ b/deps/lager/rebar.config
@@ -0,0 +1,4 @@
+{erl_opts, [debug_info]}.
+{erl_first_files, ["src/lager_util.erl"]}.
+
+{cover_enabled, true}.
diff --git a/deps/lager/src/error_logger_lager_h.erl b/deps/lager/src/error_logger_lager_h.erl
new file mode 100644
index 0000000..f52fed5
--- /dev/null
+++ b/deps/lager/src/error_logger_lager_h.erl
@@ -0,0 +1,281 @@
+%% Copyright (c) 2011 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 error_logger backend for redirecting events into lager.
+%% Error messages and crash logs are also optionally written to a crash log.
+
+%% @see lager_crash_log
+
+%% @private
+
+-module(error_logger_lager_h).
+
+-include("lager.hrl").
+
+-behaviour(gen_event).
+
+-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),
+ case ?SHOULD_LOG(Level) of
+ true ->
+ lager:log(Level, Pid, Msg);
+ _ -> ok
+ end).
+
+-define(LOG(Level, Pid, Fmt, Args),
+ case ?SHOULD_LOG(Level) of
+ true ->
+ lager:log(Level, Pid, Fmt, Args);
+ _ -> ok
+ end).
+
+-ifdef(TEST).
+%% Make CRASH synchronous when testing, to avoid timing headaches
+-define(CRASH_LOG(Event),
+ catch(gen_server:call(lager_crash_log, {log, Event}))).
+-else.
+-define(CRASH_LOG(Event),
+ gen_server:cast(lager_crash_log, {log, Event})).
+-endif.
+
+-spec init(any()) -> {ok, {}}.
+init(_) ->
+ {ok, {}}.
+
+handle_call(_Request, State) ->
+ {ok, ok, State}.
+
+handle_event(Event, State) ->
+ case Event of
+ {error, _GL, {Pid, Fmt, Args}} ->
+ case Fmt of
+ "** Generic server "++_ ->
+ %% gen_server terminate
+ [Name, _Msg, _State, Reason] = Args,
+ ?CRASH_LOG(Event),
+ ?LOG(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",
+ [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",
+ [ID, Name, format_reason(Reason)]);
+ _ ->
+ ?CRASH_LOG(Event),
+ ?LOG(error, Pid, lager:safe_format(Fmt, Args, 4096))
+ end;
+ {error_report, _GL, {Pid, std_error, D}} ->
+ ?CRASH_LOG(Event),
+ ?LOG(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,
+ "Supervisor ~w had child ~s exit with reason ~s in context ~w",
+ [element(2, Name), Offender, format_reason(Reason), Ctx]);
+ _ ->
+ ?LOG(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)]);
+ {warning_msg, _GL, {Pid, Fmt, Args}} ->
+ ?LOG(warning, Pid, lager:safe_format(Fmt, Args, 4096));
+ {warning_report, _GL, {Pid, std_warning, Report}} ->
+ ?LOG(warning, Pid, print_silly_list(Report));
+ {info_msg, _GL, {Pid, Fmt, Args}} ->
+ ?LOG(info, Pid, lager:safe_format(Fmt, Args, 4096));
+ {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)]);
+ _ ->
+ ?LOG(info, Pid, print_silly_list(D))
+ end;
+ {info_report, _GL, {Pid, std_info, D}} ->
+ ?LOG(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]);
+ [{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]);
+ _ ->
+ ?LOG(info, P, ["PROGRESS REPORT ", print_silly_list(D)])
+ end;
+ _ ->
+ ?LOG(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
+ [] ->
+ %% process_info(Pid, registered_name) returns [] for unregistered processes
+ proplists: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)
+ end,
+ io_lib:format("Process ~w with ~w neighbours crashed with reason: ~s",
+ [Name, length(Neighbours), ReasonStr]).
+
+format_offender(Off) ->
+ case proplists: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)]);
+ MFArgs ->
+ %% regular supervisor
+ MFA = format_mfa(MFArgs),
+ Name = proplists:get_value(name, Off),
+ io_lib:format("~p started with ~s at ~w",
+ [Name, MFA, proplists: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({undef, [MFA|_]}) ->
+ ["call to undefined function ", 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({{case_clause, Val}, [MFA|_]}) ->
+ ["no case clause matching ", print_val(Val), " in ", format_mfa(MFA)];
+format_reason({function_clause, [MFA|_]}) ->
+ ["no function clause matching ", format_mfa(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)];
+format_reason({badarith, [MFA|_]}) ->
+ ["bad arithmetic expression in ", format_mfa(MFA)];
+format_reason({{badmatch, Val}, [MFA|_]}) ->
+ ["no match of right hand value ", print_val(Val), " in ", format_mfa(MFA)];
+format_reason({emfile, _Trace}) ->
+ "maximum number of file descriptors exhausted, check ulimit -n";
+format_reason({system_limit, [{M, F, _}|_] = Trace}) ->
+ Limit = case {M, F} of
+ {erlang, open_port} ->
+ "maximum number of ports exceeded";
+ {erlang, spawn} ->
+ "maximum number of processes exceeded";
+ {erlang, spawn_opt} ->
+ "maximum number of processes exceeded";
+ {erlang, list_to_atom} ->
+ "tried to create an atom larger than 255, or maximum atom count exceeded";
+ {ets, new} ->
+ "maximum number of ETS tables exceeded";
+ _ ->
+ {Str, _} = lager_trunc_io:print(Trace, 500),
+ Str
+ end,
+ ["system limit: ", Limit];
+format_reason({badarg, [MFA,MFA2|_]}) ->
+ case MFA of
+ {_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({{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 ",
+ [length(Args), Arity]), format_mfa(MFA)];
+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) ->
+ {Str, _} = lager_trunc_io:print(Reason, 500),
+ Str.
+
+format_mfa({M, F, A}) when is_list(A) ->
+ {FmtStr, Args} = format_args(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(Other) ->
+ io_lib:format("~w", [Other]).
+
+format_args([], FmtAcc, ArgsAcc) ->
+ {string:join(lists:reverse(FmtAcc), ", "), lists:reverse(ArgsAcc)};
+format_args([H|T], FmtAcc, ArgsAcc) ->
+ {Str, _} = lager_trunc_io:print(H, 100),
+ format_args(T, ["~s"|FmtAcc], [Str|ArgsAcc]).
+
+print_silly_list(L) when is_list(L) ->
+ case lager_stdlib:string_p(L) of
+ true ->
+ lager_trunc_io:format("~s", [L], 4096);
+ _ ->
+ print_silly_list(L, [], [])
+ end;
+print_silly_list(L) ->
+ {Str, _} = lager_trunc_io:print(L, 4096),
+ Str.
+
+print_silly_list([], Fmt, Acc) ->
+ lager_trunc_io:format(string:join(lists:reverse(Fmt), ", "),
+ lists:reverse(Acc), 4096);
+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) ->
+ print_silly_list(T, ["~p" | Fmt], [H | Acc]).
+
+print_val(Val) ->
+ {Str, _} = lager_trunc_io:print(Val, 500),
+ Str.
diff --git a/deps/lager/src/lager.app.src b/deps/lager/src/lager.app.src
new file mode 100644
index 0000000..18bbadd
--- /dev/null
+++ b/deps/lager/src/lager.app.src
@@ -0,0 +1,41 @@
+%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*-
+%% ex: ts=4 sw=4 et
+{application, lager,
+ [
+ {description, "Erlang logging framework"},
+ {vsn, git},
+ {modules, []},
+ {applications, [
+ kernel,
+ stdlib,
+ compiler,
+ syntax_tools
+ ]},
+ {registered, []},
+ {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}
+ ]}
+ ]},
+ %% 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
+ {crash_log_msg_size, 65536},
+ %% Maximum size of the crash log in bytes, before its rotated, set
+ %% to 0 to disable rotation - default is 0
+ {crash_log_size, 10485760},
+ %% What time to rotate the crash log - default is no time
+ %% rotation. See the README for a description of this format.
+ {crash_log_date, "$D0"},
+ %% 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}
+ ]}
+ ]}.
diff --git a/deps/lager/src/lager.erl b/deps/lager/src/lager.erl
new file mode 100644
index 0000000..db40103
--- /dev/null
+++ b/deps/lager/src/lager.erl
@@ -0,0 +1,395 @@
+%% Copyright (c) 2011 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 The lager logging framework.
+
+-module(lager).
+
+-include("lager.hrl").
+
+%% 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]).
+
+-type log_level() :: debug | info | notice | warning | error | critical | alert | emergency.
+-type log_level_number() :: 0..7.
+
+-export_type([log_level/0, log_level_number/0]).
+
+%% API
+
+%% @doc Start the application. Mainly useful for using `-s lager' as a command
+%% line switch to the VM to make lager start on boot.
+start() -> start(lager).
+
+start(App) ->
+ start_ok(App, application:start(App, permanent)).
+
+start_ok(_App, ok) -> ok;
+start_ok(_App, {error, {already_started, _App}}) -> ok;
+start_ok(App, {error, {not_started, Dep}}) ->
+ ok = start(Dep),
+ start(App);
+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
+ 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
+ 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
+-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}).
+
+%% @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}).
+
+
+%% @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}).
+
+%% @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}).
+
+trace_file(File, Filter) ->
+ trace_file(File, Filter, debug).
+
+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),
+ %% 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
+ end,
+ {ok, Trace};
+ Error ->
+ Error
+ end.
+
+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 ->
+ Error
+ end.
+
+stop_trace({_Filter, _Level, Target} = Trace) ->
+ {MinLevel, Traces} = lager_mochiglobal:get(loglevel),
+ NewTraces = lists:delete(Trace, Traces),
+ lager_mochiglobal:put(loglevel, {MinLevel, NewTraces}),
+ case get_loglevel(Target) of
+ none ->
+ %% check no other traces point here
+ case lists:keyfind(Target, 3, NewTraces) of
+ false ->
+ gen_event:delete_handler(lager_event, Target, []);
+ _ ->
+ ok
+ end;
+ _ ->
+ ok
+ end,
+ ok.
+
+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.
+
+status() ->
+ Handlers = gen_event:which_handlers(lager_event),
+ Status = ["Lager status:\n",
+ [begin
+ Level = get_loglevel(Handler),
+ case Handler of
+ {lager_file_backend, File} ->
+ io_lib:format("File ~s at level ~p\n", [File, Level]);
+ lager_console_backend ->
+ io_lib:format("Console at level ~p\n", [Level]);
+ _ ->
+ []
+ end
+ end || Handler <- 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))]],
+ 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.
+
+%% @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}),
+ Reply.
+
+%% @doc Get the loglevel for a particular backend. 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
+ X when is_integer(X) ->
+ lager_util:num_to_level(X);
+ Y -> Y
+ end.
+
+%% @doc Try to convert an atom to a posix error, but fall back on printing the
+%% term if its not a valid posix error code.
+posix_error(Error) when is_atom(Error) ->
+ case erl_posix_msg:message(Error) of
+ "unknown POSIX error" -> atom_to_list(Error);
+ Message -> Message
+ end;
+posix_error(Error) ->
+ safe_format_chop("~p", [Error], 4096).
+
+%% @private
+get_loglevels() ->
+ [gen_event:call(lager_event, Handler, get_loglevel, infinity) ||
+ Handler <- gen_event:which_handlers(lager_event)].
+
+%% @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)
+ end.
+
+%% @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
+%% arguments. The caller is NOT crashed.
+
+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
+ catch
+ _:_ -> lager_trunc_io:format("FORMAT ERROR: ~p ~p", [Fmt, Args], Limit)
+ end.
+
+%% @private
+safe_format_chop(Fmt, Args, Limit) ->
+ safe_format(Fmt, Args, Limit, [{chomp, true}]).
+%%
+%% when code is not compiled with parse_transform the following code
+%% is used instead. maybe warn about the fact?
+%%
+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}.
+
+dyn_log(Severity, Attrs, Fmt, Args) ->
+ try erlang:error(fail) of
+ _ -> strange
+ 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
+ end.
diff --git a/deps/lager/src/lager_app.erl b/deps/lager/src/lager_app.erl
new file mode 100644
index 0000000..3a4f3fe
--- /dev/null
+++ b/deps/lager/src/lager_app.erl
@@ -0,0 +1,77 @@
+%% Copyright (c) 2011 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 Lager's application module. Not a lot to see here.
+
+%% @private
+
+-module(lager_app).
+
+-behaviour(application).
+-include("lager.hrl").
+
+-export([start/0,
+ start/2,
+ stop/1]).
+
+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
+ end,
+
+ [supervisor:start_child(lager_handler_watcher_sup, [lager_event, Module, Config]) ||
+ {Module, Config} <- expand_handlers(Handlers)],
+
+ %% 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}),
+
+ 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?
+ [begin error_logger:delete_report_handler(X), X end ||
+ X <- gen_event:which_handlers(error_logger) -- [error_logger_lager_h]]
+ end,
+
+ {ok, Pid, SavedHandlers}.
+
+
+stop(Handlers) ->
+ [error_logger:add_report_handler(Handler) || Handler <- Handlers],
+ ok.
+
+expand_handlers([]) ->
+ [];
+expand_handlers([{lager_file_backend, Configs}|T]) ->
+ [{{lager_file_backend, element(1, Config)}, Config} || Config <- Configs] ++
+ expand_handlers(T);
+expand_handlers([H|T]) ->
+ [H | expand_handlers(T)].
diff --git a/deps/lager/src/lager_console_backend.erl b/deps/lager/src/lager_console_backend.erl
new file mode 100644
index 0000000..1e98fe5
--- /dev/null
+++ b/deps/lager/src/lager_console_backend.erl
@@ -0,0 +1,273 @@
+%% Copyright (c) 2011 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 Console backend for lager. Configured with a single option, the loglevel
+%% desired.
+
+-module(lager_console_backend).
+
+-behaviour(gen_event).
+
+-export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2,
+ code_change/3]).
+
+-record(state, {level, verbose}).
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-compile([{parse_transform, lager_transform}]).
+-endif.
+
+-include("lager.hrl").
+
+%% @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.
+
+
+%% @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)}};
+ _ ->
+ {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
+ true ->
+ case Verbose of
+ true ->
+ io:put_chars([Date, " ", Time, " ", LevelStr, Location, Message, "\n"]);
+ _ ->
+ io:put_chars([Time, " ", LevelStr, Message, "\n"])
+ end,
+ {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}.
+
+%% @private
+handle_info(_Info, State) ->
+ {ok, State}.
+
+%% @private
+terminate(_Reason, _State) ->
+ ok.
+
+%% @private
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+-ifdef(TEST).
+console_log_test_() ->
+ %% tiny recursive fun that pretends to be a group leader
+ F = fun(Self) ->
+ fun() ->
+ YComb = fun(Fun) ->
+ receive
+ {io_request, From, ReplyAs, {put_chars, unicode, _Msg}} = Y ->
+ From ! {io_reply, ReplyAs, ok},
+ Self ! Y,
+ Fun(Fun);
+ Other ->
+ ?debugFmt("unexpected message ~p~n", [Other]),
+ Self ! Other
+ end
+ end,
+ 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)
+ end,
+ fun(_) ->
+ application:stop(lager),
+ error_logger:tty(true)
+ end,
+ [
+ {"regular console logging",
+ fun() ->
+ Pid = spawn(F(self())),
+ gen_event:add_handler(lager_event, lager_console_backend, info),
+ erlang:group_leader(Pid, whereis(lager_event)),
+ 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}]))
+ after
+ 500 ->
+ ?assert(false)
+ end
+ end
+ },
+ {"verbose console logging",
+ fun() ->
+ Pid = spawn(F(self())),
+ 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"),
+ 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}]))
+ after
+ 500 ->
+ ?assert(false)
+ end
+ end
+ },
+ {"tracing should work",
+ fun() ->
+ Pid = spawn(F(self())),
+ gen_event:add_handler(lager_event, lager_console_backend, info),
+ erlang:group_leader(Pid, whereis(lager_event)),
+ lager_mochiglobal:put(loglevel, {?INFO, []}),
+ lager:debug("Test message"),
+ receive
+ {io_request, From, ReplyAs, {put_chars, unicode, _Msg}} ->
+ From ! {io_reply, ReplyAs, ok},
+ ?assert(false)
+ after
+ 500 ->
+ ?assert(true)
+ end,
+ {ok, _} = lager:trace_console([{module, ?MODULE}]),
+ lager:debug("Test message"),
+ 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}]))
+ after
+ 500 ->
+ ?assert(false)
+ end
+ end
+ },
+ {"tracing doesn't duplicate messages",
+ fun() ->
+ Pid = spawn(F(self())),
+ gen_event:add_handler(lager_event, lager_console_backend, info),
+ lager_mochiglobal:put(loglevel, {?INFO, []}),
+ erlang:group_leader(Pid, whereis(lager_event)),
+ lager:debug("Test message"),
+ receive
+ {io_request, From, ReplyAs, {put_chars, unicode, _Msg}} ->
+ From ! {io_reply, ReplyAs, ok},
+ ?assert(false)
+ after
+ 500 ->
+ ?assert(true)
+ end,
+ {ok, _} = lager:trace_console([{module, ?MODULE}]),
+ lager:error("Test message"),
+ 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}]))
+ after
+ 1000 ->
+ ?assert(false)
+ end,
+ %% make sure this event wasn't duplicated
+ receive
+ {io_request, From2, ReplyAs2, {put_chars, unicode, _Msg2}} ->
+ From2 ! {io_reply, ReplyAs2, ok},
+ ?assert(false)
+ after
+ 500 ->
+ ?assert(true)
+ end
+ end
+ }
+
+ ]
+ }.
+
+set_loglevel_test_() ->
+ {foreach,
+ fun() ->
+ error_logger:tty(false),
+ application:load(lager),
+ application:set_env(lager, handlers, [{lager_console_backend, info}]),
+ application:set_env(lager, error_logger_redirect, false),
+ application:start(lager)
+ end,
+ fun(_) ->
+ application:stop(lager),
+ error_logger:tty(true)
+ end,
+ [
+ {"Get/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))
+ end
+ },
+ {"Get/set invalid loglevel test",
+ fun() ->
+ ?assertEqual(info, lager:get_loglevel(lager_console_backend)),
+ ?assertEqual({error, bad_log_level},
+ lager:set_loglevel(lager_console_backend, fatfinger)),
+ ?assertEqual(info, lager:get_loglevel(lager_console_backend))
+ end
+ }
+
+ ]
+ }.
+
+-endif.
diff --git a/deps/lager/src/lager_crash_log.erl b/deps/lager/src/lager_crash_log.erl
new file mode 100644
index 0000000..e4aaa33
--- /dev/null
+++ b/deps/lager/src/lager_crash_log.erl
@@ -0,0 +1,339 @@
+%% Copyright (c) 2011 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 Lager crash log writer. This module implements a gen_server which writes
+%% error_logger error messages out to a file in their original format. The
+%% location to which it logs is configured by the application var `crash_log'.
+%% Omitting this variable disables crash logging. Crash logs are printed safely
+%% using trunc_io via code mostly lifted from riak_err.
+%%
+%% The `crash_log_msg_size' application var is used to specify the maximum
+%% size of any message to be logged. `crash_log_size' is used to specify the
+%% maximum size of the crash log before it will be rotated (0 will disable).
+%% Time based rotation is configurable via `crash_log_date', the syntax is
+%% documented in the README. To control the number of rotated files to be
+%% retained, use `crash_log_count'.
+
+-module(lager_crash_log).
+
+-include("lager.hrl").
+
+-behaviour(gen_server).
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-include_lib("kernel/include/file.hrl").
+-endif.
+
+%% callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
+ code_change/3]).
+
+-export([start_link/5, start/5]).
+
+-record(state, {
+ name,
+ fd,
+ inode,
+ fmtmaxbytes,
+ size,
+ date,
+ count,
+ flap=false
+ }).
+
+%% @private
+start_link(Filename, MaxBytes, Size, Date, Count) ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [Filename, MaxBytes,
+ Size, Date, Count], []).
+
+%% @private
+start(Filename, MaxBytes, Size, Date, Count) ->
+ gen_server:start({local, ?MODULE}, ?MODULE, [Filename, MaxBytes, Size,
+ Date, Count], []).
+
+%% @private
+init([Filename, MaxBytes, Size, Date, Count]) ->
+ case lager_util:open_logfile(Filename, false) of
+ {ok, {FD, Inode, _}} ->
+ schedule_rotation(Date),
+ {ok, #state{name=Filename, fd=FD, inode=Inode,
+ fmtmaxbytes=MaxBytes, size=Size, count=Count, date=Date}};
+ {error, Reason} ->
+ ?INT_LOG(error, "Failed to open crash log file ~s with error: ~s",
+ [Filename, file:format_error(Reason)]),
+ {ok, #state{name=Filename, fmtmaxbytes=MaxBytes, flap=true,
+ size=Size, count=Count, date=Date}}
+ end.
+
+%% @private
+handle_call({log, _} = Log, _From, State) ->
+ {Reply, NewState} = do_log(Log, State),
+ {reply, Reply, NewState};
+handle_call(_Call, _From, State) ->
+ {reply, ok, State}.
+
+%% @private
+handle_cast({log, _} = Log, State) ->
+ {_, NewState} = do_log(Log, State),
+ {noreply, NewState};
+handle_cast(_Request, State) ->
+ {noreply, State}.
+
+%% @private
+handle_info(rotate, #state{name=Name, count=Count, date=Date} = State) ->
+ lager_util:rotate_logfile(Name, Count),
+ schedule_rotation(Date),
+ {noreply, State};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+%% @private
+terminate(_Reason, _State) ->
+ ok.
+
+%% @private
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+schedule_rotation(undefined) ->
+ undefined;
+schedule_rotation(Date) ->
+ erlang:send_after(lager_util:calculate_next_rotation(Date) * 1000, self(), rotate).
+
+%% ===== Begin code lifted from riak_err =====
+
+-spec limited_fmt(string(), list(), integer()) -> iolist().
+%% @doc Format Fmt and Args similar to what io_lib:format/2 does but with
+%% limits on how large the formatted string may be.
+%%
+%% If the Args list's size is larger than TermMaxSize, then the
+%% formatting is done by trunc_io:print/2, where FmtMaxBytes is used
+%% to limit the formatted string's size.
+
+limited_fmt(Fmt, Args, FmtMaxBytes) ->
+ lager:safe_format(Fmt, Args, FmtMaxBytes).
+
+limited_str(Term, FmtMaxBytes) ->
+ {Str, _} = lager_trunc_io:print(Term, FmtMaxBytes),
+ Str.
+
+other_node_suffix(Pid) when node(Pid) =/= node() ->
+ "** at node " ++ atom_to_list(node(Pid)) ++ " **\n";
+other_node_suffix(_) ->
+ "".
+
+perhaps_a_sasl_report(error_report, {Pid, Type, Report}, FmtMaxBytes) ->
+ case lager_stdlib:is_my_error_report(Type) of
+ true ->
+ {sasl_type_to_report_head(Type), Pid,
+ sasl_limited_str(Type, Report, FmtMaxBytes), true};
+ false ->
+ {ignore, ignore, ignore, false}
+ end;
+%perhaps_a_sasl_report(info_report, {Pid, Type, Report}, FmtMaxBytes) ->
+ %case lager_stdlib:is_my_info_report(Type) of
+ %true ->
+ %{sasl_type_to_report_head(Type), Pid,
+ %sasl_limited_str(Type, Report, FmtMaxBytes), false};
+ %false ->
+ %{ignore, ignore, ignore, false}
+ %end;
+perhaps_a_sasl_report(_, _, _) ->
+ {ignore, ignore, ignore, false}.
+
+sasl_type_to_report_head(supervisor_report) ->
+ "SUPERVISOR REPORT";
+sasl_type_to_report_head(crash_report) ->
+ "CRASH REPORT";
+sasl_type_to_report_head(progress) ->
+ "PROGRESS REPORT".
+
+sasl_limited_str(supervisor_report, Report, FmtMaxBytes) ->
+ Name = lager_stdlib:sup_get(supervisor, Report),
+ Context = lager_stdlib:sup_get(errorContext, Report),
+ Reason = lager_stdlib:sup_get(reason, Report),
+ Offender = lager_stdlib:sup_get(offender, Report),
+ FmtString = " Supervisor: ~p~n Context: ~p~n Reason: "
+ "~s~n Offender: ~s~n~n",
+ {ReasonStr, _} = lager_trunc_io:print(Reason, FmtMaxBytes),
+ {OffenderStr, _} = lager_trunc_io:print(Offender, FmtMaxBytes),
+ io_lib:format(FmtString, [Name, Context, ReasonStr, OffenderStr]);
+sasl_limited_str(progress, Report, FmtMaxBytes) ->
+ [begin
+ {Str, _} = lager_trunc_io:print(Data, FmtMaxBytes),
+ io_lib:format(" ~16w: ~s~n", [Tag, Str])
+ end || {Tag, Data} <- Report];
+sasl_limited_str(crash_report, Report, FmtMaxBytes) ->
+ lager_stdlib:proc_lib_format(Report, FmtMaxBytes).
+
+do_log({log, Event}, #state{name=Name, fd=FD, inode=Inode, flap=Flap,
+ fmtmaxbytes=FmtMaxBytes, size=RotSize, count=Count} = State) ->
+ %% borrowed from riak_err
+ {ReportStr, Pid, MsgStr, _ErrorP} = case Event of
+ {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, _GL, Other} ->
+ perhaps_a_sasl_report(error_report, Other, FmtMaxBytes);
+ _ ->
+ {ignore, ignore, ignore, false}
+ end,
+ if ReportStr == ignore ->
+ {ok, State};
+ true ->
+ case lager_util:ensure_logfile(Name, FD, Inode, false) of
+ {ok, {_, _, Size}} when RotSize /= 0, Size > RotSize ->
+ lager_util:rotate_logfile(Name, Count),
+ handle_cast({log, Event}, State);
+ {ok, {NewFD, NewInode, _Size}} ->
+ {Date, TS} = lager_util:format_time(
+ lager_stdlib:maybe_utc(erlang:localtime())),
+ 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
+ {error, Reason} when Flap == false ->
+ ?INT_LOG(error, "Failed to write log message to file ~s: ~s",
+ [Name, file:format_error(Reason)]),
+ {ok, State#state{fd=NewFD, inode=NewInode, flap=true}};
+ ok ->
+ {ok, State#state{fd=NewFD, inode=NewInode, flap=false}};
+ _ ->
+ {ok, State#state{fd=NewFD, inode=NewInode}}
+ end;
+ {error, Reason} ->
+ case Flap of
+ true ->
+ {ok, State};
+ _ ->
+ ?INT_LOG(error, "Failed to reopen crash log ~s with error: ~s",
+ [Name, file:format_error(Reason)]),
+ {ok, State#state{flap=true}}
+ end
+ end
+ end.
+
+
+-ifdef(TEST).
+
+filesystem_test_() ->
+ {foreach,
+ fun() ->
+ file:write_file("crash_test.log", ""),
+ error_logger:tty(false),
+ application:load(lager),
+ 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),
+ timer:sleep(100),
+ lager_test_backend:flush()
+ end,
+ fun(_) ->
+ case whereis(lager_crash_log) of
+ P when is_pid(P) ->
+ exit(P, kill);
+ _ -> ok
+ end,
+ file:delete("crash_test.log"),
+ application:stop(lager),
+ error_logger:tty(true)
+ end,
+ [
+ {"under normal circumstances, file should be opened",
+ fun() ->
+ {ok, _} = ?MODULE:start_link("crash_test.log", 65535, 0, undefined, 0),
+ _ = gen_event:which_handlers(error_logger),
+ sync_error_logger:error_msg("Test message\n"),
+ {ok, Bin} = file:read_file("crash_test.log"),
+ ?assertMatch([_, "Test message\n"], re:split(Bin, "\n", [{return, list}, {parts, 2}]))
+ end
+ },
+ {"file can't be opened on startup triggers an error message",
+ fun() ->
+ {ok, FInfo} = file:read_file_info("crash_test.log"),
+ 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(),
+ ?assertEqual("Failed to open crash log file crash_test.log with error: permission denied", lists:flatten(Message))
+ end
+ },
+ {"file that becomes unavailable at runtime should trigger an error message",
+ fun() ->
+ {ok, _} = ?MODULE:start_link("crash_test.log", 65535, 0, undefined, 0),
+ ?assertEqual(0, lager_test_backend:count()),
+ sync_error_logger:error_msg("Test message\n"),
+ _ = gen_event:which_handlers(error_logger),
+ ?assertEqual(1, lager_test_backend:count()),
+ file:delete("crash_test.log"),
+ file:write_file("crash_test.log", ""),
+ {ok, FInfo} = file:read_file_info("crash_test.log"),
+ file:write_file_info("crash_test.log", FInfo#file_info{mode = 0}),
+ sync_error_logger:error_msg("Test message\n"),
+ _ = gen_event:which_handlers(error_logger),
+ ?assertEqual(3, lager_test_backend:count()),
+ lager_test_backend:pop(),
+ {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
+ ?assertEqual("Failed to reopen crash log crash_test.log with error: permission denied", lists:flatten(Message))
+ end
+ },
+ {"unavailable files that are fixed at runtime should start having log messages written",
+ fun() ->
+ {ok, FInfo} = file:read_file_info("crash_test.log"),
+ OldPerms = FInfo#file_info.mode,
+ 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(),
+ ?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"),
+ _ = gen_event:which_handlers(error_logger),
+ {ok, Bin} = file:read_file("crash_test.log"),
+ ?assertMatch([_, "Test message\n"], re:split(Bin, "\n", [{return, list}, {parts, 2}]))
+ end
+ },
+ {"external logfile rotation/deletion should be handled",
+ fun() ->
+ {ok, _} = ?MODULE:start_link("crash_test.log", 65535, 0, undefined, 0),
+ ?assertEqual(0, lager_test_backend:count()),
+ sync_error_logger:error_msg("Test message~n"),
+ _ = gen_event:which_handlers(error_logger),
+ {ok, Bin} = file:read_file("crash_test.log"),
+ ?assertMatch([_, "Test message\n"], re:split(Bin, "\n", [{return, list}, {parts, 2}])),
+ file:delete("crash_test.log"),
+ file:write_file("crash_test.log", ""),
+ sync_error_logger:error_msg("Test message1~n"),
+ _ = gen_event:which_handlers(error_logger),
+ {ok, Bin1} = file:read_file("crash_test.log"),
+ ?assertMatch([_, "Test message1\n"], re:split(Bin1, "\n", [{return, list}, {parts, 2}])),
+ file:rename("crash_test.log", "crash_test.log.0"),
+ sync_error_logger:error_msg("Test message2~n"),
+ _ = gen_event:which_handlers(error_logger),
+ {ok, Bin2} = file:read_file("crash_test.log"),
+ ?assertMatch([_, "Test message2\n"], re:split(Bin2, "\n", [{return, list}, {parts, 2}]))
+ end
+ }
+ ]
+ }.
+
+-endif.
+
diff --git a/deps/lager/src/lager_file_backend.erl b/deps/lager/src/lager_file_backend.erl
new file mode 100644
index 0000000..1faceea
--- /dev/null
+++ b/deps/lager/src/lager_file_backend.erl
@@ -0,0 +1,392 @@
+%% Copyright (c) 2011 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 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}'.
+%% 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
+%% an alternate rotation trigger, based on time. See the README for
+%% documentation.
+
+-module(lager_file_backend).
+
+-include("lager.hrl").
+
+-behaviour(gen_event).
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-include_lib("kernel/include/file.hrl").
+-compile([{parse_transform, lager_transform}]).
+-endif.
+
+-export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2,
+ code_change/3]).
+
+-record(state, {
+ name :: string(),
+ level :: integer(),
+ fd :: file:io_device(),
+ inode :: integer(),
+ flap=false :: boolean(),
+ size = 0 :: integer(),
+ date,
+ count = 10
+ }).
+
+%% @private
+-spec init([{string(), lager:log_level()},...]) -> {ok, #state{}}.
+init(LogFile) ->
+ case validate_logfile(LogFile) of
+ {Name, Level, Size, Date, Count} ->
+ schedule_rotation(Name, Date),
+ State = case lager_util:open_logfile(Name, true) of
+ {ok, {FD, Inode, _}} ->
+ #state{name=Name, level=lager_util:level_to_num(Level),
+ fd=FD, inode=Inode, size=Size, date=Date, count=Count};
+ {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}
+ end,
+ {ok, State};
+ false ->
+ ignore
+ 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)}};
+handle_call(get_loglevel, #state{level=Level} = State) ->
+ {ok, Level, State};
+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
+ true ->
+ {ok, write(State, Level, [Date, " ", Time, " ", Message, "\n"])};
+ 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),
+ schedule_rotation(File, Date),
+ {ok, State};
+handle_info(_Info, State) ->
+ {ok, State}.
+
+%% @private
+terminate(_Reason, #state{fd=FD}) ->
+ %% flush and close any file handles
+ file:datasync(FD),
+ file:close(FD),
+ ok.
+
+%% @private
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+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;
+ ok ->
+ false;
+ _ ->
+ Flap
+ end,
+ State#state{fd=NewFD, inode=NewInode, flap=Flap2};
+ _ ->
+ State#state{fd=NewFD, inode=NewInode}
+ 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
+ end.
+
+validate_logfile({Name, Level}) ->
+ case lists:member(Level, ?LEVELS) of
+ true ->
+ {Name, Level, 0, undefined, 0};
+ _ ->
+ ?INT_LOG(error, "Invalid log level of ~p for ~s.",
+ [Level, Name]),
+ 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
+ end;
+validate_logfile(H) ->
+ ?INT_LOG(error, "Invalid log file config ~p.", [H]),
+ false.
+
+schedule_rotation(_, undefined) ->
+ undefined;
+schedule_rotation(Name, Date) ->
+ erlang:send_after(lager_util:calculate_next_rotation(Date) * 1000, self(), {rotate, Name}).
+
+-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)),
+ {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.
+
+filesystem_test_() ->
+ {foreach,
+ fun() ->
+ file:write_file("test.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),
+ application:start(compiler),
+ application:start(syntax_tools),
+ application:start(lager)
+ end,
+ fun(_) ->
+ file:delete("test.log"),
+ application:stop(lager),
+ 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}),
+ 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
+ },
+ {"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}),
+ ?assertEqual(1, lager_test_backend:count()),
+ {_Level, _Time, [_, _, Message]} = 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}),
+ ?assertEqual(0, lager_test_backend:count()),
+ lager:log(error, self(), "Test message"),
+ ?assertEqual(1, lager_test_backend:count()),
+ file:delete("test.log"),
+ file:write_file("test.log", ""),
+ {ok, FInfo} = file:read_file_info("test.log"),
+ file:write_file_info("test.log", FInfo#file_info{mode = 0}),
+ lager:log(error, self(), "Test message"),
+ ?assertEqual(3, lager_test_backend:count()),
+ lager_test_backend:pop(),
+ lager_test_backend:pop(),
+ {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
+ ?assertEqual("Failed to reopen log file test.log with error permission denied", lists:flatten(Message))
+ end
+ },
+ {"unavailable files that are fixed at runtime should start having log messages written",
+ fun() ->
+ {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}),
+ ?assertEqual(1, lager_test_backend:count()),
+ {_Level, _Time, [_, _, Message]} = 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"),
+ {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
+ },
+ {"external logfile rotation/deletion should be handled",
+ fun() ->
+ gen_event:add_handler(lager_event, lager_file_backend, {"test.log", info}),
+ ?assertEqual(0, lager_test_backend:count()),
+ lager:log(error, self(), "Test message1"),
+ ?assertEqual(1, lager_test_backend:count()),
+ file:delete("test.log"),
+ file:write_file("test.log", ""),
+ lager:log(error, self(), "Test message2"),
+ {ok, Bin} = file:read_file("test.log"),
+ Pid = pid_to_list(self()),
+ ?assertMatch([_, _, "[error]", Pid, "Test message2\n"], re:split(Bin, " ", [{return, list}, {parts, 5}])),
+ file:rename("test.log", "test.log.0"),
+ lager:log(error, self(), "Test message3"),
+ {ok, Bin2} = file:read_file("test.log"),
+ ?assertMatch([_, _, "[error]", Pid, "Test message3\n"], re:split(Bin2, " ", [{return, list}, {parts, 5}]))
+ end
+ },
+ {"runtime level changes",
+ fun() ->
+ gen_event:add_handler(lager_event, {lager_file_backend, "test.log"}, {"test.log", info}),
+ ?assertEqual(0, lager_test_backend:count()),
+ lager:log(info, self(), "Test message1"),
+ lager:log(error, self(), "Test message2"),
+ {ok, Bin} = file:read_file("test.log"),
+ Lines = length(re:split(Bin, "\n", [{return, list}, trim])),
+ ?assertEqual(Lines, 2),
+ ?assertEqual(ok, lager:set_loglevel(lager_file_backend, "test.log", warning)),
+ lager:log(info, self(), "Test message3"), %% this won't get logged
+ lager:log(error, self(), "Test message4"),
+ {ok, Bin2} = file:read_file("test.log"),
+ Lines2 = length(re:split(Bin2, "\n", [{return, list}, trim])),
+ ?assertEqual(Lines2, 3)
+ end
+ },
+ {"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, {"test3.log", info}),
+ ?assertEqual({error, bad_module}, lager:set_loglevel(lager_file_backend, "test.log", warning))
+ end
+ },
+ {"tracing should work",
+ fun() ->
+ gen_event:add_handler(lager_event, lager_file_backend,
+ {"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,
+ ?MODULE}], ?DEBUG,
+ {lager_file_backend, "test.log"}}]}),
+ lager:error("Test message"),
+ {ok, Bin} = file:read_file("test.log"),
+ ?assertMatch([_, _, "[error]", _, "Test message\n"], re:split(Bin, " ", [{return, list}, {parts, 5}]))
+ end
+ },
+ {"tracing should not duplicate messages",
+ fun() ->
+ gen_event:add_handler(lager_event, lager_file_backend,
+ {"test.log", critical}),
+ 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,
+ ?MODULE}], ?DEBUG,
+ {lager_file_backend, "test.log"}}]}),
+ lager:critical("Test message"),
+ {ok, Bin2} = file:read_file("test.log"),
+ ?assertMatch([_, _, "[critical]", _, "Test message\n"], re:split(Bin2, " ", [{return, list}, {parts, 5}])),
+ ok = file:delete("test.log"),
+ lager:error("Test message"),
+ {ok, Bin3} = file:read_file("test.log"),
+ ?assertMatch([_, _, "[error]", _, "Test message\n"], re:split(Bin3, " ", [{return, list}, {parts, 5}]))
+ end
+ },
+ {"tracing to a dedicated file should work",
+ fun() ->
+ file:delete("foo.log"),
+ {ok, _} = lager:trace_file("foo.log", [{module, ?MODULE}]),
+ lager:error("Test message"),
+ {ok, Bin3} = file:read_file("foo.log"),
+ ?assertMatch([_, _, "[error]", _, "Test message\n"], re:split(Bin3, " ", [{return, list}, {parts, 5}]))
+ end
+ }
+ ]
+ }.
+
+
+-endif.
+
diff --git a/deps/lager/src/lager_format.erl b/deps/lager/src/lager_format.erl
new file mode 100644
index 0000000..543ac07
--- /dev/null
+++ b/deps/lager/src/lager_format.erl
@@ -0,0 +1,523 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2011. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online 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.
+%%
+%% %CopyrightEnd%
+%%
+-module(lager_format).
+
+%% fork of io_lib_format that uses trunc_io to protect against large terms
+
+-export([format/3, format/4]).
+
+-record(options, {
+ chomp = false
+ }).
+
+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).
+
+collect([$~|Fmt0], Args0) ->
+ {C,Fmt1,Args1} = collect_cseq(Fmt0, Args0),
+ [C|collect(Fmt1, Args1)];
+collect([C|Fmt], Args) ->
+ [C|collect(Fmt, Args)];
+collect([], []) -> [].
+
+collect_cseq(Fmt0, Args0) ->
+ {F,Ad,Fmt1,Args1} = field_width(Fmt0, Args0),
+ {P,Fmt2,Args2} = precision(Fmt1, Args1),
+ {Pad,Fmt3,Args3} = pad_char(Fmt2, Args2),
+ {Encoding,Fmt4,Args4} = encoding(Fmt3, Args3),
+ {C,As,Fmt5,Args5} = collect_cc(Fmt4, Args4),
+ {{C,As,F,Ad,P,Pad,Encoding},Fmt5,Args5}.
+
+encoding([$t|Fmt],Args) ->
+ {unicode,Fmt,Args};
+encoding(Fmt,Args) ->
+ {latin1,Fmt,Args}.
+
+field_width([$-|Fmt0], Args0) ->
+ {F,Fmt,Args} = field_value(Fmt0, Args0),
+ field_width(-F, Fmt, Args);
+field_width(Fmt0, Args0) ->
+ {F,Fmt,Args} = field_value(Fmt0, Args0),
+ field_width(F, Fmt, Args).
+
+field_width(F, Fmt, Args) when F < 0 ->
+ {-F,left,Fmt,Args};
+field_width(F, Fmt, Args) when F >= 0 ->
+ {F,right,Fmt,Args}.
+
+precision([$.|Fmt], Args) ->
+ field_value(Fmt, Args);
+precision(Fmt, Args) ->
+ {none,Fmt,Args}.
+
+field_value([$*|Fmt], [A|Args]) when is_integer(A) ->
+ {A,Fmt,Args};
+field_value([C|Fmt], Args) when is_integer(C), C >= $0, C =< $9 ->
+ field_value([C|Fmt], Args, 0);
+field_value(Fmt, Args) ->
+ {none,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
+ {F,Fmt,Args}.
+
+pad_char([$.,$*|Fmt], [Pad|Args]) -> {Pad,Fmt,Args};
+pad_char([$.,Pad|Fmt], Args) -> {Pad,Fmt,Args};
+pad_char(Fmt, Args) -> {$\s,Fmt,Args}.
+
+%% collect_cc([FormatChar], [Argument]) ->
+%% {Control,[ControlArg],[FormatChar],[Arg]}.
+%% Here we collect the argments for each control character.
+%% Be explicit to cause failure early.
+
+collect_cc([$w|Fmt], [A|Args]) -> {$w,[A],Fmt,Args};
+collect_cc([$p|Fmt], [A|Args]) -> {$p,[A],Fmt,Args};
+collect_cc([$W|Fmt], [A,Depth|Args]) -> {$W,[A,Depth],Fmt,Args};
+collect_cc([$P|Fmt], [A,Depth|Args]) -> {$P,[A,Depth],Fmt,Args};
+collect_cc([$s|Fmt], [A|Args]) -> {$s,[A],Fmt,Args};
+collect_cc([$e|Fmt], [A|Args]) -> {$e,[A],Fmt,Args};
+collect_cc([$f|Fmt], [A|Args]) -> {$f,[A],Fmt,Args};
+collect_cc([$g|Fmt], [A|Args]) -> {$g,[A],Fmt,Args};
+collect_cc([$b|Fmt], [A|Args]) -> {$b,[A],Fmt,Args};
+collect_cc([$B|Fmt], [A|Args]) -> {$B,[A],Fmt,Args};
+collect_cc([$x|Fmt], [A,Prefix|Args]) -> {$x,[A,Prefix],Fmt,Args};
+collect_cc([$X|Fmt], [A,Prefix|Args]) -> {$X,[A,Prefix],Fmt,Args};
+collect_cc([$+|Fmt], [A|Args]) -> {$+,[A],Fmt,Args};
+collect_cc([$#|Fmt], [A|Args]) -> {$#,[A],Fmt,Args};
+collect_cc([$c|Fmt], [A|Args]) -> {$c,[A],Fmt,Args};
+collect_cc([$~|Fmt], Args) when is_list(Args) -> {$~,[],Fmt,Args};
+collect_cc([$n|Fmt], Args) when is_list(Args) -> {$n,[],Fmt,Args};
+collect_cc([$i|Fmt], [A|Args]) -> {$i,[A],Fmt,Args}.
+
+
+%% build([Control], Pc, Indentation) -> [Char].
+%% Interpret the control structures. Count the number of print
+%% remaining and only calculate indentation when necessary. Must also
+%% be smart when calculating indentation for characters in format.
+
+build([{$n, _, _, _, _, _, _}], Acc, MaxLen, #options{chomp=true}) ->
+ %% trailing ~n, ignore
+ {lists:reverse(Acc), MaxLen};
+build([{C,As,F,Ad,P,Pad,Enc}|Cs], Acc, MaxLen, O) ->
+ {S, MaxLen2} = control(C, As, F, Ad, P, Pad, Enc, MaxLen),
+ build(Cs, [S|Acc], MaxLen2, O);
+build([$\n], Acc, MaxLen, #options{chomp=true}) ->
+ %% trailing \n, ignore
+ {lists:reverse(Acc), MaxLen};
+build([$\n|Cs], Acc, MaxLen, O) ->
+ build(Cs, [$\n|Acc], MaxLen - 1, O);
+build([$\t|Cs], Acc, MaxLen, O) ->
+ build(Cs, [$\t|Acc], MaxLen - 1, O);
+build([C|Cs], Acc, MaxLen, O) ->
+ build(Cs, [C|Acc], MaxLen - 1, O);
+build([], Acc, MaxLen, _O) ->
+ {lists:reverse(Acc), MaxLen}.
+
+build2([{C,As,F,Ad,P,Pad,Enc}|Cs], Count, MaxLen) ->
+ {S, Len} = control2(C, As, F, Ad, P, Pad, Enc, MaxLen div Count),
+ [S|build2(Cs, Count - 1, MaxLen - Len)];
+build2([C|Cs], Count, MaxLen) ->
+ [C|build2(Cs, Count, MaxLen)];
+build2([], _, _) -> [].
+
+%% control(FormatChar, [Argument], FieldWidth, Adjust, Precision, PadChar,
+%% Indentation) ->
+%% [Char]
+%% This is the main dispatch function for the various formatting commands.
+%% Field widths and precisions have already been calculated.
+
+control($e, [A], F, Adj, P, Pad, _Enc, L) when is_float(A) ->
+ Res = fwrite_e(A, F, Adj, P, Pad),
+ {Res, L - lists:flatlength(Res)};
+control($f, [A], F, Adj, P, Pad, _Enc, L) when is_float(A) ->
+ Res = fwrite_f(A, F, Adj, P, Pad),
+ {Res, L - lists:flatlength(Res)};
+control($g, [A], F, Adj, P, Pad, _Enc, L) when is_float(A) ->
+ Res = fwrite_g(A, F, Adj, P, Pad),
+ {Res, L - lists:flatlength(Res)};
+control($b, [A], F, Adj, P, Pad, _Enc, L) when is_integer(A) ->
+ Res = unprefixed_integer(A, F, Adj, base(P), Pad, true),
+ {Res, L - lists:flatlength(Res)};
+control($B, [A], F, Adj, P, Pad, _Enc, L) when is_integer(A) ->
+ Res = unprefixed_integer(A, F, Adj, base(P), Pad, false),
+ {Res, L - lists:flatlength(Res)};
+control($x, [A,Prefix], F, Adj, P, Pad, _Enc, L) when is_integer(A),
+ is_atom(Prefix) ->
+ Res = prefixed_integer(A, F, Adj, base(P), Pad, atom_to_list(Prefix), true),
+ {Res, L - lists:flatlength(Res)};
+control($x, [A,Prefix], F, Adj, P, Pad, _Enc, L) when is_integer(A) ->
+ true = io_lib:deep_char_list(Prefix), %Check if Prefix a character list
+ Res = prefixed_integer(A, F, Adj, base(P), Pad, Prefix, true),
+ {Res, L - lists:flatlength(Res)};
+control($X, [A,Prefix], F, Adj, P, Pad, _Enc, L) when is_integer(A),
+ is_atom(Prefix) ->
+ Res = prefixed_integer(A, F, Adj, base(P), Pad, atom_to_list(Prefix), false),
+ {Res, L - lists:flatlength(Res)};
+control($X, [A,Prefix], F, Adj, P, Pad, _Enc, L) when is_integer(A) ->
+ true = io_lib:deep_char_list(Prefix), %Check if Prefix a character list
+ Res = prefixed_integer(A, F, Adj, base(P), Pad, Prefix, false),
+ {Res, L - lists:flatlength(Res)};
+control($+, [A], F, Adj, P, Pad, _Enc, L) when is_integer(A) ->
+ Base = base(P),
+ Prefix = [integer_to_list(Base), $#],
+ Res = prefixed_integer(A, F, Adj, Base, Pad, Prefix, true),
+ {Res, L - lists:flatlength(Res)};
+control($#, [A], F, Adj, P, Pad, _Enc, L) when is_integer(A) ->
+ Base = base(P),
+ Prefix = [integer_to_list(Base), $#],
+ Res = prefixed_integer(A, F, Adj, Base, Pad, Prefix, false),
+ {Res, L - lists:flatlength(Res)};
+control($c, [A], F, Adj, P, Pad, unicode, L) when is_integer(A) ->
+ Res = char(A, F, Adj, P, Pad),
+ {Res, L - lists:flatlength(Res)};
+control($c, [A], F, Adj, P, Pad, _Enc, L) when is_integer(A) ->
+ Res = char(A band 255, F, Adj, P, Pad),
+ {Res, L - lists:flatlength(Res)};
+control($~, [], F, Adj, P, Pad, _Enc, L) ->
+ Res = char($~, F, Adj, P, Pad),
+ {Res, L - lists:flatlength(Res)};
+control($n, [], F, Adj, P, Pad, _Enc, L) ->
+ Res = newline(F, Adj, P, Pad),
+ {Res, L - lists:flatlength(Res)};
+control($i, [_A], _F, _Adj, _P, _Pad, _Enc, L) ->
+ {[], L};
+control($s, [A], F, Adj, P, Pad, _Enc, L) when is_atom(A) ->
+ Res = string(atom_to_list(A), F, Adj, P, Pad),
+ {Res, L - lists:flatlength(Res)};
+control(C, A, F, Adj, P, Pad, Enc, L) ->
+ %% save this for later - these are all the 'large' terms
+ {{C, A, F, Adj, P, Pad, Enc}, L}.
+
+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) ->
+ Term = lager_trunc_io:fprint(A, L, [{lists_as_strings, true}]),
+ Res = term(Term, F, Adj, P, Pad),
+ {Res, lists:flatlength(Res)};
+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) ->
+ Term = lager_trunc_io:fprint(A, L, [{depth, Depth}, {lists_as_strings, true}]),
+ Res = term(Term, F, Adj, P, Pad),
+ {Res, lists:flatlength(Res)};
+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),
+ {Res, lists:flatlength(Res)};
+control2($s, [L0], F, Adj, P, Pad, unicode, L) ->
+ List = lager_trunc_io:fprint(unicode:characters_to_list(L0), L, [{force_strings, true}]),
+ Res = uniconv(string(List, F, Adj, P, Pad)),
+ {Res, lists:flatlength(Res)}.
+
+maybe_flatten(X) when is_list(X) ->
+ lists:flatten(X);
+maybe_flatten(X) ->
+ X.
+
+make_options([], Options) ->
+ Options;
+make_options([{chomp, Bool}|T], Options) when is_boolean(Bool) ->
+ make_options(T, Options#options{chomp=Bool}).
+
+-ifdef(UNICODE_AS_BINARIES).
+uniconv(C) ->
+ unicode:characters_to_binary(C,unicode).
+-else.
+uniconv(C) ->
+ C.
+-endif.
+%% Default integer base
+base(none) ->
+ 10;
+base(B) when is_integer(B) ->
+ B.
+
+%% term(TermList, Field, Adjust, Precision, PadChar)
+%% Output the characters in a term.
+%% Adjust the characters within the field if length less than Max padding
+%% with PadChar.
+
+term(T, none, _Adj, none, _Pad) -> T;
+term(T, none, Adj, P, Pad) -> term(T, P, Adj, P, Pad);
+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)
+ end.
+
+%% fwrite_e(Float, Field, Adjust, Precision, PadChar)
+
+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);
+fwrite_e(Fl, F, Adj, none, Pad) ->
+ fwrite_e(Fl, F, Adj, 6, 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)];
+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)]
+ end.
+
+%% float_man([Digit], Icount, Dcount) -> {[Chars],CarryFlag}.
+%% Generate the characters in the mantissa from the digits with Icount
+%% characters before the '.' and Dcount decimals. Handle carry and let
+%% caller decide what to do at top.
+
+float_man(Ds, 0, Dc) ->
+ {Cs,C} = float_man(Ds, 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}
+ end;
+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}
+ end;
+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.
+
+float_exp(E) when E >= 0 ->
+ [$e,$+|integer_to_list(E)];
+float_exp(E) ->
+ [$e|integer_to_list(E)].
+
+%% fwrite_f(FloatData, Field, Adjust, Precision, PadChar)
+
+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);
+fwrite_f(Fl, F, Adj, none, Pad) ->
+ fwrite_f(Fl, F, Adj, 6, Pad);
+fwrite_f(Fl, F, Adj, P, Pad) when P >= 1 ->
+ term(float_f(Fl, float_data(Fl), P), F, Adj, F, Pad).
+
+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, {Ds,E}, P) ->
+ case float_man(Ds, E, P) of
+ {Fs,true} -> "1" ++ Fs; %Handle carry
+ {Fs,false} -> Fs
+ end.
+
+%% float_data([FloatChar]) -> {[Digit],Exponent}
+
+float_data(Fl) ->
+ float_data(float_to_list(Fl), []).
+
+float_data([$e|E], Ds) ->
+ {lists:reverse(Ds),list_to_integer(E)+1};
+float_data([D|Cs], Ds) when D >= $0, D =< $9 ->
+ float_data(Cs, [D|Ds]);
+float_data([_|Cs], Ds) ->
+ float_data(Cs, Ds).
+
+%% fwrite_g(Float, Field, Adjust, Precision, PadChar)
+%% Use the f form if Float is >= 0.1 and < 1.0e4,
+%% and the prints correctly in the f form, else the e form.
+%% Precision always means the # of significant digits.
+
+fwrite_g(Fl, F, Adj, none, Pad) ->
+ fwrite_g(Fl, F, Adj, 6, 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,
+ 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)
+ end.
+
+
+%% string(String, Field, Adjust, Precision, PadChar)
+
+string(S, none, _Adj, none, _Pad) -> S;
+string(S, F, Adj, none, Pad) ->
+ string_field(S, F, Adj, lists:flatlength(S), Pad);
+string(S, none, _Adj, P, Pad) ->
+ string_field(S, P, left, lists:flatlength(S), 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;
+ true -> % F == P
+ string_field(S, F, Adj, N, Pad)
+ end.
+
+string_field(S, F, _Adj, N, _Pad) when N > F ->
+ flat_trunc(S, F);
+string_field(S, F, Adj, N, Pad) when N < F ->
+ adjust(S, chars(Pad, F-N), Adj);
+string_field(S, _, _, _, _) -> % N == F
+ S.
+
+%% unprefixed_integer(Int, Field, Adjust, Base, PadChar, Lowercase)
+%% -> [Char].
+
+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);
+ true ->
+ 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)
+%% -> [Char].
+
+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);
+ true ->
+ 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].
+
+char(C, none, _Adj, none, _Pad) -> [C];
+char(C, F, _Adj, none, _Pad) -> chars(C, F);
+char(C, none, _Adj, P, _Pad) -> chars(C, P);
+char(C, F, Adj, P, Pad) when F >= P ->
+ adjust(chars(C, P), chars(Pad, F - P), Adj).
+
+%% newline(Field, Adjust, Precision, PadChar) -> [Char].
+
+newline(none, _Adj, _P, _Pad) -> "\n";
+newline(F, right, _P, _Pad) -> chars($\n, F).
+
+%%
+%% Utilities
+%%
+
+adjust(Data, [], _) -> Data;
+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(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) ->
+ lists:reverse(R).
+
+%% A deep version of string:chars/2,3
+
+chars(_C, 0) ->
+ [];
+chars(C, 1) ->
+ [C];
+chars(C, 2) ->
+ [C,C];
+chars(C, 3) ->
+ [C,C,C];
+chars(C, N) when is_integer(N), (N band 1) =:= 0 ->
+ S = chars(C, N bsr 1),
+ [S|S];
+chars(C, N) when is_integer(N) ->
+ S = chars(C, N bsr 1),
+ [C,S|S].
+
+%chars(C, N, Tail) ->
+% [chars(C, N)|Tail].
+
+%% Lowercase conversion
+
+cond_lowercase(String, true) ->
+ lowercase(String);
+cond_lowercase(String,false) ->
+ String.
+
+lowercase([H|T]) when is_integer(H), H >= $A, H =< $Z ->
+ [(H-$A+$a)|lowercase(T)];
+lowercase([H|T]) ->
+ [H|lowercase(T)];
+lowercase([]) ->
+ [].
diff --git a/deps/lager/src/lager_handler_watcher.erl b/deps/lager/src/lager_handler_watcher.erl
new file mode 100644
index 0000000..32a7185
--- /dev/null
+++ b/deps/lager/src/lager_handler_watcher.erl
@@ -0,0 +1,159 @@
+%% Copyright (c) 2011 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 process that does a gen_event:add_sup_handler and attempts to re-add
+%% event handlers when they exit.
+
+%% @private
+
+-module(lager_handler_watcher).
+
+-behaviour(gen_server).
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-endif.
+
+%% callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
+ code_change/3]).
+
+-export([start_link/3, start/3]).
+
+-record(state, {
+ module,
+ config,
+ event
+ }).
+
+start_link(Event, Module, Config) ->
+ gen_server:start_link(?MODULE, [Event, Module, Config], []).
+
+start(Event, Module, Config) ->
+ gen_server:start(?MODULE, [Event, Module, Config], []).
+
+init([Event, Module, Config]) ->
+ install_handler(Event, Module, Config),
+ {ok, #state{event=Event, module=Module, config=Config}}.
+
+handle_call(_Call, _From, State) ->
+ {reply, ok, State}.
+
+handle_cast(_Request, State) ->
+ {noreply, State}.
+
+handle_info({gen_event_EXIT, Module, normal}, #state{module=Module} = State) ->
+ {stop, normal, 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),
+ {noreply, State};
+handle_info(reinstall_handler, #state{module=Module, config=Config, event=Event} = State) ->
+ install_handler(Event, Module, Config),
+ {noreply, State};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%% internal
+
+install_handler(Event, Module, Config) ->
+ case gen_event:add_sup_handler(Event, Module, Config) of
+ ok ->
+ lager:log(debug, self(), "Lager installed handler ~p into ~p", [Module, Event]),
+ 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)
+ end.
+
+-ifdef(TEST).
+
+from_now(Seconds) ->
+ {Mega, Secs, Micro} = os:timestamp(),
+ {Mega, Secs + Seconds, Micro}.
+
+reinstall_on_initial_failure_test_() ->
+ {timeout, 60000,
+ [
+ fun() ->
+ error_logger:tty(false),
+ application:load(lager),
+ 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),
+ try
+ ?assertEqual(1, lager_test_backend:count()),
+ {_Level, _Time, [_, _, Message]} = 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),
+ ?assertEqual(0, lager_test_backend:count()),
+ ?assert(lists:member(lager_crash_backend, gen_event:which_handlers(lager_event)))
+ after
+ application:stop(lager),
+ error_logger:tty(true)
+ end
+ end
+ ]
+ }.
+
+reinstall_on_runtime_failure_test_() ->
+ {timeout, 60000,
+ [
+ fun() ->
+ error_logger:tty(false),
+ application:load(lager),
+ 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),
+ 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)),
+ ?assertEqual(false, lists:member(lager_crash_backend, gen_event:which_handlers(lager_event)))
+ after
+ application:stop(lager),
+ error_logger:tty(true)
+ end
+ end
+ ]
+ }.
+
+
+-endif.
diff --git a/deps/lager/src/lager_handler_watcher_sup.erl b/deps/lager/src/lager_handler_watcher_sup.erl
new file mode 100644
index 0000000..bea8a63
--- /dev/null
+++ b/deps/lager/src/lager_handler_watcher_sup.erl
@@ -0,0 +1,39 @@
+%% Copyright (c) 2011 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 supervisor for monitoring lager_handler_watcher processes.
+
+%% @private
+
+-module(lager_handler_watcher_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Callbacks
+-export([init/1]).
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+init([]) ->
+ {ok, {{simple_one_for_one, 10, 60},
+ [
+ {lager_handler_watcher, {lager_handler_watcher, start_link, []},
+ transient, 5000, worker, [lager_handler_watcher]}
+ ]}}.
diff --git a/deps/lager/src/lager_mochiglobal.erl b/deps/lager/src/lager_mochiglobal.erl
new file mode 100644
index 0000000..5a92ce8
--- /dev/null
+++ b/deps/lager/src/lager_mochiglobal.erl
@@ -0,0 +1,107 @@
+%% @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_stdlib.erl b/deps/lager/src/lager_stdlib.erl
new file mode 100644
index 0000000..a492f7e
--- /dev/null
+++ b/deps/lager/src/lager_stdlib.erl
@@ -0,0 +1,507 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online 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.
+%%
+%% %CopyrightEnd%
+%%
+
+%% @doc Functions from Erlang OTP distribution that are really useful
+%% but aren't exported.
+%%
+%% All functions in this module are covered by the Erlang/OTP source
+%% distribution's license, the Erlang Public License. See
+%% http://www.erlang.org/ for full details.
+
+-module(lager_stdlib).
+
+-export([string_p/1]).
+-export([write_time/2, maybe_utc/1]).
+-export([is_my_error_report/1, is_my_info_report/1]).
+-export([sup_get/2]).
+-export([proc_lib_format/2]).
+
+
+%% from error_logger_file_h
+string_p([]) ->
+ false;
+string_p(Term) ->
+ string_p1(Term).
+
+string_p1([H|T]) when is_integer(H), H >= $\s, H < 255 ->
+ string_p1(T);
+string_p1([$\n|T]) -> string_p1(T);
+string_p1([$\r|T]) -> string_p1(T);
+string_p1([$\t|T]) -> string_p1(T);
+string_p1([$\v|T]) -> string_p1(T);
+string_p1([$\b|T]) -> string_p1(T);
+string_p1([$\f|T]) -> string_p1(T);
+string_p1([$\e|T]) -> string_p1(T);
+string_p1([H|T]) when is_list(H) ->
+ case string_p1(H) of
+ true -> string_p1(T);
+ _ -> false
+ end;
+string_p1([]) -> true;
+string_p1(_) -> false.
+
+%% From calendar
+-type year1970() :: 1970..10000. % should probably be 1970..
+-type month() :: 1..12.
+-type day() :: 1..31.
+-type hour() :: 0..23.
+-type minute() :: 0..59.
+-type second() :: 0..59.
+-type t_time() :: {hour(),minute(),second()}.
+-type t_datetime1970() :: {{year1970(),month(),day()},t_time()}.
+
+%% From OTP stdlib's error_logger_tty_h.erl ... These functions aren't
+%% exported.
+-spec write_time({utc, t_datetime1970()} | t_datetime1970(), string()) -> string().
+write_time({utc,{{Y,Mo,D},{H,Mi,S}}},Type) ->
+ io_lib:format("~n=~s==== ~p-~s-~p::~s:~s:~s UTC ===~n",
+ [Type,D,month(Mo),Y,t(H),t(Mi),t(S)]);
+write_time({{Y,Mo,D},{H,Mi,S}},Type) ->
+ io_lib:format("~n=~s==== ~p-~s-~p::~s:~s:~s ===~n",
+ [Type,D,month(Mo),Y,t(H),t(Mi),t(S)]).
+
+-spec maybe_utc(t_datetime1970()) -> {utc, t_datetime1970()} | t_datetime1970().
+maybe_utc(Time) ->
+ UTC = case application:get_env(sasl, utc_log) of
+ {ok, Val} ->
+ Val;
+ undefined ->
+ %% Backwards compatible:
+ case application:get_env(stdlib, utc_log) of
+ {ok, Val} ->
+ Val;
+ undefined ->
+ false
+ end
+ end,
+ if
+ UTC =:= true ->
+ UTCTime = case calendar:local_time_to_universal_time_dst(Time) of
+ [] -> calendar:local_time();
+ [T0|_] -> T0
+ end,
+ {utc, UTCTime};
+ true ->
+ Time
+ end.
+
+t(X) when is_integer(X) ->
+ t1(integer_to_list(X));
+t(_) ->
+ "".
+t1([X]) -> [$0,X];
+t1(X) -> X.
+
+month(1) -> "Jan";
+month(2) -> "Feb";
+month(3) -> "Mar";
+month(4) -> "Apr";
+month(5) -> "May";
+month(6) -> "Jun";
+month(7) -> "Jul";
+month(8) -> "Aug";
+month(9) -> "Sep";
+month(10) -> "Oct";
+month(11) -> "Nov";
+month(12) -> "Dec".
+
+%% From OTP sasl's sasl_report.erl ... These functions aren't
+%% exported.
+-spec is_my_error_report(atom()) -> boolean().
+is_my_error_report(supervisor_report) -> true;
+is_my_error_report(crash_report) -> true;
+is_my_error_report(_) -> false.
+
+-spec is_my_info_report(atom()) -> boolean().
+is_my_info_report(progress) -> true;
+is_my_info_report(_) -> false.
+
+-spec sup_get(term(), [proplists:property()]) -> term().
+sup_get(Tag, Report) ->
+ case lists:keysearch(Tag, 1, Report) of
+ {value, {_, Value}} ->
+ Value;
+ _ ->
+ ""
+ end.
+
+%% From OTP stdlib's proc_lib.erl ... These functions aren't exported.
+-spec proc_lib_format([term()], pos_integer()) -> string().
+proc_lib_format([OwnReport,LinkReport], FmtMaxBytes) ->
+ OwnFormat = format_report(OwnReport, FmtMaxBytes),
+ LinkFormat = format_report(LinkReport, FmtMaxBytes),
+ %% io_lib:format here is OK because we're limiting max length elsewhere.
+ Str = io_lib:format(" crasher:~n~s neighbours:~n~s",[OwnFormat,LinkFormat]),
+ lists:flatten(Str).
+
+format_report(Rep, FmtMaxBytes) when is_list(Rep) ->
+ format_rep(Rep, FmtMaxBytes);
+format_report(Rep, FmtMaxBytes) ->
+ {Str, _} = lager_trunc_io:print(Rep, FmtMaxBytes),
+ io_lib:format("~p~n", [Str]).
+
+format_rep([{initial_call,InitialCall}|Rep], FmtMaxBytes) ->
+ [format_mfa(InitialCall, FmtMaxBytes)|format_rep(Rep, FmtMaxBytes)];
+format_rep([{error_info,{Class,Reason,StackTrace}}|Rep], FmtMaxBytes) ->
+ [format_exception(Class, Reason, StackTrace, FmtMaxBytes)|format_rep(Rep, FmtMaxBytes)];
+format_rep([{Tag,Data}|Rep], FmtMaxBytes) ->
+ [format_tag(Tag, Data, FmtMaxBytes)|format_rep(Rep, FmtMaxBytes)];
+format_rep(_, _S) ->
+ [].
+
+format_exception(Class, Reason, StackTrace, FmtMaxBytes) ->
+ PF = pp_fun(FmtMaxBytes),
+ StackFun = fun(M, _F, _A) -> (M =:= erl_eval) or (M =:= ?MODULE) end,
+ %% EI = " exception: ",
+ EI = " ",
+ [EI, lib_format_exception(1+length(EI), Class, Reason,
+ StackTrace, StackFun, PF), "\n"].
+
+format_mfa({M,F,Args}=StartF, FmtMaxBytes) ->
+ try
+ A = length(Args),
+ [" initial call: ",atom_to_list(M),$:,atom_to_list(F),$/,
+ integer_to_list(A),"\n"]
+ catch
+ error:_ ->
+ format_tag(initial_call, StartF, FmtMaxBytes)
+ end.
+
+pp_fun(FmtMaxBytes) ->
+ fun(Term, _I) ->
+ {Str, _} = lager_trunc_io:print(Term, FmtMaxBytes),
+ io_lib:format("~s", [Str])
+ end.
+
+format_tag(Tag, Data, FmtMaxBytes) ->
+ {Str, _} = lager_trunc_io:print(Data, FmtMaxBytes),
+ io_lib:format(" ~p: ~s~n", [Tag, Str]).
+
+%% From OTP stdlib's lib.erl ... These functions aren't exported.
+
+lib_format_exception(I, Class, Reason, StackTrace, StackFun, FormatFun)
+ when is_integer(I), I >= 1, is_function(StackFun, 3),
+ is_function(FormatFun, 2) ->
+ Str = n_spaces(I-1),
+ {Term,Trace1,Trace} = analyze_exception(Class, Reason, StackTrace),
+ Expl0 = explain_reason(Term, Class, Trace1, FormatFun, Str),
+ Expl = io_lib:fwrite(<<"~s~s">>, [exited(Class), Expl0]),
+ case format_stacktrace1(Str, Trace, FormatFun, StackFun) of
+ [] -> Expl;
+ Stack -> [Expl, $\n, Stack]
+ end.
+
+analyze_exception(error, Term, Stack) ->
+ case {is_stacktrace(Stack), Stack, Term} of
+ {true, [{_M,_F,As}=MFA|MFAs], function_clause} when is_list(As) ->
+ {Term,[MFA],MFAs};
+ {true, [{shell,F,A}], function_clause} when is_integer(A) ->
+ {Term, [{F,A}], []};
+ {true, [{_M,_F,_AorAs}=MFA|MFAs], undef} ->
+ {Term,[MFA],MFAs};
+ {true, _, _} ->
+ {Term,[],Stack};
+ {false, _, _} ->
+ {{Term,Stack},[],[]}
+ end;
+analyze_exception(_Class, Term, Stack) ->
+ case is_stacktrace(Stack) of
+ true ->
+ {Term,[],Stack};
+ false ->
+ {{Term,Stack},[],[]}
+ end.
+
+is_stacktrace([]) ->
+ true;
+is_stacktrace([{M,F,A}|Fs]) when is_atom(M), is_atom(F), is_integer(A) ->
+ is_stacktrace(Fs);
+is_stacktrace([{M,F,As}|Fs]) when is_atom(M), is_atom(F), length(As) >= 0 ->
+ is_stacktrace(Fs);
+is_stacktrace(_) ->
+ false.
+
+%% ERTS exit codes (some of them are also returned by erl_eval):
+explain_reason(badarg, error, [], _PF, _Str) ->
+ <<"bad argument">>;
+explain_reason({badarg,V}, error=Cl, [], PF, Str) -> % orelse, andalso
+ format_value(V, <<"bad argument: ">>, Cl, PF, Str);
+explain_reason(badarith, error, [], _PF, _Str) ->
+ <<"bad argument in an arithmetic expression">>;
+explain_reason({badarity,{Fun,As}}, error, [], _PF, _Str)
+ when is_function(Fun) ->
+ %% Only the arity is displayed, not the arguments As.
+ io_lib:fwrite(<<"~s called with ~s">>,
+ [format_fun(Fun), argss(length(As))]);
+explain_reason({badfun,Term}, error=Cl, [], PF, Str) ->
+ format_value(Term, <<"bad function ">>, Cl, PF, Str);
+explain_reason({badmatch,Term}, error=Cl, [], PF, Str) ->
+ format_value(Term, <<"no match of right hand side value ">>, Cl, PF, Str);
+explain_reason({case_clause,V}, error=Cl, [], PF, Str) ->
+ %% "there is no case clause with a true guard sequence and a
+ %% pattern matching..."
+ format_value(V, <<"no case clause matching ">>, Cl, PF, Str);
+explain_reason(function_clause, error, [{F,A}], _PF, _Str) ->
+ %% Shell commands
+ FAs = io_lib:fwrite(<<"~w/~w">>, [F, A]),
+ [<<"no function clause matching call to ">> | FAs];
+explain_reason(function_clause, error=Cl, [{M,F,As}], PF, Str) ->
+ String = <<"no function clause matching ">>,
+ format_errstr_call(String, Cl, {M,F}, As, PF, Str);
+explain_reason(if_clause, error, [], _PF, _Str) ->
+ <<"no true branch found when evaluating an if expression">>;
+explain_reason(noproc, error, [], _PF, _Str) ->
+ <<"no such process or port">>;
+explain_reason(notalive, error, [], _PF, _Str) ->
+ <<"the node cannot be part of a distributed system">>;
+explain_reason(system_limit, error, [], _PF, _Str) ->
+ <<"a system limit has been reached">>;
+explain_reason(timeout_value, error, [], _PF, _Str) ->
+ <<"bad receive timeout value">>;
+explain_reason({try_clause,V}, error=Cl, [], PF, Str) ->
+ %% "there is no try clause with a true guard sequence and a
+ %% pattern matching..."
+ format_value(V, <<"no try clause matching ">>, Cl, PF, Str);
+explain_reason(undef, error, [{M,F,A}], _PF, _Str) ->
+ %% Only the arity is displayed, not the arguments, if there are any.
+ io_lib:fwrite(<<"undefined function ~s">>,
+ [mfa_to_string(M, F, n_args(A))]);
+explain_reason({shell_undef,F,A}, error, [], _PF, _Str) ->
+ %% Give nicer reports for undefined shell functions
+ %% (but not when the user actively calls shell_default:F(...)).
+ io_lib:fwrite(<<"undefined shell command ~s/~w">>, [F, n_args(A)]);
+%% Exit codes returned by erl_eval only:
+explain_reason({argument_limit,_Fun}, error, [], _PF, _Str) ->
+ io_lib:fwrite(<<"limit of number of arguments to interpreted function"
+ " exceeded">>, []);
+explain_reason({bad_filter,V}, error=Cl, [], PF, Str) ->
+ format_value(V, <<"bad filter ">>, Cl, PF, Str);
+explain_reason({bad_generator,V}, error=Cl, [], PF, Str) ->
+ format_value(V, <<"bad generator ">>, Cl, PF, Str);
+explain_reason({unbound,V}, error, [], _PF, _Str) ->
+ io_lib:fwrite(<<"variable ~w is unbound">>, [V]);
+%% Exit codes local to the shell module (restricted shell):
+explain_reason({restricted_shell_bad_return, V}, exit=Cl, [], PF, Str) ->
+ String = <<"restricted shell module returned bad value ">>,
+ format_value(V, String, Cl, PF, Str);
+explain_reason({restricted_shell_disallowed,{ForMF,As}},
+ exit=Cl, [], PF, Str) ->
+ %% ForMF can be a fun, but not a shell fun.
+ String = <<"restricted shell does not allow ">>,
+ format_errstr_call(String, Cl, ForMF, As, PF, Str);
+explain_reason(restricted_shell_started, exit, [], _PF, _Str) ->
+ <<"restricted shell starts now">>;
+explain_reason(restricted_shell_stopped, exit, [], _PF, _Str) ->
+ <<"restricted shell stopped">>;
+%% Other exit code:
+explain_reason(Reason, Class, [], PF, Str) ->
+ PF(Reason, (iolist_size(Str)+1) + exited_size(Class)).
+
+n_spaces(N) ->
+ lists:duplicate(N, $\s).
+
+exited_size(Class) ->
+ iolist_size(exited(Class)).
+
+exited(error) ->
+ <<"exception error: ">>;
+exited(exit) ->
+ <<"exception exit: ">>;
+exited(throw) ->
+ <<"exception throw: ">>.
+
+format_stacktrace1(S0, Stack0, PF, SF) ->
+ Stack1 = lists:dropwhile(fun({M,F,A}) -> SF(M, F, A)
+ end, lists:reverse(Stack0)),
+ S = [" " | S0],
+ Stack = lists:reverse(Stack1),
+ format_stacktrace2(S, Stack, 1, PF).
+
+format_stacktrace2(S, [{M,F,A}|Fs], N, PF) when is_integer(A) ->
+ [io_lib:fwrite(<<"~s~s ~s">>,
+ [sep(N, S), origin(N, M, F, A), mfa_to_string(M, F, A)])
+ | format_stacktrace2(S, Fs, N + 1, PF)];
+format_stacktrace2(S, [{M,F,As}|Fs], N, PF) when is_list(As) ->
+ A = length(As),
+ CalledAs = [S,<<" called as ">>],
+ C = format_call("", CalledAs, {M,F}, As, PF),
+ [io_lib:fwrite(<<"~s~s ~s\n~s~s">>,
+ [sep(N, S), origin(N, M, F, A), mfa_to_string(M, F, A),
+ CalledAs, C])
+ | format_stacktrace2(S, Fs, N + 1, PF)];
+format_stacktrace2(_S, [], _N, _PF) ->
+ "".
+
+argss(0) ->
+ <<"no arguments">>;
+argss(1) ->
+ <<"one argument">>;
+argss(2) ->
+ <<"two arguments">>;
+argss(I) ->
+ io_lib:fwrite(<<"~w arguments">>, [I]).
+
+format_value(V, ErrStr, Class, PF, Str) ->
+ Pre1Sz = exited_size(Class),
+ Str1 = PF(V, Pre1Sz + iolist_size([Str, ErrStr])+1),
+ [ErrStr | case count_nl(Str1) of
+ N1 when N1 > 1 ->
+ Str2 = PF(V, iolist_size(Str) + 1 + Pre1Sz),
+ case count_nl(Str2) < N1 of
+ true ->
+ [$\n, Str, n_spaces(Pre1Sz) | Str2];
+ false ->
+ Str1
+ end;
+ _ ->
+ Str1
+ end].
+
+format_fun(Fun) when is_function(Fun) ->
+ {module, M} = erlang:fun_info(Fun, module),
+ {name, F} = erlang:fun_info(Fun, name),
+ {arity, A} = erlang:fun_info(Fun, arity),
+ case erlang:fun_info(Fun, type) of
+ {type, local} when F =:= "" ->
+ io_lib:fwrite(<<"~w">>, [Fun]);
+ {type, local} when M =:= erl_eval ->
+ io_lib:fwrite(<<"interpreted function with arity ~w">>, [A]);
+ {type, local} ->
+ mfa_to_string(M, F, A);
+ {type, external} ->
+ mfa_to_string(M, F, A)
+ end.
+
+format_errstr_call(ErrStr, Class, ForMForFun, As, PF, Pre0) ->
+ Pre1 = [Pre0 | n_spaces(exited_size(Class))],
+ format_call(ErrStr, Pre1, ForMForFun, As, PF).
+
+format_call(ErrStr, Pre1, ForMForFun, As, PF) ->
+ Arity = length(As),
+ [ErrStr |
+ case is_op(ForMForFun, Arity) of
+ {yes,Op} ->
+ format_op(ErrStr, Pre1, Op, As, PF);
+ no ->
+ MFs = mf_to_string(ForMForFun, Arity),
+ I1 = iolist_size([Pre1,ErrStr|MFs]),
+ S1 = pp_arguments(PF, As, I1),
+ S2 = pp_arguments(PF, As, iolist_size([Pre1|MFs])),
+ Long = count_nl(pp_arguments(PF, [a2345,b2345], I1)) > 0,
+ case Long or (count_nl(S2) < count_nl(S1)) of
+ true ->
+ [$\n, Pre1, MFs, S2];
+ false ->
+ [MFs, S1]
+ end
+ end].
+
+mfa_to_string(M, F, A) ->
+ io_lib:fwrite(<<"~s/~w">>, [mf_to_string({M, F}, A), A]).
+
+mf_to_string({M, F}, A) ->
+ case erl_internal:bif(M, F, A) of
+ true ->
+ io_lib:fwrite(<<"~w">>, [F]);
+ false ->
+ case is_op({M, F}, A) of
+ {yes, '/'} ->
+ io_lib:fwrite(<<"~w">>, [F]);
+ {yes, F} ->
+ atom_to_list(F);
+ no ->
+ io_lib:fwrite(<<"~w:~w">>, [M, F])
+ end
+ end;
+mf_to_string(Fun, _A) when is_function(Fun) ->
+ format_fun(Fun);
+mf_to_string(F, _A) ->
+ io_lib:fwrite(<<"~w">>, [F]).
+
+n_args(A) when is_integer(A) ->
+ A;
+n_args(As) when is_list(As) ->
+ length(As).
+
+origin(1, M, F, A) ->
+ case is_op({M, F}, n_args(A)) of
+ {yes, F} -> <<"in operator ">>;
+ no -> <<"in function ">>
+ end;
+origin(_N, _M, _F, _A) ->
+ <<"in call from">>.
+
+sep(1, S) -> S;
+sep(_, S) -> [$\n | S].
+
+count_nl([E | Es]) ->
+ count_nl(E) + count_nl(Es);
+count_nl($\n) ->
+ 1;
+count_nl(Bin) when is_binary(Bin) ->
+ count_nl(binary_to_list(Bin));
+count_nl(_) ->
+ 0.
+
+is_op(ForMForFun, A) ->
+ try
+ {erlang,F} = ForMForFun,
+ _ = erl_internal:op_type(F, A),
+ {yes,F}
+ catch error:_ -> no
+ end.
+
+format_op(ErrStr, Pre, Op, [A1, A2], PF) ->
+ I1 = iolist_size([ErrStr,Pre]),
+ S1 = PF(A1, I1+1),
+ S2 = PF(A2, I1+1),
+ OpS = atom_to_list(Op),
+ Pre1 = [$\n | n_spaces(I1)],
+ case count_nl(S1) > 0 of
+ true ->
+ [S1,Pre1,OpS,Pre1|S2];
+ false ->
+ OpS2 = io_lib:fwrite(<<" ~s ">>, [Op]),
+ S2_2 = PF(A2, iolist_size([ErrStr,Pre,S1|OpS2])+1),
+ case count_nl(S2) < count_nl(S2_2) of
+ true ->
+ [S1,Pre1,OpS,Pre1|S2];
+ false ->
+ [S1,OpS2|S2_2]
+ end
+ end.
+
+pp_arguments(PF, As, I) ->
+ case {As, io_lib:printable_list(As)} of
+ {[Int | T], true} ->
+ L = integer_to_list(Int),
+ Ll = length(L),
+ A = list_to_atom(lists:duplicate(Ll, $a)),
+ S0 = binary_to_list(iolist_to_binary(PF([A | T], I+1))),
+ brackets_to_parens([$[,L,string:sub_string(S0, 2+Ll)]);
+ _ ->
+ brackets_to_parens(PF(As, I+1))
+ end.
+
+brackets_to_parens(S) ->
+ B = iolist_to_binary(S),
+ Sz = byte_size(B) - 2,
+ <<$[,R:Sz/binary,$]>> = B,
+ [$(,R,$)].
+
diff --git a/deps/lager/src/lager_sup.erl b/deps/lager/src/lager_sup.erl
new file mode 100644
index 0000000..7c2b29b
--- /dev/null
+++ b/deps/lager/src/lager_sup.erl
@@ -0,0 +1,80 @@
+%% Copyright (c) 2011 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 Lager's top level supervisor.
+
+%% @private
+
+-module(lager_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Callbacks
+-export([init/1]).
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+init([]) ->
+ Children = [
+ {lager, {gen_event, start_link, [{local, lager_event}]},
+ permanent, 5000, worker, [dynamic]},
+ {lager_handler_watcher_sup, {lager_handler_watcher_sup, start_link, []},
+ permanent, 5000, supervisor, [lager_handler_watcher_sup]}],
+
+ %% check if the crash log is enabled
+ Crash = case application:get_env(lager, crash_log) of
+ {ok, undefined} ->
+ [];
+ {ok, File} ->
+ MaxBytes = case application:get_env(lager, crash_log_msg_size) of
+ {ok, Val} when is_integer(Val) andalso Val > 0 -> Val;
+ _ -> 65536
+ end,
+ RotationSize = case application:get_env(lager, crash_log_size) of
+ {ok, Val1} when is_integer(Val1) andalso Val1 >= 0 -> Val1;
+ _ -> 0
+ end,
+ RotationCount = case application:get_env(lager, crash_log_count) of
+ {ok, Val2} when is_integer(Val2) andalso Val2 >=0 -> Val2;
+ _ -> 0
+ end,
+ RotationDate = case application:get_env(lager, crash_log_date) of
+ {ok, Val3} ->
+ case lager_util:parse_rotation_date_spec(Val3) of
+ {ok, Spec} -> Spec;
+ {error, _} when Val3 == "" -> undefined; %% blank is ok
+ {error, _} ->
+ error_logger:error_msg("Invalid date spec for "
+ "crash log ~p~n", [Val3]),
+ undefined
+ end;
+ _ -> undefined
+ end,
+
+ [{lager_crash_log, {lager_crash_log, start_link, [File, MaxBytes,
+ RotationSize, RotationDate, RotationCount]},
+ permanent, 5000, worker, [lager_crash_log]}];
+ _ ->
+ []
+ end,
+
+ {ok, {{one_for_one, 10, 60},
+ Children ++ Crash
+ }}.
diff --git a/deps/lager/src/lager_transform.erl b/deps/lager/src/lager_transform.erl
new file mode 100644
index 0000000..7cbe499
--- /dev/null
+++ b/deps/lager/src/lager_transform.erl
@@ -0,0 +1,264 @@
+%% Copyright (c) 2011 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 The parse transform used for lager messages.
+%% 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
+%% nothing wishes to consume the message.
+
+-module(lager_transform).
+
+-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.
+
+
+walk_ast(Acc, []) ->
+ lists:reverse(Acc);
+walk_ast(Acc, [{attribute, _, module, {Module, _PmodArgs}}=H|T]) ->
+ %% A wild parameterized module appears!
+ put(module, Module),
+ walk_ast([H|Acc], T);
+walk_ast(Acc, [{attribute, _, module, Module}=H|T]) ->
+ put(module, Module),
+ walk_ast([H|Acc], T);
+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, [H|T]) ->
+ walk_ast([H|Acc], T).
+
+walk_clauses(Acc, []) ->
+ lists:reverse(Acc);
+walk_clauses(Acc, [{clause, Line, Arguments, Guards, Body}|T]) ->
+ walk_clauses([{clause, Line, Arguments, Guards, walk_body([], Body)}|Acc], 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
+ 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
+ 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) ->
+ Stmt.
+
+%%
+%% Generate from key value pairs
+%% remove duplicates!
+%%
+
+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, []) ->
+ [].
+
+%%
+%% 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},[]}]}}]}].
+
+%%
+%% Concat clause from input
+%%
+concat_clauses(_L,{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]).
+
+trace_clause(K,V) ->
+ Kn = erl_parse:normalise(K),
+ Vn = erl_parse:normalise(V),
+ {{Kn,Vn},{K,V}}.
+
+
+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}.
+
diff --git a/deps/lager/src/lager_trunc_io.erl b/deps/lager/src/lager_trunc_io.erl
new file mode 100644
index 0000000..a9e8819
--- /dev/null
+++ b/deps/lager/src/lager_trunc_io.erl
@@ -0,0 +1,576 @@
+%% ``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
+%% 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.''
+%%
+%% @doc Module to print out terms for logging. Limits by length rather than depth.
+%%
+%% The resulting string may be slightly larger than the limit; the intention
+%% is to provide predictable CPU and memory consumption for formatting
+%% terms, not produce precise string lengths.
+%%
+%% Typical use:
+%%
+%% trunc_io:print(Term, 500).
+%%
+%% Source license: Erlang Public License.
+%% Original author: Matthias Lang, <tt>matthias@corelatus.se</tt>
+%%
+%% Various changes to this module, most notably the format/3 implementation
+%% were added by Andrew Thompson `<andrew@basho.com>'. The module has been renamed
+%% to avoid conflicts with the vanilla module.
+
+-module(lager_trunc_io).
+-author('matthias@corelatus.se').
+%% 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 $").
+
+-ifdef(TEST).
+-export([perf/0, perf/3, perf1/0, test/0, test/2]). % testing functions
+-include_lib("eunit/include/eunit.hrl").
+-endif.
+
+-record(print_options, {
+ %% negative depth means no depth limiting
+ depth = -1 :: integer(),
+ %% whether to print lists as strings, if possible
+ lists_as_strings = true :: boolean(),
+ %% force strings, or binaries to be printed as a string,
+ %% even if they're not printable
+ force_strings = false :: boolean()
+ }).
+
+format(Fmt, Args, Max) ->
+ format(Fmt, Args, Max, []).
+
+format(Fmt, Args, Max, Options) ->
+ try lager_format:format(Fmt, Args, Max, Options) of
+ Result -> Result
+ catch
+ _:_ ->
+ erlang:error(badarg, [Fmt, Args])
+ end.
+
+%% @doc Returns an flattened list containing the ASCII representation of the given
+%% term.
+-spec fprint(term(), pos_integer()) -> string().
+fprint(Term, Max) ->
+ 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) ->
+ {L, _} = print(T, Max, prepare_options(Options, #print_options{})),
+ lists:flatten(L).
+
+%% @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
+%% type in a future version of Erlang. If print crashes, we fall back
+%% 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.
+
+%% @doc Returns {List, Length}
+-spec print(term(), pos_integer()) -> {iolist(), pos_integer()}.
+print(Term, Max) ->
+ print(Term, Max, []).
+
+%% @doc Returns {List, Length}
+-spec print(term(), pos_integer(), #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{}));
+
+print(Term, _Max, #print_options{force_strings=true}) when not is_list(Term), not is_binary(Term), not is_atom(Term) ->
+ erlang:error(badarg);
+
+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
+%% arbitrarily long bignum. Let's assume that won't happen unless someone
+%% is being malicious.
+%%
+print(Atom, _Max, #print_options{force_strings=NoQuote}) when is_atom(Atom) ->
+ L = atom_to_list(Atom),
+ R = case atom_needs_quoting_start(L) andalso not NoQuote of
+ true -> lists:flatten([$', L, $']);
+ false -> L
+ end,
+ {R, length(R)};
+
+print(<<>>, _Max, _Options) ->
+ {"<<>>", 4};
+
+print(Binary, 0, _Options) when is_bitstring(Binary) ->
+ {"<<..>>", 6};
+
+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
+ Options#print_options.force_strings of
+ true ->
+ alist_start(B, Max-4, Options);
+ _ ->
+ list_body(B, Max-4, Options, false)
+ end,
+ {Res, Length} = case L of
+ [91, X, 93] ->
+ {X, Len - 2};
+ X ->
+ {X, Len}
+ end,
+ case Options#print_options.force_strings of
+ true ->
+ {Res, Length};
+ _ ->
+ {["<<", Res, ">>"], Length+4}
+ end;
+
+%% bitstrings are binary's evil brother who doesn't end on an 8 bit boundary.
+%% This makes printing them extremely annoying, so list_body/list_bodyc has
+%% 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(BitString, Max, Options) when is_bitstring(BitString) ->
+ case byte_size(BitString) > Max of
+ true ->
+ BL = binary_to_list(BitString, 1, Max);
+ _ ->
+ BL = erlang:bitstring_to_list(BitString)
+ end,
+ {X, Len0} = list_body(BL, Max - 4, Options, false),
+ {["<<", X, ">>"], Len0 + 4};
+
+print(Float, _Max, _Options) when is_float(Float) ->
+ %% use the same function io_lib:format uses to print floats
+ %% float_to_list is way too verbose.
+ L = io_lib_format:fwrite_g(Float),
+ {L, length(L)};
+
+print(Fun, Max, _Options) when is_function(Fun) ->
+ L = erlang:fun_to_list(Fun),
+ case length(L) > Max of
+ true ->
+ S = erlang:max(5, Max),
+ Res = string:substr(L, 1, S) ++ "..>",
+ {Res, length(Res)};
+ _ ->
+ {L, length(L)}
+ end;
+
+print(Integer, _Max, _Options) when is_integer(Integer) ->
+ L = integer_to_list(Integer),
+ {L, length(L)};
+
+print(Pid, _Max, _Options) when is_pid(Pid) ->
+ L = pid_to_list(Pid),
+ {L, length(L)};
+
+print(Ref, _Max, _Options) when is_reference(Ref) ->
+ L = erlang:ref_to_list(Ref),
+ {L, length(L)};
+
+print(Port, _Max, _Options) when is_port(Port) ->
+ L = erlang:port_to_list(Port),
+ {L, length(L)};
+
+print(List, Max, Options) when is_list(List) ->
+ case Options#print_options.lists_as_strings orelse
+ Options#print_options.force_strings of
+ true ->
+ alist_start(List, Max, dec_depth(Options));
+ _ ->
+ {R, Len} = list_body(List, Max - 2, dec_depth(Options), false),
+ {[$[, R, $]], Len + 2}
+ end.
+
+%% Returns {List, Length}
+tuple_contents(Tuple, Max, Options) ->
+ L = tuple_to_list(Tuple),
+ list_body(L, Max, dec_depth(Options), true).
+
+%% Format the inside of a list, i.e. do not add a leading [ or trailing ].
+%% Returns {List, Length}
+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, Len} = print(H, Max, Options),
+ {Final, FLen} = list_bodyc(T, Max - Len, Options, Tuple),
+ {[List|Final], FLen + Len};
+list_body(X, Max, Options, _Tuple) -> %% improper list
+ {List, Len} = print(X, Max - 1, Options),
+ {[$|,List], Len + 1}.
+
+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, Len} = print(H, Max, dec_depth(Options)),
+ {Final, FLen} = list_bodyc(T, Max - Len - 1, Options, Tuple),
+ Sep = case Depth == 1 andalso not Tuple of
+ true -> $|;
+ _ -> $,
+ end,
+ {[Sep, List|Final], FLen + Len + 1};
+list_bodyc(X, Max, Options, _Tuple) -> %% improper list
+ {List, Len} = print(X, Max - 1, Options),
+ {[$|,List], Len + 1}.
+
+%% The head of a list we hope is ascii. Examples:
+%%
+%% [65,66,67] -> "ABC"
+%% [65,0,67] -> "A"[0,67]
+%% [0,65,66] -> [0,65,66]
+%% [65,b,66] -> "A"[b,66]
+%%
+alist_start([], _Max, #print_options{force_strings=true}) -> {"", 0};
+alist_start([], _Max, _Options) -> {"[]", 2};
+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, 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 ->
+ {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 ->
+ 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;
+alist_start(L, Max, Options) ->
+ {R, Len} = list_body(L, Max-2, Options, false),
+ {[$[, R, $]], Len + 2}.
+
+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) 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 ->
+ {L, Len} = alist(T, Max-1, Options),
+ case Options#print_options.force_strings of
+ true ->
+ {[H|L], Len + 1};
+ _ ->
+ {[escape(H)|L], Len + 1}
+ end;
+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(_, _, #print_options{force_strings=true}) ->
+ erlang:error(badarg);
+alist(_L, _Max, _Options) ->
+ throw(unprintable).
+
+%% is the first character in the atom alphabetic & lowercase?
+atom_needs_quoting_start([H|T]) when H >= $a, H =< $z ->
+ atom_needs_quoting(T);
+atom_needs_quoting_start(_) ->
+ true.
+
+atom_needs_quoting([]) ->
+ false;
+atom_needs_quoting([H|T]) when (H >= $a andalso H =< $z);
+ (H >= $A andalso H =< $Z);
+ (H >= $0 andalso H =< $9);
+ H == $@; H == $_ ->
+ atom_needs_quoting(T);
+atom_needs_quoting(_) ->
+ true.
+
+prepare_options([], Options) ->
+ Options;
+prepare_options([{depth, Depth}|T], Options) when is_integer(Depth) ->
+ prepare_options(T, Options#print_options{depth=Depth});
+prepare_options([{lists_as_strings, Bool}|T], Options) when is_boolean(Bool) ->
+ prepare_options(T, Options#print_options{lists_as_strings = Bool});
+prepare_options([{force_strings, Bool}|T], Options) when is_boolean(Bool) ->
+ prepare_options(T, Options#print_options{force_strings = Bool}).
+
+dec_depth(#print_options{depth=Depth} = Options) when Depth > 0 ->
+ Options#print_options{depth=Depth-1};
+dec_depth(Options) ->
+ Options.
+
+escape(9) -> "\\t";
+escape(10) -> "\\n";
+escape(13) -> "\\r".
+
+-ifdef(TEST).
+%%--------------------
+%% The start of a test suite. So far, it only checks for not crashing.
+-spec test() -> ok.
+test() ->
+ test(trunc_io, print).
+
+-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],
+ F = fun(A) ->
+ 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,
+
+ lists:foreach(G, Simple_items),
+
+ Tuples = [ {1,2,3,a,b,c}, {"abc", def, 1234},
+ {{{{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] ],
+
+
+ lists:foreach(G, Tuples),
+ lists:foreach(G, Lists).
+
+-spec perf() -> ok.
+perf() ->
+ {New, _} = timer:tc(trunc_io, perf, [trunc_io, print, 1000]),
+ {Old, _} = timer:tc(trunc_io, perf, [io_lib, write, 1000]),
+ io:fwrite("New code took ~p us, old code ~p\n", [New, Old]).
+
+-spec perf(atom(), atom(), integer()) -> done.
+perf(M, F, Reps) when Reps > 0 ->
+ test(M,F),
+ perf(M,F,Reps-1);
+perf(_,_,_) ->
+ done.
+
+%% Performance test. Needs a particularly large term I saved as a binary...
+-spec perf1() -> {non_neg_integer(), non_neg_integer()}.
+perf1() ->
+ {ok, Bin} = file:read_file("bin"),
+ A = binary_to_term(Bin),
+ {N, _} = timer:tc(trunc_io, print, [A, 1500]),
+ {M, _} = timer:tc(io_lib, write, [A]),
+ {N, M}.
+
+format_test() ->
+ %% simple format strings
+ ?assertEqual("foobar", lists:flatten(format("~s", [["foo", $b, $a, $r]], 50))),
+ ?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("**********", 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))),
+ ok.
+
+atom_quoting_test() ->
+ ?assertEqual("hello", lists:flatten(format("~p", [hello], 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("'node@127.0.0.1'", lists:flatten(format("~p", ['node@127.0.0.1'], 50))),
+ ?assertEqual("node@nohost", lists:flatten(format("~p", [node@nohost], 50))),
+ ?assertEqual("abc123", lists:flatten(format("~p", [abc123], 50))),
+ ok.
+
+sane_float_printing_test() ->
+ ?assertEqual("1.0", lists:flatten(format("~p", [1.0], 50))),
+ ?assertEqual("1.23456789", lists:flatten(format("~p", [1.23456789], 50))),
+ ?assertEqual("1.23456789", lists:flatten(format("~p", [1.234567890], 50))),
+ ?assertEqual("0.3333333333333333", lists:flatten(format("~p", [1/3], 50))),
+ ?assertEqual("0.1234567", lists:flatten(format("~p", [0.1234567], 50))),
+ ok.
+
+float_inside_list_test() ->
+ ?assertEqual("[97,38.233913133184835,99]", lists:flatten(format("~p", [[$a, 38.233913133184835, $c]], 50))),
+ ?assertError(badarg, lists:flatten(format("~s", [[$a, 38.233913133184835, $c]], 50))),
+ ok.
+
+quote_strip_test() ->
+ ?assertEqual("\"hello\"", lists:flatten(format("~p", ["hello"], 50))),
+ ?assertEqual("hello", lists:flatten(format("~s", ["hello"], 50))),
+ ?assertEqual("hello", lists:flatten(format("~s", [hello], 50))),
+ ?assertEqual("hello", lists:flatten(format("~p", [hello], 50))),
+ ?assertEqual("'hello world'", lists:flatten(format("~p", ['hello world'], 50))),
+ ?assertEqual("hello world", lists:flatten(format("~s", ['hello world'], 50))),
+ ok.
+
+binary_printing_test() ->
+ ?assertEqual("<<>>", lists:flatten(format("~p", [<<>>], 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))),
+ ?assertEqual("<<\"hello\">>", lists:flatten(format("~p", [<<"hello">>], 50))),
+ ?assertEqual("<<104,101,108,108,111>>", lists:flatten(format("~w", [<<"hello">>], 50))),
+ ?assertEqual("<<1,2,3,4>>", lists:flatten(format("~p", [<<1, 2, 3, 4>>], 50))),
+ ?assertEqual([1,2,3,4], lists:flatten(format("~s", [<<1, 2, 3, 4>>], 50))),
+ ?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", lists:flatten(format("~10s", [<<"hello">>], 50))),
+ ok.
+
+bitstring_printing_test() ->
+ ?assertEqual("<<1,2,3,1:7>>", lists:flatten(format("~p",
+ [<<1, 2, 3, 1:7>>], 100))),
+ ?assertEqual("<<1:7>>", lists:flatten(format("~p",
+ [<<1:7>>], 100))),
+ ?assertEqual("<<1,2,3,...>>", lists:flatten(format("~p",
+ [<<1, 2, 3, 1:7>>], 12))),
+ ?assertEqual("<<1,2,3,...>>", lists:flatten(format("~p",
+ [<<1, 2, 3, 1:7>>], 13))),
+ ?assertEqual("<<1,2,3,1:7>>", lists:flatten(format("~p",
+ [<<1, 2, 3, 1:7>>], 14))),
+ ?assertEqual("<<..>>", lists:flatten(format("~p", [<<1:7>>], 0))),
+ ?assertEqual("<<...>>", lists:flatten(format("~p", [<<1:7>>], 1))),
+ ?assertEqual("[<<1>>,<<2>>]", lists:flatten(format("~p", [[<<1>>, <<2>>]],
+ 100))),
+ ok.
+
+list_printing_test() ->
+ ?assertEqual("[]", lists:flatten(format("~p", [[]], 50))),
+ ?assertEqual("[]", lists:flatten(format("~w", [[]], 50))),
+ ?assertEqual("", lists:flatten(format("~s", [[]], 50))),
+ ?assertEqual("...", lists:flatten(format("~s", [[]], -1))),
+ ?assertEqual("[[]]", lists:flatten(format("~p", [[[]]], 50))),
+ ?assertEqual("[13,11,10,8,5,4]", lists:flatten(format("~p", [[13,11,10,8,5,4]], 50))),
+ ?assertEqual("\"\\rabc\"", lists:flatten(format("~p", [[13,$a, $b, $c]], 50))),
+ ?assertEqual("[1,2,3|4]", lists:flatten(format("~p", [[1, 2, 3|4]], 50))),
+ ?assertEqual("[...]", lists:flatten(format("~p", [[1, 2, 3,4]], 4))),
+ ?assertEqual("[1,...]", lists:flatten(format("~p", [[1, 2, 3, 4]], 6))),
+ ?assertEqual("[1,...]", lists:flatten(format("~p", [[1, 2, 3, 4]], 7))),
+ ?assertEqual("[1,2,...]", lists:flatten(format("~p", [[1, 2, 3, 4]], 8))),
+ ?assertEqual("[1|4]", lists:flatten(format("~p", [[1|4]], 50))),
+ ?assertEqual("[1]", lists:flatten(format("~p", [[1]], 50))),
+ ?assertError(badarg, lists:flatten(format("~s", [[1|4]], 50))),
+ ?assertEqual("\"hello...\"", lists:flatten(format("~p", ["hello world"], 10))),
+ ?assertEqual("hello w...", lists:flatten(format("~s", ["hello world"], 10))),
+ ?assertEqual("hello world\r\n", lists:flatten(format("~s", ["hello world\r\n"], 50))),
+ ?assertEqual("\rhello world\r\n", lists:flatten(format("~s", ["\rhello world\r\n"], 50))),
+ ?assertEqual("\"\\rhello world\\r\\n\"", lists:flatten(format("~p", ["\rhello world\r\n"], 50))),
+ ?assertEqual("[13,104,101,108,108,111,32,119,111,114,108,100,13,10]", lists:flatten(format("~w", ["\rhello world\r\n"], 60))),
+ ?assertEqual("...", lists:flatten(format("~s", ["\rhello world\r\n"], 3))),
+ ?assertEqual("[22835963083295358096932575511191922182123945984,...]",
+ lists:flatten(format("~p", [
+ [22835963083295358096932575511191922182123945984,
+ 22835963083295358096932575511191922182123945984]], 9))),
+ ?assertEqual("[22835963083295358096932575511191922182123945984,...]",
+ lists:flatten(format("~p", [
+ [22835963083295358096932575511191922182123945984,
+ 22835963083295358096932575511191922182123945984]], 53))),
+ ok.
+
+tuple_printing_test() ->
+ ?assertEqual("{}", lists:flatten(format("~p", [{}], 50))),
+ ?assertEqual("{}", lists:flatten(format("~w", [{}], 50))),
+ ?assertError(badarg, lists:flatten(format("~s", [{}], 50))),
+ ?assertEqual("{...}", lists:flatten(format("~p", [{foo}], 1))),
+ ?assertEqual("{...}", lists:flatten(format("~p", [{foo}], 2))),
+ ?assertEqual("{...}", lists:flatten(format("~p", [{foo}], 3))),
+ ?assertEqual("{...}", lists:flatten(format("~p", [{foo}], 4))),
+ ?assertEqual("{...}", lists:flatten(format("~p", [{foo}], 5))),
+ ?assertEqual("{foo,...}", lists:flatten(format("~p", [{foo,bar}], 6))),
+ ?assertEqual("{foo,...}", lists:flatten(format("~p", [{foo,bar}], 7))),
+ ?assertEqual("{foo,...}", lists:flatten(format("~p", [{foo,bar}], 9))),
+ ?assertEqual("{foo,bar}", lists:flatten(format("~p", [{foo,bar}], 10))),
+ ?assertEqual("{22835963083295358096932575511191922182123945984,...}",
+ lists:flatten(format("~w", [
+ {22835963083295358096932575511191922182123945984,
+ 22835963083295358096932575511191922182123945984}], 10))),
+ ?assertEqual("{22835963083295358096932575511191922182123945984,...}",
+ lists:flatten(format("~w", [
+ {22835963083295358096932575511191922182123945984,
+ bar}], 10))),
+ ?assertEqual("{22835963083295358096932575511191922182123945984,...}",
+ lists:flatten(format("~w", [
+ {22835963083295358096932575511191922182123945984,
+ 22835963083295358096932575511191922182123945984}], 53))),
+ ok.
+
+unicode_test() ->
+ ?assertEqual([231,167,129], lists:flatten(format("~s", [<<231,167,129>>], 50))),
+ ?assertEqual([31169], lists:flatten(format("~ts", [<<231,167,129>>], 50))),
+ ok.
+
+depth_limit_test() ->
+ ?assertEqual("{...}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 1], 50))),
+ ?assertEqual("{a,...}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 2], 50))),
+ ?assertEqual("{a,[...]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 3], 50))),
+ ?assertEqual("{a,[b|...]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 4], 50))),
+ ?assertEqual("{a,[b,[...]]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 5], 50))),
+ ?assertEqual("{a,[b,[c|...]]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 6], 50))),
+ ?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))),
+ ?assertEqual("{a,[b,[c,[d]]]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 9], 50))),
+
+ ?assertEqual("{a,{...}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 3], 50))),
+ ?assertEqual("{a,{b,...}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 4], 50))),
+ ?assertEqual("{a,{b,{...}}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 5], 50))),
+ ?assertEqual("{a,{b,{c,...}}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 6], 50))),
+ ?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))),
+
+ ?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))),
+ ok.
+
+-endif.
diff --git a/deps/lager/src/lager_util.erl b/deps/lager/src/lager_util.erl
new file mode 100644
index 0000000..f05d0e6
--- /dev/null
+++ b/deps/lager/src/lager_util.erl
@@ -0,0 +1,471 @@
+%% Copyright (c) 2011 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_util).
+
+-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]).
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-endif.
+
+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.
+
+open_logfile(Name, Buffer) ->
+ case filelib:ensure_dir(Name) of
+ ok ->
+ Options = [append, raw] ++
+ if Buffer == true -> [delayed_write];
+ true -> []
+ end,
+ case file:open(Name, Options) of
+ {ok, FD} ->
+ case file:read_file_info(Name) of
+ {ok, FInfo} ->
+ Inode = FInfo#file_info.inode,
+ {ok, {FD, Inode, FInfo#file_info.size}};
+ X -> X
+ end;
+ Y -> Y
+ end;
+ Z -> Z
+ end.
+
+ensure_logfile(Name, FD, Inode, Buffer) ->
+ case file:read_file_info(Name) of
+ {ok, FInfo} ->
+ Inode2 = FInfo#file_info.inode,
+ case Inode == Inode2 of
+ true ->
+ {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),
+ case open_logfile(Name, Buffer) of
+ {ok, {FD2, Inode3, Size}} ->
+ %% inode changed, file was probably moved and
+ %% recreated
+ {ok, {FD2, Inode3, Size}};
+ Error ->
+ Error
+ end
+ end;
+ _ ->
+ %% delayed write can cause file:close not to do a close
+ file:close(FD),
+ file:close(FD),
+ case open_logfile(Name, Buffer) of
+ {ok, {FD2, Inode3, Size}} ->
+ %% file was removed
+ {ok, {FD2, Inode3, Size}};
+ Error ->
+ Error
+ end
+ end.
+
+%% returns localtime with milliseconds included
+localtime_ms() ->
+ {_, _, Micro} = Now = os:timestamp(),
+ {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}}} ->
+ {utc, {Date1, {H1, M1, S1, Ms}}};
+ {Date1, {H1, M1, S1}} ->
+ {Date1, {H1, M1, S1, Ms}}
+ end.
+
+rotate_logfile(File, 0) ->
+ file:delete(File);
+rotate_logfile(File, 1) ->
+ file:rename(File, File++".0"),
+ rotate_logfile(File, 0);
+rotate_logfile(File, Count) ->
+ 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])};
+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])};
+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])};
+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])}.
+
+parse_rotation_day_spec([], Res) ->
+ {ok, Res ++ [{hour, 0}]};
+parse_rotation_day_spec([$D, D1, D2], Res) ->
+ case list_to_integer([D1, D2]) of
+ X when X >= 0, X =< 23 ->
+ {ok, Res ++ [{hour, X}]};
+ _ ->
+ {error, invalid_date_spec}
+ end;
+parse_rotation_day_spec([$D, D], Res) when D >= $0, D =< $9 ->
+ {ok, Res ++ [{hour, D - 48}]};
+parse_rotation_day_spec(_, _) ->
+ {error, invalid_date_spec}.
+
+parse_rotation_date_spec([$$, $W, W|T]) when W >= $0, W =< $6 ->
+ Week = W - 48,
+ parse_rotation_day_spec(T, [{day, Week}]);
+parse_rotation_date_spec([$$, $M, L|T]) when L == $L; L == $l ->
+ %% last day in month.
+ parse_rotation_day_spec(T, [{date, last}]);
+parse_rotation_date_spec([$$, $M, M1, M2|[$D|_]=T]) ->
+ case list_to_integer([M1, M2]) of
+ X when X >= 1, X =< 31 ->
+ parse_rotation_day_spec(T, [{date, X}]);
+ _ ->
+ {error, invalid_date_spec}
+ end;
+parse_rotation_date_spec([$$, $M, M|[$D|_]=T]) ->
+ parse_rotation_day_spec(T, [{date, M - 48}]);
+parse_rotation_date_spec([$$, $M, M1, M2]) ->
+ case list_to_integer([M1, M2]) of
+ X when X >= 1, X =< 31 ->
+ {ok, [{date, X}, {hour, 0}]};
+ _ ->
+ {error, invalid_date_spec}
+ end;
+parse_rotation_date_spec([$$, $M, M]) ->
+ {ok, [{date, M - 48}, {hour, 0}]};
+parse_rotation_date_spec([$$|X]) when X /= [] ->
+ parse_rotation_day_spec(X, []);
+parse_rotation_date_spec(_) ->
+ {error, invalid_date_spec}.
+
+calculate_next_rotation(Spec) ->
+ Now = calendar:local_time(),
+ Later = calculate_next_rotation(Spec, Now),
+ calendar:datetime_to_gregorian_seconds(Later) -
+ calendar:datetime_to_gregorian_seconds(Now).
+
+calculate_next_rotation([], Now) ->
+ Now;
+calculate_next_rotation([{hour, X}|T], {{_, _, _}, {Hour, _, _}} = Now) when Hour < X ->
+ %% rotation is today, sometime
+ NewNow = setelement(2, Now, {X, 0, 0}),
+ calculate_next_rotation(T, NewNow);
+calculate_next_rotation([{hour, X}|T], {{_, _, _}, _} = Now) ->
+ %% rotation is not today
+ Seconds = calendar:datetime_to_gregorian_seconds(Now) + 86400,
+ DateTime = calendar:gregorian_seconds_to_datetime(Seconds),
+ NewNow = setelement(2, DateTime, {X, 0, 0}),
+ calculate_next_rotation(T, NewNow);
+calculate_next_rotation([{day, Day}|T], {Date, _Time} = Now) ->
+ DoW = calendar:day_of_the_week(Date),
+ AdjustedDay = case Day of
+ 0 -> 7;
+ X -> X
+ end,
+ case AdjustedDay of
+ DoW -> %% rotation is today
+ OldDate = element(1, Now),
+ case calculate_next_rotation(T, Now) of
+ {OldDate, _} = NewNow -> NewNow;
+ {NewDate, _} ->
+ %% rotation *isn't* today! rerun the calculation
+ NewNow = {NewDate, {0, 0, 0}},
+ calculate_next_rotation([{day, Day}|T], NewNow)
+ end;
+ Y when Y > DoW -> %% rotation is later this week
+ PlusDays = Y - DoW,
+ Seconds = calendar:datetime_to_gregorian_seconds(Now) + (86400 * PlusDays),
+ {NewDate, _} = calendar:gregorian_seconds_to_datetime(Seconds),
+ NewNow = {NewDate, {0, 0, 0}},
+ calculate_next_rotation(T, NewNow);
+ Y when Y < DoW -> %% rotation is next week
+ PlusDays = ((7 - DoW) + Y),
+ Seconds = calendar:datetime_to_gregorian_seconds(Now) + (86400 * PlusDays),
+ {NewDate, _} = calendar:gregorian_seconds_to_datetime(Seconds),
+ NewNow = {NewDate, {0, 0, 0}},
+ calculate_next_rotation(T, NewNow)
+ end;
+calculate_next_rotation([{date, last}|T], {{Year, Month, Day}, _} = Now) ->
+ Last = calendar:last_day_of_the_month(Year, Month),
+ case Last == Day of
+ true -> %% doing rotation today
+ OldDate = element(1, Now),
+ case calculate_next_rotation(T, Now) of
+ {OldDate, _} = NewNow -> NewNow;
+ {NewDate, _} ->
+ %% rotation *isn't* today! rerun the calculation
+ NewNow = {NewDate, {0, 0, 0}},
+ calculate_next_rotation([{date, last}|T], NewNow)
+ end;
+ false ->
+ NewNow = setelement(1, Now, {Year, Month, Last}),
+ calculate_next_rotation(T, NewNow)
+ end;
+calculate_next_rotation([{date, Date}|T], {{_, _, Date}, _} = Now) ->
+ %% rotation is today
+ OldDate = element(1, Now),
+ case calculate_next_rotation(T, Now) of
+ {OldDate, _} = NewNow -> NewNow;
+ {NewDate, _} ->
+ %% rotation *isn't* today! rerun the calculation
+ NewNow = setelement(1, Now, NewDate),
+ calculate_next_rotation([{date, Date}|T], NewNow)
+ end;
+calculate_next_rotation([{date, Date}|T], {{Year, Month, Day}, _} = Now) ->
+ PlusDays = case Date of
+ X when X < Day -> %% rotation is next month
+ Last = calendar:last_day_of_the_month(Year, Month),
+ (Last - Day);
+ X when X > Day -> %% rotation is later this month
+ X - Day
+ end,
+ Seconds = calendar:datetime_to_gregorian_seconds(Now) + (86400 * PlusDays),
+ 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) ->
+ 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
+ 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
+ catch
+ _:_ ->
+ {error, invalid_level}
+ end;
+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.
+
+
+check_traces(_, _, [], Acc) ->
+ lists:flatten(Acc);
+check_traces(Attrs, Level, [{_, FilterLevel, _}|Flows], Acc) when Level > FilterLevel ->
+ 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 ->
+ []
+ 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);
+ _ ->
+ false
+ end.
+
+-ifdef(TEST).
+
+parse_test() ->
+ ?assertEqual({ok, [{hour, 0}]}, parse_rotation_date_spec("$D0")),
+ ?assertEqual({ok, [{hour, 23}]}, parse_rotation_date_spec("$D23")),
+ ?assertEqual({ok, [{day, 0}, {hour, 23}]}, parse_rotation_date_spec("$W0D23")),
+ ?assertEqual({ok, [{day, 5}, {hour, 16}]}, parse_rotation_date_spec("$W5D16")),
+ ?assertEqual({ok, [{date, 1}, {hour, 0}]}, parse_rotation_date_spec("$M1D0")),
+ ?assertEqual({ok, [{date, 5}, {hour, 6}]}, parse_rotation_date_spec("$M5D6")),
+ ?assertEqual({ok, [{date, 5}, {hour, 0}]}, parse_rotation_date_spec("$M5")),
+ ?assertEqual({ok, [{date, 31}, {hour, 0}]}, parse_rotation_date_spec("$M31")),
+ ?assertEqual({ok, [{date, 31}, {hour, 1}]}, parse_rotation_date_spec("$M31D1")),
+ ?assertEqual({ok, [{date, last}, {hour, 0}]}, parse_rotation_date_spec("$ML")),
+ ?assertEqual({ok, [{date, last}, {hour, 0}]}, parse_rotation_date_spec("$Ml")),
+ ?assertEqual({ok, [{day, 5}, {hour, 0}]}, parse_rotation_date_spec("$W5")),
+ ok.
+
+parse_fail_test() ->
+ ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$D")),
+ ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$D24")),
+ ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$W7")),
+ ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$W7D1")),
+ ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$M32")),
+ ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$M32D1")),
+ ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$")),
+ ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("")),
+ ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$D15M5")),
+ ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$M5W5")),
+ ok.
+
+rotation_calculation_test() ->
+ ?assertMatch({{2000, 1, 2}, {0, 0, 0}},
+ calculate_next_rotation([{hour, 0}], {{2000, 1, 1}, {12, 34, 43}})),
+ ?assertMatch({{2000, 1, 1}, {16, 0, 0}},
+ calculate_next_rotation([{hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
+ ?assertMatch({{2000, 1, 2}, {12, 0, 0}},
+ calculate_next_rotation([{hour, 12}], {{2000, 1, 1}, {12, 34, 43}})),
+ ?assertMatch({{2000, 2, 1}, {12, 0, 0}},
+ calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 1}, {12, 34, 43}})),
+ ?assertMatch({{2000, 2, 1}, {12, 0, 0}},
+ calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 15}, {12, 34, 43}})),
+ ?assertMatch({{2000, 2, 1}, {12, 0, 0}},
+ calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 2}, {12, 34, 43}})),
+ ?assertMatch({{2000, 2, 1}, {12, 0, 0}},
+ calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 31}, {12, 34, 43}})),
+ ?assertMatch({{2000, 1, 1}, {16, 0, 0}},
+ calculate_next_rotation([{date, 1}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
+ ?assertMatch({{2000, 1, 15}, {16, 0, 0}},
+ calculate_next_rotation([{date, 15}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
+ ?assertMatch({{2000, 1, 31}, {16, 0, 0}},
+ calculate_next_rotation([{date, last}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
+ ?assertMatch({{2000, 1, 31}, {16, 0, 0}},
+ calculate_next_rotation([{date, last}, {hour, 16}], {{2000, 1, 31}, {12, 34, 43}})),
+ ?assertMatch({{2000, 2, 29}, {16, 0, 0}},
+ calculate_next_rotation([{date, last}, {hour, 16}], {{2000, 1, 31}, {17, 34, 43}})),
+ ?assertMatch({{2001, 2, 28}, {16, 0, 0}},
+ calculate_next_rotation([{date, last}, {hour, 16}], {{2001, 1, 31}, {17, 34, 43}})),
+
+ ?assertMatch({{2000, 1, 1}, {16, 0, 0}},
+ calculate_next_rotation([{day, 6}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
+ ?assertMatch({{2000, 1, 8}, {16, 0, 0}},
+ calculate_next_rotation([{day, 6}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
+ ?assertMatch({{2000, 1, 7}, {16, 0, 0}},
+ calculate_next_rotation([{day, 5}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
+ ?assertMatch({{2000, 1, 3}, {16, 0, 0}},
+ calculate_next_rotation([{day, 1}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
+ ?assertMatch({{2000, 1, 2}, {16, 0, 0}},
+ calculate_next_rotation([{day, 0}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
+ ?assertMatch({{2000, 1, 9}, {16, 0, 0}},
+ calculate_next_rotation([{day, 0}, {hour, 16}], {{2000, 1, 2}, {17, 34, 43}})),
+ ?assertMatch({{2000, 2, 3}, {16, 0, 0}},
+ calculate_next_rotation([{day, 4}, {hour, 16}], {{2000, 1, 29}, {17, 34, 43}})),
+
+ ?assertMatch({{2000, 1, 7}, {16, 0, 0}},
+ calculate_next_rotation([{day, 5}, {hour, 16}], {{2000, 1, 3}, {17, 34, 43}})),
+
+ ?assertMatch({{2000, 1, 3}, {16, 0, 0}},
+ calculate_next_rotation([{day, 1}, {hour, 16}], {{1999, 12, 28}, {17, 34, 43}})),
+ ok.
+
+rotate_file_test() ->
+ file:delete("rotation.log"),
+ [file:delete(["rotation.log.", integer_to_list(N)]) || N <- lists:seq(0, 9)],
+ [begin
+ file:write_file("rotation.log", integer_to_list(N)),
+ Count = case N > 10 of
+ true -> 10;
+ _ -> N
+ end,
+ [begin
+ FileName = ["rotation.log.", integer_to_list(M)],
+ ?assert(filelib:is_regular(FileName)),
+ %% check the expected value is in the file
+ Number = list_to_binary(integer_to_list(N - M - 1)),
+ ?assertEqual({ok, Number}, file:read_file(FileName))
+ end
+ || M <- lists:seq(0, Count-1)],
+ rotate_logfile("rotation.log", 10)
+ end || N <- lists:seq(0, 20)].
+
+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}], [])),
+ ok.
+
+-endif.
diff --git a/deps/lager/test/crash.erl b/deps/lager/test/crash.erl
new file mode 100644
index 0000000..7037379
--- /dev/null
+++ b/deps/lager/test/crash.erl
@@ -0,0 +1,96 @@
+
+%% a module that crashes in just about every way possible
+
+-module(crash).
+
+-behaviour(gen_server).
+
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
+
+-export([start/0]).
+
+start() ->
+ gen_server:start({local, ?MODULE}, ?MODULE, [], []).
+
+init(_) ->
+ {ok, {}}.
+
+handle_call(undef, _, State) ->
+ {reply, ?MODULE:booger(), State};
+handle_call(badfun, _, State) ->
+ M = booger,
+ {reply, M(), State};
+handle_call(bad_return, _, _) ->
+ bleh;
+handle_call(bad_return_string, _, _) ->
+ {tuple, {tuple, "string"}};
+handle_call(case_clause, _, State) ->
+ 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;
+handle_call(if_clause, _, State) ->
+ 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};
+handle_call(badmatch, _, State) ->
+ {A, B, C} = State,
+ {reply, [A, B, C], State};
+handle_call(function_clause, _, State) ->
+ {reply, function(State), State};
+handle_call(badarith, _, State) ->
+ Res = 1 / length(tuple_to_list(State)),
+ {reply, Res, State};
+handle_call(badarg1, _, State) ->
+ Res = list_to_binary(["foo", bar]),
+ {reply, Res, State};
+handle_call(badarg2, _, 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};
+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};
+handle_call(port_limit, _, 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};
+handle_call(badarity, _, State) ->
+ F = fun(A, B, C) -> A + B + C end,
+ Res = F(State),
+ {reply, Res, State};
+handle_call(_Call, _From, State) ->
+ {reply, ok, State}.
+
+handle_cast(_Cast, State) ->
+ {noreply, State}.
+
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_, _) ->
+ ok.
+
+code_change(_, State, _) ->
+ {ok, State}.
+
+function(X) when is_list(X) ->
+ ok.
diff --git a/deps/lager/test/lager_crash_backend.erl b/deps/lager/test/lager_crash_backend.erl
new file mode 100644
index 0000000..b5981cb
--- /dev/null
+++ b/deps/lager/test/lager_crash_backend.erl
@@ -0,0 +1,68 @@
+%% Copyright (c) 2011 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_crash_backend).
+
+-include("lager.hrl").
+
+-behaviour(gen_event).
+
+-export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2,
+ code_change/3]).
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-endif.
+
+init([CrashBefore, CrashAfter]) ->
+ case is_tuple(CrashBefore) andalso (timer:now_diff(CrashBefore, os:timestamp()) > 0) of
+ true ->
+ %?debugFmt("crashing!~n", []),
+ {error, crashed};
+ _ ->
+ %?debugFmt("Not crashing!~n", []),
+ case is_tuple(CrashAfter) of
+ true ->
+ CrashTime = timer:now_diff(CrashAfter, os:timestamp()) div 1000,
+ case CrashTime > 0 of
+ true ->
+ %?debugFmt("crashing in ~p~n", [CrashTime]),
+ erlang:send_after(CrashTime, self(), crash),
+ {ok, {}};
+ _ -> {error, crashed}
+ end;
+ _ ->
+ {ok, {}}
+ end
+ end.
+
+handle_call(_Request, State) ->
+ {ok, ok, State}.
+
+handle_event(_Event, State) ->
+ {ok, State}.
+
+handle_info(crash, _State) ->
+ %?debugFmt("Time to crash!~n", []),
+ crash;
+handle_info(_Info, State) ->
+ {ok, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
diff --git a/deps/lager/test/lager_test_backend.erl b/deps/lager/test/lager_test_backend.erl
new file mode 100644
index 0000000..b6bc71c
--- /dev/null
+++ b/deps/lager/test/lager_test_backend.erl
@@ -0,0 +1,735 @@
+%% Copyright (c) 2011 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_test_backend).
+
+-include("lager.hrl").
+
+-behaviour(gen_event).
+
+-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}]).
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-export([pop/0, count/0, count_ignored/0, flush/0]).
+-endif.
+
+init(Level) ->
+ {ok, #state{level=lager_util:level_to_num(Level), buffer=[], ignored=[]}}.
+
+handle_call(count, #state{buffer=Buffer} = State) ->
+ {ok, length(Buffer), State};
+handle_call(count_ignored, #state{ignored=Ignored} = State) ->
+ {ok, length(Ignored), State};
+handle_call(flush, State) ->
+ {ok, ok, State#state{buffer=[], ignored=[]}};
+handle_call(pop, #state{buffer=Buffer} = State) ->
+ case Buffer of
+ [] ->
+ {ok, undefined, State};
+ [H|T] ->
+ {ok, H, State#state{buffer=T}}
+ end;
+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)}};
+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(_Event, State) ->
+ {ok, State}.
+
+handle_info(_Info, State) ->
+ {ok, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+-ifdef(TEST).
+
+pop() ->
+ gen_event:call(lager_event, ?MODULE, pop).
+
+count() ->
+ gen_event:call(lager_event, ?MODULE, count).
+
+count_ignored() ->
+ gen_event:call(lager_event, ?MODULE, count_ignored).
+
+flush() ->
+ gen_event:call(lager_event, ?MODULE, flush).
+
+not_running_test() ->
+ ?assertEqual({error, lager_not_running}, lager:log(info, self(), "not running")).
+
+lager_test_() ->
+ {foreach,
+ fun setup/0,
+ fun cleanup/1,
+ [
+ {"observe that there is nothing up my sleeve",
+ fun() ->
+ ?assertEqual(undefined, pop()),
+ ?assertEqual(0, count())
+ end
+ },
+ {"logging works",
+ fun() ->
+ lager:warning("test message"),
+ ?assertEqual(1, count()),
+ {Level, _Time, Message} = 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),
+ ok
+ end
+ },
+ {"logging with arguments works",
+ fun() ->
+ lager:warning("test message ~p", [self()]),
+ ?assertEqual(1, count()),
+ {Level, _Time, Message} = 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),
+ ok
+ end
+ },
+ {"logging works from inside a begin/end block",
+ fun() ->
+ ?assertEqual(0, count()),
+ begin
+ lager:warning("test message 2")
+ end,
+ ?assertEqual(1, count()),
+ ok
+ end
+ },
+ {"logging works from inside a list comprehension",
+ fun() ->
+ ?assertEqual(0, count()),
+ [lager:warning("test message") || _N <- lists:seq(1, 10)],
+ ?assertEqual(10, count()),
+ ok
+ end
+ },
+ {"logging works from a begin/end block inside a list comprehension",
+ fun() ->
+ ?assertEqual(0, count()),
+ [ begin lager:warning("test message") end || _N <- lists:seq(1, 10)],
+ ?assertEqual(10, count()),
+ ok
+ end
+ },
+ {"logging works from a nested list comprehension",
+ fun() ->
+ ?assertEqual(0, count()),
+ [ [lager:warning("test message") || _N <- lists:seq(1, 10)] ||
+ _I <- lists:seq(1, 10)],
+ ?assertEqual(100, count()),
+ 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:debug("this message should be ignored"),
+ ?assertEqual(0, count()),
+ ?assertEqual(1, count_ignored()),
+ lager:set_loglevel(?MODULE, debug),
+ lager:debug("this message should be logged"),
+ ?assertEqual(1, count()),
+ ?assertEqual(1, count_ignored()),
+ ?assertEqual(debug, lager:get_loglevel(?MODULE)),
+ ok
+ end
+ },
+ {"tracing works",
+ fun() ->
+ lager_mochiglobal:put(loglevel, {?ERROR, []}),
+ ok = lager:info("hello world"),
+ ?assertEqual(0, count()),
+ lager_mochiglobal:put(loglevel, {?ERROR, [{[{module,
+ ?MODULE}], ?DEBUG, ?MODULE}]}),
+ ok = lager:info("hello world"),
+ ?assertEqual(1, count()),
+ ok
+ end
+ },
+ {"tracing works with custom attributes",
+ fun() ->
+ lager_mochiglobal:put(loglevel, {?ERROR, []}),
+ lager:info([{requestid, 6}], "hello world"),
+ ?assertEqual(0, count()),
+ lager_mochiglobal:put(loglevel, {?ERROR,
+ [{[{requestid, 6}], ?DEBUG, ?MODULE}]}),
+ lager:info([{requestid, 6}, {foo, bar}], "hello world"),
+ ?assertEqual(1, count()),
+ lager_mochiglobal:put(loglevel, {?ERROR,
+ [{[{requestid, '*'}], ?DEBUG, ?MODULE}]}),
+ lager:info([{requestid, 6}], "hello world"),
+ ?assertEqual(2, count()),
+ ok
+ end
+ },
+ {"tracing honors loglevel",
+ fun() ->
+ lager_mochiglobal:put(loglevel, {?ERROR, [{[{module,
+ ?MODULE}], ?NOTICE, ?MODULE}]}),
+ ok = lager:info("hello world"),
+ ?assertEqual(0, count()),
+ ok = lager:notice("hello world"),
+ ?assertEqual(1, count()),
+ ok
+ end
+ }
+ ]
+ }.
+
+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),
+ gen_event:call(lager_event, ?MODULE, flush).
+
+cleanup(_) ->
+ application:stop(lager),
+ error_logger:tty(true).
+
+
+crash(Type) ->
+ spawn(fun() -> gen_server:call(crash, Type) end),
+ timer:sleep(100),
+ _ = gen_event:which_handlers(error_logger),
+ ok.
+
+error_logger_redirect_crash_test_() ->
+ {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),
+ crash:start()
+ end,
+
+ fun(_) ->
+ application:stop(lager),
+ case whereis(crash) of
+ undefined -> ok;
+ Pid -> exit(Pid, kill)
+ end,
+ error_logger:tty(true)
+ end,
+ [
+ {"again, there is nothing up my sleeve",
+ fun() ->
+ ?assertEqual(undefined, pop()),
+ ?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
+ }
+
+ ]
+ }.
+
+error_logger_redirect_test_() ->
+ {foreach,
+ fun() ->
+ error_logger:tty(false),
+ application:load(lager),
+ application:set_env(lager, error_logger_redirect, true),
+ application:set_env(lager, handlers, [{?MODULE, info}]),
+ application:start(lager),
+ lager:log(error, self(), "flush flush"),
+ timer:sleep(100),
+ gen_event:call(lager_event, ?MODULE, flush)
+ end,
+
+ fun(_) ->
+ application:stop(lager),
+ error_logger:tty(true)
+ end,
+ [
+ {"error reports are printed",
+ 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()])),
+ ?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()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"error messages are printed",
+ 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()])),
+ ?assertEqual(Expected, 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(),
+ ?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()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"info reports are truncated at 4096 characters",
+ fun() ->
+ sync_error_logger:info_report([[{this, is}, a, {silly, format}] || _ <- lists:seq(0, 600)]),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ ?assert(length(lists:flatten(Msg)) < 5000)
+ end
+ },
+ {"single term info reports are printed",
+ 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))
+ 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))
+ 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))
+ 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(),
+ ?assert(length(lists:flatten(Msg)) < 5100)
+ end
+ },
+ {"strings in a mixed report are printed as strings",
+ 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))
+ 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))
+ 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(),
+ ?assert(length(lists:flatten(Msg)) < 5100)
+ 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))
+ end
+ },
+ {"warning reports are printed at the correct level",
+ fun() ->
+ 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))
+ end
+ },
+ {"single term warning reports are printed at the correct level",
+ fun() ->
+ 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))
+ 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))
+ 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))
+ 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))
+ end
+ },
+
+ {"supervisor_bridge reports",
+ 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))
+ 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()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"supervisor progress report",
+ fun() ->
+ lager:set_loglevel(?MODULE, debug),
+ 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))
+ end
+ },
+ {"crash report for emfile",
+ fun() ->
+ 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()])),
+ ?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}]}, []}}], []]),
+ _ = 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()])),
+ ?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}]}, []}}], []]),
+ _ = 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()])),
+ ?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}]}, []}}], []]),
+ _ = 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()])),
+ ?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}]}, []}}], []]),
+ _ = 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()])),
+ ?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}]}, []}}], []]),
+ _ = 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])),
+ ?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)]}]}, []}}], []]),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ ?assert(length(lists:flatten(Msg)) > 600),
+ ?assert(length(lists:flatten(Msg)) < 650)
+ end
+ },
+ {"crash reports for 'special processes' should be handled right",
+ 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))
+ end
+ },
+ {"messages should not be generated if they don't satisfy the threshold",
+ fun() ->
+ lager:set_loglevel(?MODULE, error),
+ 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),
+ 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, []}),
+ sync_error_logger:info_report([hello, world]),
+ _ = gen_event:which_handlers(error_logger),
+ ?assertEqual(1, count()),
+ ?assertEqual(1, count_ignored())
+ end
+ }
+ ]
+ }.
+
+safe_format_test() ->
+ ?assertEqual("foo bar", lists:flatten(lager:safe_format("~p ~p", [foo, bar], 1024))),
+ ?assertEqual("FORMAT ERROR: \"~p ~p ~p\" [foo,bar]", lists:flatten(lager:safe_format("~p ~p ~p", [foo, bar], 1024))),
+ ok.
+
+-endif.
+
+
diff --git a/deps/lager/test/special_process.erl b/deps/lager/test/special_process.erl
new file mode 100644
index 0000000..831b950
--- /dev/null
+++ b/deps/lager/test/special_process.erl
@@ -0,0 +1,28 @@
+-module(special_process).
+-export([start/0, init/1]).
+
+start() ->
+ proc_lib:start_link(?MODULE, init, [self()]).
+
+init(Parent) ->
+ proc_lib:init_ack(Parent, {ok, self()}),
+ loop().
+
+loop() ->
+ receive
+ function_clause ->
+ foo(bar),
+ loop();
+ exit ->
+ exit(byebye),
+ loop();
+ error ->
+ erlang:error(mybad),
+ loop();
+ _ ->
+ loop()
+ end.
+
+foo(baz) ->
+ ok.
+
diff --git a/deps/lager/test/sync_error_logger.erl b/deps/lager/test/sync_error_logger.erl
new file mode 100644
index 0000000..078000f
--- /dev/null
+++ b/deps/lager/test/sync_error_logger.erl
@@ -0,0 +1,89 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online 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.
+%%
+%% %CopyrightEnd%
+%%
+-module(sync_error_logger).
+
+%% The error_logger API, but synchronous!
+%% This is helpful for tests, otherwise you need lots of nasty timer:sleep.
+%% Additionally, the warning map can be set on a per-process level, for
+%% convienience, via the process dictionary value `warning_map'.
+
+-export([
+ info_msg/1, info_msg/2,
+ warning_msg/1, warning_msg/2,
+ error_msg/1,error_msg/2
+ ]).
+
+-export([
+ info_report/1, info_report/2,
+ warning_report/1, warning_report/2,
+ error_report/1, error_report/2
+ ]).
+
+info_msg(Format) ->
+ info_msg(Format, []).
+
+info_msg(Format, Args) ->
+ gen_event:sync_notify(error_logger, {info_msg, group_leader(), {self(), Format, Args}}).
+
+warning_msg(Format) ->
+ warning_msg(Format, []).
+
+warning_msg(Format, Args) ->
+ gen_event:sync_notify(error_logger, {warning_msg_tag(), group_leader(), {self(), Format, Args}}).
+
+error_msg(Format) ->
+ error_msg(Format, []).
+
+error_msg(Format, Args) ->
+ gen_event:sync_notify(error_logger, {error, group_leader(), {self(), Format, Args}}).
+
+info_report(Report) ->
+ info_report(std_info, Report).
+
+info_report(Type, Report) ->
+ gen_event:sync_notify(error_logger, {info_report, group_leader(), {self(), Type, Report}}).
+
+warning_report(Report) ->
+ warning_report(std_warning, Report).
+
+warning_report(Type, Report) ->
+ {Tag, NType} = warning_report_tag(Type),
+ gen_event:sync_notify(error_logger, {Tag, group_leader(), {self(), NType, Report}}).
+
+error_report(Report) ->
+ error_report(std_error, Report).
+
+error_report(Type, Report) ->
+ gen_event:sync_notify(error_logger, {error_report, group_leader(), {self(), Type, Report}}).
+
+warning_msg_tag() ->
+ case get(warning_map) of
+ warning -> warning_msg;
+ info -> info_msg;
+ _ -> error
+ end.
+
+warning_report_tag(Type) ->
+ case {get(warning_map), Type == std_warning} of
+ {warning, _} -> {warning_report, Type};
+ {info, true} -> {info_report, std_info};
+ {info, false} -> {info_report, Type};
+ {_, true} -> {error_report, std_error};
+ {_, false} -> {error_report, Type}
+ end.
diff --git a/deps/lager/test/trunc_io_eqc.erl b/deps/lager/test/trunc_io_eqc.erl
new file mode 100644
index 0000000..b363640
--- /dev/null
+++ b/deps/lager/test/trunc_io_eqc.erl
@@ -0,0 +1,208 @@
+%% -------------------------------------------------------------------
+%%
+%% trunc_io_eqc: QuickCheck test for trunc_io:format with maxlen
+%%
+%% Copyright (c) 2007-2011 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(trunc_io_eqc).
+
+-ifdef(TEST).
+-ifdef(EQC).
+-export([test/0, test/1, check/0, prop_format/0]).
+
+-include_lib("eqc/include/eqc.hrl").
+-include_lib("eunit/include/eunit.hrl").
+
+-define(QC_OUT(P),
+ eqc:on_output(fun(Str, Args) -> io:format(user, Str, Args) end, P)).
+
+%%====================================================================
+%% eunit test
+%%====================================================================
+
+eqc_test_() ->
+ {timeout, 300,
+ {spawn,
+ [?_assertEqual(true, quickcheck(numtests(500, ?QC_OUT(prop_format()))))]
+ }}.
+
+%%====================================================================
+%% Shell helpers
+%%====================================================================
+
+test() ->
+ test(100).
+
+test(N) ->
+ quickcheck(numtests(N, prop_format())).
+
+check() ->
+ check(prop_format(), current_counterexample()).
+
+%%====================================================================
+%% Generators
+%%====================================================================
+
+gen_fmt_args() ->
+ list(oneof([gen_print_str(),
+ "~~",
+ {"~p", gen_any(5)},
+ {"~w", gen_any(5)},
+ {"~s", gen_print_str()},
+ {"~P", gen_any(5), 4},
+ {"~W", gen_any(5), 4},
+ {"~i", gen_any(5)},
+ {"~B", nat()},
+ {"~b", nat()},
+ {"~X", nat(), "0x"},
+ {"~x", nat(), "0x"},
+ {"~.10#", nat()},
+ {"~.10+", nat()},
+ {"~.36B", nat()},
+ {"~62P", gen_any(5), 4},
+ {"~c", gen_char()},
+ {"~tc", gen_char()},
+ {"~f", real()},
+ {"~10.f", real()},
+ {"~g", real()},
+ {"~10.g", real()},
+ {"~e", real()},
+ {"~10.e", real()}
+ ])).
+
+
+%% Generates a printable string
+gen_print_str() ->
+ ?LET(Xs, list(char()), [X || X <- Xs, io_lib:printable_list([X]), X /= $~]).
+
+gen_any(MaxDepth) ->
+ oneof([largeint(),
+ gen_atom(),
+ nat(),
+ %real(),
+ binary(),
+ 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_atom() ->
+ elements([abc, def, ghi]).
+
+gen_tuple(Gen) ->
+ ?LET(Xs, list(Gen), list_to_tuple(Xs)).
+
+gen_max_len() -> %% Generate length from 3 to whatever. Needs space for ... in output
+ ?LET(Xs, int(), 3 + abs(Xs)).
+
+gen_pid() ->
+ ?LAZY(spawn(fun() -> ok end)).
+
+gen_port() ->
+ ?LAZY(begin
+ Port = erlang:open_port({spawn, "true"}, []),
+ erlang:port_close(Port),
+ Port
+ end).
+
+gen_ref() ->
+ ?LAZY(make_ref()).
+
+gen_fun() ->
+ ?LAZY(fun() -> ok end).
+
+gen_char() ->
+ oneof(lists:seq($A, $z)).
+
+%%====================================================================
+%% Property
+%%====================================================================
+
+%% Checks that trunc_io:format produces output less than or equal to MaxLen
+prop_format() ->
+ ?FORALL({FmtArgs, MaxLen}, {gen_fmt_args(), gen_max_len()},
+ begin
+ %% Because trunc_io will print '...' when its running out of
+ %% space, even if the remaining space is less than 3, it
+ %% doesn't *exactly* stick to the specified limit.
+
+ %% Also, since we don't truncate terms not printed with
+ %% ~p/~P/~w/~W/~s, we also need to calculate the wiggle room
+ %% for those. Hence the fudge factor calculated below.
+ FudgeLen = calculate_fudge(FmtArgs, 50),
+ {FmtStr, Args} = build_fmt_args(FmtArgs),
+ try
+ Str = lists:flatten(lager_trunc_io:format(FmtStr, Args, MaxLen)),
+ ?WHENFAIL(begin
+ io:format(user, "FmtStr: ~p\n", [FmtStr]),
+ io:format(user, "Args: ~p\n", [Args]),
+ io:format(user, "FudgeLen: ~p\n", [FudgeLen]),
+ io:format(user, "MaxLen: ~p\n", [MaxLen]),
+ io:format(user, "ActLen: ~p\n", [length(Str)]),
+ io:format(user, "Str: ~p\n", [Str])
+ end,
+ %% Make sure the result is a printable list
+ %% and if the format string is less than the length,
+ %% the result string is less than the length.
+ conjunction([{printable, Str == "" orelse
+ io_lib:printable_list(Str)},
+ {length, length(FmtStr) > MaxLen orelse
+ length(Str) =< MaxLen + FudgeLen}]))
+ catch
+ _:Err ->
+ io:format(user, "\nException: ~p\n", [Err]),
+ io:format(user, "FmtStr: ~p\n", [FmtStr]),
+ io:format(user, "Args: ~p\n", [Args]),
+ false
+ end
+ end).
+
+%%====================================================================
+%% Internal helpers
+%%====================================================================
+
+%% Build a tuple of {Fmt, Args} from a gen_fmt_args() return
+build_fmt_args(FmtArgs) ->
+ F = fun({Fmt, Arg}, {FmtStr0, Args0}) ->
+ {FmtStr0 ++ Fmt, Args0 ++ [Arg]};
+ ({Fmt, Arg1, Arg2}, {FmtStr0, Args0}) ->
+ {FmtStr0 ++ Fmt, Args0 ++ [Arg1, Arg2]};
+ (Str, {FmtStr0, Args0}) ->
+ {FmtStr0 ++ Str, Args0}
+ end,
+ lists:foldl(F, {"", []}, FmtArgs).
+
+calculate_fudge([], Acc) ->
+ Acc;
+calculate_fudge([{"~62P", _Arg, _Depth}|T], Acc) ->
+ calculate_fudge(T, Acc+62);
+calculate_fudge([{Fmt, Arg}|T], Acc) when
+ Fmt == "~f"; Fmt == "~10.f";
+ Fmt == "~g"; Fmt == "~10.g";
+ Fmt == "~e"; Fmt == "~10.e";
+ Fmt == "~x"; Fmt == "~X";
+ Fmt == "~B"; Fmt == "~b"; Fmt == "~36B";
+ Fmt == "~.10#"; Fmt == "~10+" ->
+ calculate_fudge(T, Acc + length(lists:flatten(io_lib:format(Fmt, [Arg]))));
+calculate_fudge([_|T], Acc) ->
+ calculate_fudge(T, Acc).
+
+-endif. % (EQC).
+-endif. % (TEST).