commit 7bcf044f1a43dbbf0a06ce795b80c7bcbdbaec15 Author: Andrew Thompson Date: Fri Jun 24 13:31:20 2011 -0400 Initial import diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e454a52 --- /dev/null +++ b/LICENSE @@ -0,0 +1,178 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..af2844b --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +all: + ./rebar compile + +clean: + ./rebar clean + +test: all + ./rebar eunit diff --git a/README.org b/README.org new file mode 100644 index 0000000..024917e --- /dev/null +++ b/README.org @@ -0,0 +1,41 @@ +* Overview + Lager (pronounced lAAger) is a logging framework for Erlang. Its purpose is + to provide a more traditional way to perform logging in an erlang application + that plays nicely with traditional UNIX logging tools like logrotate and + syslog. + + Features + - Finer grained log levels (debug, info, notice, warning, error, critical, + alert, emergency) + - Logger calls are transformed using a parse transform to allow capturing + Module/Function/Line/Pid information + - When no handler is consuming a log level (eg. debug) no event is even sent + to the log handler + - Supports multiple backends, including console, file and syslog. + +* 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 + + {parse_transform, lager_transform} + + Alternately, you can add it to the module you which to compile with logging + enabled: + + -compile([{parse_transform, lager_transform}]). + + Once you have built your code with lager, you can then generate log messages + by doing the following: + + lager:error("Some message") + + Or: + + 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 + TODO diff --git a/rebar b/rebar new file mode 100755 index 0000000..e048a8a Binary files /dev/null and b/rebar differ diff --git a/rebar.config b/rebar.config new file mode 100644 index 0000000..06012b0 --- /dev/null +++ b/rebar.config @@ -0,0 +1,2 @@ + +{erl_opts, [debug_info, fail_on_warning]}. diff --git a/src/lager.app.src b/src/lager.app.src new file mode 100644 index 0000000..23f69d2 --- /dev/null +++ b/src/lager.app.src @@ -0,0 +1,18 @@ +%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +{application, lager, + [ + {description, "Custom error handler"}, + {vsn, "1.0.0"}, + {modules, [ + ]}, + {applications, [ + kernel, + stdlib, + sasl + ]}, + {registered, []}, + {mod, {riak_err_app, []}}, + {env, [ + ]} + ]}. diff --git a/src/lager.erl b/src/lager.erl new file mode 100644 index 0000000..2addbe6 --- /dev/null +++ b/src/lager.erl @@ -0,0 +1,109 @@ +%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. + +-module(lager). + +-behaviour(gen_server). + +-export([start_link/0,start/0,sasl_log/3, log/7, log/8]). + +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, + code_change/3]). + +-record(state, {event_pid, handler_loglevels}). + +%% API + +start_link() -> + %application:start(sasl), + %application:set_env(riak_err, print_fun, {lager, sasl_log}), + %application:start(riak_err), + Handlers = case application:get_env(lager, handlers) of + undefined -> + [{lager_console_backend, [info]}]; + Val -> + Val + end, + gen_server:start_link({local, ?MODULE}, ?MODULE, [Handlers], []). + +start() -> + Handlers = case application:get_env(lager, handlers) of + undefined -> + [{lager_console_backend, [info]}, + {lager_file_backend, [{"error.log", error}, {"console.log", info}]}]; + Val -> + Val + end, + gen_server:start({local, ?MODULE}, ?MODULE, [Handlers], []). + +sasl_log(ReportStr, Pid, Message) -> + %Level = reportstr_to_level(ReportStr), + {{Y, M, D}, {H, Mi, S}} = riak_err_stdlib:maybe_utc(erlang:localtime()), + Msg = re:replace(binary:replace(Message, [<<"\r">>, <<"\n">>], <<>>, + [global]), " +", " ", [global, {return, binary}]), + io:format("~b-~b-~b ~b:~b:~b ~p ~s ~s~n", [Y, M, + D, H, Mi, S, Pid, ReportStr, Msg]). + +log(Level, Module, Function, Line, Pid, {{Y, M, D}, {H, Mi, S}}, Message) -> + Time = io_lib:format("~b-~b-~b ~b:~b:~b", [Y, M, D, H, Mi, S]), + Msg = io_lib:format("[~p] ~p@~p:~p:~p ~s", [Level, Pid, Module, + Function, Line, Message]), + gen_event:notify(lager_event, {log, lager_util:level_to_num(Level), Time, Msg}). + +log(Level, Module, Function, Line, Pid, {{Y, M, D}, {H, Mi, S}}, Format, Args) -> + Time = io_lib:format("~b-~b-~b ~b:~b:~b", [Y, M, D, H, Mi, S]), + Msg = io_lib:format("[~p] ~p@~p:~p:~p ~s", [Level, Pid, Module, + Function, Line, io_lib:format(Format, Args)]), + gen_event:notify(lager_event, {log, lager_util:level_to_num(Level), Time, Msg}). + +%% gen_server callbacks + +init([Handlers]) -> + %% start a gen_event linked to this process + gen_event:start_link({local, lager_event}), + %% spin up all the defined handlers + [gen_event:add_sup_handler(lager_event, Module, Args) || {Module, Args} <- Handlers], + MinLog = minimum_log_level(get_log_levels()), + lager_mochiglobal:put(loglevel, MinLog), + {ok, #state{}}. + +handle_call(_Request, _From, State) -> + {reply, ok, State}. + +handle_cast(_Request, State) -> + {noreply, State}. + +handle_info(Info, State) -> + io:format("got info ~p~n", [Info]), + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + + +%% internal functions + +get_log_levels() -> + [gen_event:call(lager_event, Handler, get_loglevel) || + Handler <- gen_event:which_handlers(lager_event)]. + +minimum_log_level([]) -> + 9; %% higher than any log level, logging off +minimum_log_level(Levels) -> + erlang:hd(lists:sort(lists:map(fun(Level) -> lager_util:level_to_num(Level) end, Levels))). diff --git a/src/lager_console_backend.erl b/src/lager_console_backend.erl new file mode 100644 index 0000000..46a8e07 --- /dev/null +++ b/src/lager_console_backend.erl @@ -0,0 +1,47 @@ +%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. + +-module(lager_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, nlevel}). + +init([Level]) -> + {ok, #state{level=Level, nlevel=lager_util:level_to_num(Level)}}. + +handle_call(get_loglevel, #state{level=Level} = State) -> + {ok, Level, State}; +handle_call(_Request, State) -> + {ok, ok, State}. + +handle_event({log, Level, Time, Message}, #state{nlevel=LogLevel} = State) when Level >= LogLevel -> + io:put_chars([Time, " ", Message, "\n"]), + {ok, State}; +handle_event(_Event, State) -> + {ok, State}. + +handle_info(_Info, State) -> + {ok, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. diff --git a/src/lager_file_backend.erl b/src/lager_file_backend.erl new file mode 100644 index 0000000..79bf90e --- /dev/null +++ b/src/lager_file_backend.erl @@ -0,0 +1,165 @@ +%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. + +-module(lager_file_backend). + +-behaviour(gen_event). + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. + +-include_lib("kernel/include/file.hrl"). + +-compile([{parse_transform, lager_transform}]). + +-export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2, + code_change/3]). + +-record(state, {files}). + +init(LogFiles) -> + Files = [begin + case open(Name) of + {ok, {FD, Inode}} -> + {Name, lager_util:level_to_num(Level), FD, Inode}; + Error -> + lager:error("Failed to open log file ~s with error ~p", + [Name, Error]), + undefined + end + end || + {Name, Level} <- LogFiles], + {ok, #state{files=Files}}. + +handle_call(get_loglevel, #state{files=Files} = State) -> + Result = lists:foldl(fun({_, Level, _, _}, L) -> erlang:min(Level, L); + (_, L) -> L end, 9, + Files), + {ok, Result, State}; +handle_call(_Request, State) -> + {ok, ok, State}. + +handle_event({log, Level, Time, Message}, #state{files=Files} = State) -> + NewFiles = lists:map( + fun({_, L, _, _} = File) when Level >= L -> + write(File, Level, [Time, " ", Message, "\n"]); + (File) -> + File + end, Files), + {ok, State#state{files=NewFiles}}; +handle_event(_Event, State) -> + {ok, State}. + +handle_info(_Info, State) -> + {ok, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +open(Name) -> + case file:open(Name, [append, delayed_write, raw]) of + {ok, FD} -> + case file:read_file_info(Name) of + {ok, FInfo} -> + Inode = FInfo#file_info.inode, + {ok, {FD, Inode}}; + X -> X + end; + Y -> Y + end. + +write({Name, L, FD, Inode}, Level, Msg) -> + Result = case file:read_file_info(Name) of + {ok, FInfo} -> + Inode2 = FInfo#file_info.inode, + case Inode == Inode2 of + true -> + {FD, Inode}; + false -> + case open(Name) of + {ok, {FD2, Inode3}} -> + %% inode changed, file was probably moved and + %% recreated + {FD2, Inode3}; + Error -> + lager:error("Failed to reopen file ~s with error ~p", + [Name, Error]), + undefined + end + end; + _ -> + case open(Name) of + {ok, {FD2, Inode3}} -> + %% file was removed + {FD2, Inode3}; + Error -> + lager:error("Failed to reopen file ~s with error ~p", + [Name, Error]), + undefined + end + end, + case Result of + {NewFD, NewInode} -> + file:write(NewFD, Msg), + case Level of + _ when Level >= 4 -> + %% force a sync on any message at error severity or above + file:datasync(NewFD); + _ -> ok + end, + {Name, L, NewFD, NewInode}; + _ -> + undefined + end. + + +-ifdef(TEST). + +get_loglevel_test() -> + {ok, Level, _} = handle_call(get_loglevel, + #state{files=[ + {"foo", lager_util:level_to_num(warning), 0, 0}, + {"bar", lager_util:level_to_num(info), 0, 0}]}), + ?assertEqual(Level, lager_util:level_to_num(info)), + {ok, Level2, _} = handle_call(get_loglevel, + #state{files=[ + {"foo", lager_util:level_to_num(warning), 0, 0}, + {"foo", lager_util:level_to_num(critical), 0, 0}, + {"bar", lager_util:level_to_num(error), 0, 0}]}), + ?assertEqual(Level2, lager_util:level_to_num(warning)). + +rotation_test() -> + {ok, {FD, Inode}} = open("test.log"), + ?assertEqual({"test.log", 0, FD, Inode}, + write({"test.log", 0, FD, Inode}, 0, "hello world")), + file:delete("test.log"), + Result = write({"test.log", 0, FD, Inode}, 0, "hello world"), + %% assert file has changed + ?assert({"test.log", 0, FD, Inode} =/= Result), + ?assertMatch({"test.log", 0, _, _}, Result), + file:rename("test.log", "test.log.1"), + Result2 = write(Result, 0, "hello world"), + %% assert file has changed + ?assert(Result =/= Result2), + ?assertMatch({"test.log", 0, _, _}, Result2), + ok. + +-endif. + diff --git a/src/lager_mochiglobal.erl b/src/lager_mochiglobal.erl new file mode 100644 index 0000000..51b185d --- /dev/null +++ b/src/lager_mochiglobal.erl @@ -0,0 +1,107 @@ +%% @author Bob Ippolito +%% @copyright 2010 Mochi Media, Inc. +%% @doc Abuse module constant pools as a "read-only shared heap" (since erts 5.6) +%% [1]. +-module(lager_mochiglobal). +-author("Bob Ippolito "). +-export([get/1, get/2, put/2, delete/1]). + +-spec get(atom()) -> any() | undefined. +%% @equiv get(K, undefined) +get(K) -> + get(K, undefined). + +-spec get(atom(), T) -> any() | T. +%% @doc Get the term for K or return Default. +get(K, Default) -> + get(K, Default, key_to_module(K)). + +get(_K, Default, Mod) -> + try Mod:term() + catch error:undef -> + Default + end. + +-spec put(atom(), any()) -> ok. +%% @doc Store term V at K, replaces an existing term if present. +put(K, V) -> + put(K, V, key_to_module(K)). + +put(_K, V, Mod) -> + Bin = compile(Mod, V), + code:purge(Mod), + {module, Mod} = code:load_binary(Mod, atom_to_list(Mod) ++ ".erl", Bin), + ok. + +-spec delete(atom()) -> boolean(). +%% @doc Delete term stored at K, no-op if non-existent. +delete(K) -> + delete(K, key_to_module(K)). + +delete(_K, Mod) -> + code:purge(Mod), + code:delete(Mod). + +-spec key_to_module(atom()) -> atom(). +key_to_module(K) -> + list_to_atom("mochiglobal:" ++ atom_to_list(K)). + +-spec compile(atom(), any()) -> binary(). +compile(Module, T) -> + {ok, Module, Bin} = compile:forms(forms(Module, T), + [verbose, report_errors]), + Bin. + +-spec forms(atom(), any()) -> [erl_syntax:syntaxTree()]. +forms(Module, T) -> + [erl_syntax:revert(X) || X <- term_to_abstract(Module, term, T)]. + +-spec term_to_abstract(atom(), atom(), any()) -> [erl_syntax:syntaxTree()]. +term_to_abstract(Module, Getter, T) -> + [%% -module(Module). + erl_syntax:attribute( + erl_syntax:atom(module), + [erl_syntax:atom(Module)]), + %% -export([Getter/0]). + erl_syntax:attribute( + erl_syntax:atom(export), + [erl_syntax:list( + [erl_syntax:arity_qualifier( + erl_syntax:atom(Getter), + erl_syntax:integer(0))])]), + %% Getter() -> T. + erl_syntax:function( + erl_syntax:atom(Getter), + [erl_syntax:clause([], none, [erl_syntax:abstract(T)])])]. + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +get_put_delete_test() -> + K = '$$test$$mochiglobal', + delete(K), + ?assertEqual( + bar, + get(K, bar)), + try + ?MODULE:put(K, baz), + ?assertEqual( + baz, + get(K, bar)), + ?MODULE:put(K, wibble), + ?assertEqual( + wibble, + ?MODULE:get(K)) + after + delete(K) + end, + ?assertEqual( + bar, + get(K, bar)), + ?assertEqual( + undefined, + ?MODULE:get(K)), + ok. +-endif. diff --git a/src/lager_transform.erl b/src/lager_transform.erl new file mode 100644 index 0000000..db8d20b --- /dev/null +++ b/src/lager_transform.erl @@ -0,0 +1,120 @@ +%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. + +-module(lager_transform). +-export([parse_transform/2]). + +-define(LEVELS, [debug, info, notice, warning, error, critical, alert, + emergency]). + +%% This parse transform rewrites functions calls to lager:Severity/1,2 into +%% a more complicated function that captures module, function, line, pid and +%% time as well. The entire function call is then wrapped in a case that +%$ checks the mochiglobal 'loglevel' value, so the code isn't executed if +%% nothing wishes to consume the message. + +parse_transform(AST, _Options) -> + %io:format("~n~p~n", [AST]), + walk_ast([], AST). + +walk_ast(Acc, []) -> + lists:reverse(Acc); +walk_ast(Acc, [{attribute, _, module, Module}=H|T]) -> + put(module, Module), + walk_ast([H|Acc], T); +walk_ast(Acc, [{function, Line, Name, Arity, Clauses}|T]) -> + put(function, Name), + walk_ast([{function, Line, Name, Arity, + walk_clauses([], Clauses)}|Acc], T); +walk_ast(Acc, [H|T]) -> + walk_ast([H|Acc], T). + +walk_clauses(Acc, []) -> + lists:reverse(Acc); +walk_clauses(Acc, [{clause, Line, Arguments, Guards, Body}|T]) -> + walk_clauses([{clause, Line, Arguments, Guards, walk_body([], Body)}|Acc], T). + +walk_body(Acc, []) -> + lists:reverse(Acc); +walk_body(Acc, [H|T]) -> + walk_body([transform_statement(H)|Acc], T). + +transform_statement({call, Line, {remote, Line1, {atom, Line2, lager}, + {atom, Line3, Severity}}, Arguments} = Stmt) -> + case lists:member(Severity, ?LEVELS) of + true -> + %io:format("call to lager ~p on line ~p in function ~p in module ~p~n", + %[Severity, Line, get(function), get(module)]), + %% a case to check the mochiglobal 'loglevel' key against the + %% message we're trying to log + {'case',Line, + {op,Line,'=<', + {call,Line, + {remote,Line,{atom,Line,lager_util},{atom,Line,level_to_num}}, + [{call,Line, + {remote,Line,{atom,Line,lager_mochiglobal},{atom,Line,get}}, + [{atom,Line,loglevel}]}]}, + {call,Line, + {remote,Line,{atom,Line,lager_util},{atom,Line,level_to_num}}, + [{atom,Line,Severity}]}}, + [{clause,Line, + [{atom,Line,true}], %% yes, we log! + [], + [{call, Line, {remote, Line1, {atom, Line2, lager}, + {atom, Line3, log}}, [ + {atom, Line3, Severity}, + {atom, Line3, get(module)}, + {atom, Line3, get(function)}, + {integer, Line3, Line}, + {call, Line3, {atom, Line3 ,self}, []}, + {call, Line3, {remote, Line3, + {atom, Line3 ,riak_err_stdlib}, + {atom,Line3,maybe_utc}}, + [{call,Line3,{remote,Line3, + {atom,Line3,erlang}, + {atom,Line3,localtime}},[]}]} + | Arguments + ]}]}, + %% No, don't log + {clause,Line3,[{var,Line3,'_'}],[],[{atom,Line3,ok}]}]}; + false -> + %io:format("skipping non-log lager call ~p~n", [Severity]), + Stmt + end; +transform_statement({call, Line, {remote, Line1, {atom, Line2, boston_lager}, + {atom, Line3, Severity}}, Arguments}) -> + NewArgs = case Arguments of + [{string, L, Msg}] -> [{string, L, re:replace(Msg, "r", "h", [{return, list}, global])}]; + [{string, L, Format}, Args] -> [{string, L, re:replace(Format, "r", "h", [{return, list}, global])}, Args]; + Other -> Other + end, + transform_statement({call, Line, {remote, Line1, {atom, Line2, lager}, + {atom, Line3, Severity}}, NewArgs}); +transform_statement({'case', Line, Expr, Clauses}) -> + {'case', Line, Expr, walk_clauses([], Clauses)}; +transform_statement({'if', Line, Clauses}) -> + {'if', Line, walk_clauses([], Clauses)}; +transform_statement({block, Line, Body}) -> + {block, Line, walk_body([], Body)}; +transform_statement({lc, Line, Expression, Generator}) -> + {lc, Line, transform_statement(Expression), Generator}; +transform_statement({match, Line, Var, Expression}) -> + {match, Line, Var, transform_statement(Expression)}; +transform_statement(Stmt) -> + Stmt. + + + diff --git a/src/lager_util.erl b/src/lager_util.erl new file mode 100644 index 0000000..7460ba2 --- /dev/null +++ b/src/lager_util.erl @@ -0,0 +1,41 @@ +%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. + +-module(lager_util). + +-export([levels/0, level_to_num/1]). + +levels() -> + [debug, info, notice, warning, error, critical, alert, emergency]. + +level_to_num(debug) -> + 0; +level_to_num(info) -> + 1; +level_to_num(notice) -> + 2; +level_to_num(warning) -> + 3; +level_to_num(error) -> + 4; +level_to_num(critical) -> + 5; +level_to_num(alert) -> + 6; +level_to_num(emergency) -> + 7; +level_to_num(_) -> + 0.