diff options
author | Magnus Feuer <mfeuer@jaguarlandrover.com> | 2015-04-14 08:26:38 -0700 |
---|---|---|
committer | Magnus Feuer <mfeuer@jaguarlandrover.com> | 2015-04-14 08:26:38 -0700 |
commit | 024ab61c898e782dc40e2269ad0f5a23009bcd6b (patch) | |
tree | 0fde33ef3c1dbdc54d15c91549a987fc1f47fbf9 /deps/lager | |
parent | 7feddc0a5538037acda260ea4dd6b504989bc61c (diff) | |
download | rvi_core-024ab61c898e782dc40e2269ad0f5a23009bcd6b.tar.gz |
Initial commit of deps
Diffstat (limited to 'deps/lager')
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 Binary files differnew file mode 100755 index 0000000..8645775 --- /dev/null +++ b/deps/lager/rebar 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). |