lager-3.1.0/0000755000232200023220000000000012652234453013161 5ustar debalancedebalancelager-3.1.0/LICENSE0000644000232200023220000002367712652234453014205 0ustar debalancedebalance 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 lager-3.1.0/rebar.config0000644000232200023220000000335312652234453015447 0ustar debalancedebalance%% -*- erlang -*- %% ------------------------------------------------------------------- %% %% Copyright (c) 2011-2015 Basho Technologies, Inc. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- {erl_opts, [ {lager_extra_sinks, ['__lager_test_sink']}, debug_info, report, verbose, warn_deprecated_function, warn_deprecated_type, warn_export_all, warn_export_vars, warn_obsolete_guard, warn_untyped_record, warn_unused_import % do NOT include warnings_as_errors, as rebar includes these options % when compiling for eunit, and at least one test module has code that % is deliberatly broken and will generate an un-maskable warning ]}. {erl_first_files, ["src/lager_util.erl"]}. {eunit_opts, [verbose]}. {eunit_compile_opts, [ nowarn_untyped_record, nowarn_export_all ]}. {deps, [ {goldrush, ".*", {git, "git://github.com/DeadZen/goldrush.git", {tag, "0.1.8"}}} ]}. {xref_checks, []}. {xref_queries, [{"(XC - UC) || (XU - X - B - lager_default_tracer : Mod - erlang:\"(is_map|map_size)\"/1 - maps:to_list/1)", []}]}. {cover_enabled, true}. {edoc_opts, [{stylesheet_file, "./priv/edoc.css"}]}. lager-3.1.0/test/0000755000232200023220000000000012652234453014140 5ustar debalancedebalancelager-3.1.0/test/pr_stacktrace_test.erl0000644000232200023220000000254212652234453020533 0ustar debalancedebalance-module(pr_stacktrace_test). -compile([{parse_transform, lager_transform}]). -include_lib("eunit/include/eunit.hrl"). make_throw() -> throw({test, exception}). bad_arity() -> lists:concat([], []). bad_arg() -> integer_to_list(1.0). pr_stacktrace_throw_test() -> Result = try make_throw() catch Class:Reason -> lager:pr_stacktrace(erlang:get_stacktrace(), {Class, Reason}) end, ExpectedPart = " pr_stacktrace_test:pr_stacktrace_throw_test/0 line 18 pr_stacktrace_test:make_throw/0 line 8 throw:{test,exception}", ?assertNotEqual(0, string:str(Result, ExpectedPart)). pr_stacktrace_bad_arg_test() -> Result = try bad_arg() catch Class:Reason -> lager:pr_stacktrace(erlang:get_stacktrace(), {Class, Reason}) end, ExpectedPart = " pr_stacktrace_test:pr_stacktrace_bad_arg_test/0 line 32 pr_stacktrace_test:bad_arg/0 line 14 error:badarg", ?assertNotEqual(0, string:str(Result, ExpectedPart)). pr_stacktrace_bad_arity_test() -> Result = try bad_arity() catch Class:Reason -> lager:pr_stacktrace(erlang:get_stacktrace(), {Class, Reason}) end, ExpectedPart = " pr_stacktrace_test:pr_stacktrace_bad_arity_test/0 line 46 lists:concat([], []) error:undef", ?assertNotEqual(0, string:str(Result, ExpectedPart)).lager-3.1.0/test/crash.erl0000644000232200023220000000525012652234453015746 0ustar debalancedebalance %% 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]). -record(state, { host :: term(), port :: term() }). 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(badrecord, _, State) -> Host = State#state.host, {reply, Host, 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(throw, _, _State) -> throw(a_ball); 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. lager-3.1.0/test/lager_test_backend.erl0000644000232200023220000025322712652234453020457 0ustar debalancedebalance%% ------------------------------------------------------------------- %% %% Copyright (c) 2011-2015 Basho Technologies, Inc. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- -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]). -define(TEST_SINK_NAME, '__lager_test_sink'). %% <-- used by parse transform -define(TEST_SINK_EVENT, '__lager_test_sink_lager_event'). %% <-- used by lager API calls and internals for gen_event -record(state, {level :: list(), buffer :: list(), ignored :: term()}). -compile({parse_transform, lager_transform}). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -record(test, {attrs :: list(), format :: list(), args :: list()}). -export([pop/0, count/0, count_ignored/0, flush/0, print_state/0]). -endif. init(Level) -> {ok, #state{level=lager_util:config_to_mask(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:config_to_mask(Level)}}; handle_call(print_state, State) -> spawn(fun() -> lager:info("State ~p", [lager:pr(State, ?MODULE)]) end), timer:sleep(100), {ok, ok, State}; handle_call(print_bad_state, State) -> spawn(fun() -> lager:info("State ~p", [lager:pr({state, 1}, ?MODULE)]) end), timer:sleep(100), {ok, ok, State}; handle_call(_Request, State) -> {ok, ok, State}. handle_event({log, Msg}, #state{level=LogLevel,buffer=Buffer,ignored=Ignored} = State) -> case lager_util:is_loggable(Msg, LogLevel, ?MODULE) of true -> {ok, State#state{buffer=Buffer ++ [{lager_msg:severity_as_int(Msg), lager_msg:datetime(Msg), lager_msg:message(Msg), lager_msg:metadata(Msg)}]}}; _ -> {ok, State#state{ignored=Ignored ++ [ignored]}} end; handle_event(_Event, State) -> {ok, State}. handle_info(_Info, State) -> {ok, State}. terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. -ifdef(TEST). pop() -> pop(lager_event). count() -> count(lager_event). count_ignored() -> count_ignored(lager_event). flush() -> flush(lager_event). print_state() -> print_state(lager_event). print_bad_state() -> print_bad_state(lager_event). pop(Sink) -> gen_event:call(Sink, ?MODULE, pop). count(Sink) -> gen_event:call(Sink, ?MODULE, count). count_ignored(Sink) -> gen_event:call(Sink, ?MODULE, count_ignored). flush(Sink) -> gen_event:call(Sink, ?MODULE, flush). print_state(Sink) -> gen_event:call(Sink, ?MODULE, print_state). print_bad_state(Sink) -> gen_event:call(Sink, ?MODULE, print_bad_state). has_line_numbers() -> %% are we R15 or greater % this gets called a LOT - cache the answer case erlang:get({?MODULE, has_line_numbers}) of undefined -> R = otp_version() >= 15, erlang:put({?MODULE, has_line_numbers}, R), R; Bool -> Bool end. otp_version() -> otp_version(erlang:system_info(otp_release)). otp_version([$R | Rel]) -> {Ver, _} = string:to_integer(Rel), Ver; otp_version(Rel) -> {Ver, _} = string:to_integer(Rel), Ver. not_running_test() -> ?assertEqual({error, lager_not_running}, lager:log(info, self(), "not running")). 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 }, {"test sink not running", fun() -> ?assertEqual({error, {sink_not_configured, test}}, lager:log(test, info, self(), "~p", "not running")) end }, {"logging works", fun() -> lager:warning("test message"), ?assertEqual(1, count()), {Level, _Time, Message, _Metadata} = pop(), ?assertMatch(Level, lager_util:level_to_num(warning)), ?assertEqual("test message", Message), ok end }, {"unsafe logging works", fun() -> lager:warning_unsafe("test message"), ?assertEqual(1, count()), {Level, _Time, Message, _Metadata} = pop(), ?assertMatch(Level, lager_util:level_to_num(warning)), ?assertEqual("test message", Message), ok end }, {"logging with arguments works", fun() -> lager:warning("test message ~p", [self()]), ?assertEqual(1, count()), {Level, _Time, Message,_Metadata} = pop(), ?assertMatch(Level, lager_util:level_to_num(warning)), ?assertEqual(lists:flatten(io_lib:format("test message ~p", [self()])), lists:flatten(Message)), ok end }, {"unsafe logging with args works", fun() -> lager:warning("test message ~p", [self()]), ?assertEqual(1, count()), {Level, _Time, Message,_Metadata} = pop(), ?assertMatch(Level, lager_util:level_to_num(warning)), ?assertEqual(lists:flatten(io_lib:format("test message ~p", [self()])), lists:flatten(Message)), 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 }, {"variables inplace of literals in logging statements work", fun() -> ?assertEqual(0, count()), Attr = [{a, alpha}, {b, beta}], Fmt = "format ~p", Args = [world], lager:info(Attr, "hello"), lager:info(Attr, "hello ~p", [world]), lager:info(Fmt, [world]), lager:info("hello ~p", Args), lager:info(Attr, "hello ~p", Args), lager:info([{d, delta}, {g, gamma}], Fmt, Args), ?assertEqual(6, count()), {_Level, _Time, Message, Metadata} = pop(), ?assertMatch([{a, alpha}, {b, beta}|_], Metadata), ?assertEqual("hello", lists:flatten(Message)), {_Level, _Time2, Message2, _Metadata2} = pop(), ?assertEqual("hello world", lists:flatten(Message2)), {_Level, _Time3, Message3, _Metadata3} = pop(), ?assertEqual("format world", lists:flatten(Message3)), {_Level, _Time4, Message4, _Metadata4} = pop(), ?assertEqual("hello world", lists:flatten(Message4)), {_Level, _Time5, Message5, _Metadata5} = pop(), ?assertEqual("hello world", lists:flatten(Message5)), {_Level, _Time6, Message6, Metadata6} = pop(), ?assertMatch([{d, delta}, {g, gamma}|_], Metadata6), ?assertEqual("format world", lists:flatten(Message6)), ok end }, {"list comprehension inplace of literals in logging statements work", fun() -> ?assertEqual(0, count()), Attr = [{a, alpha}, {b, beta}], Fmt = "format ~p", Args = [world], lager:info([{K, atom_to_list(V)} || {K, V} <- Attr], "hello"), lager:info([{K, atom_to_list(V)} || {K, V} <- Attr], "hello ~p", [{atom, X} || X <- Args]), lager:info([X || X <- Fmt], [world]), lager:info("hello ~p", [{atom, X} || X <- Args]), lager:info([{K, atom_to_list(V)} || {K, V} <- Attr], "hello ~p", [{atom, X} || X <- Args]), lager:info([{d, delta}, {g, gamma}], Fmt, [{atom, X} || X <- Args]), ?assertEqual(6, count()), {_Level, _Time, Message, Metadata} = pop(), ?assertMatch([{a, "alpha"}, {b, "beta"}|_], Metadata), ?assertEqual("hello", lists:flatten(Message)), {_Level, _Time2, Message2, _Metadata2} = pop(), ?assertEqual("hello {atom,world}", lists:flatten(Message2)), {_Level, _Time3, Message3, _Metadata3} = pop(), ?assertEqual("format world", lists:flatten(Message3)), {_Level, _Time4, Message4, _Metadata4} = pop(), ?assertEqual("hello {atom,world}", lists:flatten(Message4)), {_Level, _Time5, Message5, _Metadata5} = pop(), ?assertEqual("hello {atom,world}", lists:flatten(Message5)), {_Level, _Time6, Message6, Metadata6} = pop(), ?assertMatch([{d, delta}, {g, gamma}|_], Metadata6), ?assertEqual("format {atom,world}", lists:flatten(Message6)), ok end }, {"function calls inplace of literals in logging statements work", fun() -> ?assertEqual(0, count()), put(attrs, [{a, alpha}, {b, beta}]), put(format, "format ~p"), put(args, [world]), lager:info(get(attrs), "hello"), lager:info(get(attrs), "hello ~p", get(args)), lager:info(get(format), [world]), lager:info("hello ~p", erlang:get(args)), lager:info(fun() -> get(attrs) end(), "hello ~p", get(args)), lager:info([{d, delta}, {g, gamma}], get(format), get(args)), ?assertEqual(6, count()), {_Level, _Time, Message, Metadata} = pop(), ?assertMatch([{a, alpha}, {b, beta}|_], Metadata), ?assertEqual("hello", lists:flatten(Message)), {_Level, _Time2, Message2, _Metadata2} = pop(), ?assertEqual("hello world", lists:flatten(Message2)), {_Level, _Time3, Message3, _Metadata3} = pop(), ?assertEqual("format world", lists:flatten(Message3)), {_Level, _Time4, Message4, _Metadata4} = pop(), ?assertEqual("hello world", lists:flatten(Message4)), {_Level, _Time5, Message5, _Metadata5} = pop(), ?assertEqual("hello world", lists:flatten(Message5)), {_Level, _Time6, Message6, Metadata6} = pop(), ?assertMatch([{d, delta}, {g, gamma}|_], Metadata6), ?assertEqual("format world", lists:flatten(Message6)), ok end }, {"record fields inplace of literals in logging statements work", fun() -> ?assertEqual(0, count()), Test = #test{attrs=[{a, alpha}, {b, beta}], format="format ~p", args=[world]}, lager:info(Test#test.attrs, "hello"), lager:info(Test#test.attrs, "hello ~p", Test#test.args), lager:info(Test#test.format, [world]), lager:info("hello ~p", Test#test.args), lager:info(Test#test.attrs, "hello ~p", Test#test.args), lager:info([{d, delta}, {g, gamma}], Test#test.format, Test#test.args), ?assertEqual(6, count()), {_Level, _Time, Message, Metadata} = pop(), ?assertMatch([{a, alpha}, {b, beta}|_], Metadata), ?assertEqual("hello", lists:flatten(Message)), {_Level, _Time2, Message2, _Metadata2} = pop(), ?assertEqual("hello world", lists:flatten(Message2)), {_Level, _Time3, Message3, _Metadata3} = pop(), ?assertEqual("format world", lists:flatten(Message3)), {_Level, _Time4, Message4, _Metadata4} = pop(), ?assertEqual("hello world", lists:flatten(Message4)), {_Level, _Time5, Message5, _Metadata5} = pop(), ?assertEqual("hello world", lists:flatten(Message5)), {_Level, _Time6, Message6, Metadata6} = pop(), ?assertMatch([{d, delta}, {g, gamma}|_], Metadata6), ?assertEqual("format world", lists:flatten(Message6)), ok end }, {"log messages below the threshold are ignored", fun() -> ?assertEqual(0, count()), lager:debug("this message will be ignored"), ?assertEqual(0, count()), ?assertEqual(0, count_ignored()), lager_config:set(loglevel, {element(2, lager_util:config_to_mask(debug)), []}), lager:debug("this message should be ignored"), ?assertEqual(0, count()), ?assertEqual(1, count_ignored()), lager:set_loglevel(?MODULE, debug), ?assertEqual({?DEBUG bor ?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get(loglevel)), lager:debug("this message should be logged"), ?assertEqual(1, count()), ?assertEqual(1, count_ignored()), ?assertEqual(debug, lager:get_loglevel(?MODULE)), ok end }, {"tracing works", fun() -> lager_config:set(loglevel, {element(2, lager_util:config_to_mask(error)), []}), ok = lager:info("hello world"), ?assertEqual(0, count()), lager:trace(?MODULE, [{module, ?MODULE}], debug), ?assertMatch({?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, _}, lager_config:get(loglevel)), %% elegible for tracing ok = lager:info("hello world"), %% NOT elegible for tracing ok = lager:log(info, [{pid, self()}], "hello world"), ?assertEqual(1, count()), ok end }, {"tracing works with custom attributes", fun() -> lager:set_loglevel(?MODULE, error), ?assertEqual({?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get(loglevel)), lager_config:set(loglevel, {element(2, lager_util:config_to_mask(error)), []}), lager:info([{requestid, 6}], "hello world"), ?assertEqual(0, count()), lager:trace(?MODULE, [{requestid, 6}, {foo, bar}], debug), lager:info([{requestid, 6}, {foo, bar}], "hello world"), ?assertEqual(1, count()), lager:trace(?MODULE, [{requestid, '*'}], debug), lager:info([{requestid, 6}], "hello world"), ?assertEqual(2, count()), lager:clear_all_traces(), lager:info([{requestid, 6}], "hello world"), ?assertEqual(2, count()), ok end }, {"tracing works with custom attributes and event stream processing", fun() -> lager:set_loglevel(?MODULE, error), ?assertEqual({?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get(loglevel)), lager_config:set(loglevel, {element(2, lager_util:config_to_mask(error)), []}), lager:info([{requestid, 6}], "hello world"), ?assertEqual(0, count()), lager:trace(?MODULE, [{requestid, '>', 5}, {requestid, '<', 7}, {foo, bar}], debug), lager:info([{requestid, 5}, {foo, bar}], "hello world"), lager:info([{requestid, 6}, {foo, bar}], "hello world"), ?assertEqual(1, count()), lager:clear_all_traces(), lager:trace(?MODULE, [{requestid, '>', 8}, {foo, bar}]), lager:info([{foo, bar}], "hello world"), lager:info([{requestid, 6}], "hello world"), lager:info([{requestid, 7}], "hello world"), lager:info([{requestid, 8}], "hello world"), lager:info([{requestid, 9}, {foo, bar}], "hello world"), lager:info([{requestid, 10}], "hello world"), ?assertEqual(2, count()), lager:trace(?MODULE, [{requestid, '>', 8}]), lager:info([{foo, bar}], "hello world"), lager:info([{requestid, 6}], "hello world"), lager:info([{requestid, 7}], "hello world"), lager:info([{requestid, 8}], "hello world"), lager:info([{requestid, 9}, {foo, bar}], "hello world"), lager:info([{requestid, 10}], "hello world"), ?assertEqual(4, count()), lager:trace(?MODULE, [{foo, '=', bar}]), lager:info([{foo, bar}], "hello world"), lager:info([{requestid, 6}], "hello world"), lager:info([{requestid, 7}], "hello world"), lager:info([{requestid, 8}], "hello world"), lager:info([{requestid, 9}, {foo, bar}], "hello world"), lager:info([{requestid, 10}], "hello world"), lager:trace(?MODULE, [{fu, '!'}]), lager:info([{foo, bar}], "hello world"), lager:info([{ooh, car}], "hello world"), lager:info([{fu, bar}], "hello world"), lager:trace(?MODULE, [{fu, '*'}]), lager:info([{fu, bar}], "hello world"), ?assertEqual(10, count()), lager:clear_all_traces(), lager:info([{requestid, 6}], "hello world"), ?assertEqual(10, count()), ok end }, {"tracing custom attributes works with event stream processing statistics and reductions", fun() -> lager:set_loglevel(?MODULE, error), ?assertEqual({?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get(loglevel)), lager_config:set(loglevel, {element(2, lager_util:config_to_mask(error)), []}), lager:info([{requestid, 6}], "hello world"), ?assertEqual(0, count()), lager:trace(?MODULE, [{beta, '*'}]), lager:trace(?MODULE, [{meta, "data"}]), lager:info([{meta, "data"}], "hello world"), lager:info([{beta, 2}], "hello world"), lager:info([{beta, 2.1}, {foo, bar}], "hello world"), lager:info([{meta, <<"data">>}], "hello world"), ?assertEqual(8, ?DEFAULT_TRACER:info(input)), ?assertEqual(6, ?DEFAULT_TRACER:info(output)), ?assertEqual(2, ?DEFAULT_TRACER:info(filter)), lager:clear_all_traces(), lager:trace(?MODULE, [{meta, "data"}]), lager:trace(?MODULE, [{beta, '>', 2}, {beta, '<', 2.12}]), lager:info([{meta, "data"}], "hello world"), lager:info([{beta, 2}], "hello world"), lager:info([{beta, 2.1}, {foo, bar}], "hello world"), lager:info([{meta, <<"data">>}], "hello world"), ?assertEqual(8, ?DEFAULT_TRACER:info(input)), ?assertEqual(4, ?DEFAULT_TRACER:info(output)), ?assertEqual(4, ?DEFAULT_TRACER:info(filter)), lager:clear_all_traces(), lager:trace_console([{beta, '>', 2}, {meta, "data"}]), lager:trace_console([{beta, '>', 2}, {beta, '<', 2.12}]), Reduced = {all,[{any,[{beta,'<',2.12},{meta,'=',"data"}]}, {beta,'>',2}]}, ?assertEqual(Reduced, ?DEFAULT_TRACER:info('query')), lager:clear_all_traces(), lager:info([{requestid, 6}], "hello world"), ?assertEqual(5, count()), ok end }, {"persistent traces work", fun() -> ?assertEqual(0, count()), lager:debug([{foo, bar}], "hello world"), ?assertEqual(0, count()), application:stop(lager), application:set_env(lager, traces, [{lager_test_backend, [{foo, bar}], debug}]), lager:start(), lager:debug([{foo, bar}], "hello world"), ?assertEqual(1, count()), application:unset_env(lager, traces), ok end }, {"tracing honors loglevel", fun() -> lager:set_loglevel(?MODULE, error), ?assertEqual({?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get(loglevel)), {ok, T} = lager:trace(?MODULE, [{module, ?MODULE}], notice), ok = lager:info("hello world"), ?assertEqual(0, count()), ok = lager:notice("hello world"), ?assertEqual(1, count()), lager:stop_trace(T), ok = lager:notice("hello world"), ?assertEqual(1, count()), ok end }, {"stopped trace stops and removes its event handler - default sink (gh#267)", fun() -> Sink = ?DEFAULT_SINK, StartHandlers = gen_event:which_handlers(Sink), {_, T0} = lager_config:get({Sink, loglevel}), StartGlobal = lager_config:global_get(handlers), ?assertEqual([], T0), {ok, TestTrace1} = lager:trace_file("/tmp/test", [{a,b}]), MidHandlers = gen_event:which_handlers(Sink), {ok, TestTrace2} = lager:trace_file("/tmp/test", [{c,d}]), MidHandlers = gen_event:which_handlers(Sink), ?assertEqual(length(StartHandlers)+1, length(MidHandlers)), MidGlobal = lager_config:global_get(handlers), ?assertEqual(length(StartGlobal)+1, length(MidGlobal)), {_, T1} = lager_config:get({Sink, loglevel}), ?assertEqual(2, length(T1)), ok = lager:stop_trace(TestTrace1), {_, T2} = lager_config:get({Sink, loglevel}), ?assertEqual(1, length(T2)), ?assertEqual(length(StartHandlers)+1, length( gen_event:which_handlers(Sink))), ?assertEqual(length(StartGlobal)+1, length(lager_config:global_get(handlers))), ok = lager:stop_trace(TestTrace2), EndHandlers = gen_event:which_handlers(?DEFAULT_SINK), EndGlobal = lager_config:global_get(handlers), {_, T3} = lager_config:get({Sink, loglevel}), ?assertEqual([], T3), ?assertEqual(StartHandlers, EndHandlers), ?assertEqual(StartGlobal, EndGlobal), ok end }, {"record printing works", fun() -> print_state(), {Level, _Time, Message, _Metadata} = pop(), ?assertMatch(Level, lager_util:level_to_num(info)), {mask, Mask} = lager_util:config_to_mask(info), ?assertEqual("State #state{level={mask,"++integer_to_list(Mask)++"},buffer=[],ignored=[]}", lists:flatten(Message)), ok end }, {"record printing fails gracefully", fun() -> print_bad_state(), {Level, _Time, Message, _Metadata} = pop(), ?assertMatch(Level, lager_util:level_to_num(info)), ?assertEqual("State {state,1}", lists:flatten(Message)), ok end }, {"record printing fails gracefully when no lager_record attribute", fun() -> spawn(fun() -> lager:info("State ~p", [lager:pr({state, 1}, lager)]) end), timer:sleep(100), {Level, _Time, Message, _Metadata} = pop(), ?assertMatch(Level, lager_util:level_to_num(info)), ?assertEqual("State {state,1}", lists:flatten(Message)), ok end }, {"record printing fails gracefully when input is not a tuple", fun() -> spawn(fun() -> lager:info("State ~p", [lager:pr(ok, lager)]) end), timer:sleep(100), {Level, _Time, Message, _Metadata} = pop(), ?assertMatch(Level, lager_util:level_to_num(info)), ?assertEqual("State ok", lists:flatten(Message)), ok end }, {"record printing fails gracefully when module is invalid", fun() -> spawn(fun() -> lager:info("State ~p", [lager:pr({state, 1}, not_a_module)]) end), timer:sleep(1000), {Level, _Time, Message, _Metadata} = pop(), ?assertMatch(Level, lager_util:level_to_num(info)), ?assertEqual("State {state,1}", lists:flatten(Message)), ok end }, {"installing a new handler adjusts the global loglevel if necessary", fun() -> ?assertEqual({?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get(loglevel)), supervisor:start_child(lager_handler_watcher_sup, [lager_event, {?MODULE, foo}, debug]), ?assertEqual({?DEBUG bor ?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get(loglevel)), ok end }, {"metadata in the process dictionary works", fun() -> lager:md([{platypus, gravid}, {sloth, hirsute}, {duck, erroneous}]), lager:info("I sing the animal kingdom electric!"), {_Level, _Time, _Message, Metadata} = pop(), ?assertEqual(gravid, proplists:get_value(platypus, Metadata)), ?assertEqual(hirsute, proplists:get_value(sloth, Metadata)), ?assertEqual(erroneous, proplists:get_value(duck, Metadata)), ?assertEqual(undefined, proplists:get_value(eagle, Metadata)), lager:md([{platypus, gravid}, {sloth, hirsute}, {eagle, superincumbent}]), lager:info("I sing the animal kingdom dielectric!"), {_Level2, _Time2, _Message2, Metadata2} = pop(), ?assertEqual(gravid, proplists:get_value(platypus, Metadata2)), ?assertEqual(hirsute, proplists:get_value(sloth, Metadata2)), ?assertEqual(undefined, proplists:get_value(duck, Metadata2)), ?assertEqual(superincumbent, proplists:get_value(eagle, Metadata2)), ok end }, {"unsafe messages really are not truncated", fun() -> lager:info_unsafe("doom, doom has come upon you all ~p", [string:copies("doom", 1500)]), {_, _, Msg,_Metadata} = pop(), ?assert(length(lists:flatten(Msg)) == 6035) end }, {"can't store invalid metadata", fun() -> ?assertEqual(ok, lager:md([{platypus, gravid}, {sloth, hirsute}, {duck, erroneous}])), ?assertError(badarg, lager:md({flamboyant, flamingo})), ?assertError(badarg, lager:md("zookeeper zephyr")), ok end } ] }. extra_sinks_test_() -> {foreach, fun setup_sink/0, fun cleanup/1, [ {"observe that there is nothing up my sleeve", fun() -> ?assertEqual(undefined, pop(?TEST_SINK_EVENT)), ?assertEqual(0, count(?TEST_SINK_EVENT)) end }, {"logging works", fun() -> ?TEST_SINK_NAME:warning("test message"), ?assertEqual(1, count(?TEST_SINK_EVENT)), {Level, _Time, Message, _Metadata} = pop(?TEST_SINK_EVENT), ?assertMatch(Level, lager_util:level_to_num(warning)), ?assertEqual("test message", Message), ok end }, {"logging with arguments works", fun() -> ?TEST_SINK_NAME:warning("test message ~p", [self()]), ?assertEqual(1, count(?TEST_SINK_EVENT)), {Level, _Time, Message,_Metadata} = pop(?TEST_SINK_EVENT), ?assertMatch(Level, lager_util:level_to_num(warning)), ?assertEqual(lists:flatten(io_lib:format("test message ~p", [self()])), lists:flatten(Message)), ok end }, {"variables inplace of literals in logging statements work", fun() -> ?assertEqual(0, count(?TEST_SINK_EVENT)), Attr = [{a, alpha}, {b, beta}], Fmt = "format ~p", Args = [world], ?TEST_SINK_NAME:info(Attr, "hello"), ?TEST_SINK_NAME:info(Attr, "hello ~p", [world]), ?TEST_SINK_NAME:info(Fmt, [world]), ?TEST_SINK_NAME:info("hello ~p", Args), ?TEST_SINK_NAME:info(Attr, "hello ~p", Args), ?TEST_SINK_NAME:info([{d, delta}, {g, gamma}], Fmt, Args), ?assertEqual(6, count(?TEST_SINK_EVENT)), {_Level, _Time, Message, Metadata} = pop(?TEST_SINK_EVENT), ?assertMatch([{a, alpha}, {b, beta}|_], Metadata), ?assertEqual("hello", lists:flatten(Message)), {_Level, _Time2, Message2, _Metadata2} = pop(?TEST_SINK_EVENT), ?assertEqual("hello world", lists:flatten(Message2)), {_Level, _Time3, Message3, _Metadata3} = pop(?TEST_SINK_EVENT), ?assertEqual("format world", lists:flatten(Message3)), {_Level, _Time4, Message4, _Metadata4} = pop(?TEST_SINK_EVENT), ?assertEqual("hello world", lists:flatten(Message4)), {_Level, _Time5, Message5, _Metadata5} = pop(?TEST_SINK_EVENT), ?assertEqual("hello world", lists:flatten(Message5)), {_Level, _Time6, Message6, Metadata6} = pop(?TEST_SINK_EVENT), ?assertMatch([{d, delta}, {g, gamma}|_], Metadata6), ?assertEqual("format world", lists:flatten(Message6)), ok end }, {"stopped trace stops and removes its event handler - test sink (gh#267)", fun() -> Sink = ?TEST_SINK_EVENT, StartHandlers = gen_event:which_handlers(Sink), {_, T0} = lager_config:get({Sink, loglevel}), StartGlobal = lager_config:global_get(handlers), ?assertEqual([], T0), {ok, TestTrace1} = lager:trace_file("/tmp/test", [{sink, Sink}, {a,b}]), MidHandlers = gen_event:which_handlers(Sink), {ok, TestTrace2} = lager:trace_file("/tmp/test", [{sink, Sink}, {c,d}]), MidHandlers = gen_event:which_handlers(Sink), ?assertEqual(length(StartHandlers)+1, length(MidHandlers)), MidGlobal = lager_config:global_get(handlers), ?assertEqual(length(StartGlobal)+1, length(MidGlobal)), {_, T1} = lager_config:get({Sink, loglevel}), ?assertEqual(2, length(T1)), ok = lager:stop_trace(TestTrace1), {_, T2} = lager_config:get({Sink, loglevel}), ?assertEqual(1, length(T2)), ?assertEqual(length(StartHandlers)+1, length( gen_event:which_handlers(Sink))), ?assertEqual(length(StartGlobal)+1, length(lager_config:global_get(handlers))), ok = lager:stop_trace(TestTrace2), EndHandlers = gen_event:which_handlers(Sink), EndGlobal = lager_config:global_get(handlers), {_, T3} = lager_config:get({Sink, loglevel}), ?assertEqual([], T3), ?assertEqual(StartHandlers, EndHandlers), ?assertEqual(StartGlobal, EndGlobal), ok end }, {"log messages below the threshold are ignored", fun() -> ?assertEqual(0, count(?TEST_SINK_EVENT)), ?TEST_SINK_NAME:debug("this message will be ignored"), ?assertEqual(0, count(?TEST_SINK_EVENT)), ?assertEqual(0, count_ignored(?TEST_SINK_EVENT)), lager_config:set({?TEST_SINK_EVENT, loglevel}, {element(2, lager_util:config_to_mask(debug)), []}), ?TEST_SINK_NAME:debug("this message should be ignored"), ?assertEqual(0, count(?TEST_SINK_EVENT)), ?assertEqual(1, count_ignored(?TEST_SINK_EVENT)), lager:set_loglevel(?TEST_SINK_EVENT, ?MODULE, undefined, debug), ?assertEqual({?DEBUG bor ?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get({?TEST_SINK_EVENT, loglevel})), ?TEST_SINK_NAME:debug("this message should be logged"), ?assertEqual(1, count(?TEST_SINK_EVENT)), ?assertEqual(1, count_ignored(?TEST_SINK_EVENT)), ?assertEqual(debug, lager:get_loglevel(?TEST_SINK_EVENT, ?MODULE)), ok end } ] }. setup_sink() -> error_logger:tty(false), application:load(lager), application:set_env(lager, handlers, []), application:set_env(lager, error_logger_redirect, false), application:set_env(lager, extra_sinks, [{?TEST_SINK_EVENT, [{handlers, [{?MODULE, info}]}]}]), lager:start(), gen_event:call(lager_event, ?MODULE, flush), gen_event:call(?TEST_SINK_EVENT, ?MODULE, flush). setup() -> error_logger:tty(false), application:load(lager), application:set_env(lager, handlers, [{?MODULE, info}]), application:set_env(lager, error_logger_redirect, false), lager:start(), gen_event:call(lager_event, ?MODULE, flush). cleanup(_) -> application:stop(lager), application:stop(goldrush), error_logger:tty(true). crash(Type) -> spawn(fun() -> gen_server:call(crash, Type) end), timer:sleep(100), _ = gen_event:which_handlers(error_logger), ok. test_body(Expected, Actual) -> case has_line_numbers() of true -> ExLen = length(Expected), {Body, Rest} = case length(Actual) > ExLen of true -> {string:substr(Actual, 1, ExLen), string:substr(Actual, (ExLen + 1))}; _ -> {Actual, []} end, ?assertEqual(Expected, Body), % OTP-17 (and maybe later releases) may tack on additional info % about the failure, so if Actual starts with Expected (already % confirmed by having gotten past assertEqual above) and ends % with " line NNN" we can ignore what's in-between. By extension, % since there may not be line information appended at all, any % text we DO find is reportable, but not a test failure. case Rest of [] -> ok; _ -> % isolate the extra data and report it if it's not just % a line number indicator case re:run(Rest, "^.*( line \\d+)$", [{capture, [1]}]) of nomatch -> ?debugFmt( "Trailing data \"~s\" following \"~s\"", [Rest, Expected]); {match, [{0, _}]} -> % the whole sting is " line NNN" ok; {match, [{Off, _}]} -> ?debugFmt( "Trailing data \"~s\" following \"~s\"", [string:substr(Rest, 1, Off), Expected]) end end; _ -> ?assertEqual(Expected, Actual) end. error_logger_redirect_crash_setup() -> error_logger:tty(false), application:load(lager), application:set_env(lager, error_logger_redirect, true), application:set_env(lager, handlers, [{?MODULE, error}]), lager:start(), crash:start(), lager_event. error_logger_redirect_crash_setup_sink() -> error_logger:tty(false), application:load(lager), application:set_env(lager, error_logger_redirect, true), application:unset_env(lager, handlers), application:set_env(lager, extra_sinks, [ {error_logger_lager_event, [ {handlers, [{?MODULE, error}]}]}]), lager:start(), crash:start(), error_logger_lager_event. error_logger_redirect_crash_cleanup(_Sink) -> application:stop(lager), application:stop(goldrush), application:unset_env(lager, extra_sinks), case whereis(crash) of undefined -> ok; Pid -> exit(Pid, kill) end, error_logger:tty(true). error_logger_redirect_crash_test_() -> TestBody=fun(Name,CrashReason,Expected) -> fun(Sink) -> {Name, fun() -> Pid = whereis(crash), crash(CrashReason), {Level, _, Msg,Metadata} = pop(Sink), test_body(Expected, lists:flatten(Msg)), ?assertEqual(Pid,proplists:get_value(pid,Metadata)), ?assertEqual(lager_util:level_to_num(error),Level) end } end end, Tests = [ fun(Sink) -> {"again, there is nothing up my sleeve", fun() -> ?assertEqual(undefined, pop(Sink)), ?assertEqual(0, count(Sink)) end } end, TestBody("bad return value",bad_return,"gen_server crash terminated with reason: bad return value: bleh"), TestBody("bad return value with string",bad_return_string,"gen_server crash terminated with reason: bad return value: {tuple,{tuple,\"string\"}}"), TestBody("bad return uncaught throw",throw,"gen_server crash terminated with reason: bad return value: a_ball"), TestBody("case clause",case_clause,"gen_server crash terminated with reason: no case clause matching {} in crash:handle_call/3"), TestBody("case clause string",case_clause_string,"gen_server crash terminated with reason: no case clause matching \"crash\" in crash:handle_call/3"), TestBody("function clause",function_clause,"gen_server crash terminated with reason: no function clause matching crash:function({})"), TestBody("if clause",if_clause,"gen_server crash terminated with reason: no true branch found while evaluating if expression in crash:handle_call/3"), TestBody("try clause",try_clause,"gen_server crash terminated with reason: no try clause matching [] in crash:handle_call/3"), TestBody("undefined function",undef,"gen_server crash terminated with reason: call to undefined function crash:booger/0 from crash:handle_call/3"), TestBody("bad math",badarith,"gen_server crash terminated with reason: bad arithmetic expression in crash:handle_call/3"), TestBody("bad match",badmatch,"gen_server crash terminated with reason: no match of right hand value {} in crash:handle_call/3"), TestBody("bad arity",badarity,"gen_server crash terminated with reason: fun called with wrong arity of 1 instead of 3 in crash:handle_call/3"), TestBody("bad arg1",badarg1,"gen_server crash terminated with reason: bad argument in crash:handle_call/3"), TestBody("bad arg2",badarg2,"gen_server crash terminated with reason: bad argument in call to erlang:iolist_to_binary([\"foo\",bar]) in crash:handle_call/3"), TestBody("bad record",badrecord,"gen_server crash terminated with reason: bad record state in crash:handle_call/3"), TestBody("noproc",noproc,"gen_server crash terminated with reason: no such process or port in call to gen_event:call(foo, bar, baz)"), TestBody("badfun",badfun,"gen_server crash terminated with reason: bad function booger in crash:handle_call/3") ], {"Error logger redirect crash", [ {"Redirect to default sink", {foreach, fun error_logger_redirect_crash_setup/0, fun error_logger_redirect_crash_cleanup/1, Tests}}, {"Redirect to error_logger_lager_event sink", {foreach, fun error_logger_redirect_crash_setup_sink/0, fun error_logger_redirect_crash_cleanup/1, Tests}} ]}. error_logger_redirect_setup() -> error_logger:tty(false), application:load(lager), application:set_env(lager, error_logger_redirect, true), application:set_env(lager, handlers, [{?MODULE, info}]), lager:start(), lager:log(error, self(), "flush flush"), timer:sleep(100), gen_event:call(lager_event, ?MODULE, flush), lager_event. error_logger_redirect_setup_sink() -> error_logger:tty(false), application:load(lager), application:set_env(lager, error_logger_redirect, true), application:unset_env(lager, handlers), application:set_env(lager, extra_sinks, [ {error_logger_lager_event, [ {handlers, [{?MODULE, info}]}]}]), lager:start(), lager:log(error_logger_lager_event, error, self(), "flush flush", []), timer:sleep(100), gen_event:call(error_logger_lager_event, ?MODULE, flush), error_logger_lager_event. error_logger_redirect_cleanup(_) -> application:stop(lager), application:stop(goldrush), application:unset_env(lager, extra_sinks), error_logger:tty(true). error_logger_redirect_test_() -> Tests = [ {"error reports are printed", fun(Sink) -> sync_error_logger:error_report([{this, is}, a, {silly, format}]), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), Expected = "this: is, a, silly: format", ?assertEqual(Expected, lists:flatten(Msg)) end }, {"string error reports are printed", fun(Sink) -> sync_error_logger:error_report("this is less silly"), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), Expected = "this is less silly", ?assertEqual(Expected, lists:flatten(Msg)) end }, {"error messages are printed", fun(Sink) -> sync_error_logger:error_msg("doom, doom has come upon you all"), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), Expected = "doom, doom has come upon you all", ?assertEqual(Expected, lists:flatten(Msg)) end }, {"error messages with unicode characters in Args are printed", fun(Sink) -> sync_error_logger:error_msg("~ts", ["Привет!"]), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("Привет!", lists:flatten(Msg)) end }, {"error messages are truncated at 4096 characters", fun(Sink) -> sync_error_logger:error_msg("doom, doom has come upon you all ~p", [string:copies("doom", 10000)]), _ = gen_event:which_handlers(error_logger), {_, _, Msg,_Metadata} = pop(Sink), ?assert(length(lists:flatten(Msg)) < 5100) end }, {"info reports are printed", fun(Sink) -> sync_error_logger:info_report([{this, is}, a, {silly, format}]), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(info),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), Expected = "this: is, a, silly: format", ?assertEqual(Expected, lists:flatten(Msg)) end }, {"info reports are truncated at 4096 characters", fun(Sink) -> sync_error_logger:info_report([[{this, is}, a, {silly, format}] || _ <- lists:seq(0, 600)]), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(info),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assert(length(lists:flatten(Msg)) < 5000) end }, {"single term info reports are printed", fun(Sink) -> sync_error_logger:info_report({foolish, bees}), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(info),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("{foolish,bees}", lists:flatten(Msg)) end }, {"single term error reports are printed", fun(Sink) -> sync_error_logger:error_report({foolish, bees}), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("{foolish,bees}", lists:flatten(Msg)) end }, {"string info reports are printed", fun(Sink) -> sync_error_logger:info_report("this is less silly"), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(info),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("this is less silly", lists:flatten(Msg)) end }, {"string info reports are truncated at 4096 characters", fun(Sink) -> sync_error_logger:info_report(string:copies("this is less silly", 1000)), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(info),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assert(length(lists:flatten(Msg)) < 5100) end }, {"strings in a mixed report are printed as strings", fun(Sink) -> sync_error_logger:info_report(["this is less silly", {than, "this"}]), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(info),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("\"this is less silly\", than: \"this\"", lists:flatten(Msg)) end }, {"info messages are printed", fun(Sink) -> sync_error_logger:info_msg("doom, doom has come upon you all"), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(info),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("doom, doom has come upon you all", lists:flatten(Msg)) end }, {"info messages are truncated at 4096 characters", fun(Sink) -> sync_error_logger:info_msg("doom, doom has come upon you all ~p", [string:copies("doom", 10000)]), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(info),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assert(length(lists:flatten(Msg)) < 5100) end }, {"info messages with unicode characters in Args are printed", fun(Sink) -> sync_error_logger:info_msg("~ts", ["Привет!"]), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(info),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("Привет!", lists:flatten(Msg)) end }, {"warning messages with unicode characters in Args are printed", fun(Sink) -> sync_error_logger:warning_msg("~ts", ["Привет!"]), Map = error_logger:warning_map(), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(Map),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("Привет!", lists:flatten(Msg)) end }, {"warning messages are printed at the correct level", fun(Sink) -> sync_error_logger:warning_msg("doom, doom has come upon you all"), Map = error_logger:warning_map(), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(Map),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("doom, doom has come upon you all", lists:flatten(Msg)) end }, {"warning reports are printed at the correct level", fun(Sink) -> sync_error_logger:warning_report([{i, like}, pie]), Map = error_logger:warning_map(), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(Map),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("i: like, pie", lists:flatten(Msg)) end }, {"single term warning reports are printed at the correct level", fun(Sink) -> sync_error_logger:warning_report({foolish, bees}), Map = error_logger:warning_map(), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(Map),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("{foolish,bees}", lists:flatten(Msg)) end }, {"application stop reports", fun(Sink) -> sync_error_logger:info_report([{application, foo}, {exited, quittin_time}, {type, lazy}]), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(info),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("Application foo exited with reason: quittin_time", lists:flatten(Msg)) end }, {"supervisor reports", fun(Sink) -> 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), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("Supervisor steve had child mini_steve started with a:b(c) at bleh exit with reason fired in context france", lists:flatten(Msg)) end }, {"supervisor reports with real error", fun(Sink) -> 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), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("Supervisor steve had child mini_steve started with a:b(c) at bleh exit with reason no function clause matching crash:handle_info(foo) in context france", lists:flatten(Msg)) end }, {"supervisor reports with real error and pid", fun(Sink) -> sync_error_logger:error_report(supervisor_report, [{errorContext, france}, {offender, [{name, mini_steve}, {mfargs, {a, b, [c]}}, {pid, bleh}]}, {reason, {function_clause,[{crash,handle_info,[foo]}]}}, {supervisor, somepid}]), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("Supervisor somepid had child mini_steve started with a:b(c) at bleh exit with reason no function clause matching crash:handle_info(foo) in context france", lists:flatten(Msg)) end }, {"supervisor_bridge reports", fun(Sink) -> 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), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("Supervisor steve had child at module mini_steve at bleh exit with reason fired in context france", lists:flatten(Msg)) end }, {"application progress report", fun(Sink) -> sync_error_logger:info_report(progress, [{application, foo}, {started_at, node()}]), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(info),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), Expected = lists:flatten(io_lib:format("Application foo started on node ~w", [node()])), ?assertEqual(Expected, lists:flatten(Msg)) end }, {"supervisor progress report", fun(Sink) -> lager:set_loglevel(Sink, ?MODULE, undefined, debug), ?assertEqual({?DEBUG bor ?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get({Sink, loglevel})), sync_error_logger:info_report(progress, [{supervisor, {local, foo}}, {started, [{mfargs, {foo, bar, 1}}, {pid, baz}]}]), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(debug),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("Supervisor foo started foo:bar/1 at pid baz", lists:flatten(Msg)) end }, {"supervisor progress report with pid", fun(Sink) -> lager:set_loglevel(Sink, ?MODULE, undefined, debug), ?assertEqual({?DEBUG bor ?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get({Sink, loglevel})), sync_error_logger:info_report(progress, [{supervisor, somepid}, {started, [{mfargs, {foo, bar, 1}}, {pid, baz}]}]), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(debug),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("Supervisor somepid started foo:bar/1 at pid baz", lists:flatten(Msg)) end }, {"crash report for emfile", fun(Sink) -> sync_error_logger:error_report(crash_report, [[{pid, self()}, {registered_name, []}, {error_info, {error, emfile, [{stack, trace, 1}]}}], []]), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~w with 0 neighbours crashed with reason: maximum number of file descriptors exhausted, check ulimit -n", [self()])), ?assertEqual(Expected, lists:flatten(Msg)) end }, {"crash report for system process limit", fun(Sink) -> 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), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: maximum number of processes exceeded", [self()])), ?assertEqual(Expected, lists:flatten(Msg)) end }, {"crash report for system process limit2", fun(Sink) -> 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), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: maximum number of processes exceeded", [self()])), ?assertEqual(Expected, lists:flatten(Msg)) end }, {"crash report for system port limit", fun(Sink) -> 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), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: maximum number of ports exceeded", [self()])), ?assertEqual(Expected, lists:flatten(Msg)) end }, {"crash report for system port limit", fun(Sink) -> 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), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: tried to create an atom larger than 255, or maximum atom count exceeded", [self()])), ?assertEqual(Expected, lists:flatten(Msg)) end }, {"crash report for system ets table limit", fun(Sink) -> 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), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: maximum number of ETS tables exceeded", [test])), ?assertEqual(Expected, lists:flatten(Msg)) end }, {"crash report for unknown system limit should be truncated at 500 characters", fun(Sink) -> 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,_Metadata} = pop(Sink), ?assert(length(lists:flatten(Msg)) > 550), ?assert(length(lists:flatten(Msg)) < 600) end }, {"crash reports for 'special processes' should be handled right - function_clause", fun(Sink) -> {ok, Pid} = special_process:start(), unlink(Pid), Pid ! function_clause, timer:sleep(500), _ = gen_event:which_handlers(error_logger), {_, _, Msg, _Metadata} = pop(Sink), Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~p with 0 neighbours crashed with reason: no function clause matching special_process:foo(bar)", [Pid])), test_body(Expected, lists:flatten(Msg)) end }, {"crash reports for 'special processes' should be handled right - case_clause", fun(Sink) -> {ok, Pid} = special_process:start(), unlink(Pid), Pid ! {case_clause, wtf}, timer:sleep(500), _ = gen_event:which_handlers(error_logger), {_, _, Msg, _Metadata} = pop(Sink), Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~p with 0 neighbours crashed with reason: no case clause matching wtf in special_process:loop/0", [Pid])), test_body(Expected, lists:flatten(Msg)) end }, {"crash reports for 'special processes' should be handled right - exit", fun(Sink) -> {ok, Pid} = special_process:start(), unlink(Pid), Pid ! exit, timer:sleep(500), _ = gen_event:which_handlers(error_logger), {_, _, Msg, _Metadata} = pop(Sink), Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~p with 0 neighbours exited with reason: byebye in special_process:loop/0", [Pid])), test_body(Expected, lists:flatten(Msg)) end }, {"crash reports for 'special processes' should be handled right - error", fun(Sink) -> {ok, Pid} = special_process:start(), unlink(Pid), Pid ! error, timer:sleep(500), _ = gen_event:which_handlers(error_logger), {_, _, Msg, _Metadata} = pop(Sink), Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~p with 0 neighbours crashed with reason: mybad in special_process:loop/0", [Pid])), test_body(Expected, lists:flatten(Msg)) end }, {"webmachine error reports", fun(Sink) -> Path = "/cgi-bin/phpmyadmin", Reason = {error,{error,{badmatch,{error,timeout}}, [{myapp,dostuff,2,[{file,"src/myapp.erl"},{line,123}]}, {webmachine_resource,resource_call,3,[{file,"src/webmachine_resource.erl"},{line,169}]}]}}, sync_error_logger:error_msg("webmachine error: path=~p~n~p~n", [Path, Reason]), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("Webmachine error at path \"/cgi-bin/phpmyadmin\" : no match of right hand value {error,timeout} in myapp:dostuff/2 line 123", lists:flatten(Msg)) end }, {"Cowboy error reports, 8 arg version", fun(Sink) -> Stack = [{my_handler,init, 3,[{file,"src/my_handler.erl"},{line,123}]}, {cowboy_handler,handler_init,4,[{file,"src/cowboy_handler.erl"},{line,169}]}], sync_error_logger:error_msg( "** Cowboy handler ~p terminating in ~p/~p~n" " for the reason ~p:~p~n" "** Options were ~p~n" "** Request was ~p~n" "** Stacktrace: ~p~n~n", [my_handler, init, 3, error, {badmatch, {error, timeout}}, [], "Request", Stack]), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("Cowboy handler my_handler terminated in my_handler:init/3 with reason: no match of right hand value {error,timeout} in my_handler:init/3 line 123", lists:flatten(Msg)) end }, {"Cowboy error reports, 10 arg version", fun(Sink) -> Stack = [{my_handler,somecallback, 3,[{file,"src/my_handler.erl"},{line,123}]}, {cowboy_handler,handler_init,4,[{file,"src/cowboy_handler.erl"},{line,169}]}], sync_error_logger:error_msg( "** Cowboy handler ~p terminating in ~p/~p~n" " for the reason ~p:~p~n** Message was ~p~n" "** Options were ~p~n** Handler state was ~p~n" "** Request was ~p~n** Stacktrace: ~p~n~n", [my_handler, somecallback, 3, error, {badmatch, {error, timeout}}, hello, [], {}, "Request", Stack]), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("Cowboy handler my_handler terminated in my_handler:somecallback/3 with reason: no match of right hand value {error,timeout} in my_handler:somecallback/3 line 123", lists:flatten(Msg)) end }, {"Cowboy error reports, 5 arg version", fun(Sink) -> sync_error_logger:error_msg( "** Cowboy handler ~p terminating; " "function ~p/~p was not exported~n" "** Request was ~p~n** State was ~p~n~n", [my_handler, to_json, 2, "Request", {}]), _ = gen_event:which_handlers(error_logger), {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("Cowboy handler my_handler terminated with reason: call to undefined function my_handler:to_json/2", lists:flatten(Msg)) end }, {"messages should not be generated if they don't satisfy the threshold", fun(Sink) -> lager:set_loglevel(Sink, ?MODULE, undefined, error), ?assertEqual({?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get({Sink, loglevel})), sync_error_logger:info_report([hello, world]), _ = gen_event:which_handlers(error_logger), ?assertEqual(0, count(Sink)), ?assertEqual(0, count_ignored(Sink)), lager:set_loglevel(Sink, ?MODULE, undefined, info), ?assertEqual({?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get({Sink, loglevel})), sync_error_logger:info_report([hello, world]), _ = gen_event:which_handlers(error_logger), ?assertEqual(1, count(Sink)), ?assertEqual(0, count_ignored(Sink)), lager:set_loglevel(Sink, ?MODULE, undefined, error), ?assertEqual({?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get({Sink, loglevel})), lager_config:set({Sink, loglevel}, {element(2, lager_util:config_to_mask(debug)), []}), sync_error_logger:info_report([hello, world]), _ = gen_event:which_handlers(error_logger), ?assertEqual(1, count(Sink)), ?assertEqual(1, count_ignored(Sink)) end } ], SinkTests = lists:map( fun({Name, F}) -> fun(Sink) -> {Name, fun() -> F(Sink) end} end end, Tests), {"Error logger redirect", [ {"Redirect to default sink", {foreach, fun error_logger_redirect_setup/0, fun error_logger_redirect_cleanup/1, SinkTests}}, {"Redirect to error_logger_lager_event sink", {foreach, fun error_logger_redirect_setup_sink/0, fun error_logger_redirect_cleanup/1, SinkTests}} ]}. 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. unsafe_format_test() -> ?assertEqual("foo bar", lists:flatten(lager:unsafe_format("~p ~p", [foo, bar]))), ?assertEqual("FORMAT ERROR: \"~p ~p ~p\" [foo,bar]", lists:flatten(lager:unsafe_format("~p ~p ~p", [foo, bar]))), ok. async_threshold_test_() -> {foreach, fun() -> error_logger:tty(false), ets:new(async_threshold_test, [set, named_table, public]), ets:insert_new(async_threshold_test, {sync_toggled, 0}), ets:insert_new(async_threshold_test, {async_toggled, 0}), application:load(lager), application:set_env(lager, error_logger_redirect, false), application:set_env(lager, async_threshold, 2), application:set_env(lager, async_threshold_window, 1), application:set_env(lager, handlers, [{?MODULE, info}]), lager:start() end, fun(_) -> application:unset_env(lager, async_threshold), application:stop(lager), application:stop(goldrush), ets:delete(async_threshold_test), error_logger:tty(true) end, [ {"async threshold works", fun() -> %% we start out async ?assertEqual(true, lager_config:get(async)), %% put a ton of things in the queue Workers = [spawn_monitor(fun() -> [lager:info("hello world") || _ <- lists:seq(1, 1000)] end) || _ <- lists:seq(1, 15)], %% serialize on mailbox _ = gen_event:which_handlers(lager_event), timer:sleep(500), %% By now the flood of messages will have %% forced the backend throttle to turn off %% async mode, but it's possible all %% outstanding requests have been processed, %% so checking the current status (sync or %% async) is an exercise in race control. %% Instead, we'll see whether the backend %% throttle has toggled into sync mode at any %% point in the past ?assertMatch([{sync_toggled, N}] when N > 0, ets:lookup(async_threshold_test, sync_toggled)), %% wait for all the workers to return, meaning that all the messages have been logged (since we're definitely in sync mode at the end of the run) collect_workers(Workers), %% serialize on the mailbox again _ = gen_event:which_handlers(lager_event), %% just in case... timer:sleep(1000), lager:info("hello world"), _ = gen_event:which_handlers(lager_event), %% async is true again now that the mailbox has drained ?assertEqual(true, lager_config:get(async)), ok end } ] }. collect_workers([]) -> ok; collect_workers(Workers) -> receive {'DOWN', Ref, _, _, _} -> collect_workers(lists:keydelete(Ref, 2, Workers)) end. produce_n_error_logger_msgs(N) -> lists:foreach(fun (K) -> error_logger:error_msg("Foo ~p!", [K]) end, lists:seq(0, N-1) ). high_watermark_test_() -> {foreach, fun() -> error_logger:tty(false), application:load(lager), application:set_env(lager, error_logger_redirect, true), application:set_env(lager, handlers, [{lager_test_backend, info}]), application:set_env(lager, async_threshold, undefined), lager:start() end, fun(_) -> application:stop(lager), error_logger:tty(true) end, [ {"Nothing dropped when error_logger high watermark is undefined", fun () -> ok = error_logger_lager_h:set_high_water(undefined), timer:sleep(100), produce_n_error_logger_msgs(10), timer:sleep(500), ?assert(count() >= 10) end }, {"Mostly dropped according to error_logger high watermark", fun () -> ok = error_logger_lager_h:set_high_water(5), timer:sleep(100), produce_n_error_logger_msgs(50), timer:sleep(1000), ?assert(count() < 20) end }, {"Non-notifications are not dropped", fun () -> ok = error_logger_lager_h:set_high_water(2), timer:sleep(100), spawn(fun () -> produce_n_error_logger_msgs(300) end), timer:sleep(50), %% if everything were dropped, this call would be dropped %% too, so lets hope it's not ?assert(is_integer(count())), timer:sleep(1000), ?assert(count() < 10) end } ] }. -endif. lager-3.1.0/test/compress_pr_record_test.erl0000644000232200023220000000103212652234453021571 0ustar debalancedebalance-module(compress_pr_record_test). -compile([{parse_transform, lager_transform}]). -record(a, {field1, field2, foo, bar, baz, zyu, zix}). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. nested_record_test() -> A = #a{field1 = "Notice me senpai"}, Pr_A = lager:pr(A, ?MODULE), Pr_A_Comp = lager:pr(A, ?MODULE, [compress]), ?assertMatch({'$lager_record', a, [{field1, "Notice me senpai"}, {field2, undefined} | _]}, Pr_A), ?assertEqual({'$lager_record', a, [{field1, "Notice me senpai"}]}, Pr_A_Comp). lager-3.1.0/test/lager_crash_backend.erl0000644000232200023220000000401512652234453020565 0ustar debalancedebalance%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. -module(lager_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}. lager-3.1.0/test/lager_rotate.erl0000644000232200023220000001322112652234453017313 0ustar debalancedebalance-module(lager_rotate). -compile(export_all). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. rotate_test_() -> {foreach, fun() -> file:write_file("test1.log", ""), file:write_file("test2.log", ""), file:write_file("test3.log", ""), file:delete("test1.log.0"), file:delete("test2.log.0"), file:delete("test3.log.0"), error_logger:tty(false), application:load(lager), application:set_env(lager, handlers, [{lager_file_backend, [{file, "test1.log"}, {level, info}]}, {lager_file_backend, [{file, "test2.log"}, {level, info}]}]), application:set_env(lager, extra_sinks, [{sink_event, [{handlers, [{lager_file_backend, [{file, "test3.log"}, {level, info}]}]} ]}]), application:set_env(lager, error_logger_redirect, false), application:set_env(lager, async_threshold, undefined), lager:start() end, fun(_) -> file:delete("test1.log"), file:delete("test2.log"), file:delete("test3.log"), file:delete("test1.log.0"), file:delete("test2.log.0"), file:delete("test3.log.0"), application:stop(lager), application:stop(goldrush), error_logger:tty(true) end, [{"Rotate single file", fun() -> lager:log(error, self(), "Test message 1"), lager:log(sink_event, error, self(), "Sink test message 1", []), lager:rotate_handler({lager_file_backend, "test1.log"}), timer:sleep(1000), true = filelib:is_regular("test1.log.0"), lager:log(error, self(), "Test message 2"), lager:log(sink_event, error, self(), "Sink test message 2", []), {ok, File1} = file:read_file("test1.log"), {ok, File2} = file:read_file("test2.log"), {ok, SinkFile} = file:read_file("test3.log"), {ok, File1Old} = file:read_file("test1.log.0"), have_no_log(File1, <<"Test message 1">>), have_log(File1, <<"Test message 2">>), have_log(File2, <<"Test message 1">>), have_log(File2, <<"Test message 2">>), have_log(File1Old, <<"Test message 1">>), have_no_log(File1Old, <<"Test message 2">>), have_log(SinkFile, <<"Sink test message 1">>), have_log(SinkFile, <<"Sink test message 2">>) end}, {"Rotate sink", fun() -> lager:log(error, self(), "Test message 1"), lager:log(sink_event, error, self(), "Sink test message 1", []), lager:rotate_sink(sink_event), timer:sleep(1000), true = filelib:is_regular("test3.log.0"), lager:log(error, self(), "Test message 2"), lager:log(sink_event, error, self(), "Sink test message 2", []), {ok, File1} = file:read_file("test1.log"), {ok, File2} = file:read_file("test2.log"), {ok, SinkFile} = file:read_file("test3.log"), {ok, SinkFileOld} = file:read_file("test3.log.0"), have_log(File1, <<"Test message 1">>), have_log(File1, <<"Test message 2">>), have_log(File2, <<"Test message 1">>), have_log(File2, <<"Test message 2">>), have_log(SinkFileOld, <<"Sink test message 1">>), have_no_log(SinkFileOld, <<"Sink test message 2">>), have_no_log(SinkFile, <<"Sink test message 1">>), have_log(SinkFile, <<"Sink test message 2">>) end}, {"Rotate all", fun() -> lager:log(error, self(), "Test message 1"), lager:log(sink_event, error, self(), "Sink test message 1", []), lager:rotate_all(), timer:sleep(1000), true = filelib:is_regular("test3.log.0"), lager:log(error, self(), "Test message 2"), lager:log(sink_event, error, self(), "Sink test message 2", []), {ok, File1} = file:read_file("test1.log"), {ok, File2} = file:read_file("test2.log"), {ok, SinkFile} = file:read_file("test3.log"), {ok, File1Old} = file:read_file("test1.log.0"), {ok, File2Old} = file:read_file("test2.log.0"), {ok, SinkFileOld} = file:read_file("test3.log.0"), have_no_log(File1, <<"Test message 1">>), have_log(File1, <<"Test message 2">>), have_no_log(File2, <<"Test message 1">>), have_log(File2, <<"Test message 2">>), have_no_log(SinkFile, <<"Sink test message 1">>), have_log(SinkFile, <<"Sink test message 2">>), have_log(SinkFileOld, <<"Sink test message 1">>), have_no_log(SinkFileOld, <<"Sink test message 2">>), have_log(File1Old, <<"Test message 1">>), have_no_log(File1Old, <<"Test message 2">>), have_log(File2Old, <<"Test message 1">>), have_no_log(File2Old, <<"Test message 2">>) end}]}. have_log(Data, Log) -> {_,_} = binary:match(Data, Log). have_no_log(Data, Log) -> nomatch = binary:match(Data, Log). lager-3.1.0/test/pr_nested_record_test.erl0000644000232200023220000000110012652234453021214 0ustar debalancedebalance-module(pr_nested_record_test). -compile([{parse_transform, lager_transform}]). -record(a, {field1 :: term(), field2 :: term()}). -record(b, {field1 :: term() , field2 :: term()}). -include_lib("eunit/include/eunit.hrl"). nested_record_test() -> A = #a{field1 = x, field2 = y}, B = #b{field1 = A, field2 = {}}, Pr_B = lager:pr(B, ?MODULE), ?assertEqual({'$lager_record', b, [{field1, {'$lager_record', a, [{field1, x},{field2, y}]}}, {field2, {}}]}, Pr_B). lager-3.1.0/test/special_process.erl0000644000232200023220000000125012652234453020020 0ustar debalancedebalance-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(); {case_clause, X} -> case X of notgonnamatch -> ok; notthiseither -> error end, loop(); _ -> loop() end. foo(baz) -> ok. lager-3.1.0/test/sync_error_logger.erl0000644000232200023220000000540112652234453020370 0ustar debalancedebalance%% %% %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. lager-3.1.0/test/trunc_io_eqc.erl0000644000232200023220000002121112652234453017313 0ustar debalancedebalance%% ------------------------------------------------------------------- %% %% trunc_io_eqc: QuickCheck test for trunc_io:format with maxlen %% %% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- -module(trunc_io_eqc). -ifdef(TEST). -ifdef(EQC). -export([test/0, test/1, check/0, prop_format/0, prop_equivalence/0]). -include_lib("eqc/include/eqc.hrl"). -include_lib("eunit/include/eunit.hrl"). -define(QC_OUT(P), eqc:on_output(fun(Str, Args) -> io:format(user, Str, Args) end, P)). %%==================================================================== %% eunit test %%==================================================================== eqc_test_() -> {timeout, 60, {spawn, [ {timeout, 30, ?_assertEqual(true, eqc:quickcheck(eqc:testing_time(14, ?QC_OUT(prop_format()))))}, {timeout, 30, ?_assertEqual(true, eqc:quickcheck(eqc:testing_time(14, ?QC_OUT(prop_equivalence()))))} ] }}. %%==================================================================== %% 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(), "~~", {"~10000000.p", gen_any(5)}, {"~w", gen_any(5)}, {"~s", oneof([gen_print_str(), gen_atom(), gen_quoted_atom(), gen_print_bin(), gen_iolist(5)])}, {"~1000000.P", gen_any(5), 4}, {"~W", gen_any(5), 4}, {"~i", gen_any(5)}, {"~B", nat()}, {"~b", nat()}, {"~X", nat(), "0x"}, {"~x", nat(), "0x"}, {"~.10#", nat()}, {"~.10+", nat()}, {"~.36B", nat()}, {"~1000000.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 /= $~, X < 256]). gen_print_bin() -> ?LET(Xs, gen_print_str(), list_to_binary(Xs)). gen_any(MaxDepth) -> oneof([largeint(), gen_atom(), gen_quoted_atom(), nat(), %real(), binary(), gen_bitstring(), gen_pid(), gen_port(), gen_ref(), gen_fun()] ++ [?LAZY(list(gen_any(MaxDepth - 1))) || MaxDepth /= 0] ++ [?LAZY(gen_tuple(gen_any(MaxDepth - 1))) || MaxDepth /= 0]). gen_iolist(0) -> []; gen_iolist(Depth) -> list(oneof([gen_char(), gen_print_str(), gen_print_bin(), gen_iolist(Depth-1)])). gen_atom() -> elements([abc, def, ghi]). gen_quoted_atom() -> elements(['abc@bar', '@bar', '10gen']). gen_bitstring() -> ?LET(XS, binary(), <>). 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"}, []), catch(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). %% Checks for equivalent formatting to io_lib prop_equivalence() -> ?FORALL(FmtArgs, gen_fmt_args(), begin {FmtStr, Args} = build_fmt_args(FmtArgs), Expected = lists:flatten(io_lib:format(FmtStr, Args)), Actual = lists:flatten(lager_trunc_io:format(FmtStr, Args, 10485760)), ?WHENFAIL(begin io:format(user, "FmtStr: ~p\n", [FmtStr]), io:format(user, "Args: ~p\n", [Args]), io:format(user, "Expected: ~p\n", [Expected]), io:format(user, "Actual: ~p\n", [Actual]) end, Expected == Actual) end). %%==================================================================== %% Internal helpers %%==================================================================== %% 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). lager-3.1.0/test/zzzz_gh280_crash.erl0000644000232200023220000000225512652234453017767 0ustar debalancedebalance%% @doc This test is named zzzz_gh280_crash because it has to be run first and tests are run in %% reverse alphabetical order. %% %% The problem we are attempting to detect here is when log_mf_h is installed as a handler for error_logger %% and lager starts up to replace the current handlers with its own. This causes a start up crash because %% OTP error logging modules do not have any notion of a lager-style log level. -module(zzzz_gh280_crash). -compile(export_all). -include_lib("eunit/include/eunit.hrl"). gh280_crash_test() -> application:stop(lager), application:stop(goldrush), error_logger:tty(false), %% see https://github.com/erlang/otp/blob/maint/lib/stdlib/src/log_mf_h.erl#L81 %% for an explanation of the init arguments to log_mf_h ok = gen_event:add_sup_handler(error_logger, log_mf_h, log_mf_h:init("/tmp", 10000, 5)), lager:start(), Result = receive {gen_event_EXIT,log_mf_h,normal} -> true; {gen_event_EXIT,Handler,Reason} -> {Handler,Reason}; X -> X after 1000 -> timeout end, ?assert(Result), application:stop(lager), application:stop(goldrush). lager-3.1.0/priv/0000755000232200023220000000000012652234453014141 5ustar debalancedebalancelager-3.1.0/priv/edoc.css0000644000232200023220000000422012652234453015563 0ustar debalancedebalance/* Baseline rhythm */ body { font-size: 16px; font-family: Helvetica, sans-serif; margin: 8px; } p { font-size: 1em; /* 16px */ line-height: 1.5em; /* 24px */ margin: 0 0 1.5em 0; } h1 { font-size: 1.5em; /* 24px */ line-height: 1em; /* 24px */ margin-top: 1em; margin-bottom: 0em; } h2 { font-size: 1.375em; /* 22px */ line-height: 1.0909em; /* 24px */ margin-top: 1.0909em; margin-bottom: 0em; } h3 { font-size: 1.25em; /* 20px */ line-height: 1.2em; /* 24px */ margin-top: 1.2em; margin-bottom: 0em; } h4 { font-size: 1.125em; /* 18px */ line-height: 1.3333em; /* 24px */ margin-top: 1.3333em; margin-bottom: 0em; } .class-for-16px { font-size: 1em; /* 16px */ line-height: 1.5em; /* 24px */ margin-top: 1.5em; margin-bottom: 0em; } .class-for-14px { font-size: 0.875em; /* 14px */ line-height: 1.7143em; /* 24px */ margin-top: 1.7143em; margin-bottom: 0em; } ul { margin: 0 0 1.5em 0; } /* Customizations */ body { color: #333; } tt, code, pre { font-family: "Andale Mono", "Inconsolata", "Monaco", "DejaVu Sans Mono", monospaced; } tt, code { font-size: 0.875em } pre { font-size: 0.875em; /* 14px */ line-height: 1.7143em; /* 24px */ margin: 0 1em 1.7143em; padding: 0 1em; background: #eee; } .navbar img, hr { display: none } table { border-collapse: collapse; } h1 { border-left: 0.5em solid #fa0; padding-left: 0.5em; } h2.indextitle { font-size: 1.25em; /* 20px */ line-height: 1.2em; /* 24px */ margin: -8px -8px 0.6em; background-color: #fa0; color: white; padding: 0.3em; } ul.index { list-style: none; margin-left: 0em; padding-left: 0; } ul.index li { display: inline; padding-right: 0.75em } div.spec p { margin-bottom: 0; padding-left: 1.25em; background-color: #eee; } h3.function { border-left: 0.5em solid #fa0; padding-left: 0.5em; background: #fc9; } a, a:visited, a:hover, a:active { color: #C60 } h2 a, h3 a { color: #333 } i { font-size: 0.875em; /* 14px */ line-height: 1.7143em; /* 24px */ margin-top: 1.7143em; margin-bottom: 0em; font-style: normal; } lager-3.1.0/README.md0000644000232200023220000005115112652234453014443 0ustar debalancedebalanceOverview -------- Lager (as in the beer) is a logging framework for Erlang. Its purpose is to provide a more traditional way to perform logging in an erlang application that plays nicely with traditional UNIX logging tools like logrotate and syslog. [Travis-CI](http://travis-ci.org/basho/lager) :: ![Travis-CI](https://secure.travis-ci.org/basho/lager.png) Features -------- * Finer grained log levels (debug, info, notice, warning, error, critical, alert, emergency) * Logger calls are transformed using a parse transform to allow capturing Module/Function/Line/Pid information * When no handler is consuming a log level (eg. debug) no event is sent to the log handler * Supports multiple backends, including console and file. * Supports multiple sinks * Rewrites common OTP error messages into more readable messages * Support for pretty printing records encountered at compile time * Tolerant in the face of large or many log messages, won't out of memory the node * Optional feature to bypass log size truncation ("unsafe") * Supports internal time and date based rotation, as well as external rotation tools * Syslog style log level comparison flags * Colored terminal output (requires R16+) * Map support (requires 17+) Usage ----- To use lager in your application, you need to define it as a rebar dep or have some other way of including it in Erlang's path. You can then add the following option to the erlang compiler flags: ```erlang {parse_transform, lager_transform} ``` Alternately, you can add it to the module you wish to compile with logging enabled: ```erlang -compile([{parse_transform, lager_transform}]). ``` Before logging any messages, you'll need to start the lager application. The lager module's `start` function takes care of loading and starting any dependencies lager requires. ```erlang lager:start(). ``` You can also start lager on startup with a switch to `erl`: ```erlang erl -pa path/to/lager/ebin -s lager ``` Once you have built your code with lager and started the lager application, you can then generate log messages by doing the following: ```erlang lager:error("Some message") ``` Or: ```erlang lager:warning("Some message with a term: ~p", [Term]) ``` The general form is `lager:Severity()` where `Severity` is one of the log levels mentioned above. Configuration ------------- To configure lager's backends, you use an application variable (probably in your app.config): ```erlang {lager, [ {log_root, "/var/log/hello"}, {handlers, [ {lager_console_backend, info}, {lager_file_backend, [{file, "error.log"}, {level, error}]}, {lager_file_backend, [{file, "console.log"}, {level, info}]} ]} ]}. ``` ```log_root``` variable is optional, by default file paths are relative to CWD. The available configuration options for each backend are listed in their module's documentation. Sinks ----- Lager has traditionally supported a single sink (implemented as a `gen_event` manager) named `lager_event` to which all backends were connected. Lager now supports extra sinks; each sink can have different sync/async message thresholds and different backends. ### Sink configuration To use multiple sinks (beyond the built-in sink of lager and lager_event), you need to: 1. Setup rebar.config 2. Configure the backends in app.config #### Names Each sink has two names: one atom to be used like a module name for sending messages, and that atom with `_lager_event` appended for backend configuration. This reflects the legacy behavior: `lager:info` (or `critical`, or `debug`, etc) is a way of sending a message to a sink named `lager_event`. Now developers can invoke `audit:info` or `myCompanyName:debug` so long as the corresponding `audit_lager_event` or `myCompanyName_lager_event` sinks are configured. #### rebar.config In `rebar.config` for the project that requires lager, include a list of sink names (without the `_lager_event` suffix) in `erl_opts`: `{lager_extra_sinks, [audit]}` #### Runtime requirements To be useful, sinks must be configured at runtime with backends. In `app.config` for the project that requires lager, for example, extend the lager configuration to include an `extra_sinks` tuple with backends (aka "handlers") and optionally `async_threshold` and `async_threshold_window` values (see **Overload Protection** below). If async values are not configured, no overload protection will be applied on that sink. ```erlang [{lager, [ {log_root, "/tmp"}, %% Default handlers for lager/lager_event {handlers, [ {lager_console_backend, info}, {lager_file_backend, [{file, "error.log"}, {level, error}]}, {lager_file_backend, [{file, "console.log"}, {level, info}]} ]}, %% Any other sinks {extra_sinks, [ {audit_lager_event, [{handlers, [{lager_file_backend, [{file, "sink1.log"}, {level, info} ] }] }, {async_threshold, 500}, {async_threshold_window, 50}] }] } ] } ]. ``` Custom Formatting ----------------- All loggers have a default formatting that can be overriden. A formatter is any module that exports `format(#lager_log_message{},Config#any())`. It is specified as part of the configuration for the backend: ```erlang {lager, [ {handlers, [ {lager_console_backend, [info, {lager_default_formatter, [time," [",severity,"] ", message, "\n"]}]}, {lager_file_backend, [{file, "error.log"}, {level, error}, {formatter, lager_default_formatter}, {formatter_config, [date, " ", time," [",severity,"] ",pid, " ", message, "\n"]}]}, {lager_file_backend, [{file, "console.log"}, {level, info}]} ]} ]}. ``` Included is `lager_default_formatter`. This provides a generic, default formatting for log messages using a structure similar to Erlang's [iolist](http://learnyousomeerlang.com/buckets-of-sockets#io-lists) which we call "semi-iolist": * Any traditional iolist elements in the configuration are printed verbatim. * Atoms in the configuration are treated as placeholders for lager metadata and extracted from the log message. * The placeholders `date`, `time`, `message`, `sev` and `severity` will always exist. * `sev` is an abbreviated severity which is interpreted as a capitalized single letter encoding of the severity level (e.g. `'debug'` -> `$D`) * The placeholders `pid`, `file`, `line`, `module`, `function`, and `node` will always exist if the parse transform is used. * Applications can define their own metadata placeholder. * A tuple of `{atom(), semi-iolist()}` allows for a fallback for the atom placeholder. If the value represented by the atom cannot be found, the semi-iolist will be interpreted instead. * A tuple of `{atom(), semi-iolist(), semi-iolist()}` represents a conditional operator: if a value for the atom placeholder can be found, the first semi-iolist will be output; otherwise, the second will be used. Examples: ``` ["Foo"] -> "Foo", regardless of message content. [message] -> The content of the logged message, alone. [{pid,"Unknown Pid"}] -> "" if pid is in the metadata, "Unknown Pid" if not. [{pid, ["My pid is ", pid], ["Unknown Pid"]}] -> if pid is in the metadata print "My pid is ", otherwise print "Unknown Pid" [{server,{pid, ["(", pid, ")"], ["(Unknown Server)"]}}] -> user provided server metadata, otherwise "()", otherwise "(Unknown Server)" ``` Error logger integration ------------------------ Lager is also supplied with a `error_logger` handler module that translates traditional erlang error messages into a friendlier format and sends them into lager itself to be treated like a regular lager log call. To disable this, set the lager application variable `error_logger_redirect` to `false`. You can also disable reformatting for OTP and Cowboy messages by setting variable `error_logger_format_raw` to `true`. The `error_logger` handler will also log more complete error messages (protected with use of `trunc_io`) to a "crash log" which can be referred to for further information. The location of the crash log can be specified by the crash_log application variable. If set to `undefined` it is not written at all. Messages in the crash log are subject to a maximum message size which can be specified via the `crash_log_msg_size` application variable. Messages from `error_logger` will be redirected to `error_logger_lager_event` sink if it is defined so it can be redirected to another log file. For example: ``` [{lager, [ {extra_sinks, [ {error_logger_lager_event, [{handlers, [ {lager_file_backend, [{file, "error_logger.log"}, {level, info}]}] }] }] }] }]. ``` Will send all `error_logger` messages to `error_logger.log` file. Overload Protection ------------------- Prior to lager 2.0, the `gen_event` at the core of lager operated purely in synchronous mode. Asynchronous mode is faster, but has no protection against message queue overload. In lager 2.0, the `gen_event` takes a hybrid approach. it polls its own mailbox size and toggles the messaging between synchronous and asynchronous depending on mailbox size. ```erlang {async_threshold, 20}, {async_threshold_window, 5} ``` This will use async messaging until the mailbox exceeds 20 messages, at which point synchronous messaging will be used, and switch back to asynchronous, when size reduces to `20 - 5 = 15`. If you wish to disable this behaviour, simply set it to `undefined`. It defaults to a low number to prevent the mailbox growing rapidly beyond the limit and causing problems. In general, lager should process messages as fast as they come in, so getting 20 behind should be relatively exceptional anyway. If you want to limit the number of messages per second allowed from `error_logger`, which is a good idea if you want to weather a flood of messages when lots of related processes crash, you can set a limit: ```erlang {error_logger_hwm, 50} ``` It is probably best to keep this number small. "Unsafe" -------- The unsafe code pathway bypasses the normal lager formatting code and uses the same code as error_logger in OTP. This provides a marginal speedup to your logging code (we measured between 0.5-1.3% improvement during our benchmarking; others have reported better improvements.) This is a **dangerous** feature. It *will not* protect you against large log messages - large messages can kill your application and even your Erlang VM dead due to memory exhaustion as large terms are copied over and over in a failure cascade. We strongly recommend that this code pathway only be used by log messages with a well bounded upper size of around 500 bytes. If there's any possibility the log messages could exceed that limit, you should use the normal lager message formatting code which will provide the appropriate size limitations and protection against memory exhaustion. If you want to format an unsafe log message, you may use the severity level (as usual) followed by `_unsafe`. Here's an example: ```erlang lager:info_unsafe("The quick brown ~s jumped over the lazy ~s", ["fox", "dog"]). ``` Runtime loglevel changes ------------------------ You can change the log level of any lager backend at runtime by doing the following: ```erlang lager:set_loglevel(lager_console_backend, debug). ``` Or, for the backend with multiple handles (files, mainly): ```erlang lager:set_loglevel(lager_file_backend, "console.log", debug). ``` Lager keeps track of the minimum log level being used by any backend and suppresses generation of messages lower than that level. This means that debug log messages, when no backend is consuming debug messages, are effectively free. A simple benchmark of doing 1 million debug log messages while the minimum threshold was above that takes less than half a second. Syslog style loglevel comparison flags -------------------------------------- In addition to the regular log level names, you can also do finer grained masking of what you want to log: ``` info - info and higher (>= is implicit) =debug - only the debug level !=info - everything but the info level <=notice - notice and below lager:error( "~nStacktrace:~s", [lager:pr_stacktrace(erlang:get_stacktrace(), {Class, Reason})]) end. ``` Record Pretty Printing ---------------------- Lager's parse transform will keep track of any record definitions it encounters and store them in the module's attributes. You can then, at runtime, print any record a module compiled with the lager parse transform knows about by using the `lager:pr/2` function, which takes the record and the module that knows about the record: ```erlang lager:info("My state is ~p", [lager:pr(State, ?MODULE)]) ``` Often, `?MODULE` is sufficent, but you can obviously substitute that for a literal module name. `lager:pr` also works from the shell. Colored terminal output ----------------------- If you have Erlang R16 or higher, you can tell lager's console backend to be colored. Simply add to lager's application environment config: ```erlang {colored, true} ``` If you don't like the default colors, they are also configurable; see the `.app.src` file for more details. The output will be colored from the first occurrence of the atom color in the formatting configuration. For example: ```erlang {lager_console_backend, [info, {lager_default_formatter, [time, color, " [",severity,"] ", message, "\e[0m\r\n"]}]} ``` This will make the entire log message, except time, colored. The escape sequence before the line break is needed in order to reset the color after each log message. Tracing ------- Lager supports basic support for redirecting log messages based on log message attributes. Lager automatically captures the pid, module, function and line at the log message callsite. However, you can add any additional attributes you wish: ```erlang lager:warning([{request, RequestID},{vhost, Vhost}], "Permission denied to ~s", [User]) ``` Then, in addition to the default trace attributes, you'll be able to trace based on request or vhost: ```erlang lager:trace_file("logs/example.com.error", [{vhost, "example.com"}], error) ``` To persist metadata for the life of a process, you can use `lager:md/1` to store metadata in the process dictionary: ```erlang lager:md([{zone, forbidden}]) ``` Note that `lager:md` will *only* accept a list of key/value pairs keyed by atoms. You can also omit the final argument, and the loglevel will default to `debug`. Tracing to the console is similar: ```erlang lager:trace_console([{request, 117}]) ``` In the above example, the loglevel is omitted, but it can be specified as the second argument if desired. You can also specify multiple expressions in a filter, or use the `*` atom as a wildcard to match any message that has that attribute, regardless of its value. Tracing to an existing logfile is also supported (but see **Multiple sink support** below): ```erlang lager:trace_file("log/error.log", [{module, mymodule}, {function, myfunction}], warning) ``` To view the active log backends and traces, you can use the `lager:status()` function. To clear all active traces, you can use `lager:clear_all_traces()`. To delete a specific trace, store a handle for the trace when you create it, that you later pass to `lager:stop_trace/1`: ```erlang {ok, Trace} = lager:trace_file("log/error.log", [{module, mymodule}]), ... lager:stop_trace(Trace) ``` Tracing to a pid is somewhat of a special case, since a pid is not a data-type that serializes well. To trace by pid, use the pid as a string: ```erlang lager:trace_console([{pid, "<0.410.0>"}]) ``` As of lager 2.0, you can also use a 3 tuple while tracing, where the second element is a comparison operator. The currently supported comparison operators are: * `<` - less than * `=` - equal to * `>` - greater than ```erlang lager:trace_console([{request, '>', 117}, {request, '<', 120}]) ``` Using `=` is equivalent to the 2-tuple form. ### Multiple sink support If using multiple sinks, there are limitations on tracing that you should be aware of. Traces are specific to a sink, which can be specified via trace filters: ```erlang lager:trace_file("log/security.log", [{sink, audit}, {function, myfunction}], warning) ``` If no sink is thus specified, the default lager sink will be used. This has two ramifications: * Traces cannot intercept messages sent to a different sink. * Tracing to a file already opened via `lager:trace_file` will only be successful if the same sink is specified. The former can be ameliorated by opening multiple traces; the latter can be fixed by rearchitecting lager's file backend, but this has not been tackled. Setting the truncation limit at compile-time -------------------------------------------- Lager defaults to truncating messages at 4096 bytes, you can alter this by using the `{lager_truncation_size, X}` option. In rebar, you can add it to `erl_opts`: ```erlang {erl_opts, [{parse_transform, lager_transform}, {lager_truncation_size, 1024}]}. ``` You can also pass it to `erlc`, if you prefer: ``` erlc -pa lager/ebin +'{parse_transform, lager_transform}' +'{lager_truncation_size, 1024}' file.erl ``` 3.x Changelog ------------- 3.1.0 - 27 January 2016 * Feature: API calls to a rotate handler, sink or all. This change introduces a new `rotate` message for 3rd party lager backends; that's why this is released as a new minor version number. (#311) 3.0.3 - 27 January 2016 * Feature: Pretty printer for human readable stack traces (#298) * Feature: Make error reformatting optional (#305) * Feature: Optional and explicit sink for error_logger messages (#303) * Bugfix: Always explicitly close a file after its been rotated (#316) * Bugfix: If a relative path already contains the log root, do not add it again (#317) * Bugfix: Configure and start extra sinks before traces are evaluated (#307) * Bugfix: Stop and remove traces correctly (#306) * Bugfix: A byte value of 255 is valid for Unicode (#300) * Dependency: Bump to goldrush 0.1.8 (#313) lager-3.1.0/tools.mk0000644000232200023220000001315312652234453014655 0ustar debalancedebalance# ------------------------------------------------------------------- # # Copyright (c) 2014 Basho Technologies, Inc. # # This file is provided to you under the Apache License, # Version 2.0 (the "License"); you may not use this file # except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # # ------------------------------------------------------------------- # ------------------------------------------------------------------- # NOTE: This file is is from https://github.com/basho/tools.mk. # It should not be edited in a project. It should simply be updated # wholesale when a new version of tools.mk is released. # ------------------------------------------------------------------- REBAR ?= ./rebar REVISION ?= $(shell git rev-parse --short HEAD) PROJECT ?= $(shell basename `find src -name "*.app.src"` .app.src) .PHONY: compile-no-deps test docs xref dialyzer-run dialyzer-quick dialyzer \ cleanplt upload-docs compile-no-deps: ${REBAR} compile skip_deps=true test: compile ${REBAR} eunit skip_deps=true upload-docs: docs @if [ -z "${BUCKET}" -o -z "${PROJECT}" -o -z "${REVISION}" ]; then \ echo "Set BUCKET, PROJECT, and REVISION env vars to upload docs"; \ exit 1; fi @cd doc; s3cmd put -P * "s3://${BUCKET}/${PROJECT}/${REVISION}/" > /dev/null @echo "Docs built at: http://${BUCKET}.s3-website-us-east-1.amazonaws.com/${PROJECT}/${REVISION}" docs: ${REBAR} doc skip_deps=true xref: compile ${REBAR} xref skip_deps=true PLT ?= $(HOME)/.combo_dialyzer_plt LOCAL_PLT = .local_dialyzer_plt DIALYZER_FLAGS ?= -Wunmatched_returns ${PLT}: compile @if [ -f $(PLT) ]; then \ dialyzer --check_plt --plt $(PLT) --apps $(DIALYZER_APPS) && \ dialyzer --add_to_plt --plt $(PLT) --output_plt $(PLT) --apps $(DIALYZER_APPS) ; test $$? -ne 1; \ else \ dialyzer --build_plt --output_plt $(PLT) --apps $(DIALYZER_APPS); test $$? -ne 1; \ fi ${LOCAL_PLT}: compile @if [ -d deps ]; then \ if [ -f $(LOCAL_PLT) ]; then \ dialyzer --check_plt --plt $(LOCAL_PLT) deps/*/ebin && \ dialyzer --add_to_plt --plt $(LOCAL_PLT) --output_plt $(LOCAL_PLT) deps/*/ebin ; test $$? -ne 1; \ else \ dialyzer --build_plt --output_plt $(LOCAL_PLT) deps/*/ebin ; test $$? -ne 1; \ fi \ fi dialyzer-run: @echo "==> $(shell basename $(shell pwd)) (dialyzer)" # The bulk of the code below deals with the dialyzer.ignore-warnings file # which contains strings to ignore if output by dialyzer. # Typically the strings include line numbers. Using them exactly is hard # to maintain as the code changes. This approach instead ignores the line # numbers, but takes into account the number of times a string is listed # for a given file. So if one string is listed once, for example, and it # appears twice in the warnings, the user is alerted. It is possible but # unlikely that this approach could mask a warning if one ignored warning # is removed and two warnings of the same kind appear in the file, for # example. But it is a trade-off that seems worth it. # Details of the cryptic commands: # - Remove line numbers from dialyzer.ignore-warnings # - Pre-pend duplicate count to each warning with sort | uniq -c # - Remove annoying white space around duplicate count # - Save in dialyer.ignore-warnings.tmp # - Do the same to dialyzer_warnings # - Remove matches from dialyzer.ignore-warnings.tmp from output # - Remove duplicate count # - Escape regex special chars to use lines as regex patterns # - Add pattern to match any line number (file.erl:\d+:) # - Anchor to match the entire line (^entire line$) # - Save in dialyzer_unhandled_warnings # - Output matches for those patterns found in the original warnings @if [ -f $(LOCAL_PLT) ]; then \ PLTS="$(PLT) $(LOCAL_PLT)"; \ else \ PLTS=$(PLT); \ fi; \ if [ -f dialyzer.ignore-warnings ]; then \ if [ $$(grep -cvE '[^[:space:]]' dialyzer.ignore-warnings) -ne 0 ]; then \ echo "ERROR: dialyzer.ignore-warnings contains a blank/empty line, this will match all messages!"; \ exit 1; \ fi; \ dialyzer $(DIALYZER_FLAGS) --plts $${PLTS} -c ebin > dialyzer_warnings ; \ cat dialyzer.ignore-warnings \ | sed -E 's/^([^:]+:)[^:]+:/\1/' \ | sort \ | uniq -c \ | sed -E '/.*\.erl: /!s/^[[:space:]]*[0-9]+[[:space:]]*//' \ > dialyzer.ignore-warnings.tmp ; \ egrep -v "^[[:space:]]*(done|Checking|Proceeding|Compiling)" dialyzer_warnings \ | sed -E 's/^([^:]+:)[^:]+:/\1/' \ | sort \ | uniq -c \ | sed -E '/.*\.erl: /!s/^[[:space:]]*[0-9]+[[:space:]]*//' \ | grep -F -f dialyzer.ignore-warnings.tmp -v \ | sed -E 's/^[[:space:]]*[0-9]+[[:space:]]*//' \ | sed -E 's/([]\^:+?|()*.$${}\[])/\\\1/g' \ | sed -E 's/(\\\.erl\\\:)/\1\\d+:/g' \ | sed -E 's/^(.*)$$/^\1$$/g' \ > dialyzer_unhandled_warnings ; \ rm dialyzer.ignore-warnings.tmp; \ if [ $$(cat dialyzer_unhandled_warnings | wc -l) -gt 0 ]; then \ egrep -f dialyzer_unhandled_warnings dialyzer_warnings ; \ found_warnings=1; \ fi; \ [ "$$found_warnings" != 1 ] ; \ else \ dialyzer $(DIALYZER_FLAGS) --plts $${PLTS} -c ebin; \ fi dialyzer-quick: compile-no-deps dialyzer-run dialyzer: ${PLT} ${LOCAL_PLT} dialyzer-run cleanplt: @echo @echo "Are you sure? It takes several minutes to re-build." @echo Deleting $(PLT) and $(LOCAL_PLT) in 5 seconds. @echo sleep 5 rm $(PLT) rm $(LOCAL_PLT) lager-3.1.0/.travis.yml0000644000232200023220000000034412652234453015273 0ustar debalancedebalancelanguage: erlang notifications: webhooks: http://basho-engbot.herokuapp.com/travis?key=8c2550739c2f776d4b05d993aa032f0724fe5450 email: eng@basho.com otp_release: - R16B - R15B03 - R15B01 - R15B - R14B04 - R14B03 lager-3.1.0/dialyzer.ignore-warnings0000644000232200023220000000034212652234453020036 0ustar debalancedebalancelager_trunc_io.erl:283: Call to missing or unexported function erlang:is_map/1 lager_trunc_io.erl:335: Call to missing or unexported function erlang:map_size/1 Unknown functions: lager_default_tracer:info/1 maps:to_list/1 lager-3.1.0/include/0000755000232200023220000000000012652234453014604 5ustar debalancedebalancelager-3.1.0/include/lager.hrl0000644000232200023220000001025612652234453016411 0ustar debalancedebalance%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. -define(DEFAULT_TRUNCATION, 4096). -define(DEFAULT_TRACER, lager_default_tracer). -define(DEFAULT_SINK, lager_event). -define(ERROR_LOGGER_SINK, error_logger_lager_event). -define(LEVELS, [debug, info, notice, warning, error, critical, alert, emergency, none]). %% Use of these "functions" means that the argument list will not be %% truncated for safety -define(LEVELS_UNSAFE, [{debug_unsafe, debug}, {info_unsafe, info}, {notice_unsafe, notice}, {warning_unsafe, warning}, {error_unsafe, error}, {critical_unsafe, critical}, {alert_unsafe, alert}, {emergency_unsafe, emergency}]). -define(DEBUG, 128). -define(INFO, 64). -define(NOTICE, 32). -define(WARNING, 16). -define(ERROR, 8). -define(CRITICAL, 4). -define(ALERT, 2). -define(EMERGENCY, 1). -define(LOG_NONE, 0). -define(LEVEL2NUM(Level), case Level of 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(Sink, Level), (lager_util:level_to_num(Level) band element(1, lager_config:get({Sink, loglevel}, {?LOG_NONE, []}))) /= 0). -define(SHOULD_LOG(Level), (lager_util:level_to_num(Level) band element(1, lager_config:get(loglevel, {?LOG_NONE, []}))) /= 0). -define(NOTIFY(Level, Pid, Format, Args), gen_event:notify(lager_event, {log, lager_msg:new(io_lib:format(Format, Args), Level, [{pid,Pid},{line,?LINE},{file,?FILE},{module,?MODULE}], [])} )). %% FOR INTERNAL USE ONLY %% internal non-blocking logging call %% 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}; X == [lager_backend_throttle] -> %% 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. -record(lager_shaper, { %% how many messages per second we try to deliver hwm = undefined :: 'undefined' | pos_integer(), %% how many messages we've received this second mps = 0 :: non_neg_integer(), %% the current second lasttime = os:timestamp() :: erlang:timestamp(), %% count of dropped messages this second dropped = 0 :: non_neg_integer() }). -type lager_shaper() :: #lager_shaper{}. lager-3.1.0/TODO0000644000232200023220000000012212652234453013644 0ustar debalancedebalanceTime based log rotation Syslog backends (local & remote) debug_module & debug_pid lager-3.1.0/src/0000755000232200023220000000000012652234453013750 5ustar debalancedebalancelager-3.1.0/src/lager_sup.erl0000644000232200023220000000614212652234453016440 0ustar debalancedebalance%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% @doc 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([]) -> %% set up the config, is safe even during relups lager_config:new(), %% TODO: %% Always start lager_event as the default and make sure that %% other gen_event stuff can start up as needed %% %% Maybe a new API to handle the sink and its policy? Children = [ {lager, {gen_event, start_link, [{local, lager_event}]}, permanent, 5000, worker, dynamic}, {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, false} -> []; {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 }}. lager-3.1.0/src/lager_util.erl0000644000232200023220000010220712652234453016605 0ustar debalancedebalance%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. -module(lager_util). -include_lib("kernel/include/file.hrl"). -export([levels/0, level_to_num/1, level_to_chr/1, num_to_level/1, config_to_mask/1, config_to_levels/1, mask_to_levels/1, open_logfile/2, ensure_logfile/4, rotate_logfile/2, format_time/0, format_time/1, localtime_ms/0, localtime_ms/1, maybe_utc/1, parse_rotation_date_spec/1, calculate_next_rotation/1, validate_trace/1, check_traces/4, is_loggable/3, trace_filter/1, trace_filter/2, expand_path/1, check_hwm/1, make_internal_sink_name/1]). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. -include("lager.hrl"). levels() -> [debug, info, notice, warning, error, critical, alert, emergency, none]. level_to_num(debug) -> ?DEBUG; level_to_num(info) -> ?INFO; level_to_num(notice) -> ?NOTICE; level_to_num(warning) -> ?WARNING; level_to_num(error) -> ?ERROR; level_to_num(critical) -> ?CRITICAL; level_to_num(alert) -> ?ALERT; level_to_num(emergency) -> ?EMERGENCY; level_to_num(none) -> ?LOG_NONE. level_to_chr(debug) -> $D; level_to_chr(info) -> $I; level_to_chr(notice) -> $N; level_to_chr(warning) -> $W; level_to_chr(error) -> $E; level_to_chr(critical) -> $C; level_to_chr(alert) -> $A; level_to_chr(emergency) -> $M; level_to_chr(none) -> $ . num_to_level(?DEBUG) -> debug; num_to_level(?INFO) -> info; num_to_level(?NOTICE) -> notice; num_to_level(?WARNING) -> warning; num_to_level(?ERROR) -> error; num_to_level(?CRITICAL) -> critical; num_to_level(?ALERT) -> alert; num_to_level(?EMERGENCY) -> emergency; num_to_level(?LOG_NONE) -> none. -spec config_to_mask(atom()|string()) -> {'mask', integer()}. config_to_mask(Conf) -> Levels = config_to_levels(Conf), {mask, lists:foldl(fun(Level, Acc) -> level_to_num(Level) bor Acc end, 0, Levels)}. -spec mask_to_levels(non_neg_integer()) -> [lager:log_level()]. mask_to_levels(Mask) -> mask_to_levels(Mask, levels(), []). mask_to_levels(_Mask, [], Acc) -> lists:reverse(Acc); mask_to_levels(Mask, [Level|Levels], Acc) -> NewAcc = case (level_to_num(Level) band Mask) /= 0 of true -> [Level|Acc]; false -> Acc end, mask_to_levels(Mask, Levels, NewAcc). -spec config_to_levels(atom()|string()) -> [lager:log_level()]. config_to_levels(Conf) when is_atom(Conf) -> config_to_levels(atom_to_list(Conf)); config_to_levels([$! | Rest]) -> levels() -- config_to_levels(Rest); config_to_levels([$=, $< | Rest]) -> [_|Levels] = config_to_levels_int(Rest), lists:filter(fun(E) -> not lists:member(E, Levels) end, levels()); config_to_levels([$<, $= | Rest]) -> [_|Levels] = config_to_levels_int(Rest), lists:filter(fun(E) -> not lists:member(E, Levels) end, levels()); config_to_levels([$>, $= | Rest]) -> config_to_levels_int(Rest); config_to_levels([$=, $> | Rest]) -> config_to_levels_int(Rest); config_to_levels([$= | Rest]) -> [level_to_atom(Rest)]; config_to_levels([$< | Rest]) -> Levels = config_to_levels_int(Rest), lists:filter(fun(E) -> not lists:member(E, Levels) end, levels()); config_to_levels([$> | Rest]) -> [_|Levels] = config_to_levels_int(Rest), lists:filter(fun(E) -> lists:member(E, Levels) end, levels()); config_to_levels(Conf) -> config_to_levels_int(Conf). %% internal function to break the recursion loop config_to_levels_int(Conf) -> Level = level_to_atom(Conf), lists:dropwhile(fun(E) -> E /= Level end, levels()). level_to_atom(String) -> Levels = levels(), try list_to_existing_atom(String) of Atom -> case lists:member(Atom, Levels) of true -> Atom; false -> erlang:error(badarg) end catch _:_ -> erlang:error(badarg) end. open_logfile(Name, Buffer) -> case filelib:ensure_dir(Name) of ok -> Options = [append, raw] ++ case Buffer of {Size, Interval} when is_integer(Interval), Interval >= 0, is_integer(Size), Size >= 0 -> [{delayed_write, Size, Interval}]; _ -> [] end, case file:open(Name, Options) of {ok, FD} -> 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() -> Now = os:timestamp(), localtime_ms(Now). localtime_ms(Now) -> {_, _, Micro} = Now, {Date, {Hours, Minutes, Seconds}} = calendar:now_to_local_time(Now), {Date, {Hours, Minutes, Seconds, Micro div 1000 rem 1000}}. maybe_utc({Date, {H, M, S, Ms}}) -> case lager_stdlib:maybe_utc({Date, {H, M, S}}) of {utc, {Date1, {H1, M1, S1}}} -> {utc, {Date1, {H1, M1, S1, Ms}}}; {Date1, {H1, M1, S1}} -> {Date1, {H1, M1, S1, Ms}} end. %% renames failing are OK rotate_logfile(File, 0) -> file:delete(File); rotate_logfile(File, 1) -> case file:rename(File, File++".0") of ok -> ok; _ -> rotate_logfile(File, 0) end; rotate_logfile(File, Count) -> _ = file:rename(File ++ "." ++ integer_to_list(Count - 2), File ++ "." ++ integer_to_list(Count - 1)), rotate_logfile(File, Count - 1). format_time() -> format_time(maybe_utc(localtime_ms())). format_time({utc, {{Y, M, D}, {H, Mi, S, Ms}}}) -> {[integer_to_list(Y), $-, i2l(M), $-, i2l(D)], [i2l(H), $:, i2l(Mi), $:, i2l(S), $., i3l(Ms), $ , $U, $T, $C]}; format_time({{Y, M, D}, {H, Mi, S, Ms}}) -> {[integer_to_list(Y), $-, i2l(M), $-, i2l(D)], [i2l(H), $:, i2l(Mi), $:, i2l(S), $., i3l(Ms)]}; format_time({utc, {{Y, M, D}, {H, Mi, S}}}) -> {[integer_to_list(Y), $-, i2l(M), $-, i2l(D)], [i2l(H), $:, i2l(Mi), $:, i2l(S), $ , $U, $T, $C]}; format_time({{Y, M, D}, {H, Mi, S}}) -> {[integer_to_list(Y), $-, i2l(M), $-, i2l(D)], [i2l(H), $:, i2l(Mi), $:, i2l(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). -spec trace_filter(Query :: 'none' | [tuple()]) -> {ok, any()}. trace_filter(Query) -> trace_filter(?DEFAULT_TRACER, Query). %% TODO: Support multiple trace modules %-spec trace_filter(Module :: atom(), Query :: 'none' | [tuple()]) -> {ok, any()}. trace_filter(Module, Query) when Query == none; Query == [] -> {ok, _} = glc:compile(Module, glc:null(false)); trace_filter(Module, Query) when is_list(Query) -> {ok, _} = glc:compile(Module, glc_lib:reduce(trace_any(Query))). validate_trace({Filter, Level, {Destination, ID}}) when is_tuple(Filter); is_list(Filter), is_atom(Level), is_atom(Destination) -> case validate_trace({Filter, Level, Destination}) of {ok, {F, L, D}} -> {ok, {F, L, {D, ID}}}; Error -> Error end; validate_trace({Filter, Level, Destination}) when is_tuple(Filter); is_list(Filter), is_atom(Level), is_atom(Destination) -> ValidFilter = validate_trace_filter(Filter), try config_to_mask(Level) of _ when not ValidFilter -> {error, invalid_trace}; L when is_list(Filter) -> {ok, {trace_all(Filter), L, Destination}}; L -> {ok, {Filter, L, Destination}} catch _:_ -> {error, invalid_level} end; validate_trace(_) -> {error, invalid_trace}. validate_trace_filter(Filter) when is_tuple(Filter), is_atom(element(1, Filter)) =:= false -> false; validate_trace_filter(Filter) -> case lists:all(fun({Key, '*'}) when is_atom(Key) -> true; ({Key, '!'}) when is_atom(Key) -> true; ({Key, _Value}) when is_atom(Key) -> true; ({Key, '=', _Value}) when is_atom(Key) -> true; ({Key, '<', _Value}) when is_atom(Key) -> true; ({Key, '>', _Value}) when is_atom(Key) -> true; (_) -> false end, Filter) of true -> true; _ -> false end. trace_all(Query) -> glc:all(trace_acc(Query)). trace_any(Query) -> glc:any(Query). trace_acc(Query) -> trace_acc(Query, []). trace_acc([], Acc) -> lists:reverse(Acc); trace_acc([{Key, '*'}|T], Acc) -> trace_acc(T, [glc:wc(Key)|Acc]); trace_acc([{Key, '!'}|T], Acc) -> trace_acc(T, [glc:nf(Key)|Acc]); trace_acc([{Key, Val}|T], Acc) -> trace_acc(T, [glc:eq(Key, Val)|Acc]); trace_acc([{Key, '=', Val}|T], Acc) -> trace_acc(T, [glc:eq(Key, Val)|Acc]); trace_acc([{Key, '>', Val}|T], Acc) -> trace_acc(T, [glc:gt(Key, Val)|Acc]); trace_acc([{Key, '<', Val}|T], Acc) -> trace_acc(T, [glc:lt(Key, Val)|Acc]). check_traces(_, _, [], Acc) -> lists:flatten(Acc); check_traces(Attrs, Level, [{_, {mask, FilterLevel}, _}|Flows], Acc) when (Level band FilterLevel) == 0 -> check_traces(Attrs, Level, Flows, Acc); check_traces(Attrs, Level, [{Filter, _, _}|Flows], Acc) when length(Attrs) < length(Filter) -> check_traces(Attrs, Level, Flows, Acc); check_traces(Attrs, Level, [Flow|Flows], Acc) -> check_traces(Attrs, Level, Flows, [check_trace(Attrs, Flow)|Acc]). check_trace(Attrs, {Filter, _Level, Dest}) when is_list(Filter) -> check_trace(Attrs, {trace_all(Filter), _Level, Dest}); check_trace(Attrs, {Filter, _Level, Dest}) when is_tuple(Filter) -> Made = gre:make(Attrs, [list]), glc:handle(?DEFAULT_TRACER, Made), Match = glc_lib:matches(Filter, Made), case Match of true -> Dest; false -> [] end. -spec is_loggable(lager_msg:lager_msg(), non_neg_integer()|{'mask', non_neg_integer()}, term()) -> boolean(). is_loggable(Msg, {mask, Mask}, MyName) -> %% using syslog style comparison flags %S = lager_msg:severity_as_int(Msg), %?debugFmt("comparing masks ~.2B and ~.2B -> ~p~n", [S, Mask, S band Mask]), (lager_msg:severity_as_int(Msg) band Mask) /= 0 orelse lists:member(MyName, lager_msg:destinations(Msg)); is_loggable(Msg ,SeverityThreshold,MyName) -> lager_msg:severity_as_int(Msg) =< SeverityThreshold orelse lists:member(MyName, lager_msg:destinations(Msg)). i2l(I) when I < 10 -> [$0, $0+I]; i2l(I) -> integer_to_list(I). i3l(I) when I < 100 -> [$0 | i2l(I)]; i3l(I) -> integer_to_list(I). %% When log_root option is provided, get the real path to a file expand_path(RelPath) -> case application:get_env(lager, log_root) of {ok, LogRoot} when is_list(LogRoot) -> % Join relative path %% check if the given RelPath contains LogRoot, if so, do not add %% it again; see gh #304 case string:str(filename:dirname(RelPath), LogRoot) of X when X > 0 -> RelPath; _Zero -> filename:join(LogRoot, RelPath) end; undefined -> % No log_root given, keep relative path RelPath end. %% Log rate limit, i.e. high water mark for incoming messages check_hwm(Shaper = #lager_shaper{hwm = undefined}) -> {true, 0, Shaper}; check_hwm(Shaper = #lager_shaper{mps = Mps, hwm = Hwm}) when Mps < Hwm -> %% haven't hit high water mark yet, just log it {true, 0, Shaper#lager_shaper{mps=Mps+1}}; check_hwm(Shaper = #lager_shaper{lasttime = Last, dropped = Drop}) -> %% are we still in the same second? {M, S, _} = Now = os:timestamp(), case Last of {M, S, _} -> %% still in same second, but have exceeded the high water mark NewDrops = discard_messages(Now, 0), {false, 0, Shaper#lager_shaper{dropped=Drop+NewDrops}}; _ -> %% different second, reset all counters and allow it {true, Drop, Shaper#lager_shaper{dropped = 0, mps=1, lasttime = Now}} end. discard_messages(Second, Count) -> {M, S, _} = os:timestamp(), case Second of {M, S, _} -> receive %% we only discard gen_event notifications, because %% otherwise we might discard gen_event internal %% messages, such as trapped EXITs {notify, _Event} -> discard_messages(Second, Count+1) after 0 -> Count end; _ -> Count end. %% @private Build an atom for the gen_event process based on a sink name. %% For historical reasons, the default gen_event process for lager itself is named %% `lager_event'. For all other sinks, it is SinkName++`_lager_event' make_internal_sink_name(lager) -> ?DEFAULT_SINK; make_internal_sink_name(Sink) -> list_to_atom(atom_to_list(Sink) ++ "_lager_event"). -ifdef(TEST). parse_test() -> ?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)]. rotate_file_fail_test() -> %% make sure the directory exists ?assertEqual(ok, filelib:ensure_dir("rotation/rotation.log")), %% fix the permissions on it os:cmd("chown -R u+rwx rotation"), %% delete any old files [ok = file:delete(F) || F <- filelib:wildcard("rotation/*")], %% write a file file:write_file("rotation/rotation.log", "hello"), %% hose up the permissions os:cmd("chown u-w rotation"), ?assertMatch({error, _}, rotate_logfile("rotation.log", 10)), ?assert(filelib:is_regular("rotation/rotation.log")), os:cmd("chown u+w rotation"), ?assertMatch(ok, rotate_logfile("rotation/rotation.log", 10)), ?assert(filelib:is_regular("rotation/rotation.log.0")), ?assertEqual(false, filelib:is_regular("rotation/rotation.log")), ok. check_trace_test() -> lager:start(), trace_filter(none), %% match by module ?assertEqual([foo], check_traces([{module, ?MODULE}], ?EMERGENCY, [ {[{module, ?MODULE}], config_to_mask(emergency), foo}, {[{module, test}], config_to_mask(emergency), bar}], [])), %% match by module, but other unsatisfyable attribute ?assertEqual([], check_traces([{module, ?MODULE}], ?EMERGENCY, [ {[{module, ?MODULE}, {foo, bar}], config_to_mask(emergency), foo}, {[{module, test}], config_to_mask(emergency), bar}], [])), %% match by wildcard module ?assertEqual([bar], check_traces([{module, ?MODULE}], ?EMERGENCY, [ {[{module, ?MODULE}, {foo, bar}], config_to_mask(emergency), foo}, {[{module, '*'}], config_to_mask(emergency), bar}], [])), %% wildcard module, one trace with unsatisfyable attribute ?assertEqual([bar], check_traces([{module, ?MODULE}], ?EMERGENCY, [ {[{module, '*'}, {foo, bar}], config_to_mask(emergency), foo}, {[{module, '*'}], config_to_mask(emergency), bar}], [])), %% wildcard but not present custom trace attribute ?assertEqual([bar], check_traces([{module, ?MODULE}], ?EMERGENCY, [ {[{module, '*'}, {foo, '*'}], config_to_mask(emergency), foo}, {[{module, '*'}], config_to_mask(emergency), bar}], [])), %% wildcarding a custom attribute works when it is present ?assertEqual([bar, foo], check_traces([{module, ?MODULE}, {foo, bar}], ?EMERGENCY, [ {[{module, '*'}, {foo, '*'}], config_to_mask(emergency), foo}, {[{module, '*'}], config_to_mask(emergency), bar}], [])), %% denied by level ?assertEqual([], check_traces([{module, ?MODULE}, {foo, bar}], ?INFO, [ {[{module, '*'}, {foo, '*'}], config_to_mask(emergency), foo}, {[{module, '*'}], config_to_mask(emergency), bar}], [])), %% allowed by level ?assertEqual([foo], check_traces([{module, ?MODULE}, {foo, bar}], ?INFO, [ {[{module, '*'}, {foo, '*'}], config_to_mask(debug), foo}, {[{module, '*'}], config_to_mask(emergency), bar}], [])), ?assertEqual([anythingbutnotice, infoandbelow, infoonly], check_traces([{module, ?MODULE}], ?INFO, [ {[{module, '*'}], config_to_mask('=debug'), debugonly}, {[{module, '*'}], config_to_mask('=info'), infoonly}, {[{module, '*'}], config_to_mask('<=info'), infoandbelow}, {[{module, '*'}], config_to_mask('!=info'), anythingbutinfo}, {[{module, '*'}], config_to_mask('!=notice'), anythingbutnotice} ], [])), application:stop(lager), application:stop(goldrush), ok. is_loggable_test_() -> [ {"Loggable by severity only", ?_assert(is_loggable(lager_msg:new("", alert, [], []),2,me))}, {"Not loggable by severity only", ?_assertNot(is_loggable(lager_msg:new("", critical, [], []),1,me))}, {"Loggable by severity with destination", ?_assert(is_loggable(lager_msg:new("", alert, [], [you]),2,me))}, {"Not loggable by severity with destination", ?_assertNot(is_loggable(lager_msg:new("", critical, [], [you]),1,me))}, {"Loggable by destination overriding severity", ?_assert(is_loggable(lager_msg:new("", critical, [], [me]),1,me))} ]. format_time_test_() -> [ ?_assertEqual("2012-10-04 11:16:23.002", begin {D, T} = format_time({{2012,10,04},{11,16,23,2}}), lists:flatten([D,$ ,T]) end), ?_assertEqual("2012-10-04 11:16:23.999", begin {D, T} = format_time({{2012,10,04},{11,16,23,999}}), lists:flatten([D,$ ,T]) end), ?_assertEqual("2012-10-04 11:16:23", begin {D, T} = format_time({{2012,10,04},{11,16,23}}), lists:flatten([D,$ ,T]) end), ?_assertEqual("2012-10-04 00:16:23.092 UTC", begin {D, T} = format_time({utc, {{2012,10,04},{0,16,23,92}}}), lists:flatten([D,$ ,T]) end), ?_assertEqual("2012-10-04 11:16:23 UTC", begin {D, T} = format_time({utc, {{2012,10,04},{11,16,23}}}), lists:flatten([D,$ ,T]) end) ]. config_to_levels_test() -> ?assertEqual([none], config_to_levels('none')), ?assertEqual({mask, 0}, config_to_mask('none')), ?assertEqual([debug], config_to_levels('=debug')), ?assertEqual([debug], config_to_levels('debug')), ?assertEqual(levels() -- [debug], config_to_levels('>=info')), ?assertEqual(levels() -- [debug], config_to_levels('=>info')), ?assertEqual([debug, info, notice], config_to_levels('<=notice')), ?assertEqual([debug, info, notice], config_to_levels('=info')), ?assertError(badarg, config_to_levels('=<=info')), ?assertError(badarg, config_to_levels('<==>=<=>info')), %% double negatives DO work, however ?assertEqual([debug], config_to_levels('!!=debug')), ?assertEqual(levels() -- [debug], config_to_levels('!!!=debug')), ok. config_to_mask_test() -> ?assertEqual({mask, 0}, config_to_mask('none')), ?assertEqual({mask, ?DEBUG bor ?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY}, config_to_mask('debug')), ?assertEqual({mask, ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY}, config_to_mask('warning')), ?assertEqual({mask, ?DEBUG bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY}, config_to_mask('!=info')), ok. mask_to_levels_test() -> ?assertEqual([], mask_to_levels(0)), ?assertEqual([debug], mask_to_levels(2#10000000)), ?assertEqual([debug, info], mask_to_levels(2#11000000)), ?assertEqual([debug, info, emergency], mask_to_levels(2#11000001)), ?assertEqual([debug, notice, error], mask_to_levels(?DEBUG bor ?NOTICE bor ?ERROR)), ok. expand_path_test() -> OldRootVal = application:get_env(lager, log_root), ok = application:unset_env(lager, log_root), ?assertEqual("/foo/bar", expand_path("/foo/bar")), ?assertEqual("foo/bar", expand_path("foo/bar")), ok = application:set_env(lager, log_root, "log/dir"), ?assertEqual("/foo/bar", expand_path("/foo/bar")), % Absolute path should not be changed ?assertEqual("log/dir/foo/bar", expand_path("foo/bar")), ?assertEqual("log/dir/foo/bar", expand_path("log/dir/foo/bar")), %% gh #304 case OldRootVal of undefined -> application:unset_env(lager, log_root); {ok, Root} -> application:set_env(lager, log_root, Root) end, ok. sink_name_test_() -> [ ?_assertEqual(lager_event, make_internal_sink_name(lager)), ?_assertEqual(audit_lager_event, make_internal_sink_name(audit)) ]. -endif. lager-3.1.0/src/lager_handler_watcher_sup.erl0000644000232200023220000000224312652234453021650 0ustar debalancedebalance%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% @doc 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, []}, temporary, 5000, worker, [lager_handler_watcher]} ]}}. lager-3.1.0/src/lager_stdlib.erl0000644000232200023220000004234012652234453017112 0ustar debalancedebalance%% %% %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 < 256 -> 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,$)]. lager-3.1.0/src/lager_console_backend.erl0000644000232200023220000004345112652234453020746 0ustar debalancedebalance%% Copyright (c) 2011-2012, 2014 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% 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 :: {'mask', integer()}, formatter :: atom(), format_config :: any(), colors=[] :: list()}). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -compile([{parse_transform, lager_transform}]). -endif. -include("lager.hrl"). -define(TERSE_FORMAT,[time, " ", color, "[", severity,"] ", message]). %% @private init([Level]) when is_atom(Level) -> init(Level); init([Level, true]) -> % for backwards compatibility init([Level,{lager_default_formatter,[{eol, eol()}]}]); init([Level,false]) -> % for backwards compatibility init([Level,{lager_default_formatter,?TERSE_FORMAT ++ [eol()]}]); init([Level,{Formatter,FormatterConfig}]) when is_atom(Formatter) -> Colors = case application:get_env(lager, colored) of {ok, true} -> {ok, LagerColors} = application:get_env(lager, colors), LagerColors; _ -> [] end, try {is_new_style_console_available(), lager_util:config_to_mask(Level)} of {false, _} -> Msg = "Lager's console backend is incompatible with the 'old' shell, not enabling it", %% be as noisy as possible, log to every possible place try alarm_handler:set_alarm({?MODULE, "WARNING: " ++ Msg}) catch _:_ -> error_logger:warning_msg(Msg ++ "~n") end, io:format("WARNING: " ++ Msg ++ "~n"), ?INT_LOG(warning, Msg, []), {error, {fatal, old_shell}}; {true, Levels} -> {ok, #state{level=Levels, formatter=Formatter, format_config=FormatterConfig, colors=Colors}} catch _:_ -> {error, {fatal, bad_log_level}} end; init(Level) -> init([Level,{lager_default_formatter,?TERSE_FORMAT ++ [eol()]}]). %% @private handle_call(get_loglevel, #state{level=Level} = State) -> {ok, Level, State}; handle_call({set_loglevel, Level}, State) -> try lager_util:config_to_mask(Level) of Levels -> {ok, ok, State#state{level=Levels}} catch _:_ -> {ok, {error, bad_log_level}, State} end; handle_call(_Request, State) -> {ok, ok, State}. %% @private handle_event({log, Message}, #state{level=L,formatter=Formatter,format_config=FormatConfig,colors=Colors} = State) -> case lager_util:is_loggable(Message, L, ?MODULE) of true -> io:put_chars(user, Formatter:format(Message,FormatConfig,Colors)), {ok, State}; false -> {ok, State} end; 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}. eol() -> case application:get_env(lager, colored) of {ok, true} -> "\e[0m\r\n"; _ -> "\r\n" end. -ifdef(TEST). is_new_style_console_available() -> true. -else. is_new_style_console_available() -> %% Criteria: %% 1. If the user has specified '-noshell' on the command line, %% then we will pretend that the new-style console is available. %% If there is no shell at all, then we don't have to worry %% about log events being blocked by the old-style shell. %% 2. Windows doesn't support the new shell, so all windows users %% have is the oldshell. %% 3. If the user_drv process is registered, all is OK. %% 'user_drv' is a registered proc name used by the "new" %% console driver. init:get_argument(noshell) /= error orelse element(1, os:type()) /= win32 orelse is_pid(whereis(user_drv)). -endif. -ifdef(TEST). console_log_test_() -> %% tiny recursive fun that pretends to be a group leader 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), lager:start(), whereis(user) end, fun(User) -> unregister(user), register(user, User), application:stop(lager), application:stop(goldrush), error_logger:tty(true) end, [ {"regular console logging", fun() -> Pid = spawn(F(self())), unregister(user), register(user, Pid), erlang:group_leader(Pid, whereis(lager_event)), gen_event:add_handler(lager_event, lager_console_backend, info), lager_config:set({lager_event, loglevel}, {element(2, lager_util:config_to_mask(info)), []}), lager:log(info, self(), "Test message"), receive {io_request, From, ReplyAs, {put_chars, unicode, Msg}} -> From ! {io_reply, ReplyAs, ok}, TestMsg = "Test message" ++ eol(), ?assertMatch([_, "[info]", TestMsg], re:split(Msg, " ", [{return, list}, {parts, 3}])) after 500 -> ?assert(false) end end }, {"verbose console logging", fun() -> Pid = spawn(F(self())), unregister(user), register(user, Pid), erlang:group_leader(Pid, whereis(lager_event)), gen_event:add_handler(lager_event, lager_console_backend, [info, true]), lager_config:set({lager_event, loglevel}, {element(2, lager_util:config_to_mask(info)), []}), lager:info("Test message"), PidStr = pid_to_list(self()), receive {io_request, _, _, {put_chars, unicode, Msg}} -> TestMsg = "Test message" ++ eol(), ?assertMatch([_, _, "[info]", PidStr, _, TestMsg], re:split(Msg, "[ @]", [{return, list}, {parts, 6}])) after 500 -> ?assert(false) end end }, {"custom format console logging", fun() -> Pid = spawn(F(self())), unregister(user), register(user, Pid), erlang:group_leader(Pid, whereis(lager_event)), gen_event:add_handler(lager_event, lager_console_backend, [info, {lager_default_formatter, [date,"#",time,"#",severity,"#",node,"#",pid,"#", module,"#",function,"#",file,"#",line,"#",message,"\r\n"]}]), lager_config:set({lager_event, loglevel}, {?INFO, []}), lager:info("Test message"), PidStr = pid_to_list(self()), NodeStr = atom_to_list(node()), ModuleStr = atom_to_list(?MODULE), receive {io_request, _, _, {put_chars, unicode, Msg}} -> TestMsg = "Test message" ++ eol(), ?assertMatch([_, _, "info", NodeStr, PidStr, ModuleStr, _, _, _, TestMsg], re:split(Msg, "#", [{return, list}, {parts, 10}])) after 500 -> ?assert(false) end end }, {"tracing should work", fun() -> Pid = spawn(F(self())), unregister(user), register(user, Pid), gen_event:add_handler(lager_event, lager_console_backend, info), erlang:group_leader(Pid, whereis(lager_event)), lager_config:set({lager_event, loglevel}, {element(2, lager_util:config_to_mask(info)), []}), lager:debug("Test message"), receive {io_request, From, ReplyAs, {put_chars, unicode, _Msg}} -> 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}, TestMsg = "Test message" ++ eol(), ?assertMatch([_, "[debug]", TestMsg], re:split(Msg1, " ", [{return, list}, {parts, 3}])) after 500 -> ?assert(false) end end }, {"tracing doesn't duplicate messages", fun() -> Pid = spawn(F(self())), unregister(user), register(user, Pid), gen_event:add_handler(lager_event, lager_console_backend, info), lager_config:set({lager_event, loglevel}, {element(2, lager_util:config_to_mask(info)), []}), erlang:group_leader(Pid, whereis(lager_event)), lager:debug("Test message"), receive {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}, TestMsg = "Test message" ++ eol(), ?assertMatch([_, "[error]", TestMsg], 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 }, {"blacklisting a loglevel works", fun() -> Pid = spawn(F(self())), unregister(user), register(user, Pid), gen_event:add_handler(lager_event, lager_console_backend, info), lager_config:set({lager_event, loglevel}, {element(2, lager_util:config_to_mask(info)), []}), lager:set_loglevel(lager_console_backend, '!=info'), erlang:group_leader(Pid, whereis(lager_event)), lager:debug("Test message"), receive {io_request, From1, ReplyAs1, {put_chars, unicode, Msg1}} -> From1 ! {io_reply, ReplyAs1, ok}, TestMsg = "Test message" ++ eol(), ?assertMatch([_, "[debug]", TestMsg], re:split(Msg1, " ", [{return, list}, {parts, 3}])) after 1000 -> ?assert(false) end, %% info is blacklisted lager:info("Test message"), receive {io_request, From2, ReplyAs2, {put_chars, unicode, _Msg2}} -> From2 ! {io_reply, ReplyAs2, ok}, ?assert(false) after 500 -> ?assert(true) end end }, {"whitelisting a loglevel works", fun() -> Pid = spawn(F(self())), unregister(user), register(user, Pid), gen_event:add_handler(lager_event, lager_console_backend, info), lager_config:set({lager_event, loglevel}, {element(2, lager_util:config_to_mask(info)), []}), lager:set_loglevel(lager_console_backend, '=debug'), erlang:group_leader(Pid, whereis(lager_event)), lager:debug("Test message"), receive {io_request, From1, ReplyAs1, {put_chars, unicode, Msg1}} -> From1 ! {io_reply, ReplyAs1, ok}, TestMsg = "Test message" ++ eol(), ?assertMatch([_, "[debug]", TestMsg], re:split(Msg1, " ", [{return, list}, {parts, 3}])) after 1000 -> ?assert(false) end, %% info is blacklisted lager:error("Test message"), receive {io_request, From2, ReplyAs2, {put_chars, unicode, _Msg2}} -> From2 ! {io_reply, ReplyAs2, ok}, ?assert(false) after 500 -> ?assert(true) end end } ] }. 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), lager:start() end, fun(_) -> application:stop(lager), application:stop(goldrush), 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)), lager:set_loglevel(lager_console_backend, '!=debug'), ?assertEqual(info, lager:get_loglevel(lager_console_backend)), lager:set_loglevel(lager_console_backend, '!=info'), ?assertEqual(debug, lager:get_loglevel(lager_console_backend)), ok end }, {"Get/set invalid loglevel test", 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. lager-3.1.0/src/error_logger_lager_h.erl0000644000232200023220000004460512652234453020636 0ustar debalancedebalance%% Copyright (c) 2011-2015 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% 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([set_high_water/1]). -export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2, code_change/3]). -export([format_reason/1, format_mfa/1, format_args/3]). -record(state, { sink :: atom(), shaper :: lager_shaper(), %% group leader strategy groupleader_strategy :: handle | ignore | mirror, raw :: boolean() }). -define(LOGMSG(Sink, Level, Pid, Msg), case ?SHOULD_LOG(Sink, Level) of true -> _ =lager:log(Sink, Level, Pid, Msg, []), ok; _ -> ok end). -define(LOGFMT(Sink, Level, Pid, Fmt, Args), case ?SHOULD_LOG(Sink, Level) of true -> _ = lager:log(Sink, Level, Pid, Fmt, Args), ok; _ -> ok end). -ifdef(TEST). -compile(export_all). %% Make CRASH synchronous when testing, to avoid timing headaches -define(CRASH_LOG(Event), catch(gen_server:call(lager_crash_log, {log, Event}))). -else. -define(CRASH_LOG(Event), gen_server:cast(lager_crash_log, {log, Event})). -endif. set_high_water(N) -> gen_event:call(error_logger, ?MODULE, {set_high_water, N}, infinity). -spec init(any()) -> {ok, #state{}}. init([HighWaterMark, GlStrategy]) -> Shaper = #lager_shaper{hwm=HighWaterMark}, Raw = application:get_env(lager, error_logger_format_raw, false), Sink = configured_sink(), {ok, #state{sink=Sink, shaper=Shaper, groupleader_strategy=GlStrategy, raw=Raw}}. handle_call({set_high_water, N}, #state{shaper=Shaper} = State) -> NewShaper = Shaper#lager_shaper{hwm=N}, {ok, ok, State#state{shaper = NewShaper}}; handle_call(_Request, State) -> {ok, unknown_call, State}. handle_event(Event, #state{sink=Sink, shaper=Shaper} = State) -> case lager_util:check_hwm(Shaper) of {true, 0, NewShaper} -> eval_gl(Event, State#state{shaper=NewShaper}); {true, Drop, #lager_shaper{hwm=Hwm} = NewShaper} when Drop > 0 -> ?LOGFMT(Sink, warning, self(), "lager_error_logger_h dropped ~p messages in the last second that exceeded the limit of ~p messages/sec", [Drop, Hwm]), eval_gl(Event, State#state{shaper=NewShaper}); {false, _, NewShaper} -> {ok, State#state{shaper=NewShaper}} end. handle_info(_Info, State) -> {ok, State}. terminate(_Reason, _State) -> ok. code_change(_OldVsn, {state, Shaper, GLStrategy}, _Extra) -> Raw = application:get_env(lager, error_logger_format_raw, false), {ok, #state{ sink=configured_sink(), shaper=Shaper, groupleader_strategy=GLStrategy, raw=Raw }}; code_change(_OldVsn, {state, Sink, Shaper, GLS}, _Extra) -> Raw = application:get_env(lager, error_logger_format_raw, false), {ok, #state{sink=Sink, shaper=Shaper, groupleader_strategy=GLS, raw=Raw}}; code_change(_OldVsn, State, _Extra) -> {ok, State}. %% internal functions configured_sink() -> case proplists:get_value(?ERROR_LOGGER_SINK, application:get_env(lager, extra_sinks, [])) of undefined -> ?DEFAULT_SINK; _ -> ?ERROR_LOGGER_SINK end. eval_gl(Event, #state{groupleader_strategy=GlStrategy0}=State) when is_pid(element(2, Event)) -> case element(2, Event) of GL when node(GL) =/= node(), GlStrategy0 =:= ignore -> gen_event:notify({error_logger, node(GL)}, Event), {ok, State}; GL when node(GL) =/= node(), GlStrategy0 =:= mirror -> gen_event:notify({error_logger, node(GL)}, Event), log_event(Event, State); _ -> log_event(Event, State) end; eval_gl(Event, State) -> log_event(Event, State). log_event(Event, #state{sink=Sink} = State) -> case Event of {error, _GL, {Pid, Fmt, Args}} -> FormatRaw = State#state.raw, case {FormatRaw, Fmt} of {false, "** Generic server "++_} -> %% gen_server terminate [Name, _Msg, _State, Reason] = Args, ?CRASH_LOG(Event), ?LOGFMT(Sink, error, Pid, "gen_server ~w terminated with reason: ~s", [Name, format_reason(Reason)]); {false, "** State machine "++_} -> %% gen_fsm terminate [Name, _Msg, StateName, _StateData, Reason] = Args, ?CRASH_LOG(Event), ?LOGFMT(Sink, error, Pid, "gen_fsm ~w in state ~w terminated with reason: ~s", [Name, StateName, format_reason(Reason)]); {false, "** gen_event handler"++_} -> %% gen_event handler terminate [ID, Name, _Msg, _State, Reason] = Args, ?CRASH_LOG(Event), ?LOGFMT(Sink, error, Pid, "gen_event ~w installed in ~w terminated with reason: ~s", [ID, Name, format_reason(Reason)]); {false, "** Cowboy handler"++_} -> %% Cowboy HTTP server error ?CRASH_LOG(Event), case Args of [Module, Function, Arity, _Request, _State] -> %% we only get the 5-element list when its a non-exported function ?LOGFMT(Sink, error, Pid, "Cowboy handler ~p terminated with reason: call to undefined function ~p:~p/~p", [Module, Module, Function, Arity]); [Module, Function, Arity, _Class, Reason | Tail] -> %% any other cowboy error_format list *always* ends with the stacktrace StackTrace = lists:last(Tail), ?LOGFMT(Sink, error, Pid, "Cowboy handler ~p terminated in ~p:~p/~p with reason: ~s", [Module, Module, Function, Arity, format_reason({Reason, StackTrace})]) end; {false, "Ranch listener "++_} -> %% Ranch errors ?CRASH_LOG(Event), case Args of [Ref, _Protocol, Worker, {[{reason, Reason}, {mfa, {Module, Function, Arity}}, {stacktrace, StackTrace} | _], _}] -> ?LOGFMT(Sink, error, Worker, "Ranch listener ~p terminated in ~p:~p/~p with reason: ~s", [Ref, Module, Function, Arity, format_reason({Reason, StackTrace})]); [Ref, _Protocol, Worker, Reason] -> ?LOGFMT(Sink, error, Worker, "Ranch listener ~p terminated with reason: ~s", [Ref, format_reason(Reason)]) end; {false, "webmachine error"++_} -> %% Webmachine HTTP server error ?CRASH_LOG(Event), [Path, Error] = Args, %% webmachine likes to mangle the stack, for some reason StackTrace = case Error of {error, {error, Reason, Stack}} -> {Reason, Stack}; _ -> Error end, ?LOGFMT(Sink, error, Pid, "Webmachine error at path ~p : ~s", [Path, format_reason(StackTrace)]); _ -> ?CRASH_LOG(Event), ?LOGFMT(Sink, error, Pid, Fmt, Args) end; {error_report, _GL, {Pid, std_error, D}} -> ?CRASH_LOG(Event), ?LOGMSG(Sink, 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), ?LOGFMT(Sink, error, Pid, "Supervisor ~w had child ~s exit with reason ~s in context ~w", [supervisor_name(Name), Offender, format_reason(Reason), Ctx]); _ -> ?LOGMSG(Sink, error, Pid, "SUPERVISOR REPORT " ++ print_silly_list(D)) end; {error_report, _GL, {Pid, crash_report, [Self, Neighbours]}} -> ?CRASH_LOG(Event), ?LOGMSG(Sink, error, Pid, "CRASH REPORT " ++ format_crash_report(Self, Neighbours)); {warning_msg, _GL, {Pid, Fmt, Args}} -> ?LOGFMT(Sink, warning, Pid, Fmt, Args); {warning_report, _GL, {Pid, std_warning, Report}} -> ?LOGMSG(Sink, warning, Pid, print_silly_list(Report)); {info_msg, _GL, {Pid, Fmt, Args}} -> ?LOGFMT(Sink, info, Pid, Fmt, Args); {info_report, _GL, {Pid, std_info, D}} when is_list(D) -> Details = lists:sort(D), case Details of [{application, App}, {exited, Reason}, {type, _Type}] -> case application:get_env(lager, suppress_application_start_stop) of {ok, true} when Reason == stopped -> ok; _ -> ?LOGFMT(Sink, info, Pid, "Application ~w exited with reason: ~s", [App, format_reason(Reason)]) end; _ -> ?LOGMSG(Sink, info, Pid, print_silly_list(D)) end; {info_report, _GL, {Pid, std_info, D}} -> ?LOGFMT(Sink, info, Pid, "~w", [D]); {info_report, _GL, {P, progress, D}} -> Details = lists:sort(D), case Details of [{application, App}, {started_at, Node}] -> case application:get_env(lager, suppress_application_start_stop) of {ok, true} -> ok; _ -> ?LOGFMT(Sink, info, P, "Application ~w started on node ~w", [App, Node]) end; [{started, Started}, {supervisor, Name}] -> MFA = format_mfa(get_value(mfargs, Started)), Pid = get_value(pid, Started), ?LOGFMT(Sink, debug, P, "Supervisor ~w started ~s at pid ~w", [supervisor_name(Name), MFA, Pid]); _ -> ?LOGMSG(Sink, info, P, "PROGRESS REPORT " ++ print_silly_list(D)) end; _ -> ?LOGFMT(Sink, warning, self(), "Unexpected error_logger event ~w", [Event]) end, {ok, State}. format_crash_report(Report, Neighbours) -> Name = case get_value(registered_name, Report, []) of [] -> %% process_info(Pid, registered_name) returns [] for unregistered processes get_value(pid, Report); Atom -> Atom end, {Class, Reason, Trace} = get_value(error_info, Report), ReasonStr = format_reason({Reason, Trace}), Type = case Class of exit -> "exited"; _ -> "crashed" end, io_lib:format("Process ~w with ~w neighbours ~s with reason: ~s", [Name, length(Neighbours), Type, ReasonStr]). format_offender(Off) -> case get_value(mfargs, Off) of undefined -> %% supervisor_bridge io_lib:format("at module ~w at ~w", [get_value(mod, Off), get_value(pid, Off)]); MFArgs -> %% regular supervisor MFA = format_mfa(MFArgs), Name = get_value(name, Off), io_lib:format("~p started with ~s at ~w", [Name, MFA, get_value(pid, Off)]) end. format_reason({'function not exported', [{M, F, A},MFA|_]}) -> ["call to undefined function ", format_mfa({M, F, length(A)}), " from ", format_mfa(MFA)]; format_reason({'function not exported', [{M, F, A, _Props},MFA|_]}) -> %% R15 line numbers ["call to undefined function ", format_mfa({M, F, length(A)}), " from ", format_mfa(MFA)]; format_reason({undef, [MFA|_]}) -> ["call to undefined function ", format_mfa(MFA)]; format_reason({bad_return, {_MFA, {'EXIT', Reason}}}) -> format_reason(Reason); format_reason({bad_return, {MFA, Val}}) -> ["bad return value ", print_val(Val), " from ", format_mfa(MFA)]; format_reason({bad_return_value, Val}) -> ["bad return value: ", print_val(Val)]; format_reason({{bad_return_value, Val}, MFA}) -> ["bad return value: ", print_val(Val), " in ", format_mfa(MFA)]; format_reason({{badrecord, Record}, [MFA|_]}) -> ["bad record ", print_val(Record), " in ", format_mfa(MFA)]; format_reason({{case_clause, Val}, [MFA|_]}) -> ["no case clause matching ", print_val(Val), " in ", format_mfa(MFA)]; format_reason({function_clause, [MFA|_]}) -> ["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, _Props} when is_list(A) -> %% R15 line numbers ["bad argument in call to ", format_mfa(MFA), " in ", format_mfa(MFA2)]; {_M, _F, A} when is_list(A) -> ["bad argument in call to ", format_mfa(MFA), " in ", format_mfa(MFA2)]; _ -> %% seems to be generated by a bad call to a BIF ["bad argument in ", format_mfa(MFA)] end; format_reason({{badarg, Stack}, _}) -> format_reason({badarg, Stack}); format_reason({{badarity, {Fun, Args}}, [MFA|_]}) -> {arity, Arity} = lists:keyfind(arity, 1, erlang:fun_info(Fun)), [io_lib:format("fun called with wrong arity of ~w instead of ~w in ", [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, [{M, F, A}|_]}) when is_atom(M), is_atom(F), is_integer(A) -> [format_reason(Reason), " in ", format_mfa({M, F, A})]; format_reason({Reason, [{M, F, A, Props}|_]}) when is_atom(M), is_atom(F), is_integer(A), is_list(Props) -> %% line numbers [format_reason(Reason), " in ", format_mfa({M, F, A, Props})]; format_reason(Reason) -> {Str, _} = lager_trunc_io:print(Reason, 500), Str. 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({M, F, A, Props}) when is_list(Props) -> case get_value(line, Props) of undefined -> format_mfa({M, F, A}); Line -> [format_mfa({M, F, A}), io_lib:format(" line ~w", [Line])] end; format_mfa([{M, F, A}, _]) -> %% this kind of weird stacktrace can be generated by a uncaught throw in a gen_server format_mfa({M, F, A}); format_mfa([{M, F, A, Props}, _]) when is_list(Props) -> %% this kind of weird stacktrace can be generated by a uncaught throw in a gen_server format_mfa({M, F, A, Props}); format_mfa(Other) -> io_lib:format("~w", [Other]). 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], ?DEFAULT_TRUNCATION); _ -> print_silly_list(L, [], []) end; print_silly_list(L) -> {Str, _} = lager_trunc_io:print(L, ?DEFAULT_TRUNCATION), Str. print_silly_list([], Fmt, Acc) -> lager_trunc_io:format(string:join(lists:reverse(Fmt), ", "), lists:reverse(Acc), ?DEFAULT_TRUNCATION); print_silly_list([{K,V}|T], Fmt, Acc) -> print_silly_list(T, ["~p: ~p" | Fmt], [V, K | Acc]); print_silly_list([H|T], Fmt, Acc) -> print_silly_list(T, ["~p" | Fmt], [H | Acc]). print_val(Val) -> {Str, _} = lager_trunc_io:print(Val, 500), Str. %% @doc Faster than proplists, but with the same API as long as you don't need to %% handle bare atom keys get_value(Key, Value) -> get_value(Key, Value, undefined). get_value(Key, List, Default) -> case lists:keyfind(Key, 1, List) of false -> Default; {Key, Value} -> Value end. supervisor_name({local, Name}) -> Name; supervisor_name(Name) -> Name. lager-3.1.0/src/lager_file_backend.erl0000644000232200023220000012562212652234453020224 0ustar debalancedebalance%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% @doc 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 %% key-value 2-tuples. See the init() function for the available options. %% This backend supports external and internal log %% rotation and will re-open handles to files if the inode changes. It will %% also rotate the files itself if the size of the file exceeds the %% `size' and keep `count' rotated files. `date' is %% an alternate rotation trigger, based on time. See the README for %% documentation. %% For performance, the file backend does delayed writes, although it will %% sync at specific log levels, configured via the `sync_on' option. By default %% the error level or above will trigger a sync. -module(lager_file_backend). -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]). -export([config_to_id/1]). -define(DEFAULT_LOG_LEVEL, info). -define(DEFAULT_ROTATION_SIZE, 10485760). %% 10mb -define(DEFAULT_ROTATION_DATE, "$D0"). %% midnight -define(DEFAULT_ROTATION_COUNT, 5). -define(DEFAULT_SYNC_LEVEL, error). -define(DEFAULT_SYNC_INTERVAL, 1000). -define(DEFAULT_SYNC_SIZE, 1024*64). %% 64kb -define(DEFAULT_CHECK_INTERVAL, 1000). -record(state, { name :: string(), level :: {'mask', integer()}, fd :: file:io_device(), inode :: integer(), flap=false :: boolean(), size = 0 :: integer(), date :: undefined | string(), count = 10 :: integer(), shaper :: lager_shaper(), formatter :: atom(), formatter_config :: any(), sync_on :: {'mask', integer()}, check_interval = ?DEFAULT_CHECK_INTERVAL :: non_neg_integer(), sync_interval = ?DEFAULT_SYNC_INTERVAL :: non_neg_integer(), sync_size = ?DEFAULT_SYNC_SIZE :: non_neg_integer(), last_check = os:timestamp() :: erlang:timestamp() }). -type option() :: {file, string()} | {level, lager:log_level()} | {size, non_neg_integer()} | {date, string()} | {count, non_neg_integer()} | {high_water_mark, non_neg_integer()} | {sync_interval, non_neg_integer()} | {sync_size, non_neg_integer()} | {sync_on, lager:log_level()} | {check_interval, non_neg_integer()} | {formatter, atom()} | {formatter_config, term()}. -spec init([option(),...]) -> {ok, #state{}} | {error, bad_config}. init({FileName, LogLevel}) when is_list(FileName), is_atom(LogLevel) -> %% backwards compatibility hack init([{file, FileName}, {level, LogLevel}]); init({FileName, LogLevel, Size, Date, Count}) when is_list(FileName), is_atom(LogLevel) -> %% backwards compatibility hack init([{file, FileName}, {level, LogLevel}, {size, Size}, {date, Date}, {count, Count}]); init([{FileName, LogLevel, Size, Date, Count}, {Formatter,FormatterConfig}]) when is_list(FileName), is_atom(LogLevel), is_atom(Formatter) -> %% backwards compatibility hack init([{file, FileName}, {level, LogLevel}, {size, Size}, {date, Date}, {count, Count}, {formatter, Formatter}, {formatter_config, FormatterConfig}]); init([LogFile,{Formatter}]) -> %% backwards compatibility hack init([LogFile,{Formatter,[]}]); init([{FileName, LogLevel}, {Formatter,FormatterConfig}]) when is_list(FileName), is_atom(LogLevel), is_atom(Formatter) -> %% backwards compatibility hack init([{file, FileName}, {level, LogLevel}, {formatter, Formatter}, {formatter_config, FormatterConfig}]); init(LogFileConfig) when is_list(LogFileConfig) -> case validate_logfile_proplist(LogFileConfig) of false -> %% falied to validate config {error, {fatal, bad_config}}; Config -> %% probabably a better way to do this, but whatever [RelName, Level, Date, Size, Count, HighWaterMark, SyncInterval, SyncSize, SyncOn, CheckInterval, Formatter, FormatterConfig] = [proplists:get_value(Key, Config) || Key <- [file, level, date, size, count, high_water_mark, sync_interval, sync_size, sync_on, check_interval, formatter, formatter_config]], Name = lager_util:expand_path(RelName), schedule_rotation(Name, Date), Shaper = #lager_shaper{hwm=HighWaterMark}, State0 = #state{name=Name, level=Level, size=Size, date=Date, count=Count, shaper=Shaper, formatter=Formatter, formatter_config=FormatterConfig, sync_on=SyncOn, sync_interval=SyncInterval, sync_size=SyncSize, check_interval=CheckInterval}, State = case lager_util:open_logfile(Name, {SyncSize, SyncInterval}) of {ok, {FD, Inode, _}} -> State0#state{fd=FD, inode=Inode}; {error, Reason} -> ?INT_LOG(error, "Failed to open log file ~s with error ~s", [Name, file:format_error(Reason)]), State0#state{flap=true} end, {ok, State} end. %% @private handle_call({set_loglevel, Level}, #state{name=Ident} = State) -> case validate_loglevel(Level) of false -> {ok, {error, bad_loglevel}, State}; Levels -> ?INT_LOG(notice, "Changed loglevel of ~s to ~p", [Ident, Level]), {ok, ok, State#state{level=Levels}} end; handle_call(get_loglevel, #state{level=Level} = State) -> {ok, Level, State}; handle_call({set_loghwm, Hwm}, #state{shaper=Shaper, name=Name} = State) -> case validate_logfile_proplist([{file, Name}, {high_water_mark, Hwm}]) of false -> {ok, {error, bad_log_hwm}, State}; _ -> NewShaper = Shaper#lager_shaper{hwm=Hwm}, ?INT_LOG(notice, "Changed loghwm of ~s to ~p", [Name, Hwm]), {ok, {last_loghwm, Shaper#lager_shaper.hwm}, State#state{shaper=NewShaper}} end; handle_call(rotate, State = #state{name=File}) -> {ok, NewState} = handle_info({rotate, File}, State), {ok, ok, NewState}; handle_call(_Request, State) -> {ok, ok, State}. %% @private handle_event({log, Message}, #state{name=Name, level=L, shaper=Shaper, formatter=Formatter,formatter_config=FormatConfig} = State) -> case lager_util:is_loggable(Message,L,{lager_file_backend, Name}) of true -> case lager_util:check_hwm(Shaper) of {true, Drop, #lager_shaper{hwm=Hwm} = NewShaper} -> NewState = case Drop > 0 of true -> Report = io_lib:format( "lager_file_backend dropped ~p messages in the last second that exceeded the limit of ~p messages/sec", [Drop, Hwm]), ReportMsg = lager_msg:new(Report, warning, [], []), write(State, lager_msg:timestamp(ReportMsg), lager_msg:severity_as_int(ReportMsg), Formatter:format(ReportMsg, FormatConfig)); false -> State end, {ok,write(NewState#state{shaper=NewShaper}, lager_msg:timestamp(Message), lager_msg:severity_as_int(Message), Formatter:format(Message,FormatConfig))}; {false, _, NewShaper} -> {ok, State#state{shaper=NewShaper}} end; false -> {ok, State} end; handle_event(_Event, State) -> {ok, State}. %% @private handle_info({rotate, File}, #state{name=File,count=Count,date=Date} = State) -> _ = lager_util:rotate_logfile(File, Count), State1 = close_file(State), schedule_rotation(File, Date), {ok, State1}; handle_info(_Info, State) -> {ok, State}. %% @private terminate(_Reason, State) -> %% leaving this function call unmatched makes dialyzer cranky _ = close_file(State), ok. %% @private code_change(_OldVsn, State, _Extra) -> {ok, State}. %% Convert the config into a gen_event handler ID config_to_id({Name,_Severity}) when is_list(Name) -> {?MODULE, Name}; config_to_id({Name,_Severity,_Size,_Rotation,_Count}) -> {?MODULE, Name}; config_to_id([{Name,_Severity,_Size,_Rotation,_Count}, _Format]) -> {?MODULE, Name}; config_to_id([{Name,_Severity}, _Format]) when is_list(Name) -> {?MODULE, Name}; config_to_id(Config) -> case proplists:get_value(file, Config) of undefined -> erlang:error(no_file); File -> {?MODULE, File} end. write(#state{name=Name, fd=FD, inode=Inode, flap=Flap, size=RotSize, count=Count} = State, Timestamp, Level, Msg) -> LastCheck = timer:now_diff(Timestamp, State#state.last_check) div 1000, case LastCheck >= State#state.check_interval orelse FD == undefined of true -> %% need to check for rotation case lager_util:ensure_logfile(Name, FD, Inode, {State#state.sync_size, State#state.sync_interval}) of {ok, {_, _, Size}} when RotSize /= 0, Size > RotSize -> case lager_util:rotate_logfile(Name, Count) of ok -> %% go around the loop again, we'll do another rotation check and hit the next clause of ensure_logfile write(State, Timestamp, Level, Msg); {error, Reason} -> case Flap of true -> State; _ -> ?INT_LOG(error, "Failed to rotate log file ~s with error ~s", [Name, file:format_error(Reason)]), State#state{flap=true} end end; {ok, {NewFD, NewInode, _}} -> %% update our last check and try again do_write(State#state{last_check=Timestamp, fd=NewFD, inode=NewInode}, Level, Msg); {error, Reason} -> case Flap of true -> State; _ -> ?INT_LOG(error, "Failed to reopen log file ~s with error ~s", [Name, file:format_error(Reason)]), State#state{flap=true} end end; false -> do_write(State, Level, Msg) end. do_write(#state{fd=FD, name=Name, flap=Flap} = State, Level, Msg) -> %% delayed_write doesn't report errors _ = file:write(FD, unicode:characters_to_binary(Msg)), {mask, SyncLevel} = State#state.sync_on, case (Level band SyncLevel) /= 0 of true -> %% force a sync on any message that matches the 'sync_on' bitmask Flap2 = case file:datasync(FD) of {error, Reason2} when Flap == false -> ?INT_LOG(error, "Failed to write log message to file ~s: ~s", [Name, file:format_error(Reason2)]), true; ok -> false; _ -> Flap end, State#state{flap=Flap2}; _ -> State end. validate_loglevel(Level) -> try lager_util:config_to_mask(Level) of Levels -> Levels catch _:_ -> false end. validate_logfile_proplist(List) -> try validate_logfile_proplist(List, []) of Res -> case proplists:get_value(file, Res) of undefined -> ?INT_LOG(error, "Missing required file option", []), false; _File -> %% merge with the default options {ok, DefaultRotationDate} = lager_util:parse_rotation_date_spec(?DEFAULT_ROTATION_DATE), lists:keymerge(1, lists:sort(Res), lists:sort([ {level, validate_loglevel(?DEFAULT_LOG_LEVEL)}, {date, DefaultRotationDate}, {size, ?DEFAULT_ROTATION_SIZE}, {count, ?DEFAULT_ROTATION_COUNT}, {sync_on, validate_loglevel(?DEFAULT_SYNC_LEVEL)}, {sync_interval, ?DEFAULT_SYNC_INTERVAL}, {sync_size, ?DEFAULT_SYNC_SIZE}, {check_interval, ?DEFAULT_CHECK_INTERVAL}, {formatter, lager_default_formatter}, {formatter_config, []} ])) end catch {bad_config, Msg, Value} -> ?INT_LOG(error, "~s ~p for file ~p", [Msg, Value, proplists:get_value(file, List)]), false end. validate_logfile_proplist([], Acc) -> Acc; validate_logfile_proplist([{file, File}|Tail], Acc) -> %% is there any reasonable validation we can do here? validate_logfile_proplist(Tail, [{file, File}|Acc]); validate_logfile_proplist([{level, Level}|Tail], Acc) -> case validate_loglevel(Level) of false -> throw({bad_config, "Invalid loglevel", Level}); Res -> validate_logfile_proplist(Tail, [{level, Res}|Acc]) end; validate_logfile_proplist([{size, Size}|Tail], Acc) -> case Size of S when is_integer(S), S >= 0 -> validate_logfile_proplist(Tail, [{size, Size}|Acc]); _ -> throw({bad_config, "Invalid rotation size", Size}) end; validate_logfile_proplist([{count, Count}|Tail], Acc) -> case Count of C when is_integer(C), C >= 0 -> validate_logfile_proplist(Tail, [{count, Count}|Acc]); _ -> throw({bad_config, "Invalid rotation count", Count}) end; validate_logfile_proplist([{high_water_mark, HighWaterMark}|Tail], Acc) -> case HighWaterMark of Hwm when is_integer(Hwm), Hwm >= 0 -> validate_logfile_proplist(Tail, [{high_water_mark, Hwm}|Acc]); _ -> throw({bad_config, "Invalid high water mark", HighWaterMark}) end; validate_logfile_proplist([{date, Date}|Tail], Acc) -> case lager_util:parse_rotation_date_spec(Date) of {ok, Spec} -> validate_logfile_proplist(Tail, [{date, Spec}|Acc]); {error, _} when Date == "" -> %% legacy config allowed blanks validate_logfile_proplist(Tail, [{date, undefined}|Acc]); {error, _} -> throw({bad_config, "Invalid rotation date", Date}) end; validate_logfile_proplist([{sync_interval, SyncInt}|Tail], Acc) -> case SyncInt of Val when is_integer(Val), Val >= 0 -> validate_logfile_proplist(Tail, [{sync_interval, Val}|Acc]); _ -> throw({bad_config, "Invalid sync interval", SyncInt}) end; validate_logfile_proplist([{sync_size, SyncSize}|Tail], Acc) -> case SyncSize of Val when is_integer(Val), Val >= 0 -> validate_logfile_proplist(Tail, [{sync_size, Val}|Acc]); _ -> throw({bad_config, "Invalid sync size", SyncSize}) end; validate_logfile_proplist([{check_interval, CheckInt}|Tail], Acc) -> case CheckInt of Val when is_integer(Val), Val >= 0 -> validate_logfile_proplist(Tail, [{check_interval, Val}|Acc]); always -> validate_logfile_proplist(Tail, [{check_interval, 0}|Acc]); _ -> throw({bad_config, "Invalid check interval", CheckInt}) end; validate_logfile_proplist([{sync_on, Level}|Tail], Acc) -> case validate_loglevel(Level) of false -> throw({bad_config, "Invalid sync on level", Level}); Res -> validate_logfile_proplist(Tail, [{sync_on, Res}|Acc]) end; validate_logfile_proplist([{formatter, Fmt}|Tail], Acc) -> case is_atom(Fmt) of true -> validate_logfile_proplist(Tail, [{formatter, Fmt}|Acc]); false -> throw({bad_config, "Invalid formatter module", Fmt}) end; validate_logfile_proplist([{formatter_config, FmtCfg}|Tail], Acc) -> case is_list(FmtCfg) of true -> validate_logfile_proplist(Tail, [{formatter_config, FmtCfg}|Acc]); false -> throw({bad_config, "Invalid formatter config", FmtCfg}) end; validate_logfile_proplist([Other|_Tail], _Acc) -> throw({bad_config, "Invalid option", Other}). schedule_rotation(_, undefined) -> ok; schedule_rotation(Name, Date) -> erlang:send_after(lager_util:calculate_next_rotation(Date) * 1000, self(), {rotate, Name}), ok. close_file(#state{fd=undefined} = State) -> State; close_file(#state{fd=FD} = State) -> %% Flush and close any file handles. _ = file:datasync(FD), _ = file:close(FD), State#state{fd=undefined}. -ifdef(TEST). get_loglevel_test() -> {ok, Level, _} = handle_call(get_loglevel, #state{name="bar", level=lager_util:config_to_mask(info), fd=0, inode=0}), ?assertEqual(Level, lager_util:config_to_mask(info)), {ok, Level2, _} = handle_call(get_loglevel, #state{name="foo", level=lager_util:config_to_mask(warning), fd=0, inode=0}), ?assertEqual(Level2, lager_util:config_to_mask(warning)). rotation_test_() -> {foreach, fun() -> [file:delete(F)||F <- filelib:wildcard("test.log*")], SyncLevel = validate_loglevel(?DEFAULT_SYNC_LEVEL), SyncSize = ?DEFAULT_SYNC_SIZE, SyncInterval = ?DEFAULT_SYNC_INTERVAL, CheckInterval = 0, %% hard to test delayed mode #state{name="test.log", level=?DEBUG, sync_on=SyncLevel, sync_size=SyncSize, sync_interval=SyncInterval, check_interval=CheckInterval} end, fun(_) -> [file:delete(F)||F <- filelib:wildcard("test.log*")] end, [fun(DefaultState = #state{sync_size=SyncSize, sync_interval = SyncInterval}) -> {"External rotation should work", fun() -> {ok, {FD, Inode, _}} = lager_util:open_logfile("test.log", {SyncSize, SyncInterval}), State0 = DefaultState#state{fd=FD, inode=Inode}, ?assertMatch(#state{name="test.log", level=?DEBUG, fd=FD, inode=Inode}, write(State0, os:timestamp(), ?DEBUG, "hello world")), file:delete("test.log"), Result = write(State0, os:timestamp(), ?DEBUG, "hello world"), %% assert file has changed ?assert(#state{name="test.log", level=?DEBUG, fd=FD, inode=Inode} =/= Result), ?assertMatch(#state{name="test.log", level=?DEBUG}, Result), file:rename("test.log", "test.log.1"), Result2 = write(Result, os:timestamp(), ?DEBUG, "hello world"), %% assert file has changed ?assert(Result =/= Result2), ?assertMatch(#state{name="test.log", level=?DEBUG}, Result2), ok end} end, fun(DefaultState = #state{sync_size=SyncSize, sync_interval = SyncInterval}) -> {"Internal rotation and delayed write", fun() -> CheckInterval = 3000, % 3 sec RotationSize = 15, PreviousCheck = os:timestamp(), {ok, {FD, Inode, _}} = lager_util:open_logfile("test.log", {SyncSize, SyncInterval}), State0 = DefaultState#state{ fd=FD, inode=Inode, size=RotationSize, check_interval=CheckInterval, last_check=PreviousCheck}, %% new message within check interval with sync_on level Msg1Timestamp = add_secs(PreviousCheck, 1), State0 = State1 = write(State0, Msg1Timestamp, ?ERROR, "big big message 1"), %% new message within check interval under sync_on level %% not written to disk yet Msg2Timestamp = add_secs(PreviousCheck, 2), State0 = State2 = write(State1, Msg2Timestamp, ?DEBUG, "buffered message 2"), %% although file size is big enough... {ok, FInfo} = file:read_file_info("test.log"), ?assert(RotationSize < FInfo#file_info.size), %% ...no rotation yet ?assertEqual(PreviousCheck, State2#state.last_check), ?assertNot(filelib:is_regular("test.log.0")), %% new message after check interval Msg3Timestamp = add_secs(PreviousCheck, 4), _State3 = write(State2, Msg3Timestamp, ?DEBUG, "message 3"), %% rotation happened ?assert(filelib:is_regular("test.log.0")), {ok, Bin1} = file:read_file("test.log.0"), {ok, Bin2} = file:read_file("test.log"), %% message 1-3 written to file ?assertEqual(<<"big big message 1buffered message 2">>, Bin1), %% message 4 buffered, not yet written to file ?assertEqual(<<"">>, Bin2), ok end} end ]}. add_secs({Mega, Secs, Micro}, Add) -> NewSecs = Secs + Add, {Mega + NewSecs div 10000000, NewSecs rem 10000000, Micro}. filesystem_test_() -> {foreach, fun() -> file:write_file("test.log", ""), file:delete("foo.log"), file:delete("foo.log.0"), file:delete("foo.log.1"), error_logger:tty(false), application:load(lager), application:set_env(lager, handlers, [{lager_test_backend, info}]), application:set_env(lager, error_logger_redirect, false), application:set_env(lager, async_threshold, undefined), lager:start() end, fun(_) -> file:delete("test.log"), file:delete("test.log.0"), file:delete("test.log.1"), application:stop(lager), application:stop(goldrush), error_logger:tty(true) end, [ {"under normal circumstances, file should be opened", fun() -> gen_event:add_handler(lager_event, lager_file_backend, [{"test.log", info}, {lager_default_formatter}]), lager:log(error, self(), "Test message"), {ok, Bin} = file:read_file("test.log"), Pid = pid_to_list(self()), ?assertMatch([_, _, "[error]", Pid, "Test message\n"], re:split(Bin, " ", [{return, list}, {parts, 5}])) end }, {"don't choke on unicode", fun() -> gen_event:add_handler(lager_event, lager_file_backend, [{"test.log", info}, {lager_default_formatter}]), lager:log(error, self(),"~ts", [[20013,25991,27979,35797]]), {ok, Bin} = file:read_file("test.log"), Pid = pid_to_list(self()), ?assertMatch([_, _, "[error]", Pid, [228,184,173,230,150,135,230,181,139,232,175,149, $\n]], re:split(Bin, " ", [{return, list}, {parts, 5}])) end }, {"don't choke on latin-1", fun() -> %% XXX if this test fails, check that this file is encoded latin-1, not utf-8! gen_event:add_handler(lager_event, lager_file_backend, [{"test.log", info}, {lager_default_formatter}]), lager:log(error, self(),"~ts", [[76, 198, 221, 206, 78, $-, 239]]), {ok, Bin} = file:read_file("test.log"), Pid = pid_to_list(self()), Res = re:split(Bin, " ", [{return, list}, {parts, 5}]), ?assertMatch([_, _, "[error]", Pid, [76,195,134,195,157,195,142,78,45,195,175,$\n]], Res) end }, {"file can't be opened on startup triggers an error message", fun() -> {ok, FInfo} = file:read_file_info("test.log"), file:write_file_info("test.log", FInfo#file_info{mode = 0}), gen_event:add_handler(lager_event, lager_file_backend, {"test.log", info, 10*1024*1024, "$D0", 5}), ?assertEqual(1, lager_test_backend:count()), {_Level, _Time,Message,_Metadata} = lager_test_backend:pop(), ?assertEqual("Failed to open log file test.log with error permission denied", lists:flatten(Message)) end }, {"file that becomes unavailable at runtime should trigger an error message", fun() -> gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {check_interval, 0}]), ?assertEqual(0, lager_test_backend:count()), lager:log(error, self(), "Test message"), ?assertEqual(1, lager_test_backend:count()), 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,_Metadata} = 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, [{file, "test.log"},{check_interval, 0}]), ?assertEqual(1, lager_test_backend:count()), {_Level, _Time, Message,_Metadata} = lager_test_backend:pop(), ?assertEqual("Failed to open log file test.log with error permission denied", lists:flatten(Message)), file:write_file_info("test.log", FInfo#file_info{mode = OldPerms}), lager:log(error, self(), "Test message"), {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, [{file, "test.log"}, {level, info}, {check_interval, 0}]), ?assertEqual(0, lager_test_backend:count()), lager:log(error, self(), "Test message1"), ?assertEqual(1, lager_test_backend:count()), 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 }, {"internal size rotation should work", fun() -> gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {check_interval, 0}, {size, 10}]), lager:log(error, self(), "Test message1"), lager:log(error, self(), "Test message1"), ?assert(filelib:is_regular("test.log.0")) end }, {"internal time rotation should work", fun() -> gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {check_interval, 1000}]), lager:log(error, self(), "Test message1"), lager:log(error, self(), "Test message1"), whereis(lager_event) ! {rotate, "test.log"}, lager:log(error, self(), "Test message1"), ?assert(filelib:is_regular("test.log.0")) end }, {"rotation call should work", fun() -> gen_event:add_handler(lager_event, {lager_file_backend, "test.log"}, [{file, "test.log"}, {level, info}, {check_interval, 1000}]), lager:log(error, self(), "Test message1"), lager:log(error, self(), "Test message1"), gen_event:call(lager_event, {lager_file_backend, "test.log"}, rotate, infinity), lager:log(error, self(), "Test message1"), ?assert(filelib:is_regular("test.log.0")) end }, {"sync_on option should work", fun() -> gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {sync_on, "=info"}, {check_interval, 5000}, {sync_interval, 5000}]), lager:log(error, self(), "Test message1"), lager:log(error, self(), "Test message1"), ?assertEqual({ok, <<>>}, file:read_file("test.log")), lager:log(info, self(), "Test message1"), {ok, Bin} = file:read_file("test.log"), ?assert(<<>> /= Bin) end }, {"sync_on none option should work (also tests sync_interval)", fun() -> gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {sync_on, "none"}, {check_interval, 5000}, {sync_interval, 1000}]), lager:log(error, self(), "Test message1"), lager:log(error, self(), "Test message1"), ?assertEqual({ok, <<>>}, file:read_file("test.log")), lager:log(info, self(), "Test message1"), ?assertEqual({ok, <<>>}, file:read_file("test.log")), timer:sleep(2000), {ok, Bin} = file:read_file("test.log"), ?assert(<<>> /= Bin) end }, {"sync_size option should work", fun() -> gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {sync_on, "none"}, {check_interval, 5001}, {sync_size, 640}, {sync_interval, 5001}]), lager:log(error, self(), "Test messageis64bytes"), lager:log(error, self(), "Test messageis64bytes"), lager:log(error, self(), "Test messageis64bytes"), lager:log(error, self(), "Test messageis64bytes"), lager:log(error, self(), "Test messageis64bytes"), ?assertEqual({ok, <<>>}, file:read_file("test.log")), lager:log(error, self(), "Test messageis64bytes"), lager:log(error, self(), "Test messageis64bytes"), lager:log(error, self(), "Test messageis64bytes"), lager:log(error, self(), "Test messageis64bytes"), ?assertEqual({ok, <<>>}, file:read_file("test.log")), %% now we've written enough bytes lager:log(error, self(), "Test messageis64bytes"), {ok, Bin} = file:read_file("test.log"), ?assert(<<>> /= Bin) end }, {"runtime level changes", fun() -> gen_event:add_handler(lager_event, {lager_file_backend, "test.log"}, {"test.log", info}), ?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, 10*1024*1024, "$D0", 5}, {lager_default_formatter}]), gen_event:add_handler(lager_event, lager_file_backend, {"test3.log", info}), ?assertEqual({error, bad_module}, lager:set_loglevel(lager_file_backend, "test.log", warning)) end }, {"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_config:get({lager_event, loglevel}), lager_config:set({lager_event, loglevel}, {Level, [{[{module, ?MODULE}], ?DEBUG, {lager_file_backend, "test.log"}}]}), lager:error("Test message"), timer:sleep(1000), {ok, Bin} = file:read_file("test.log"), ?assertMatch([_, _, "[error]", _, "Test message\n"], re:split(Bin, " ", [{return, list}, {parts, 5}])) end }, {"tracing should not duplicate messages", fun() -> gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, critical}, {check_interval, always}]), lager:critical("Test message"), {ok, Bin1} = file:read_file("test.log"), ?assertMatch([_, _, "[critical]", _, "Test message\n"], re:split(Bin1, " ", [{return, list}, {parts, 5}])), ok = file:delete("test.log"), {Level, _} = lager_config:get({lager_event, loglevel}), lager_config:set({lager_event, 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"), %% not elegible for trace lager:log(error, self(), "Test message"), {ok, Bin3} = file:read_file("foo.log"), ?assertMatch([_, _, "[error]", _, "Test message\n"], re:split(Bin3, " ", [{return, list}, {parts, 5}])) end }, {"tracing to a dedicated file should work even if root_log is set", fun() -> {ok, P} = file:get_cwd(), file:delete(P ++ "/test_root_log/foo.log"), application:set_env(lager, log_root, P++"/test_root_log"), {ok, _} = lager:trace_file("foo.log", [{module, ?MODULE}]), lager:error("Test message"), %% not elegible for trace lager:log(error, self(), "Test message"), {ok, Bin3} = file:read_file(P++"/test_root_log/foo.log"), application:unset_env(lager, log_root), ?assertMatch([_, _, "[error]", _, "Test message\n"], re:split(Bin3, " ", [{return, list}, {parts, 5}])) end }, {"tracing with options should work", fun() -> file:delete("foo.log"), {ok, _} = lager:trace_file("foo.log", [{module, ?MODULE}], [{size, 20}, {check_interval, 1}]), lager:error("Test message"), ?assertNot(filelib:is_regular("foo.log.0")), %% rotation is sensitive to intervals between %% writes so we sleep to exceed the 1 %% millisecond interval specified by %% check_interval above timer:sleep(2), lager:error("Test message"), timer:sleep(10), ?assert(filelib:is_regular("foo.log.0")) end } ] }. formatting_test_() -> {foreach, fun() -> file:write_file("test.log", ""), file:write_file("test2.log", ""), error_logger:tty(false), application:load(lager), application:set_env(lager, handlers, [{lager_test_backend, info}]), application:set_env(lager, error_logger_redirect, false), lager:start() end, fun(_) -> file:delete("test.log"), file:delete("test2.log"), application:stop(lager), application:stop(goldrush), error_logger:tty(true) end, [{"Should have two log files, the second prefixed with 2>", fun() -> gen_event:add_handler(lager_event, lager_file_backend,[{"test.log", debug},{lager_default_formatter,["[",severity,"] ", message, "\n"]}]), gen_event:add_handler(lager_event, lager_file_backend,[{"test2.log", debug},{lager_default_formatter,["2> [",severity,"] ", message, "\n"]}]), lager:log(error, self(), "Test message"), ?assertMatch({ok, <<"[error] Test message\n">>},file:read_file("test.log")), ?assertMatch({ok, <<"2> [error] Test message\n">>},file:read_file("test2.log")) end } ]}. config_validation_test_() -> [ {"missing file", ?_assertEqual(false, validate_logfile_proplist([{level, info},{size, 10}])) }, {"bad level", ?_assertEqual(false, validate_logfile_proplist([{file, "test.log"}, {level, blah},{size, 10}])) }, {"bad size", ?_assertEqual(false, validate_logfile_proplist([{file, "test.log"}, {size, infinity}])) }, {"bad count", ?_assertEqual(false, validate_logfile_proplist([{file, "test.log"}, {count, infinity}])) }, {"bad high water mark", ?_assertEqual(false, validate_logfile_proplist([{file, "test.log"}, {high_water_mark, infinity}])) }, {"bad date", ?_assertEqual(false, validate_logfile_proplist([{file, "test.log"}, {date, "midnight"}])) }, {"blank date is ok", ?_assertMatch([_|_], validate_logfile_proplist([{file, "test.log"}, {date, ""}])) }, {"bad sync_interval", ?_assertEqual(false, validate_logfile_proplist([{file, "test.log"}, {sync_interval, infinity}])) }, {"bad sync_size", ?_assertEqual(false, validate_logfile_proplist([{file, "test.log"}, {sync_size, infinity}])) }, {"bad check_interval", ?_assertEqual(false, validate_logfile_proplist([{file, "test.log"}, {check_interval, infinity}])) }, {"bad sync_on level", ?_assertEqual(false, validate_logfile_proplist([{file, "test.log"}, {sync_on, infinity}])) }, {"bad formatter module", ?_assertEqual(false, validate_logfile_proplist([{file, "test.log"}, {formatter, "io:format"}])) }, {"bad formatter config", ?_assertEqual(false, validate_logfile_proplist([{file, "test.log"}, {formatter_config, blah}])) }, {"unknown option", ?_assertEqual(false, validate_logfile_proplist([{file, "test.log"}, {rhubarb, spicy}])) } ]. -endif. lager-3.1.0/src/lager_default_formatter.erl0000644000232200023220000003611612652234453021344 0ustar debalancedebalance%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. -module(lager_default_formatter). %% %% Include files %% -include("lager.hrl"). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. %% %% Exported Functions %% -export([format/2, format/3]). %% %% API Functions %% %% @doc Provides a generic, default formatting for log messages using a semi-iolist as configuration. Any iolist allowed %% elements in the configuration are printed verbatim. Atoms in the configuration are treated as metadata properties %% and extracted from the log message. Optionally, a tuple of {atom(),semi-iolist()} can be used. The atom will look %% up the property, but if not found it will use the semi-iolist() instead. These fallbacks can be similarly nested %% or refer to other properties, if desired. You can also use a {atom, semi-iolist(), semi-iolist()} formatter, which %% acts like a ternary operator's true/false branches. %% %% The metadata properties date,time, message, severity, and sev will always exist. %% The properties pid, file, line, module, and function will always exist if the parser transform is used. %% %% Example: %% %% `["Foo"]' -> "Foo", regardless of message content. %% %% `[message]' -> The content of the logged message, alone. %% %% `[{pid,"Unknown Pid"}]' -> "?.?.?" if pid is in the metadata, "Unknown Pid" if not. %% %% `[{pid, ["My pid is ", pid], ["Unknown Pid"]}]' -> if pid is in the metada print "My pid is ?.?.?", otherwise print "Unknown Pid" %% @end -spec format(lager_msg:lager_msg(),list(),list()) -> any(). format(Msg,[], Colors) -> format(Msg, [{eol, "\n"}], Colors); format(Msg,[{eol, EOL}], Colors) -> format(Msg, [date, " ", time, " ", color, "[", severity, "] ", {pid, ""}, {module, [ {pid, ["@"], ""}, module, {function, [":", function], ""}, {line, [":",line], ""}], ""}, " ", message, EOL], Colors); format(Message,Config,Colors) -> [ case V of color -> output_color(Message,Colors); _ -> output(V,Message) end || V <- Config ]. -spec format(lager_msg:lager_msg(),list()) -> any(). format(Msg, Config) -> format(Msg, Config, []). -spec output(term(),lager_msg:lager_msg()) -> iolist(). output(message,Msg) -> lager_msg:message(Msg); output(date,Msg) -> {D, _T} = lager_msg:datetime(Msg), D; output(time,Msg) -> {_D, T} = lager_msg:datetime(Msg), T; output(severity,Msg) -> atom_to_list(lager_msg:severity(Msg)); output(blank,_Msg) -> output({blank," "},_Msg); output({blank,Fill},_Msg) -> Fill; output(sev,Msg) -> %% Write brief acronym for the severity level (e.g. debug -> $D) [lager_util:level_to_chr(lager_msg:severity(Msg))]; output(metadata, Msg) -> output({metadata, "=", " "}, Msg); output({metadata, IntSep, FieldSep}, Msg) -> MD = lists:keysort(1, lager_msg:metadata(Msg)), string:join([io_lib:format("~s~s~p", [K, IntSep, V]) || {K, V} <- MD], FieldSep); output(Prop,Msg) when is_atom(Prop) -> Metadata = lager_msg:metadata(Msg), make_printable(get_metadata(Prop,Metadata,<<"Undefined">>)); output({Prop,Default},Msg) when is_atom(Prop) -> Metadata = lager_msg:metadata(Msg), make_printable(get_metadata(Prop,Metadata,output(Default,Msg))); output({Prop, Present, Absent}, Msg) when is_atom(Prop) -> %% sort of like a poor man's ternary operator Metadata = lager_msg:metadata(Msg), case get_metadata(Prop, Metadata) of undefined -> [ output(V, Msg) || V <- Absent]; _ -> [ output(V, Msg) || V <- Present] end; output({Prop, Present, Absent, Width}, Msg) when is_atom(Prop) -> %% sort of like a poor man's ternary operator Metadata = lager_msg:metadata(Msg), case get_metadata(Prop, Metadata) of undefined -> [ output(V, Msg, Width) || V <- Absent]; _ -> [ output(V, Msg, Width) || V <- Present] end; output(Other,_) -> make_printable(Other). output(message, Msg, _Width) -> lager_msg:message(Msg); output(date,Msg, _Width) -> {D, _T} = lager_msg:datetime(Msg), D; output(time, Msg, _Width) -> {_D, T} = lager_msg:datetime(Msg), T; output(severity, Msg, Width) -> make_printable(atom_to_list(lager_msg:severity(Msg)), Width); output(sev,Msg, _Width) -> %% Write brief acronym for the severity level (e.g. debug -> $D) [lager_util:level_to_chr(lager_msg:severity(Msg))]; output(blank,_Msg, _Width) -> output({blank, " "},_Msg, _Width); output({blank, Fill},_Msg, _Width) -> Fill; output(metadata, Msg, _Width) -> output({metadata, "=", " "}, Msg, _Width); output({metadata, IntSep, FieldSep}, Msg, _Width) -> MD = lists:keysort(1, lager_msg:metadata(Msg)), [string:join([io_lib:format("~s~s~p", [K, IntSep, V]) || {K, V} <- MD], FieldSep)]; output(Prop, Msg, Width) when is_atom(Prop) -> Metadata = lager_msg:metadata(Msg), make_printable(get_metadata(Prop,Metadata,<<"Undefined">>), Width); output({Prop,Default},Msg, Width) when is_atom(Prop) -> Metadata = lager_msg:metadata(Msg), make_printable(get_metadata(Prop,Metadata,output(Default,Msg)), Width); output(Other,_, Width) -> make_printable(Other, Width). output_color(_Msg,[]) -> []; output_color(Msg,Colors) -> Level = lager_msg:severity(Msg), case lists:keyfind(Level, 1, Colors) of {_, Color} -> Color; _ -> [] end. -spec make_printable(any()) -> iolist(). make_printable(A) when is_atom(A) -> atom_to_list(A); make_printable(P) when is_pid(P) -> pid_to_list(P); make_printable(L) when is_list(L) orelse is_binary(L) -> L; make_printable(Other) -> io_lib:format("~p",[Other]). make_printable(A,W) when is_integer(W)-> string:left(make_printable(A),W); make_printable(A,{Align,W}) when is_integer(W) -> case Align of left -> string:left(make_printable(A),W); centre -> string:centre(make_printable(A),W); right -> string:right(make_printable(A),W); _ -> string:left(make_printable(A),W) end; make_printable(A,_W) -> make_printable(A). get_metadata(Key, Metadata) -> get_metadata(Key, Metadata, undefined). get_metadata(Key, Metadata, Default) -> case lists:keyfind(Key, 1, Metadata) of false -> Default; {Key, Value} -> Value end. -ifdef(TEST). date_time_now() -> Now = os:timestamp(), {Date, Time} = lager_util:format_time(lager_util:maybe_utc(lager_util:localtime_ms(Now))), {Date, Time, Now}. basic_test_() -> {Date, Time, Now} = date_time_now(), [{"Default formatting test", ?_assertEqual(iolist_to_binary([Date, " ", Time, " [error] ", pid_to_list(self()), " Message\n"]), iolist_to_binary(format(lager_msg:new("Message", Now, error, [{pid, self()}], []), []))) }, {"Basic Formatting", ?_assertEqual(<<"Simplist Format">>, iolist_to_binary(format(lager_msg:new("Message", Now, error, [{pid, self()}], []), ["Simplist Format"]))) }, {"Default equivalent formatting test", ?_assertEqual(iolist_to_binary([Date, " ", Time, " [error] ", pid_to_list(self()), " Message\n"]), iolist_to_binary(format(lager_msg:new("Message", Now, error, [{pid, self()}], []), [date, " ", time," [",severity,"] ",pid, " ", message, "\n"] ))) }, {"Non existent metadata can default to string", ?_assertEqual(iolist_to_binary([Date, " ", Time, " [error] Fallback Message\n"]), iolist_to_binary(format(lager_msg:new("Message", Now, error, [{pid, self()}], []), [date, " ", time," [",severity,"] ",{does_not_exist,"Fallback"}, " ", message, "\n"] ))) }, {"Non existent metadata can default to other metadata", ?_assertEqual(iolist_to_binary([Date, " ", Time, " [error] Fallback Message\n"]), iolist_to_binary(format(lager_msg:new("Message", Now, error, [{pid, "Fallback"}], []), [date, " ", time," [",severity,"] ",{does_not_exist,pid}, " ", message, "\n"] ))) }, {"Non existent metadata can default to a string2", ?_assertEqual(iolist_to_binary(["Unknown Pid"]), iolist_to_binary(format(lager_msg:new("Message", Now, error, [], []), [{pid, ["My pid is ", pid], ["Unknown Pid"]}] ))) }, {"Metadata can have extra formatting", ?_assertEqual(iolist_to_binary(["My pid is hello"]), iolist_to_binary(format(lager_msg:new("Message", Now, error, [{pid, hello}], []), [{pid, ["My pid is ", pid], ["Unknown Pid"]}] ))) }, {"Metadata can have extra formatting1", ?_assertEqual(iolist_to_binary(["servername"]), iolist_to_binary(format(lager_msg:new("Message", Now, error, [{pid, hello}, {server, servername}], []), [{server,{pid, ["(", pid, ")"], ["(Unknown Server)"]}}] ))) }, {"Metadata can have extra formatting2", ?_assertEqual(iolist_to_binary(["(hello)"]), iolist_to_binary(format(lager_msg:new("Message", Now, error, [{pid, hello}], []), [{server,{pid, ["(", pid, ")"], ["(Unknown Server)"]}}] ))) }, {"Metadata can have extra formatting3", ?_assertEqual(iolist_to_binary(["(Unknown Server)"]), iolist_to_binary(format(lager_msg:new("Message", Now, error, [], []), [{server,{pid, ["(", pid, ")"], ["(Unknown Server)"]}}] ))) }, {"Metadata can be printed in its enterity", ?_assertEqual(iolist_to_binary(["bar=2 baz=3 foo=1"]), iolist_to_binary(format(lager_msg:new("Message", Now, error, [{foo, 1}, {bar, 2}, {baz, 3}], []), [metadata] ))) }, {"Metadata can be printed in its enterity with custom seperators", ?_assertEqual(iolist_to_binary(["bar->2, baz->3, foo->1"]), iolist_to_binary(format(lager_msg:new("Message", Now, error, [{foo, 1}, {bar, 2}, {baz, 3}], []), [{metadata, "->", ", "}] ))) }, {"Metadata can have extra formatting with width 1", ?_assertEqual(iolist_to_binary(["(hello )(hello )(hello)(hello)(hello)"]), iolist_to_binary(format(lager_msg:new("Message", Now, error, [{pid, hello}], []), ["(",{pid, [pid], "", 10},")", "(",{pid, [pid], "", {bad_align,10}},")", "(",{pid, [pid], "", bad10},")", "(",{pid, [pid], "", {right,bad20}},")", "(",{pid, [pid], "", {bad_align,bad20}},")"] ))) }, {"Metadata can have extra formatting with width 2", ?_assertEqual(iolist_to_binary(["(hello )"]), iolist_to_binary(format(lager_msg:new("Message", Now, error, [{pid, hello}], []), ["(",{pid, [pid], "", {left,10}},")"] ))) }, {"Metadata can have extra formatting with width 3", ?_assertEqual(iolist_to_binary(["( hello)"]), iolist_to_binary(format(lager_msg:new("Message", Now, error, [{pid, hello}], []), ["(",{pid, [pid], "", {right,10}},")"] ))) }, {"Metadata can have extra formatting with width 4", ?_assertEqual(iolist_to_binary(["( hello )"]), iolist_to_binary(format(lager_msg:new("Message", Now, error, [{pid, hello}], []), ["(",{pid, [pid], "", {centre,10}},")"] ))) }, {"Metadata can have extra formatting with width 5", ?_assertEqual(iolist_to_binary(["error |hello ! ( hello )"]), iolist_to_binary(format(lager_msg:new("Message", Now, error, [{pid, hello}], []), [{x,"",[severity,{blank,"|"},pid], 10},"!",blank,"(",{pid, [pid], "", {centre,10}},")"] ))) }, {"Metadata can have extra formatting with width 6", ?_assertEqual(iolist_to_binary([Time,Date," bar=2 baz=3 foo=1 pid=hello EMessage"]), iolist_to_binary(format(lager_msg:new("Message", Now, error, [{pid, hello},{foo, 1}, {bar, 2}, {baz, 3}], []), [{x,"",[time]}, {x,"",[date],20},blank,{x,"",[metadata],30},blank,{x,"",[sev],10},message, {message,message,"", {right,20}}] ))) } ]. -endif. lager-3.1.0/src/lager_trunc_io.erl0000644000232200023220000012166612652234453017464 0ustar debalancedebalance%% ``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, matthias@corelatus.se %% %% Various changes to this module, most notably the format/3 implementation %% were added by Andrew Thompson `'. 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. -type option() :: {'depth', integer()} | {'lists_as_strings', boolean()} | {'force_strings', boolean()}. -type options() :: [option()]. -record(print_options, { %% negative depth means no depth limiting depth = -1 :: integer(), %% 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) catch _What:_Why -> 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(), 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(), options() | #print_options{}) -> {iolist(), pos_integer()}. print(Term, Max, Options) when is_list(Options) -> %% need to convert the proplist to a record print(Term, Max, prepare_options(Options, #print_options{})); 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}; %% @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, #print_options{depth=1}) -> {"<<>>", 4}; print(Bin, _Max, #print_options{depth=1}) when is_binary(Bin) -> {"<<...>>", 7}; print(<<>>, _Max, Options) -> case Options#print_options.force_strings of true -> {"", 0}; false -> {"<<>>", 4} end; print(Binary, 0, _Options) when is_bitstring(Binary) -> {"<<..>>", 6}; print(Bin, Max, _Options) when is_binary(Bin), Max < 2 -> {"<<...>>", 7}; print(Binary, Max, Options) when is_binary(Binary) -> B = binary_to_list(Binary, 1, lists:min([Max, byte_size(Binary)])), {Res, Length} = case Options#print_options.lists_as_strings orelse Options#print_options.force_strings of true -> Depth = Options#print_options.depth, MaxSize = (Depth - 1) * 4, %% check if we need to truncate based on depth In = case Depth > -1 andalso MaxSize < length(B) andalso not Options#print_options.force_strings of true -> string:substr(B, 1, MaxSize); false -> B end, MaxLen = case Options#print_options.force_strings of true -> Max; false -> %% make room for the leading doublequote Max - 1 end, try alist(In, MaxLen, Options) of {L0, Len0} -> case Options#print_options.force_strings of false -> case B /= In of true -> {[$", L0, "..."], Len0+4}; false -> {[$"|L0], Len0+1} end; true -> {L0, Len0} end catch throw:{unprintable, C} -> Index = string:chr(In, C), case Index > 1 andalso Options#print_options.depth =< Index andalso Options#print_options.depth > -1 andalso not Options#print_options.force_strings of true -> %% print first Index-1 characters followed by ... {L0, Len0} = alist_start(string:substr(In, 1, Index - 1), Max - 1, Options), {L0++"...", Len0+3}; false -> list_body(In, Max-4, dec_depth(Options), true) end end; _ -> list_body(B, Max-4, dec_depth(Options), true) 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({inline_bitstring, B}, _Max, _Options) when is_bitstring(B) -> Size = bit_size(B), <> = B, ValueStr = integer_to_list(Value), SizeStr = integer_to_list(Size), {[ValueStr, $:, SizeStr], length(ValueStr) + length(SizeStr) +1}; print(BitString, Max, Options) when is_bitstring(BitString) -> BL = case byte_size(BitString) > Max of true -> binary_to_list(BitString, 1, Max); _ -> R = erlang:bitstring_to_list(BitString), {Bytes, [Bits]} = lists:splitwith(fun erlang:is_integer/1, R), %% tag the trailing bits with a special tuple we catch when %% list_body calls print again Bytes ++ [{inline_bitstring, Bits}] end, {X, Len0} = list_body(BL, Max - 4, dec_depth(Options), true), {["<<", 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({'$lager_record', Name, Fields}, Max, Options) -> Leader = "#" ++ atom_to_list(Name) ++ "{", {RC, Len} = record_fields(Fields, Max - length(Leader) + 1, dec_depth(Options)), {[Leader, RC, "}"], Len + length(Leader) + 1}; print(Tuple, Max, Options) when is_tuple(Tuple) -> {TC, Len} = tuple_contents(Tuple, Max-2, Options), {[${, TC, $}], Len + 2}; print(List, Max, Options) when is_list(List) -> case Options#print_options.lists_as_strings orelse Options#print_options.force_strings of true -> alist_start(List, Max, dec_depth(Options)); _ -> {R, Len} = list_body(List, Max - 2, dec_depth(Options), false), {[$[, R, $]], Len + 2} end; print(Map, Max, Options) -> case erlang:is_builtin(erlang, is_map, 1) andalso erlang:is_map(Map) of true -> {MapBody, Len} = map_body(Map, Max - 3, dec_depth(Options)), {[$#, ${, MapBody, $}], Len + 3}; false -> error(badarg, [Map, Max, Options]) end. %% Returns {List, Length} 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([H], Max, Options=#print_options{depth=1}, _Tuple) -> print(H, Max, Options); list_body([H|_], Max, Options=#print_options{depth=1}, Tuple) -> {List, Len} = print(H, Max-4, Options), Sep = case Tuple of true -> $,; false -> $| end, {[List ++ [Sep | "..."]], Len + 4}; list_body([H|T], Max, Options, Tuple) -> {List, Len} = print(H, Max, Options), {Final, FLen} = list_bodyc(T, Max - Len, Options, Tuple), {[List|Final], FLen + Len}; 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(_, _Max, #print_options{depth=1}, true) -> {",...", 4}; list_bodyc(_, _Max, #print_options{depth=1}, false) -> {"|...", 4}; list_bodyc([H|T], Max, #print_options{depth=Depth} = Options, Tuple) -> {List, Len} = print(H, Max, dec_depth(Options)), {Final, FLen} = list_bodyc(T, Max - Len - 1, dec_depth(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}. map_body(Map, Max, #print_options{depth=Depth}) when Max < 4; Depth =:= 0 -> case erlang:map_size(Map) of 0 -> {[], 0}; _ -> {"...", 3} end; map_body(Map, Max, Options) -> case maps:to_list(Map) of [] -> {[], 0}; [{Key, Value} | Rest] -> {KeyStr, KeyLen} = print(Key, Max - 4, Options), DiffLen = KeyLen + 4, {ValueStr, ValueLen} = print(Value, Max - DiffLen, Options), DiffLen2 = DiffLen + ValueLen, {Final, FLen} = map_bodyc(Rest, Max - DiffLen2, dec_depth(Options)), {[KeyStr, " => ", ValueStr | Final], DiffLen2 + FLen} end. map_bodyc([], _Max, _Options) -> {[], 0}; map_bodyc(_Rest, Max,#print_options{depth=Depth}) when Max < 5; Depth =:= 0 -> {",...", 4}; map_bodyc([{Key, Value} | Rest], Max, Options) -> {KeyStr, KeyLen} = print(Key, Max - 5, Options), DiffLen = KeyLen + 5, {ValueStr, ValueLen} = print(Value, Max - DiffLen, Options), DiffLen2 = DiffLen + ValueLen, {Final, FLen} = map_bodyc(Rest, Max - DiffLen2, dec_depth(Options)), {[$,, KeyStr, " => ", ValueStr | Final], DiffLen2 + FLen}. %% The head of a list we hope is ascii. Examples: %% %% [65,66,67] -> "ABC" %% [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, #print_options{depth=1}) when is_integer(H) -> {[$[, H, $|, $., $., $., $]], 7}; alist_start([H|T], Max, Options) when is_integer(H), H >= 16#20, H =< 16#7e -> % definitely printable try alist([H|T], Max -1, Options) of {L, Len} -> {[$"|L], Len + 1} catch throw:{unprintable, _} -> {R, Len} = list_body([H|T], Max-2, Options, false), {[$[, R, $]], Len + 2} end; alist_start([H|T], Max, Options) when is_integer(H), H >= 16#a0, H =< 16#ff -> % definitely printable try alist([H|T], Max -1, Options) of {L, Len} -> {[$"|L], Len + 1} catch throw:{unprintable, _} -> {R, Len} = list_body([H|T], Max-2, Options, false), {[$[, R, $]], Len + 2} end; alist_start([H|T], Max, Options) when H =:= $\t; H =:= $\n; H =:= $\r; H =:= $\v; H =:= $\e; H=:= $\f; H=:= $\b -> try alist([H|T], Max -1, Options) of {L, Len} -> {[$"|L], Len + 1} catch throw:{unprintable, _} -> {R, Len} = list_body([H|T], Max-2, Options, false), {[$[, R, $]], Len + 2} end; 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 = #print_options{force_strings=false,lists_as_strings=true}) when H =:= $"; H =:= $\\ -> %% preserve escaping around quotes {L, Len} = alist(T, Max-1, Options), {[$\\,H|L], Len + 2}; alist([H|T], Max, Options) when is_integer(H), H >= 16#20, H =< 16#7e -> % definitely printable {L, Len} = alist(T, Max-1, Options), {[H|L], Len + 1}; alist([H|T], Max, Options) when is_integer(H), H >= 16#a0, H =< 16#ff -> % definitely printable {L, Len} = alist(T, Max-1, Options), {[H|L], Len + 1}; alist([H|T], Max, Options) when H =:= $\t; H =:= $\n; H =:= $\r; H =:= $\v; H =:= $\e; H=:= $\f; H=:= $\b -> {L, Len} = alist(T, Max-1, Options), case Options#print_options.force_strings of true -> {[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([H|T], Max, Options = #print_options{force_strings=true}) when is_binary(H); is_list(H) -> {List, Len} = print(H, Max, Options), case (Max - Len) =< 0 of true -> %% no more room to print anything {List, Len}; false -> %% no need to decrement depth, as we're in printable string mode {Final, FLen} = alist(T, Max - Len, Options), {[List|Final], FLen+Len} end; alist(_, _, #print_options{force_strings=true}) -> erlang:error(badarg); alist([H|_L], _Max, _Options) -> throw({unprintable, H}); alist(H, _Max, _Options) -> %% improper list throw({unprintable, H}). %% is the first character in the atom alphabetic & lowercase? atom_needs_quoting_start([H|T]) when H >= $a, H =< $z -> 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. -spec prepare_options(options(), #print_options{}) -> #print_options{}. 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($\t) -> "\\t"; escape($\n) -> "\\n"; escape($\r) -> "\\r"; escape($\e) -> "\\e"; escape($\f) -> "\\f"; escape($\b) -> "\\b"; escape($\v) -> "\\v". record_fields([], _, _) -> {"", 0}; record_fields(_, Max, #print_options{depth=D}) when Max < 4; D == 0 -> {"...", 3}; record_fields([{Field, Value}|T], Max, Options) -> {ExtraChars, Terminator} = case T of [] -> {1, []}; _ -> {2, ","} end, {FieldStr, FieldLen} = print(Field, Max - ExtraChars, Options), {ValueStr, ValueLen} = print(Value, Max - (FieldLen + ExtraChars), Options), {Final, FLen} = record_fields(T, Max - (FieldLen + ValueLen + ExtraChars), dec_depth(Options)), {[FieldStr++"="++ValueStr++Terminator|Final], FLen + FieldLen + ValueLen + ExtraChars}. -ifdef(TEST). %%-------------------- %% 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("f", lists:flatten(format("~1s", [["foo", $b, $a, $r]], 50))), ?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~22p", [["foo", $b, $a, $r]], 50))), ?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~22P", [["foo", $b, $a, $r], 10], 50))), ?assertEqual("**********", lists:flatten(format("~10W", [["foo", $b, $a, $r], 10], 50))), ?assertEqual("[[102,111,111],98,97,114]", lists:flatten(format("~25W", [["foo", $b, $a, $r], 10], 50))), % Note these next two diverge from io_lib:format; the field width is % ignored, when it should be used as max line length. ?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~10p", [["foo", $b, $a, $r]], 50))), ?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~10P", [["foo", $b, $a, $r], 10], 50))), ok. atom_quoting_test() -> ?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("~s", [<<>>], 50))), ?assertEqual("<<..>>", lists:flatten(format("~p", [<<"hi">>], 0))), ?assertEqual("<<...>>", lists:flatten(format("~p", [<<"hi">>], 1))), ?assertEqual("<<\"hello\">>", lists:flatten(format("~p", [<<$h, $e, $l, $l, $o>>], 50))), ?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 world\\\"\">>", lists:flatten(format("~p", [<<"\"hello world\"">>], 50))), ?assertEqual("<<\"hello\\\\world\">>", lists:flatten(format("~p", [<<"hello\\world">>], 50))), ?assertEqual("<<\"hello\\\\\world\">>", lists:flatten(format("~p", [<<"hello\\\world">>], 50))), ?assertEqual("<<\"hello\\\\\\\\world\">>", lists:flatten(format("~p", [<<"hello\\\\world">>], 50))), ?assertEqual("<<\"hello\\bworld\">>", lists:flatten(format("~p", [<<"hello\bworld">>], 50))), ?assertEqual("<<\"hello\\tworld\">>", lists:flatten(format("~p", [<<"hello\tworld">>], 50))), ?assertEqual("<<\"hello\\nworld\">>", lists:flatten(format("~p", [<<"hello\nworld">>], 50))), ?assertEqual("<<\"hello\\rworld\">>", lists:flatten(format("~p", [<<"hello\rworld">>], 50))), ?assertEqual("<<\"hello\\eworld\">>", lists:flatten(format("~p", [<<"hello\eworld">>], 50))), ?assertEqual("<<\"hello\\fworld\">>", lists:flatten(format("~p", [<<"hello\fworld">>], 50))), ?assertEqual("<<\"hello\\vworld\">>", lists:flatten(format("~p", [<<"hello\vworld">>], 50))), ?assertEqual(" hello", lists:flatten(format("~10s", [<<"hello">>], 50))), ?assertEqual("[a]", lists:flatten(format("~s", [<<"[a]">>], 50))), ?assertEqual("[a]", lists:flatten(format("~s", [[<<"[a]">>]], 50))), ok. bitstring_printing_test() -> ?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))), ?assertEqual("{<<1:7>>}", lists:flatten(format("~p", [{<<1:7>>}], 50))), 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))), %%improper list ?assertEqual("[1,2,3|4]", lists:flatten(format("~P", [[1|[2|[3|4]]], 5], 50))), ?assertEqual("[1|1]", lists:flatten(format("~P", [[1|1], 5], 50))), ?assertEqual("[9|9]", lists:flatten(format("~p", [[9|9]], 50))), ok. iolist_printing_test() -> ?assertEqual("iolist: HelloIamaniolist", lists:flatten(format("iolist: ~s", [[$H, $e, $l, $l, $o, "I", ["am", [<<"an">>], [$i, $o, $l, $i, $s, $t]]]], 1000))), ?assertEqual("123...", lists:flatten(format("~s", [[<<"123456789">>, "HellIamaniolist"]], 6))), ?assertEqual("123456...", lists:flatten(format("~s", [[<<"123456789">>, "HellIamaniolist"]], 9))), ?assertEqual("123456789H...", lists:flatten(format("~s", [[<<"123456789">>, "HellIamaniolist"]], 13))), ?assertEqual("123456789HellIamaniolist", lists:flatten(format("~s", [[<<"123456789">>, "HellIamaniolist"]], 30))), ok. tuple_printing_test() -> ?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. map_printing_test() -> case erlang:is_builtin(erlang, is_map, 1) of true -> ?assertEqual("#{}", lists:flatten(format("~p", [maps:new()], 50))), ?assertEqual("#{}", lists:flatten(format("~p", [maps:new()], 3))), ?assertEqual("#{}", lists:flatten(format("~w", [maps:new()], 50))), ?assertError(badarg, lists:flatten(format("~s", [maps:new()], 50))), ?assertEqual("#{...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}])], 1))), ?assertEqual("#{...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}])], 6))), ?assertEqual("#{bar => ...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}])], 7))), ?assertEqual("#{bar => ...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}])], 9))), ?assertEqual("#{bar => foo}", lists:flatten(format("~p", [maps:from_list([{bar, foo}])], 10))), ?assertEqual("#{bar => ...,...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 9))), ?assertEqual("#{bar => foo,...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 10))), ?assertEqual("#{bar => foo,...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 17))), ?assertEqual("#{bar => foo,foo => ...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 18))), ?assertEqual("#{bar => foo,foo => ...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 19))), ?assertEqual("#{bar => foo,foo => ...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 20))), ?assertEqual("#{bar => foo,foo => bar}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 21))), ?assertEqual("#{22835963083295358096932575511191922182123945984 => ...}", lists:flatten(format("~w", [ maps:from_list([{22835963083295358096932575511191922182123945984, 22835963083295358096932575511191922182123945984}])], 10))), ?assertEqual("#{22835963083295358096932575511191922182123945984 => ...}", lists:flatten(format("~w", [ maps:from_list([{22835963083295358096932575511191922182123945984, bar}])], 10))), ?assertEqual("#{22835963083295358096932575511191922182123945984 => ...}", lists:flatten(format("~w", [ maps:from_list([{22835963083295358096932575511191922182123945984, bar}])], 53))), ?assertEqual("#{22835963083295358096932575511191922182123945984 => bar}", lists:flatten(format("~w", [ maps:from_list([{22835963083295358096932575511191922182123945984, bar}])], 54))), ok; false -> ok end. unicode_test() -> ?assertEqual([231,167,129], lists:flatten(format("~s", [<<231,167,129>>], 50))), ?assertEqual([31169], lists:flatten(format("~ts", [<<231,167,129>>], 50))), 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))), case erlang:is_builtin(erlang, is_map, 1) of true -> ?assertEqual("#{a => #{...}}", lists:flatten(format("~P", [maps:from_list([{a, maps:from_list([{b, maps:from_list([{c, d}])}])}]), 2], 50))), ?assertEqual("#{a => #{b => #{...}}}", lists:flatten(format("~P", [maps:from_list([{a, maps:from_list([{b, maps:from_list([{c, d}])}])}]), 3], 50))), ?assertEqual("#{a => #{b => #{c => d}}}", lists:flatten(format("~P", [maps:from_list([{a, maps:from_list([{b, maps:from_list([{c, d}])}])}]), 4], 50))), ?assertEqual("#{}", lists:flatten(format("~P", [maps:new(), 1], 50))), ?assertEqual("#{...}", lists:flatten(format("~P", [maps:from_list([{1,1}, {2,2}, {3,3}]), 1], 50))), ?assertEqual("#{1 => 1,...}", lists:flatten(format("~P", [maps:from_list([{1,1}, {2,2}, {3,3}]), 2], 50))), ?assertEqual("#{1 => 1,2 => 2,...}", lists:flatten(format("~P", [maps:from_list([{1,1}, {2,2}, {3,3}]), 3], 50))), ?assertEqual("#{1 => 1,2 => 2,3 => 3}", lists:flatten(format("~P", [maps:from_list([{1,1}, {2,2}, {3,3}]), 4], 50))), ok; false -> ok end, ?assertEqual("{\"a\",[...]}", lists:flatten(format("~P", [{"a", ["b", ["c", ["d"]]]}, 3], 50))), ?assertEqual("{\"a\",[\"b\",[[...]|...]]}", lists:flatten(format("~P", [{"a", ["b", ["c", ["d"]]]}, 6], 50))), ?assertEqual("{\"a\",[\"b\",[\"c\",[\"d\"]]]}", lists:flatten(format("~P", [{"a", ["b", ["c", ["d"]]]}, 9], 50))), ?assertEqual("[...]", lists:flatten(format("~P", [[1, 2, 3], 1], 50))), ?assertEqual("[1|...]", lists:flatten(format("~P", [[1, 2, 3], 2], 50))), ?assertEqual("[1,2|...]", lists:flatten(format("~P", [[1, 2, 3], 3], 50))), ?assertEqual("[1,2,3]", lists:flatten(format("~P", [[1, 2, 3], 4], 50))), ?assertEqual("{1,...}", lists:flatten(format("~P", [{1, 2, 3}, 2], 50))), ?assertEqual("{1,2,...}", lists:flatten(format("~P", [{1, 2, 3}, 3], 50))), ?assertEqual("{1,2,3}", lists:flatten(format("~P", [{1, 2, 3}, 4], 50))), ?assertEqual("{1,...}", lists:flatten(format("~P", [{1, 2, 3}, 2], 50))), ?assertEqual("[1,2|...]", lists:flatten(format("~P", [[1, 2, <<3>>], 3], 50))), ?assertEqual("[1,2,<<...>>]", lists:flatten(format("~P", [[1, 2, <<3>>], 4], 50))), ?assertEqual("[1,2,<<3>>]", lists:flatten(format("~P", [[1, 2, <<3>>], 5], 50))), ?assertEqual("<<...>>", lists:flatten(format("~P", [<<0, 0, 0, 0>>, 1], 50))), ?assertEqual("<<0,...>>", lists:flatten(format("~P", [<<0, 0, 0, 0>>, 2], 50))), ?assertEqual("<<0,0,...>>", lists:flatten(format("~P", [<<0, 0, 0, 0>>, 3], 50))), ?assertEqual("<<0,0,0,...>>", lists:flatten(format("~P", [<<0, 0, 0, 0>>, 4], 50))), ?assertEqual("<<0,0,0,0>>", lists:flatten(format("~P", [<<0, 0, 0, 0>>, 5], 50))), %% this is a seriously weird edge case ?assertEqual("<<\" \"...>>", lists:flatten(format("~P", [<<32, 32, 32, 0>>, 2], 50))), ?assertEqual("<<\" \"...>>", lists:flatten(format("~P", [<<32, 32, 32, 0>>, 3], 50))), ?assertEqual("<<\" \"...>>", lists:flatten(format("~P", [<<32, 32, 32, 0>>, 4], 50))), ?assertEqual("<<32,32,32,0>>", lists:flatten(format("~P", [<<32, 32, 32, 0>>, 5], 50))), ?assertEqual("<<32,32,32,0>>", lists:flatten(format("~p", [<<32, 32, 32, 0>>], 50))), %% depth limiting for some reason works in 4 byte chunks on printable binaries? ?assertEqual("<<\"hell\"...>>", lists:flatten(format("~P", [<<"hello world">>, 2], 50))), ?assertEqual("<<\"abcd\"...>>", lists:flatten(format("~P", [<<$a, $b, $c, $d, $e, 0>>, 2], 50))), %% I don't even know... ?assertEqual("<<>>", lists:flatten(format("~P", [<<>>, 1], 50))), ?assertEqual("<<>>", lists:flatten(format("~W", [<<>>, 1], 50))), ?assertEqual("{abc,<<\"abc\\\"\">>}", lists:flatten(format("~P", [{abc,<<"abc\"">>}, 4], 50))), ok. print_terms_without_format_string_test() -> ?assertError(badarg, format({hello, world}, [], 50)), ?assertError(badarg, format([{google, bomb}], [], 50)), ?assertError(badarg, format([$h,$e,$l,$l,$o, 3594], [], 50)), ?assertEqual("helloworld", lists:flatten(format([$h,$e,$l,$l,$o, "world"], [], 50))), ?assertEqual("hello", lists:flatten(format(<<"hello">>, [], 50))), ?assertEqual("hello", lists:flatten(format('hello', [], 50))), ?assertError(badarg, format(<<1, 2, 3, 1:7>>, [], 100)), ?assertError(badarg, format(65535, [], 50)), ok. -endif. lager-3.1.0/src/lager_config.erl0000644000232200023220000000475112652234453017102 0ustar debalancedebalance%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% @doc Helper functions for working with lager's runtime config -module(lager_config). -include("lager.hrl"). -export([new/0, new_sink/1, get/1, get/2, set/2, global_get/1, global_get/2, global_set/2]). -define(TBL, lager_config). -define(GLOBAL, '_global'). %% For multiple sinks, the key is now the registered event name and the old key %% as a tuple. %% %% {{lager_event, loglevel}, Value} instead of {loglevel, Value} new() -> %% set up the ETS configuration table _ = try ets:new(?TBL, [named_table, public, set, {keypos, 1}, {read_concurrency, true}]) of _Result -> ok catch error:badarg -> ?INT_LOG(warning, "Table ~p already exists", [?TBL]) end, new_sink(?DEFAULT_SINK), %% Need to be able to find the `lager_handler_watcher' for all handlers ets:insert_new(?TBL, {{?GLOBAL, handlers}, []}), ok. new_sink(Sink) -> %% use insert_new here so that if we're in an appup we don't mess anything up %% %% until lager is completely started, allow all messages to go through ets:insert_new(?TBL, {{Sink, loglevel}, {element(2, lager_util:config_to_mask(debug)), []}}). global_get(Key) -> global_get(Key, undefined). global_get(Key, Default) -> get({?GLOBAL, Key}, Default). global_set(Key, Value) -> set({?GLOBAL, Key}, Value). get({_Sink, _Key}=FullKey) -> get(FullKey, undefined); get(Key) -> get({?DEFAULT_SINK, Key}, undefined). get({Sink, Key}, Default) -> try case ets:lookup(?TBL, {Sink, Key}) of [] -> Default; [{{Sink, Key}, Res}] -> Res end catch _:_ -> Default end; get(Key, Default) -> get({?DEFAULT_SINK, Key}, Default). set({Sink, Key}, Value) -> ets:insert(?TBL, {{Sink, Key}, Value}); set(Key, Value) -> set({?DEFAULT_SINK, Key}, Value). lager-3.1.0/src/lager_msg.erl0000644000232200023220000000353612652234453016423 0ustar debalancedebalance-module(lager_msg). -export([new/4, new/5]). -export([message/1]). -export([timestamp/1]). -export([datetime/1]). -export([severity/1]). -export([severity_as_int/1]). -export([metadata/1]). -export([destinations/1]). -record(lager_msg,{ destinations :: list(), metadata :: [tuple()], severity :: lager:log_level(), datetime :: {string(), string()}, timestamp :: erlang:timestamp(), message :: list() }). -opaque lager_msg() :: #lager_msg{}. -export_type([lager_msg/0]). %% create with provided timestamp, handy for testing mostly -spec new(list(), erlang:timestamp(), lager:log_level(), [tuple()], list()) -> lager_msg(). new(Msg, Timestamp, Severity, Metadata, Destinations) -> {Date, Time} = lager_util:format_time(lager_util:maybe_utc(lager_util:localtime_ms(Timestamp))), #lager_msg{message=Msg, datetime={Date, Time}, timestamp=Timestamp, severity=Severity, metadata=Metadata, destinations=Destinations}. -spec new(list(), lager:log_level(), [tuple()], list()) -> lager_msg(). new(Msg, Severity, Metadata, Destinations) -> Now = os:timestamp(), new(Msg, Now, Severity, Metadata, Destinations). -spec message(lager_msg()) -> list(). message(Msg) -> Msg#lager_msg.message. -spec timestamp(lager_msg()) -> erlang:timestamp(). timestamp(Msg) -> Msg#lager_msg.timestamp. -spec datetime(lager_msg()) -> {string(), string()}. datetime(Msg) -> Msg#lager_msg.datetime. -spec severity(lager_msg()) -> lager:log_level(). severity(Msg) -> Msg#lager_msg.severity. -spec severity_as_int(lager_msg()) -> lager:log_level_number(). severity_as_int(Msg) -> lager_util:level_to_num(Msg#lager_msg.severity). -spec metadata(lager_msg()) -> [tuple()]. metadata(Msg) -> Msg#lager_msg.metadata. -spec destinations(lager_msg()) -> list(). destinations(Msg) -> Msg#lager_msg.destinations. lager-3.1.0/src/lager_app.erl0000644000232200023220000003506412652234453016416 0ustar debalancedebalance%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% @doc Lager's application module. Not a lot to see here. %% @private -module(lager_app). -behaviour(application). -include("lager.hrl"). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. -export([start/0, start/2, start_handler/3, stop/1]). -define(FILENAMES, '__lager_file_backend_filenames'). -define(THROTTLE, lager_backend_throttle). -define(DEFAULT_HANDLER_CONF, [{lager_console_backend, info}, {lager_file_backend, [{file, "log/error.log"}, {level, error}, {size, 10485760}, {date, "$D0"}, {count, 5}] }, {lager_file_backend, [{file, "log/console.log"}, {level, info}, {size, 10485760}, {date, "$D0"}, {count, 5}] } ]). start() -> application:start(lager). start_throttle(Sink, Threshold, Window) -> _ = supervisor:start_child(lager_handler_watcher_sup, [Sink, ?THROTTLE, [Threshold, Window]]), ok. determine_async_behavior(_Sink, {ok, undefined}, _Window) -> ok; determine_async_behavior(_Sink, undefined, _Window) -> ok; determine_async_behavior(_Sink, {ok, Threshold}, _Window) when not is_integer(Threshold) orelse Threshold < 0 -> error_logger:error_msg("Invalid value for 'async_threshold': ~p~n", [Threshold]), throw({error, bad_config}); determine_async_behavior(Sink, {ok, Threshold}, undefined) -> start_throttle(Sink, Threshold, erlang:trunc(Threshold * 0.2)); determine_async_behavior(_Sink, {ok, Threshold}, {ok, Window}) when not is_integer(Window) orelse Window > Threshold orelse Window < 0 -> error_logger:error_msg( "Invalid value for 'async_threshold_window': ~p~n", [Window]), throw({error, bad_config}); determine_async_behavior(Sink, {ok, Threshold}, {ok, Window}) -> start_throttle(Sink, Threshold, Window). start_handlers(_Sink, undefined) -> ok; start_handlers(_Sink, Handlers) when not is_list(Handlers) -> error_logger:error_msg( "Invalid value for 'handlers' (must be list): ~p~n", [Handlers]), throw({error, bad_config}); start_handlers(Sink, Handlers) -> %% handlers failing to start are handled in the handler_watcher lager_config:global_set(handlers, lager_config:global_get(handlers, []) ++ lists:map(fun({Module, Config}) -> check_handler_config(Module, Config), start_handler(Sink, Module, Config); (_) -> throw({error, bad_config}) end, expand_handlers(Handlers))), ok. start_handler(Sink, Module, Config) -> {ok, Watcher} = supervisor:start_child(lager_handler_watcher_sup, [Sink, Module, Config]), {Module, Watcher, Sink}. check_handler_config({lager_file_backend, F}, Config) when is_list(Config) -> Fs = case get(?FILENAMES) of undefined -> ordsets:new(); X -> X end, case ordsets:is_element(F, Fs) of true -> error_logger:error_msg( "Cannot have same file (~p) in multiple file backends~n", [F]), throw({error, bad_config}); false -> put(?FILENAMES, ordsets:add_element(F, Fs)) end, ok; check_handler_config(_Handler, Config) when is_list(Config) orelse is_atom(Config) -> ok; check_handler_config(Handler, _BadConfig) -> throw({error, {bad_config, Handler}}). clean_up_config_checks() -> erase(?FILENAMES). interpret_hwm(undefined) -> undefined; interpret_hwm({ok, undefined}) -> undefined; interpret_hwm({ok, HWM}) when not is_integer(HWM) orelse HWM < 0 -> _ = lager:log(warning, self(), "Invalid error_logger high water mark: ~p, disabling", [HWM]), undefined; interpret_hwm({ok, HWM}) -> HWM. start_error_logger_handler({ok, false}, _HWM, _Whitelist) -> []; start_error_logger_handler(_, HWM, undefined) -> start_error_logger_handler(ignore_me, HWM, {ok, []}); start_error_logger_handler(_, HWM, {ok, WhiteList}) -> GlStrategy = case application:get_env(lager, error_logger_groupleader_strategy) of undefined -> handle; {ok, GlStrategy0} when GlStrategy0 =:= handle; GlStrategy0 =:= ignore; GlStrategy0 =:= mirror -> GlStrategy0; {ok, BadGlStrategy} -> error_logger:error_msg( "Invalid value for 'error_logger_groupleader_strategy': ~p~n", [BadGlStrategy]), throw({error, bad_config}) end, case supervisor:start_child(lager_handler_watcher_sup, [error_logger, error_logger_lager_h, [HWM, GlStrategy]]) of {ok, _} -> [begin error_logger:delete_report_handler(X), X end || X <- gen_event:which_handlers(error_logger) -- [error_logger_lager_h | WhiteList]]; {error, _} -> [] end. %% `determine_async_behavior/3' is called with the results from either %% `application:get_env/2' and `proplists:get_value/2'. Since %% `application:get_env/2' wraps a successful retrieval in an `{ok, %% Value}' tuple, do the same for the result from %% `proplists:get_value/2'. wrap_proplist_value(undefined) -> undefined; wrap_proplist_value(Value) -> {ok, Value}. configure_sink(Sink, SinkDef) -> lager_config:new_sink(Sink), ChildId = lager_util:make_internal_sink_name(Sink), _ = supervisor:start_child(lager_sup, {ChildId, {gen_event, start_link, [{local, Sink}]}, permanent, 5000, worker, dynamic}), determine_async_behavior(Sink, wrap_proplist_value( proplists:get_value(async_threshold, SinkDef)), wrap_proplist_value( proplists:get_value(async_threshold_window, SinkDef)) ), start_handlers(Sink, proplists:get_value(handlers, SinkDef, [])), lager:update_loglevel_config(Sink). configure_extra_sinks(Sinks) -> lists:foreach(fun({Sink, Proplist}) -> configure_sink(Sink, Proplist) end, Sinks). %% R15 doesn't know about application:get_env/3 get_env(Application, Key, Default) -> get_env_default(application:get_env(Application, Key), Default). get_env_default(undefined, Default) -> Default; get_env_default({ok, Value}, _Default) -> Value. start(_StartType, _StartArgs) -> {ok, Pid} = lager_sup:start_link(), %% Handle the default sink. determine_async_behavior(?DEFAULT_SINK, application:get_env(lager, async_threshold), application:get_env(lager, async_threshold_window)), start_handlers(?DEFAULT_SINK, get_env(lager, handlers, ?DEFAULT_HANDLER_CONF)), lager:update_loglevel_config(?DEFAULT_SINK), SavedHandlers = start_error_logger_handler( application:get_env(lager, error_logger_redirect), interpret_hwm(application:get_env(lager, error_logger_hwm)), application:get_env(lager, error_logger_whitelist) ), _ = lager_util:trace_filter(none), %% Now handle extra sinks configure_extra_sinks(get_env(lager, extra_sinks, [])), ok = add_configured_traces(), clean_up_config_checks(), {ok, Pid, SavedHandlers}. stop(Handlers) -> lists:foreach(fun(Handler) -> error_logger:add_report_handler(Handler) end, Handlers). expand_handlers([]) -> []; expand_handlers([{lager_file_backend, [{Key, _Value}|_]=Config}|T]) when is_atom(Key) -> %% this is definitely a new-style config, no expansion needed [maybe_make_handler_id(lager_file_backend, Config) | expand_handlers(T)]; expand_handlers([{lager_file_backend, Configs}|T]) -> ?INT_LOG(notice, "Deprecated lager_file_backend config detected, please consider updating it", []), [ {lager_file_backend:config_to_id(Config), Config} || Config <- Configs] ++ expand_handlers(T); expand_handlers([{Mod, Config}|T]) when is_atom(Mod) -> [maybe_make_handler_id(Mod, Config) | expand_handlers(T)]; expand_handlers([H|T]) -> [H | expand_handlers(T)]. add_configured_traces() -> Traces = case application:get_env(lager, traces) of undefined -> []; {ok, TraceVal} -> TraceVal end, lists:foreach(fun({Handler, Filter, Level}) -> {ok, _} = lager:trace(Handler, Filter, Level) end, Traces), ok. maybe_make_handler_id(Mod, Config) -> %% Allow the backend to generate a gen_event handler id, if it wants to. %% We don't use erlang:function_exported here because that requires the module %% already be loaded, which is unlikely at this phase of startup. Using code:load %% caused undesirable side-effects with generating code-coverage reports. try Mod:config_to_id(Config) of Id -> {Id, Config} catch error:undef -> {Mod, Config} end. -ifdef(TEST). application_config_mangling_test_() -> [ {"Explode the file backend handlers", ?_assertMatch( [{lager_console_backend, info}, {{lager_file_backend,"error.log"},{"error.log",error,10485760, "$D0",5}}, {{lager_file_backend,"console.log"},{"console.log",info,10485760, "$D0",5}} ], expand_handlers([{lager_console_backend, info}, {lager_file_backend, [ {"error.log", error, 10485760, "$D0", 5}, {"console.log", info, 10485760, "$D0", 5} ]}] )) }, {"Explode the short form of backend file handlers", ?_assertMatch( [{lager_console_backend, info}, {{lager_file_backend,"error.log"},{"error.log",error}}, {{lager_file_backend,"console.log"},{"console.log",info}} ], expand_handlers([{lager_console_backend, info}, {lager_file_backend, [ {"error.log", error}, {"console.log", info} ]}] )) }, {"Explode with formatter info", ?_assertMatch( [{{lager_file_backend,"test.log"}, [{"test.log", debug, 10485760, "$D0", 5},{lager_default_formatter,["[",severity,"] ", message, "\n"]}]}, {{lager_file_backend,"test2.log"}, [{"test2.log",debug, 10485760, "$D0", 5},{lager_default_formatter,["2>[",severity,"] ", message, "\n"]}]}], expand_handlers([{lager_file_backend, [ [{"test.log", debug, 10485760, "$D0", 5},{lager_default_formatter,["[",severity,"] ", message, "\n"]}], [{"test2.log",debug, 10485760, "$D0", 5},{lager_default_formatter,["2>[",severity,"] ",message, "\n"]}] ] }]) ) }, {"Explode short form with short formatter info", ?_assertMatch( [{{lager_file_backend,"test.log"}, [{"test.log", debug},{lager_default_formatter,["[",severity,"] ", message, "\n"]}]}, {{lager_file_backend,"test2.log"}, [{"test2.log",debug},{lager_default_formatter}]}], expand_handlers([{lager_file_backend, [ [{"test.log", debug},{lager_default_formatter,["[",severity,"] ", message, "\n"]}], [{"test2.log",debug},{lager_default_formatter}] ] }]) ) }, {"New form needs no expansion", ?_assertMatch([ {{lager_file_backend,"test.log"}, [{file, "test.log"}]}, {{lager_file_backend,"test2.log"}, [{file, "test2.log"}, {level, info}, {sync_on, none}]}, {{lager_file_backend,"test3.log"}, [{formatter, lager_default_formatter}, {file, "test3.log"}]} ], expand_handlers([ {lager_file_backend, [{file, "test.log"}]}, {lager_file_backend, [{file, "test2.log"}, {level, info}, {sync_on, none}]}, {lager_file_backend, [{formatter, lager_default_formatter},{file, "test3.log"}]} ]) ) } ]. check_handler_config_test_() -> Good = expand_handlers(?DEFAULT_HANDLER_CONF), Bad = expand_handlers([{lager_console_backend, info}, {lager_file_backend, [{file, "same_file.log"}]}, {lager_file_backend, [{file, "same_file.log"}, {level, info}]}]), AlsoBad = [{lager_logstash_backend, {level, info}, {output, {udp, "localhost", 5000}}, {format, json}, {json_encoder, jiffy}}], BadToo = [{fail, {fail}}], [ {"lager_file_backend_good", ?_assertEqual([ok, ok, ok], [ check_handler_config(M,C) || {M,C} <- Good ]) }, {"lager_file_backend_bad", ?_assertThrow({error, bad_config}, [ check_handler_config(M,C) || {M,C} <- Bad ]) }, {"Invalid config dies", ?_assertThrow({error, bad_config}, start_handlers(foo, AlsoBad)) }, {"Invalid config dies", ?_assertThrow({error, {bad_config, _}}, start_handlers(foo, BadToo)) } ]. -endif. lager-3.1.0/src/lager.app.src0000644000232200023220000000516412652234453016340 0ustar debalancedebalance%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et {application, lager, [ {description, "Erlang logging framework"}, {vsn, "3.1.0"}, {modules, []}, {applications, [ kernel, stdlib, goldrush ]}, {registered, [lager_sup, lager_event, lager_crash_log, lager_handler_watcher_sup]}, {mod, {lager_app, []}}, {env, [ %% Note: application:start(lager) overwrites previously defined environment variables %% thus declaration of default handlers is done at lager_app.erl %% What colors to use with what log levels {colored, false}, {colors, [ {debug, "\e[0;38m" }, {info, "\e[1;37m" }, {notice, "\e[1;36m" }, {warning, "\e[1;33m" }, {error, "\e[1;31m" }, {critical, "\e[1;35m" }, {alert, "\e[1;44m" }, {emergency, "\e[1;41m" } ]}, %% Whether to write a crash log, and where. Undefined means no crash logger. {crash_log, "log/crash.log"}, %% Maximum size in bytes of events in the crash log - defaults to 65536 {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 the default lager_event sink - defaults to true {error_logger_redirect, true}, %% How many messages per second to allow from error_logger before we start dropping them {error_logger_hwm, 50}, %% How big the gen_event mailbox can get before it is %% switched into sync mode. This value only applies to %% the default sink; extra sinks can supply their own. {async_threshold, 20}, %% Switch back to async mode, when gen_event mailbox size %% decrease from `async_threshold' to async_threshold - %% async_threshold_window. This value only applies to the %% default sink; extra sinks can supply their own. {async_threshold_window, 5} ]} ]}. lager-3.1.0/src/lager_handler_watcher.erl0000644000232200023220000001570012652234453020763 0ustar debalancedebalance%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% @doc 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). -include("lager.hrl"). -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 :: atom(), config :: any(), sink :: pid() | atom() }). start_link(Sink, Module, Config) -> gen_server:start_link(?MODULE, [Sink, Module, Config], []). start(Sink, Module, Config) -> gen_server:start(?MODULE, [Sink, Module, Config], []). init([Sink, Module, Config]) -> install_handler(Sink, Module, Config), {ok, #state{sink=Sink, module=Module, config=Config}}. handle_call(_Call, _From, State) -> {reply, ok, State}. 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, sink=Sink} = State) -> case lager:log(error, self(), "Lager event handler ~p exited with reason ~s", [Module, error_logger_lager_h:format_reason(Reason)]) of ok -> install_handler(Sink, Module, Config); {error, _} -> %% lager is not working, so installing a handler won't work ok end, {noreply, State}; handle_info(reinstall_handler, #state{module=Module, config=Config, sink=Sink} = State) -> install_handler(Sink, Module, Config), {noreply, State}; handle_info(stop, State) -> {stop, normal, State}; handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. %% internal install_handler(Sink, lager_backend_throttle, Config) -> %% The lager_backend_throttle needs to know to which sink it is %% attached, hence this admittedly ugly workaround. Handlers are %% sensitive to the structure of the configuration sent to `init', %% sadly, so it's not trivial to add a configuration item to be %% ignored to backends without breaking 3rd party handlers. install_handler2(Sink, lager_backend_throttle, [{sink, Sink}|Config]); install_handler(Sink, Module, Config) -> install_handler2(Sink, Module, Config). %% private install_handler2(Sink, Module, Config) -> case gen_event:add_sup_handler(Sink, Module, Config) of ok -> ?INT_LOG(debug, "Lager installed handler ~p into ~p", [Module, Sink]), lager:update_loglevel_config(Sink), ok; {error, {fatal, Reason}} -> ?INT_LOG(error, "Lager fatally failed to install handler ~p into" " ~p, NOT retrying: ~p", [Module, Sink, Reason]), %% tell ourselves to stop self() ! stop, ok; Error -> %% try to reinstall it later ?INT_LOG(error, "Lager failed to install handler ~p into" " ~p, retrying later : ~p", [Module, Sink, Error]), erlang:send_after(5000, self(), reinstall_handler), ok end. -ifdef(TEST). 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), lager:start(), try ?assertEqual(1, lager_test_backend:count()), {_Level, _Time, Message, _Metadata} = lager_test_backend:pop(), ?assertMatch("Lager failed to install handler lager_crash_backend into lager_event, retrying later :"++_, lists:flatten(Message)), ?assertEqual(0, lager_test_backend:count()), timer:sleep(6000), ?assertEqual(0, lager_test_backend:count()), ?assert(lists:member(lager_crash_backend, gen_event:which_handlers(lager_event))) after application:stop(lager), application:stop(goldrush), error_logger:tty(true) end end ] }. 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), lager:start(), try ?assertEqual(0, lager_test_backend:count()), ?assert(lists:member(lager_crash_backend, gen_event:which_handlers(lager_event))), timer:sleep(6000), ?assertEqual(2, lager_test_backend:count()), {_Severity, _Date, Msg, _Metadata} = lager_test_backend:pop(), ?assertEqual("Lager event handler lager_crash_backend exited with reason crash", lists:flatten(Msg)), {_Severity2, _Date2, Msg2, _Metadata2} = lager_test_backend:pop(), ?assertMatch("Lager failed to install handler lager_crash_backend into lager_event, retrying later :"++_, lists:flatten(Msg2)), ?assertEqual(false, lists:member(lager_crash_backend, gen_event:which_handlers(lager_event))) after application:stop(lager), application:stop(goldrush), error_logger:tty(true) end end ] }. -endif. lager-3.1.0/src/lager.erl0000644000232200023220000006125212652234453015554 0ustar debalancedebalance%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% @doc The lager logging framework. -module(lager). -include("lager.hrl"). -define(LAGER_MD_KEY, '__lager_metadata'). -define(TRACE_SINK, '__trace_sink'). -define(ROTATE_TIMEOUT, 100000). %% API -export([start/0, log/3, log/4, log/5, log_unsafe/4, md/0, md/1, rotate_handler/1, rotate_handler/2, rotate_sink/1, rotate_all/0, trace/2, trace/3, trace_file/2, trace_file/3, trace_file/4, trace_console/1, trace_console/2, list_all_sinks/0, clear_all_traces/0, stop_trace/1, stop_trace/3, status/0, get_loglevel/1, get_loglevel/2, set_loglevel/2, set_loglevel/3, set_loglevel/4, get_loglevels/1, update_loglevel_config/1, posix_error/1, set_loghwm/2, set_loghwm/3, set_loghwm/4, safe_format/3, safe_format_chop/3, unsafe_format/2, dispatch_log/5, dispatch_log/7, dispatch_log/9, do_log/9, do_log/10, do_log_unsafe/10, pr/2, pr/3, pr_stacktrace/1, pr_stacktrace/2]). -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}). %% @doc Get lager metadata for current process -spec md() -> [{atom(), any()}]. md() -> case erlang:get(?LAGER_MD_KEY) of undefined -> []; MD -> MD end. %% @doc Set lager metadata for current process. %% Will badarg if you don't supply a list of {key, value} tuples keyed by atoms. -spec md([{atom(), any()},...]) -> ok. md(NewMD) when is_list(NewMD) -> %% make sure its actually a real proplist case lists:all( fun({Key, _Value}) when is_atom(Key) -> true; (_) -> false end, NewMD) of true -> erlang:put(?LAGER_MD_KEY, NewMD), ok; false -> erlang:error(badarg) end; md(_) -> erlang:error(badarg). -spec dispatch_log(atom(), log_level(), list(), string(), list() | none, pos_integer(), safe | unsafe) -> ok | {error, lager_not_running} | {error, {sink_not_configured, atom()}}. %% this is the same check that the parse transform bakes into the module at compile time %% see lager_transform (lines 173-216) dispatch_log(Sink, Severity, Metadata, Format, Args, Size, Safety) when is_atom(Severity)-> SeverityAsInt=lager_util:level_to_num(Severity), case {whereis(Sink), whereis(?DEFAULT_SINK), lager_config:get({Sink, loglevel}, {?LOG_NONE, []})} of {undefined, undefined, _} -> {error, lager_not_running}; {undefined, _LagerEventPid0, _} -> {error, {sink_not_configured, Sink}}; {SinkPid, _LagerEventPid1, {Level, Traces}} when Safety =:= safe andalso ( (Level band SeverityAsInt) /= 0 orelse Traces /= [] ) -> do_log(Severity, Metadata, Format, Args, Size, SeverityAsInt, Level, Traces, Sink, SinkPid); {SinkPid, _LagerEventPid1, {Level, Traces}} when Safety =:= unsafe andalso ( (Level band SeverityAsInt) /= 0 orelse Traces /= [] ) -> do_log_unsafe(Severity, Metadata, Format, Args, Size, SeverityAsInt, Level, Traces, Sink, SinkPid); _ -> ok end. %% @private Should only be called externally from code generated from the parse transform do_log(Severity, Metadata, Format, Args, Size, SeverityAsInt, LevelThreshold, TraceFilters, Sink, SinkPid) when is_atom(Severity) -> FormatFun = fun() -> safe_format_chop(Format, Args, Size) end, do_log_impl(Severity, Metadata, Format, Args, SeverityAsInt, LevelThreshold, TraceFilters, Sink, SinkPid, FormatFun). do_log_impl(Severity, Metadata, Format, Args, SeverityAsInt, LevelThreshold, TraceFilters, Sink, SinkPid, FormatFun) -> {Destinations, TraceSinkPid} = case TraceFilters of [] -> {[], undefined}; _ -> {lager_util:check_traces(Metadata,SeverityAsInt,TraceFilters,[]), whereis(?TRACE_SINK)} end, case (LevelThreshold band SeverityAsInt) /= 0 orelse Destinations /= [] of true -> Msg = case Args of A when is_list(A) -> FormatFun(); _ -> Format end, LagerMsg = lager_msg:new(Msg, Severity, Metadata, Destinations), case lager_config:get({Sink, async}, false) of true -> gen_event:notify(SinkPid, {log, LagerMsg}); false -> gen_event:sync_notify(SinkPid, {log, LagerMsg}) end, case TraceSinkPid /= undefined of true -> gen_event:notify(TraceSinkPid, {log, LagerMsg}); false -> ok end; false -> ok end. %% @private Should only be called externally from code generated from the parse transform %% Specifically, it would be level ++ `_unsafe' as in `info_unsafe'. do_log_unsafe(Severity, Metadata, Format, Args, _Size, SeverityAsInt, LevelThreshold, TraceFilters, Sink, SinkPid) when is_atom(Severity) -> FormatFun = fun() -> unsafe_format(Format, Args) end, do_log_impl(Severity, Metadata, Format, Args, SeverityAsInt, LevelThreshold, TraceFilters, Sink, SinkPid, FormatFun). %% backwards compatible with beams compiled with lager 1.x dispatch_log(Severity, _Module, _Function, _Line, _Pid, Metadata, Format, Args, Size) -> dispatch_log(Severity, Metadata, Format, Args, Size). %% backwards compatible with beams compiled with lager 2.x dispatch_log(Severity, Metadata, Format, Args, Size) -> dispatch_log(?DEFAULT_SINK, Severity, Metadata, Format, Args, Size, safe). %% backwards compatible with beams compiled with lager 2.x do_log(Severity, Metadata, Format, Args, Size, SeverityAsInt, LevelThreshold, TraceFilters, SinkPid) -> do_log(Severity, Metadata, Format, Args, Size, SeverityAsInt, LevelThreshold, TraceFilters, ?DEFAULT_SINK, SinkPid). %% TODO: %% Consider making log2/4 that takes the Level, Pid and Message params of log/3 %% along with a Sink param?? %% @doc Manually log a message into lager without using the parse transform. -spec log(log_level(), pid() | atom() | [tuple(),...], list()) -> ok | {error, lager_not_running}. log(Level, Pid, Message) when is_pid(Pid); is_atom(Pid) -> dispatch_log(Level, [{pid,Pid}], Message, [], ?DEFAULT_TRUNCATION); log(Level, Metadata, Message) when is_list(Metadata) -> dispatch_log(Level, Metadata, Message, [], ?DEFAULT_TRUNCATION). %% @doc Manually log a message into lager without using the parse transform. -spec log(log_level(), pid() | atom() | [tuple(),...], string(), list()) -> ok | {error, lager_not_running}. log(Level, Pid, Format, Args) when is_pid(Pid); is_atom(Pid) -> dispatch_log(Level, [{pid,Pid}], Format, Args, ?DEFAULT_TRUNCATION); log(Level, Metadata, Format, Args) when is_list(Metadata) -> dispatch_log(Level, Metadata, Format, Args, ?DEFAULT_TRUNCATION). log_unsafe(Level, Metadata, Format, Args) when is_list(Metadata) -> dispatch_log(?DEFAULT_SINK, Level, Metadata, Format, Args, ?DEFAULT_TRUNCATION, unsafe). %% @doc Manually log a message into lager without using the parse transform. -spec log(atom(), log_level(), pid() | atom() | [tuple(),...], string(), list()) -> ok | {error, lager_not_running}. log(Sink, Level, Pid, Format, Args) when is_pid(Pid); is_atom(Pid) -> dispatch_log(Sink, Level, [{pid,Pid}], Format, Args, ?DEFAULT_TRUNCATION, safe); log(Sink, Level, Metadata, Format, Args) when is_list(Metadata) -> dispatch_log(Sink, Level, Metadata, Format, Args, ?DEFAULT_TRUNCATION, safe). validate_trace_filters(Filters, Level, Backend) -> Sink = proplists:get_value(sink, Filters, ?DEFAULT_SINK), {Sink, lager_util:validate_trace({ proplists:delete(sink, Filters), Level, Backend }) }. trace_file(File, Filter) -> trace_file(File, Filter, debug, []). trace_file(File, Filter, Level) when is_atom(Level) -> trace_file(File, Filter, Level, []); trace_file(File, Filter, Options) when is_list(Options) -> trace_file(File, Filter, debug, Options). trace_file(File, Filter, Level, Options) -> FileName = lager_util:expand_path(File), case validate_trace_filters(Filter, Level, {lager_file_backend, FileName}) of {Sink, {ok, Trace}} -> Handlers = lager_config:global_get(handlers, []), %% check if this file backend is already installed Res = case lists:keyfind({lager_file_backend, FileName}, 1, Handlers) of false -> %% install the handler LogFileConfig = lists:keystore(level, 1, lists:keystore(file, 1, Options, {file, FileName}), {level, none}), HandlerInfo = lager_app:start_handler(Sink, {lager_file_backend, FileName}, LogFileConfig), lager_config:global_set(handlers, [HandlerInfo|Handlers]), {ok, installed}; {_Watcher, _Handler, Sink} -> {ok, exists}; {_Watcher, _Handler, _OtherSink} -> {error, file_in_use} end, case Res of {ok, _} -> add_trace_to_loglevel_config(Trace, Sink), {ok, {{lager_file_backend, FileName}, Filter, Level}}; {error, _} = E -> E end; {_Sink, Error} -> Error end. trace_console(Filter) -> trace_console(Filter, debug). trace_console(Filter, Level) -> trace(lager_console_backend, Filter, Level). trace(Backend, Filter) -> trace(Backend, Filter, debug). trace({lager_file_backend, File}, Filter, Level) -> trace_file(File, Filter, Level); trace(Backend, Filter, Level) -> case validate_trace_filters(Filter, Level, Backend) of {Sink, {ok, Trace}} -> add_trace_to_loglevel_config(Trace, Sink), {ok, {Backend, Filter, Level}}; {_Sink, Error} -> Error end. stop_trace(Backend, Filter, Level) -> case validate_trace_filters(Filter, Level, Backend) of {Sink, {ok, Trace}} -> stop_trace_int(Trace, Sink); {_Sink, Error} -> Error end. stop_trace({Backend, Filter, Level}) -> stop_trace(Backend, Filter, Level). %% Important: validate_trace_filters orders the arguments of %% trace tuples differently than the way outside callers have %% the trace tuple. %% %% That is to say, outside they are represented as %% `{Backend, Filter, Level}' %% %% and when they come back from validation, they're %% `{Filter, Level, Backend}' stop_trace_int({_Filter, _Level, Backend} = Trace, Sink) -> {Level, Traces} = lager_config:get({Sink, loglevel}), NewTraces = lists:delete(Trace, Traces), _ = lager_util:trace_filter([ element(1, T) || T <- NewTraces ]), %MinLevel = minimum_loglevel(get_loglevels() ++ get_trace_levels(NewTraces)), lager_config:set({Sink, loglevel}, {Level, NewTraces}), case get_loglevel(Sink, Backend) of none -> %% check no other traces point here case lists:keyfind(Backend, 3, NewTraces) of false -> gen_event:delete_handler(Sink, Backend, []), lager_config:global_set(handlers, lists:keydelete(Backend, 1, lager_config:global_get(handlers))); _ -> ok end; _ -> ok end, ok. list_all_sinks() -> sets:to_list( lists:foldl(fun({_Watcher, _Handler, Sink}, Set) -> sets:add_element(Sink, Set) end, sets:new(), lager_config:global_get(handlers, []))). clear_traces_by_sink(Sinks) -> lists:foreach(fun(S) -> {Level, _Traces} = lager_config:get({S, loglevel}), lager_config:set({S, loglevel}, {Level, []}) end, Sinks). clear_all_traces() -> Handlers = lager_config:global_get(handlers, []), clear_traces_by_sink(list_all_sinks()), _ = lager_util:trace_filter(none), lager_config:global_set(handlers, lists:filter( fun({Handler, _Watcher, Sink}) -> case get_loglevel(Sink, Handler) of none -> gen_event:delete_handler(Sink, Handler, []), false; _ -> true end end, Handlers)). find_traces(Sinks) -> lists:foldl(fun(S, Acc) -> {_Level, Traces} = lager_config:get({S, loglevel}), Acc ++ lists:map(fun(T) -> {S, T} end, Traces) end, [], Sinks). status() -> Handlers = lager_config:global_get(handlers, []), Sinks = lists:sort(list_all_sinks()), Traces = find_traces(Sinks), TraceCount = case length(Traces) of 0 -> 1; N -> N end, Status = ["Lager status:\n", [begin Level = get_loglevel(Sink, Handler), case Handler of {lager_file_backend, File} -> io_lib:format("File ~s (~s) at level ~p\n", [File, Sink, Level]); lager_console_backend -> io_lib:format("Console (~s) at level ~p\n", [Sink, Level]); _ -> [] end end || {Handler, _Watcher, Sink} <- lists:sort(fun({_, _, S1}, {_, _, S2}) -> S1 =< S2 end, Handlers)], "Active Traces:\n", [begin LevelName = case Level of {mask, Mask} -> case lager_util:mask_to_levels(Mask) of [] -> none; Levels -> hd(Levels) end; Num -> lager_util:num_to_level(Num) end, io_lib:format("Tracing messages matching ~p (sink ~s) at level ~p to ~p\n", [Filter, Sink, LevelName, Destination]) end || {Sink, {Filter, Level, Destination}} <- Traces], [ "Tracing Reductions:\n", case ?DEFAULT_TRACER:info('query') of {null, false} -> ""; Query -> io_lib:format("~p~n", [Query]) end ], [ "Tracing Statistics:\n ", [ begin [" ", atom_to_list(Table), ": ", integer_to_list(?DEFAULT_TRACER:info(Table) div TraceCount), "\n"] end || Table <- [input, output, filter] ] ]], io:put_chars(Status). %% @doc Set the loglevel for a particular backend. set_loglevel(Handler, Level) when is_atom(Level) -> set_loglevel(?DEFAULT_SINK, Handler, undefined, Level). %% @doc Set the loglevel for a particular backend that has multiple identifiers %% (eg. the file backend). set_loglevel(Handler, Ident, Level) when is_atom(Level) -> set_loglevel(?DEFAULT_SINK, Handler, Ident, Level). %% @doc Set the loglevel for a particular sink's backend that potentially has %% multiple identifiers. (Use `undefined' if it doesn't have any.) set_loglevel(Sink, Handler, Ident, Level) when is_atom(Level) -> HandlerArg = case Ident of undefined -> Handler; _ -> {Handler, Ident} end, Reply = gen_event:call(Sink, HandlerArg, {set_loglevel, Level}, infinity), update_loglevel_config(Sink), Reply. %% @doc Get the loglevel for a particular backend on the default sink. In the case that the backend %% has multiple identifiers, the lowest is returned. get_loglevel(Handler) -> get_loglevel(?DEFAULT_SINK, Handler). %% @doc Get the loglevel for a particular sink's backend. In the case that the backend %% has multiple identifiers, the lowest is returned. get_loglevel(Sink, Handler) -> case gen_event:call(Sink, Handler, get_loglevel, infinity) of {mask, Mask} -> case lager_util:mask_to_levels(Mask) of [] -> none; Levels -> hd(Levels) end; X when is_integer(X) -> lager_util:num_to_level(X); Y -> Y 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], ?DEFAULT_TRUNCATION). %% @private get_loglevels(Sink) -> [gen_event:call(Sink, Handler, get_loglevel, infinity) || Handler <- gen_event:which_handlers(Sink)]. %% @doc Set the loghwm for the default sink. set_loghwm(Handler, Hwm) when is_integer(Hwm) -> set_loghwm(?DEFAULT_SINK, Handler, Hwm). %% @doc Set the loghwm for a particular backend. set_loghwm(Sink, Handler, Hwm) when is_integer(Hwm) -> gen_event:call(Sink, Handler, {set_loghwm, Hwm}, infinity). %% @doc Set the loghwm (log high water mark) for file backends with multiple identifiers set_loghwm(Sink, Handler, Ident, Hwm) when is_integer(Hwm) -> gen_event:call(Sink, {Handler, Ident}, {set_loghwm, Hwm}, infinity). %% @private add_trace_to_loglevel_config(Trace, Sink) -> {MinLevel, Traces} = lager_config:get({Sink, loglevel}), case lists:member(Trace, Traces) of false -> NewTraces = [Trace|Traces], _ = lager_util:trace_filter([ element(1, T) || T <- NewTraces]), lager_config:set({Sink, loglevel}, {MinLevel, [Trace|Traces]}); _ -> ok end. %% @doc recalculate min log level update_loglevel_config(error_logger) -> %% Not a sink under our control, part of the Erlang logging %% utility that error_logger_lager_h attaches to true; update_loglevel_config(Sink) -> {_, Traces} = lager_config:get({Sink, loglevel}, {ignore_me, []}), MinLog = minimum_loglevel(get_loglevels(Sink)), lager_config:set({Sink, loglevel}, {MinLog, Traces}). %% @private minimum_loglevel(Levels) -> lists:foldl(fun({mask, Mask}, Acc) -> Mask bor Acc; (Level, Acc) when is_integer(Level) -> {mask, Mask} = lager_util:config_to_mask(lager_util:num_to_level(Level)), Mask bor Acc; (_, Acc) -> Acc end, 0, Levels). %% @doc Print the format string `Fmt' with `Args' safely with a size %% limit of `Limit'. If the format string is invalid, or not enough %% arguments are supplied 'FORMAT ERROR' is printed with the offending %% 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) 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}]). %% @private Print the format string `Fmt' with `Args' without a size limit. %% This is unsafe because the output of this function is unbounded. %% %% Log messages with unbounded size will kill your application dead as %% OTP mechanisms stuggle to cope with them. So this function is %% intended only for messages which have a reasonable bounded %% size before they're formatted. %% %% If the format string is invalid or not enough arguments are %% supplied a 'FORMAT ERROR' message is printed instead with the %% offending arguments. The caller is NOT crashed. unsafe_format(Fmt, Args) -> try io_lib:format(Fmt, Args) catch _:_ -> io_lib:format("FORMAT ERROR: ~p ~p", [Fmt, Args]) end. %% @doc Print a record lager found during parse transform pr(Record, Module) when is_tuple(Record), is_atom(element(1, Record)) -> pr(Record, Module, []); pr(Record, _) -> Record. %% @doc Print a record lager found during parse transform pr(Record, Module, Options) when is_tuple(Record), is_atom(element(1, Record)), is_list(Options) -> try case is_record_known(Record, Module) of false -> Record; {RecordName, RecordFields} -> {'$lager_record', RecordName, zip(RecordFields, tl(tuple_to_list(Record)), Module, Options, [])} end catch error:undef -> Record end; pr(Record, _, _) -> Record. zip([FieldName|RecordFields], [FieldValue|Record], Module, Options, ToReturn) -> Compress = lists:member(compress, Options), case is_tuple(FieldValue) andalso tuple_size(FieldValue) > 0 andalso is_atom(element(1, FieldValue)) andalso is_record_known(FieldValue, Module) of false when Compress andalso FieldValue =:= undefined -> zip(RecordFields, Record, Module, Options, ToReturn); false -> zip(RecordFields, Record, Module, Options, [{FieldName, FieldValue}|ToReturn]); _Else -> F = {FieldName, pr(FieldValue, Module, Options)}, zip(RecordFields, Record, Module, Options, [F|ToReturn]) end; zip([], [], _Module, _Compress, ToReturn) -> lists:reverse(ToReturn). is_record_known(Record, Module) -> Name = element(1, Record), Attrs = Module:module_info(attributes), case lists:keyfind(lager_records, 1, Attrs) of false -> false; {lager_records, Records} -> case lists:keyfind(Name, 1, Records) of false -> false; {Name, RecordFields} -> case (tuple_size(Record) - 1) =:= length(RecordFields) of false -> false; true -> {Name, RecordFields} end end end. %% @doc Print stacktrace in human readable form pr_stacktrace(Stacktrace) -> Indent = "\n ", lists:foldl( fun(Entry, Acc) -> Acc ++ Indent ++ error_logger_lager_h:format_mfa(Entry) end, [], lists:reverse(Stacktrace)). pr_stacktrace(Stacktrace, {Class, Reason}) -> lists:flatten( pr_stacktrace(Stacktrace) ++ "\n" ++ io_lib:format("~s:~p", [Class, Reason])). rotate_sink(Sink) -> Handlers = lager_config:global_get(handlers), RotateHandlers = lists:filtermap( fun({Handler,_,S}) when S == Sink -> {true, {Handler, Sink}}; (_) -> false end, Handlers), rotate_handlers(RotateHandlers). rotate_all() -> rotate_handlers(lists:map(fun({H,_,S}) -> {H, S} end, lager_config:global_get(handlers))). rotate_handlers(Handlers) -> [ rotate_handler(Handler, Sink) || {Handler, Sink} <- Handlers ]. rotate_handler(Handler) -> Handlers = lager_config:global_get(handlers), case lists:keyfind(Handler, 1, Handlers) of {Handler, _, Sink} -> rotate_handler(Handler, Sink); false -> ok end. rotate_handler(Handler, Sink) -> gen_event:call(Sink, Handler, rotate, ?ROTATE_TIMEOUT). lager-3.1.0/src/lager_backend_throttle.erl0000644000232200023220000000605012652234453021143 0ustar debalancedebalance%% Copyright (c) 2011-2013 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% @doc A simple gen_event backend used to monitor mailbox size and %% switch log messages between synchronous and asynchronous modes. %% A gen_event handler is used because a process getting its own mailbox %% size doesn't involve getting a lock, and gen_event handlers run in their %% parent's process. -module(lager_backend_throttle). -include("lager.hrl"). -behaviour(gen_event). -export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2, code_change/3]). %% %% Allow test code to verify that we're doing the needful. -ifdef(TEST). -define(ETS_TABLE, async_threshold_test). -define(TOGGLE_SYNC(), test_increment(sync_toggled)). -define(TOGGLE_ASYNC(), test_increment(async_toggled)). -else. -define(TOGGLE_SYNC(), true). -define(TOGGLE_ASYNC(), true). -endif. -record(state, { sink :: atom(), hwm :: non_neg_integer(), window_min :: non_neg_integer(), async = true :: boolean() }). init([{sink, Sink}, Hwm, Window]) -> lager_config:set({Sink, async}, true), {ok, #state{sink=Sink, hwm=Hwm, window_min=Hwm - Window}}. handle_call(get_loglevel, State) -> {ok, {mask, ?LOG_NONE}, State}; handle_call({set_loglevel, _Level}, State) -> {ok, ok, State}; handle_call(_Request, State) -> {ok, ok, State}. handle_event({log, _Message},State) -> {message_queue_len, Len} = erlang:process_info(self(), message_queue_len), case {Len > State#state.hwm, Len < State#state.window_min, State#state.async} of {true, _, true} -> %% need to flip to sync mode ?TOGGLE_SYNC(), lager_config:set({State#state.sink, async}, false), {ok, State#state{async=false}}; {_, true, false} -> %% need to flip to async mode ?TOGGLE_ASYNC(), lager_config:set({State#state.sink, async}, true), {ok, State#state{async=true}}; _ -> %% nothing needs to change {ok, State} end; handle_event(_Event, State) -> {ok, State}. handle_info(_Info, State) -> {ok, State}. %% @private terminate(_Reason, _State) -> ok. %% @private code_change(_OldVsn, State, _Extra) -> {ok, State}. -ifdef(TEST). test_get(Key) -> get_default(ets:lookup(?ETS_TABLE, Key)). test_increment(Key) -> ets:insert(?ETS_TABLE, {Key, test_get(Key) + 1}). get_default([]) -> 0; get_default([{_Key, Value}]) -> Value. -endif. lager-3.1.0/src/lager_format.erl0000644000232200023220000004411412652234453017122 0ustar debalancedebalance%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1996-2011-2012. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% 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 :: boolean() }). format(FmtStr, Args, MaxLen) -> format(FmtStr, Args, MaxLen, []). format([], [], _, _) -> ""; format(FmtStr, Args, MaxLen, Opts) when is_atom(FmtStr) -> format(atom_to_list(FmtStr), Args, MaxLen, Opts); format(FmtStr, Args, MaxLen, Opts) when is_binary(FmtStr) -> format(binary_to_list(FmtStr), Args, MaxLen, Opts); format(FmtStr, Args, MaxLen, Opts) when is_list(FmtStr) -> case lager_stdlib:string_p(FmtStr) of true -> Options = make_options(Opts, #options{}), Cs = collect(FmtStr, Args), {Cs2, MaxLen2} = build(Cs, [], MaxLen, Options), %% count how many terms remain {Count, StrLen} = lists:foldl( fun({_C, _As, _F, _Adj, _P, _Pad, _Enc}, {Terms, Chars}) -> {Terms + 1, Chars}; (_, {Terms, Chars}) -> {Terms, Chars + 1} end, {0, 0}, Cs2), build2(Cs2, Count, MaxLen2 - StrLen); false -> erlang:error(badarg) end; format(_FmtStr, _Args, _MaxLen, _Opts) -> erlang:error(badarg). collect([$~|Fmt0], Args0) -> {C,Fmt1,Args1} = collect_cseq(Fmt0, Args0), [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}]), {Term, lists:flatlength(Term)}; control2($W, [A,Depth], F, Adj, P, Pad, _Enc, L) when is_integer(Depth) -> Term = lager_trunc_io:fprint(A, L, [{depth, Depth}, {lists_as_strings, false}]), Res = term(Term, F, Adj, P, Pad), {Res, lists:flatlength(Res)}; control2($P, [A,Depth], _F, _Adj, _P, _Pad, _Enc, L) when is_integer(Depth) -> Term = lager_trunc_io:fprint(A, L, [{depth, Depth}, {lists_as_strings, true}]), {Term, lists:flatlength(Term)}; control2($s, [L0], F, Adj, P, Pad, latin1, L) -> List = lager_trunc_io:fprint(maybe_flatten(L0), L, [{force_strings, true}]), Res = string(List, F, Adj, P, Pad), {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, R) -> flat_trunc(T, N-1, [H|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([]) -> []. lager-3.1.0/src/lager_transform.erl0000644000232200023220000003517212652234453017651 0ustar debalancedebalance%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% @doc 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 lager_config '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]). %% @private parse_transform(AST, Options) -> TruncSize = proplists:get_value(lager_truncation_size, Options, ?DEFAULT_TRUNCATION), Enable = proplists:get_value(lager_print_records_flag, Options, true), Sinks = [lager] ++ proplists:get_value(lager_extra_sinks, Options, []), put(print_records_flag, Enable), put(truncation_size, TruncSize), put(sinks, Sinks), erlang:put(records, []), %% .app file should either be in the outdir, or the same dir as the source file guess_application(proplists:get_value(outdir, Options), hd(AST)), walk_ast([], AST). walk_ast(Acc, []) -> case get(print_records_flag) of true -> insert_record_attribute(Acc); false -> lists:reverse(Acc) end; walk_ast(Acc, [{attribute, _, module, {Module, _PmodArgs}}=H|T]) -> %% A wild parameterized module appears! put(module, Module), 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, [{attribute, _, record, {Name, Fields}}=H|T]) -> FieldNames = lists:map(fun({record_field, _, {atom, _, FieldName}}) -> FieldName; ({record_field, _, {atom, _, FieldName}, _Default}) -> FieldName end, Fields), stash_record({Name, FieldNames}), walk_ast([H|Acc], T); walk_ast(Acc, [H|T]) -> walk_ast([H|Acc], T). 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, get(sinks))|Acc], T). transform_statement({call, Line, {remote, _Line1, {atom, _Line2, Module}, {atom, _Line3, Function}}, Arguments0} = Stmt, Sinks) -> case lists:member(Module, Sinks) of true -> case lists:member(Function, ?LEVELS) of true -> SinkName = lager_util:make_internal_sink_name(Module), do_transform(Line, SinkName, Function, Arguments0); false -> case lists:keyfind(Function, 1, ?LEVELS_UNSAFE) of {Function, Severity} -> SinkName = lager_util:make_internal_sink_name(Module), do_transform(Line, SinkName, Severity, Arguments0, unsafe); false -> Stmt end end; false -> list_to_tuple(transform_statement(tuple_to_list(Stmt), Sinks)) end; transform_statement(Stmt, Sinks) when is_tuple(Stmt) -> list_to_tuple(transform_statement(tuple_to_list(Stmt), Sinks)); transform_statement(Stmt, Sinks) when is_list(Stmt) -> [transform_statement(S, Sinks) || S <- Stmt]; transform_statement(Stmt, _Sinks) -> Stmt. do_transform(Line, SinkName, Severity, Arguments0) -> do_transform(Line, SinkName, Severity, Arguments0, safe). do_transform(Line, SinkName, Severity, Arguments0, Safety) -> SeverityAsInt=lager_util:level_to_num(Severity), DefaultAttrs0 = {cons, Line, {tuple, Line, [ {atom, Line, module}, {atom, Line, get(module)}]}, {cons, Line, {tuple, Line, [ {atom, Line, function}, {atom, Line, get(function)}]}, {cons, Line, {tuple, Line, [ {atom, Line, line}, {integer, Line, Line}]}, {cons, Line, {tuple, Line, [ {atom, Line, pid}, {call, Line, {atom, Line, pid_to_list}, [ {call, Line, {atom, Line ,self}, []}]}]}, {cons, Line, {tuple, Line, [ {atom, Line, node}, {call, Line, {atom, Line, node}, []}]}, %% get the metadata with lager:md(), this will always return a list so we can use it as the tail here {call, Line, {remote, Line, {atom, Line, lager}, {atom, Line, md}}, []}}}}}}, %{nil, Line}}}}}}}, DefaultAttrs = case erlang:get(application) of undefined -> DefaultAttrs0; App -> %% stick the application in the attribute list concat_lists({cons, Line, {tuple, Line, [ {atom, Line, application}, {atom, Line, App}]}, {nil, Line}}, DefaultAttrs0) end, {Meta, Message, Arguments} = case Arguments0 of [Format] -> {DefaultAttrs, Format, {atom, Line, none}}; [Arg1, Arg2] -> %% some ambiguity here, figure out if these arguments are %% [Format, Args] or [Attr, Format]. %% The trace attributes will be a list of tuples, so check %% for that. case {element(1, Arg1), Arg1} of {_, {cons, _, {tuple, _, _}, _}} -> {concat_lists(Arg1, DefaultAttrs), Arg2, {atom, Line, none}}; {Type, _} when Type == var; Type == lc; Type == call; Type == record_field -> %% crap, its not a literal. look at the second %% argument to see if it is a string case Arg2 of {string, _, _} -> {concat_lists(Arg1, DefaultAttrs), Arg2, {atom, Line, none}}; _ -> %% not a string, going to have to guess %% it's the argument list {DefaultAttrs, Arg1, Arg2} end; _ -> {DefaultAttrs, Arg1, Arg2} end; [Attrs, Format, Args] -> {concat_lists(Attrs, DefaultAttrs), Format, Args} end, %% Generate some unique variable names so we don't accidentally export from case clauses. %% Note that these are not actual atoms, but the AST treats variable names as atoms. LevelVar = make_varname("__Level", Line), TracesVar = make_varname("__Traces", Line), PidVar = make_varname("__Pid", Line), LogFun = case Safety of safe -> do_log; unsafe -> do_log_unsafe end, %% Wrap the call to lager:dispatch_log/6 in case that will avoid doing any work if this message is not elegible for logging %% See lager.erl (lines 89-100) for lager:dispatch_log/6 %% case {whereis(Sink), whereis(?DEFAULT_SINK), lager_config:get({Sink, loglevel}, {?LOG_NONE, []})} of {'case',Line, {tuple,Line, [{call,Line,{atom,Line,whereis},[{atom,Line,SinkName}]}, {call,Line,{atom,Line,whereis},[{atom,Line,?DEFAULT_SINK}]}, {call,Line, {remote,Line,{atom,Line,lager_config},{atom,Line,get}}, [{tuple,Line,[{atom,Line,SinkName},{atom,Line,loglevel}]}, {tuple,Line,[{integer,Line,0},{nil,Line}]}]}]}, %% {undefined, undefined, _} -> {error, lager_not_running}; [{clause,Line, [{tuple,Line, [{atom,Line,undefined},{atom,Line,undefined},{var,Line,'_'}]}], [], %% trick the linter into avoiding a 'term constructed but not used' error: %% (fun() -> {error, lager_not_running} end)() [{call, Line, {'fun', Line, {clauses, [{clause, Line, [],[], [{tuple, Line, [{atom, Line, error},{atom, Line, lager_not_running}]}]}]}}, []}] }, %% {undefined, _, _} -> {error, {sink_not_configured, Sink}}; {clause,Line, [{tuple,Line, [{atom,Line,undefined},{var,Line,'_'},{var,Line,'_'}]}], [], %% same trick as above to avoid linter error [{call, Line, {'fun', Line, {clauses, [{clause, Line, [],[], [{tuple,Line, [{atom,Line,error}, {tuple,Line,[{atom,Line,sink_not_configured},{atom,Line,SinkName}]}]}]}]}}, []}] }, %% {SinkPid, _, {Level, Traces}} when ... -> lager:do_log/9; {clause,Line, [{tuple,Line, [{var,Line,PidVar}, {var,Line,'_'}, {tuple,Line,[{var,Line,LevelVar},{var,Line,TracesVar}]}]}], [[{op, Line, 'orelse', {op, Line, '/=', {op, Line, 'band', {var, Line, LevelVar}, {integer, Line, SeverityAsInt}}, {integer, Line, 0}}, {op, Line, '/=', {var, Line, TracesVar}, {nil, Line}}}]], [{call,Line,{remote, Line, {atom, Line, lager}, {atom, Line, LogFun}}, [{atom,Line,Severity}, Meta, Message, Arguments, {integer, Line, get(truncation_size)}, {integer, Line, SeverityAsInt}, {var, Line, LevelVar}, {var, Line, TracesVar}, {atom, Line, SinkName}, {var, Line, PidVar}]}]}, %% _ -> ok {clause,Line,[{var,Line,'_'}],[],[{atom,Line,ok}]}]}. make_varname(Prefix, Line) -> list_to_atom(Prefix ++ atom_to_list(get(module)) ++ integer_to_list(Line)). %% concat 2 list ASTs by replacing the terminating [] in A with the contents of B concat_lists({var, Line, _Name}=Var, B) -> %% concatenating a var with a cons {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}}, [{cons, Line, Var, B}]}; concat_lists({lc, Line, _Body, _Generator} = LC, B) -> %% concatenating a LC with a cons {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}}, [{cons, Line, LC, B}]}; concat_lists({call, Line, _Function, _Args} = Call, B) -> %% concatenating a call with a cons {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}}, [{cons, Line, Call, B}]}; concat_lists({record_field, Line, _Var, _Record, _Field} = Rec, B) -> %% concatenating a record_field with a cons {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}}, [{cons, Line, Rec, B}]}; concat_lists({nil, _Line}, B) -> B; concat_lists({cons, Line, Element, Tail}, B) -> {cons, Line, Element, concat_lists(Tail, B)}. stash_record(Record) -> Records = case erlang:get(records) of undefined -> []; R -> R end, erlang:put(records, [Record|Records]). insert_record_attribute(AST) -> lists:foldl(fun({attribute, Line, module, _}=E, Acc) -> [E, {attribute, Line, lager_records, erlang:get(records)}|Acc]; (E, Acc) -> [E|Acc] end, [], AST). guess_application(Dirname, Attr) when Dirname /= undefined -> case find_app_file(Dirname) of no_idea -> %% try it based on source file directory (app.src most likely) guess_application(undefined, Attr); _ -> ok end; guess_application(undefined, {attribute, _, file, {Filename, _}}) -> Dir = filename:dirname(Filename), find_app_file(Dir); guess_application(_, _) -> ok. find_app_file(Dir) -> case filelib:wildcard(Dir++"/*.{app,app.src}") of [] -> no_idea; [File] -> case file:consult(File) of {ok, [{application, Appname, _Attributes}|_]} -> erlang:put(application, Appname); _ -> no_idea end; _ -> %% multiple files, uh oh no_idea end. lager-3.1.0/src/lager_common_test_backend.erl0000644000232200023220000000776012652234453021636 0ustar debalancedebalance-module(lager_common_test_backend). -behavior(gen_event). %% gen_event callbacks -export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2, code_change/3]). -export([get_logs/0, bounce/0, bounce/1]). %% holds the log messages for retreival on terminate -record(state, {level :: {mask, integer()}, formatter :: atom(), format_config :: any(), log = [] :: list()}). -include("lager.hrl"). -define(TERSE_FORMAT,[time, " ", color, "[", severity,"] ", message]). %% @doc Before every test, just %% lager_common_test_backend:bounce(Level) with the log level of your %% choice. Every message will be passed along to ct:pal for your %% viewing in the common_test reports. Also, you can call %% lager_common_test_backend:get_logs/0 to get a list of all log %% messages this backend has received during your test. You can then %% search that list for expected log messages. -spec get_logs() -> [iolist()] | {error, term()}. get_logs() -> gen_event:call(lager_event, ?MODULE, get_logs, infinity). bounce() -> bounce(error). bounce(Level) -> _ = application:stop(lager), application:set_env(lager, suppress_application_start_stop, true), application:set_env(lager, handlers, [ {lager_common_test_backend, [Level, false]} ]), ok = lager:start(), %% we care more about getting all of our messages here than being %% careful with the amount of memory that we're using. error_logger_lager_h:set_high_water(100000), ok. -spec(init(integer()|atom()|[term()]) -> {ok, #state{}} | {error, atom()}). %% @private %% @doc Initializes the event handler init([Level, true]) -> % for backwards compatibility init([Level,{lager_default_formatter,[{eol, "\n"}]}]); init([Level,false]) -> % for backwards compatibility init([Level,{lager_default_formatter,?TERSE_FORMAT ++ ["\n"]}]); init([Level,{Formatter,FormatterConfig}]) when is_atom(Formatter) -> case lists:member(Level, ?LEVELS) of true -> {ok, #state{level=lager_util:config_to_mask(Level), formatter=Formatter, format_config=FormatterConfig}}; _ -> {error, bad_log_level} end; init(Level) -> init([Level,{lager_default_formatter,?TERSE_FORMAT ++ ["\n"]}]). -spec(handle_event(tuple(), #state{}) -> {ok, #state{}}). %% @private handle_event({log, Message}, #state{level=L,formatter=Formatter,format_config=FormatConfig,log=Logs} = State) -> case lager_util:is_loggable(Message,L,?MODULE) of true -> Log = Formatter:format(Message,FormatConfig), ct:pal(Log), {ok, State#state{log=[Log|Logs]}}; false -> {ok, State} end; handle_event(Event, State) -> ct:pal(Event), {ok, State#state{log = [Event|State#state.log]}}. -spec(handle_call(any(), #state{}) -> {ok, any(), #state{}}). %% @private %% @doc gets and sets loglevel. This is part of the lager backend api. handle_call(get_loglevel, #state{level=Level} = State) -> {ok, Level, State}; handle_call({set_loglevel, Level}, State) -> case lists:member(Level, ?LEVELS) of true -> {ok, ok, State#state{level=lager_util:config_to_mask(Level)}}; _ -> {ok, {error, bad_log_level}, State} end; handle_call(get_logs, #state{log = Logs} = State) -> {ok, lists:reverse(Logs), State}; handle_call(_, State) -> {ok, ok, State}. -spec(handle_info(any(), #state{}) -> {ok, #state{}}). %% @private %% @doc gen_event callback, does nothing. handle_info(_, State) -> {ok, State}. -spec(code_change(any(), #state{}, any()) -> {ok, #state{}}). %% @private %% @doc gen_event callback, does nothing. code_change(_OldVsn, State, _Extra) -> {ok, State}. -spec(terminate(any(), #state{}) -> {ok, list()}). %% @doc gen_event callback, does nothing. terminate(_Reason, #state{log=Logs}) -> {ok, lists:reverse(Logs)}. lager-3.1.0/src/lager_crash_log.erl0000644000232200023220000003541012652234453017572 0ustar debalancedebalance%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% @doc 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 :: string(), fd :: pid(), inode :: integer(), fmtmaxbytes :: integer(), size :: integer(), date :: undefined | string(), count :: integer(), flap=false :: boolean() }). %% @private start_link(Filename, MaxBytes, Size, Date, Count) -> 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([RelFilename, MaxBytes, Size, Date, Count]) -> Filename = lager_util:expand_path(RelFilename), case lager_util:open_logfile(Filename, false) of {ok, {FD, Inode, _}} -> schedule_rotation(Date), {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) -> ok; schedule_rotation(Date) -> erlang:send_after(lager_util:calculate_next_rotation(Date) * 1000, self(), rotate), ok. %% ===== 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) ++ "\n", 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, unicode:characters_to_binary(Msg)) of {error, Reason} when Flap == false -> ?INT_LOG(error, "Failed to write log message to file ~s: ~s", [Name, file:format_error(Reason)]), {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), lager:start(), 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), application:stop(goldrush), 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,_Metadata} = 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,_Metadata} = 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,_Metadata} = lager_test_backend:pop(), ?assertEqual("Failed to open crash log file crash_test.log with error: permission denied", lists:flatten(Message)), file:write_file_info("crash_test.log", FInfo#file_info{mode = OldPerms}), sync_error_logger:error_msg("Test message~n"), _ = 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. lager-3.1.0/Makefile0000644000232200023220000000054512652234453014625 0ustar debalancedebalance.PHONY: all compile deps clean distclean test check_plt build_plt dialyzer \ cleanplt all: deps compile compile: deps ./rebar compile deps: test -d deps || ./rebar get-deps clean: ./rebar clean distclean: clean ./rebar delete-deps DIALYZER_APPS = kernel stdlib erts sasl eunit syntax_tools compiler crypto \ common_test include tools.mk