diff options
Diffstat (limited to 'deps/lager/src/lager_transform.erl')
-rw-r--r-- | deps/lager/src/lager_transform.erl | 442 |
1 files changed, 242 insertions, 200 deletions
diff --git a/deps/lager/src/lager_transform.erl b/deps/lager/src/lager_transform.erl index 7cbe499..aa5e6d7 100644 --- a/deps/lager/src/lager_transform.erl +++ b/deps/lager/src/lager_transform.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. +%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file @@ -18,7 +18,7 @@ %% This parse transform rewrites functions calls to lager:Severity/1,2 into %% a more complicated function that captures module, function, line, pid and %% time as well. The entire function call is then wrapped in a case that -%% checks the mochiglobal 'loglevel' value, so the code isn't executed if +%% checks the lager_config 'loglevel' value, so the code isn't executed if %% nothing wishes to consume the message. -module(lager_transform). @@ -26,20 +26,27 @@ -include("lager.hrl"). -export([parse_transform/2]). --export([format_error/1]). %% @private -parse_transform(AST, _Options) -> - try walk_ast([], AST) of - Forms -> Forms - catch - throw:E -> - E - end. - +parse_transform(AST, Options) -> + TruncSize = proplists:get_value(lager_truncation_size, Options, ?DEFAULT_TRUNCATION), + Enable = proplists:get_value(lager_print_records_flag, Options, true), + Sinks = [lager] ++ proplists:get_value(lager_extra_sinks, Options, []), + put(print_records_flag, Enable), + put(truncation_size, TruncSize), + put(sinks, Sinks), + erlang:put(records, []), + %% .app file should either be in the outdir, or the same dir as the source file + guess_application(proplists:get_value(outdir, Options), hd(AST)), + walk_ast([], AST). walk_ast(Acc, []) -> - lists:reverse(Acc); + case get(print_records_flag) of + true -> + insert_record_attribute(Acc); + false -> + lists:reverse(Acc) + end; walk_ast(Acc, [{attribute, _, module, {Module, _PmodArgs}}=H|T]) -> %% A wild parameterized module appears! put(module, Module), @@ -51,6 +58,14 @@ walk_ast(Acc, [{function, Line, Name, Arity, Clauses}|T]) -> put(function, Name), walk_ast([{function, Line, Name, Arity, walk_clauses([], Clauses)}|Acc], T); +walk_ast(Acc, [{attribute, _, record, {Name, Fields}}=H|T]) -> + FieldNames = lists:map(fun({record_field, _, {atom, _, FieldName}}) -> + FieldName; + ({record_field, _, {atom, _, FieldName}, _Default}) -> + FieldName + end, Fields), + stash_record({Name, FieldNames}), + walk_ast([H|Acc], T); walk_ast(Acc, [H|T]) -> walk_ast([H|Acc], T). @@ -62,203 +77,230 @@ walk_clauses(Acc, [{clause, Line, Arguments, Guards, Body}|T]) -> walk_body(Acc, []) -> lists:reverse(Acc); walk_body(Acc, [H|T]) -> - walk_body([transform_statement(H)|Acc], T). -%% -%% lager:debug([{a,1},{b,2}], "Hello ~w", [world]) -%% -%% generate: -%% case lager_mochiglobal:get(loglevel, {- 1,[]}) of -%% {_lager_transform_level3,[]} -%% when _lager_transform_level3 < 7 -> -%% ok; -%% {_,_lager_transform_match4} -> -%% lager:dispatch_log1(_lager_transform_match4, -%% debug, -%% ?MODULE, -%% ?FUNCTION, -%% ?LINE, -%% self(), -%% fun({a,1}) -> true; -%% ({a,'*'}) -> true; -%% ({b,2}) -> true; -%% ({b,'*'}) -> true; -%% ({module,?MODULE}) -> true; -%% ({module,'*'}) -> true; -%% ({function,?FUNCTION}) -> true; -%% ({function,'*'}) -> true; -%% ({line,?LINE}) -> true; -%% ({line,'*'}) -> true; -%% ({pid,'*'}) -> true; -%% ({pid,PID}) -> -%% PID =:= pid_to_list(self()); -%% (_) -> false -%% end, -%% "Hello ~w", -%% [world]) -%% end -%% -transform_statement({call, L, {remote, L1, {atom, L2, lager}, - {atom, L3, Severity}}, Arguments0} = Stmt) -> - case lists:member(Severity, ?LEVELS) of + walk_body([transform_statement(H, get(sinks))|Acc], T). + +transform_statement({call, Line, {remote, _Line1, {atom, _Line2, Module}, + {atom, _Line3, Function}}, Arguments0} = Stmt, + Sinks) -> + case lists:member(Module, Sinks) of true -> - Trace0 = [trace_clause({atom,L,module},{atom,L,'*'}), - trace_clause({atom,L,module},{atom,L,get(module)}), - trace_clause({atom,L,function},{atom,L,'*'}), - trace_clause({atom,L,function},{atom,L,get(function)}), - trace_clause({atom,L,line},{atom,L,'*'}), - trace_clause({atom,L,line},{integer,L,L}), - trace_clause({atom,L,pid}, {atom,L,'*'}), - trace_clause({atom,L,pid}, {atom,L,pid}) - ], - {Trace1, Message, Arguments} = - case Arguments0 of - [Format] -> - {Trace0, Format, {nil, L}}; - [Arg1, Arg2] -> - %% some ambiguity here, figure out if these arguments are - %% [Format, Args] or [Attr, Format]. - %% The trace attributes will be a list of tuples, so check - %% for that. - case Arg1 of - {cons, _, {tuple, _, _}, _} -> - {concat_clauses(L,Arg1,Trace0), - Arg2, {nil,L}}; - _ -> - {Trace0, Arg1, Arg2} - end; - [Attrs, Format, Args] -> - {concat_clauses(L,Attrs,Trace0), Format, Args} - end, - Trace2 = lists:ukeysort(1, Trace1), - Clauses = generate_clauses(L,Trace2) ++ - [{clause,L,[{var,L,'_'}],[], - [{atom,L,false}]}], - Traces = {'fun',L,{clauses,Clauses}}, - SeverityNum = - case Severity of - debug -> ?DEBUG; - info -> ?INFO; - notice -> ?NOTICE; - warning -> ?WARNING; - error -> ?ERROR; - critical -> ?CRITICAL; - alert -> ?ALERT; - emergency -> ?EMERGENCY; - none -> ?LOG_NONE - end, - LevelThreshold = new_var("level",L), - Match = new_var("match",L), - {block, L, - [ - {'case',L, - {call,L, - {remote,L,{atom,L,lager_mochiglobal},{atom,L,get}}, - [{atom,L,loglevel}, - {tuple,L,[{op,L,'-',{integer,L,1}},{nil,L}]}]}, - [{clause,L, - [{tuple,L,[LevelThreshold,{nil,L}]}], - [[{op,L,'<',LevelThreshold,{integer,L,SeverityNum}}]], - [{atom,L,ok}]}, - {clause,L, - [{tuple,L,[{var,L,'_'},Match]}], - [], - [{call,L, - {remote,L1,{atom,L,lager}, - {atom,L2,dispatch_log1}}, - [Match, - {atom,L3,Severity}, - {atom,L3,get(module)}, - {atom,L3,get(function)}, - {integer,L3,L}, - {call, L3, {atom, L3 ,self}, []}, - Traces, - Message, - Arguments - ]}]}]} - ]}; - false -> - Stmt + case lists:member(Function, ?LEVELS) of + true -> + SinkName = lager_util:make_internal_sink_name(Module), + do_transform(Line, SinkName, Function, Arguments0); + false -> + case lists:keyfind(Function, 1, ?LEVELS_UNSAFE) of + {Function, Severity} -> + SinkName = lager_util:make_internal_sink_name(Module), + do_transform(Line, SinkName, Severity, Arguments0, unsafe); + false -> + Stmt + end + end; + false -> + list_to_tuple(transform_statement(tuple_to_list(Stmt), Sinks)) end; -transform_statement({call, L, {remote, L1, {atom, L2, boston_lager}, - {atom, L3, Severity}}, Arguments}) -> - NewArgs = - case Arguments of - [{string, L, Msg}] -> - [{string, L, re:replace(Msg, "r", "h", - [{return, list}, global])}]; - [{string, L, Format}, Args] -> - [{string, L, re:replace(Format, "r", "h", - [{return, list}, global])}, Args]; - Other -> Other - end, - transform_statement({call, L, {remote, L1, {atom, L2, lager}, - {atom, L3, Severity}}, NewArgs}); -transform_statement(Stmt) when is_tuple(Stmt) -> - list_to_tuple(transform_statement(tuple_to_list(Stmt))); -transform_statement(Stmt) when is_list(Stmt) -> - [transform_statement(S) || S <- Stmt]; -transform_statement(Stmt) -> +transform_statement(Stmt, Sinks) when is_tuple(Stmt) -> + list_to_tuple(transform_statement(tuple_to_list(Stmt), Sinks)); +transform_statement(Stmt, Sinks) when is_list(Stmt) -> + [transform_statement(S, Sinks) || S <- Stmt]; +transform_statement(Stmt, _Sinks) -> Stmt. -%% -%% Generate from key value pairs -%% remove duplicates! -%% +do_transform(Line, SinkName, Severity, Arguments0) -> + do_transform(Line, SinkName, Severity, Arguments0, safe). -generate_clauses(L,[{{pid,pid},{_K,_V}}|Cs]) -> - make_pid_clause(L) ++ generate_clauses(L, Cs); -generate_clauses(L,[{{_,_},{K,V}}|Cs]) -> - make_clause(L,K,V) ++ generate_clauses(L, Cs); -generate_clauses(_L, []) -> - []. +do_transform(Line, SinkName, Severity, Arguments0, Safety) -> + SeverityAsInt=lager_util:level_to_num(Severity), + DefaultAttrs0 = {cons, Line, {tuple, Line, [ + {atom, Line, module}, {atom, Line, get(module)}]}, + {cons, Line, {tuple, Line, [ + {atom, Line, function}, {atom, Line, get(function)}]}, + {cons, Line, {tuple, Line, [ + {atom, Line, line}, + {integer, Line, Line}]}, + {cons, Line, {tuple, Line, [ + {atom, Line, pid}, + {call, Line, {atom, Line, pid_to_list}, [ + {call, Line, {atom, Line ,self}, []}]}]}, + {cons, Line, {tuple, Line, [ + {atom, Line, node}, + {call, Line, {atom, Line, node}, []}]}, + %% get the metadata with lager:md(), this will always return a list so we can use it as the tail here + {call, Line, {remote, Line, {atom, Line, lager}, {atom, Line, md}}, []}}}}}}, + %{nil, Line}}}}}}}, + DefaultAttrs = case erlang:get(application) of + undefined -> + DefaultAttrs0; + App -> + %% stick the application in the attribute list + concat_lists({cons, Line, {tuple, Line, [ + {atom, Line, application}, + {atom, Line, App}]}, + {nil, Line}}, DefaultAttrs0) + end, + {Meta, Message, Arguments} = case Arguments0 of + [Format] -> + {DefaultAttrs, Format, {atom, Line, none}}; + [Arg1, Arg2] -> + %% some ambiguity here, figure out if these arguments are + %% [Format, Args] or [Attr, Format]. + %% The trace attributes will be a list of tuples, so check + %% for that. + case {element(1, Arg1), Arg1} of + {_, {cons, _, {tuple, _, _}, _}} -> + {concat_lists(Arg1, DefaultAttrs), + Arg2, {atom, Line, none}}; + {Type, _} when Type == var; + Type == lc; + Type == call; + Type == record_field -> + %% crap, its not a literal. look at the second + %% argument to see if it is a string + case Arg2 of + {string, _, _} -> + {concat_lists(Arg1, DefaultAttrs), + Arg2, {atom, Line, none}}; + _ -> + %% not a string, going to have to guess + %% it's the argument list + {DefaultAttrs, Arg1, Arg2} + end; + _ -> + {DefaultAttrs, Arg1, Arg2} + end; + [Attrs, Format, Args] -> + {concat_lists(Attrs, DefaultAttrs), Format, Args} + end, + %% Generate some unique variable names so we don't accidentaly export from case clauses. + %% Note that these are not actual atoms, but the AST treats variable names as atoms. + LevelVar = make_varname("__Level", Line), + TracesVar = make_varname("__Traces", Line), + PidVar = make_varname("__Pid", Line), + LogFun = case Safety of + safe -> + do_log; + unsafe -> + do_log_unsafe + end, + %% Wrap the call to lager:dispatch_log/6 in case that will avoid doing any work if this message is not elegible for logging + %% See lager.erl (lines 89-100) for lager:dispatch_log/6 + %% case {whereis(Sink), whereis(?DEFAULT_SINK), lager_config:get({Sink, loglevel}, {?LOG_NONE, []})} of + {'case',Line, + {tuple,Line, + [{call,Line,{atom,Line,whereis},[{atom,Line,SinkName}]}, + {call,Line,{atom,Line,whereis},[{atom,Line,?DEFAULT_SINK}]}, + {call,Line, + {remote,Line,{atom,Line,lager_config},{atom,Line,get}}, + [{tuple,Line,[{atom,Line,SinkName},{atom,Line,loglevel}]}, + {tuple,Line,[{integer,Line,0},{nil,Line}]}]}]}, + %% {undefined, undefined, _} -> {error, lager_not_running}; + [{clause,Line, + [{tuple,Line, + [{atom,Line,undefined},{atom,Line,undefined},{var,Line,'_'}]}], + [], + %% trick the linter into avoiding a 'term constructed but not used' error: + %% (fun() -> {error, lager_not_running} end)() + [{call, Line, {'fun', Line, {clauses, [{clause, Line, [],[], [{tuple, Line, [{atom, Line, error},{atom, Line, lager_not_running}]}]}]}}, []}] + }, + %% {undefined, _, _} -> {error, {sink_not_configured, Sink}}; + {clause,Line, + [{tuple,Line, + [{atom,Line,undefined},{var,Line,'_'},{var,Line,'_'}]}], + [], + %% same trick as above to avoid linter error + [{call, Line, {'fun', Line, {clauses, [{clause, Line, [],[], [{tuple,Line, [{atom,Line,error}, {tuple,Line,[{atom,Line,sink_not_configured},{atom,Line,SinkName}]}]}]}]}}, []}] + }, + %% {SinkPid, _, {Level, Traces}} when ... -> lager:do_log/9; + {clause,Line, + [{tuple,Line, + [{var,Line,PidVar}, + {var,Line,'_'}, + {tuple,Line,[{var,Line,LevelVar},{var,Line,TracesVar}]}]}], + [[{op, Line, 'orelse', + {op, Line, '/=', {op, Line, 'band', {var, Line, LevelVar}, {integer, Line, SeverityAsInt}}, {integer, Line, 0}}, + {op, Line, '/=', {var, Line, TracesVar}, {nil, Line}}}]], + [{call,Line,{remote, Line, {atom, Line, lager}, {atom, Line, LogFun}}, + [{atom,Line,Severity}, + Meta, + Message, + Arguments, + {integer, Line, get(truncation_size)}, + {integer, Line, SeverityAsInt}, + {var, Line, LevelVar}, + {var, Line, TracesVar}, + {atom, Line, SinkName}, + {var, Line, PidVar}]}]}, + %% _ -> ok + {clause,Line,[{var,Line,'_'}],[],[{atom,Line,ok}]}]}. -%% -%% create trace clause -%% ({key, Value}) -> true; -%% -make_clause(L,K,V) -> - [{clause,L,[{tuple,L,[K,V]}],[],[{atom,L,true}]}]. - -%% -%% create special pid clauses -%% ({pid, PID}) -> PID =:= pid_to_list(self()) -%% -make_pid_clause(L) -> - PID = new_var("pid", L), - K = {atom,L,pid}, - [{clause,L,[{tuple,L,[K,PID]}],[], - [{op,L,'=:=', PID, {call,L,{atom,L,pid_to_list}, - [{call,L,{atom,L,self},[]}]}}]}]. +make_varname(Prefix, Line) -> + list_to_atom(Prefix ++ atom_to_list(get(module)) ++ integer_to_list(Line)). -%% -%% Concat clause from input -%% -concat_clauses(_L,{nil, _Line}, B) -> +%% concat 2 list ASTs by replacing the terminating [] in A with the contents of B +concat_lists({var, Line, _Name}=Var, B) -> + %% concatenating a var with a cons + {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}}, + [{cons, Line, Var, B}]}; +concat_lists({lc, Line, _Body, _Generator} = LC, B) -> + %% concatenating a LC with a cons + {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}}, + [{cons, Line, LC, B}]}; +concat_lists({call, Line, _Function, _Args} = Call, B) -> + %% concatenating a call with a cons + {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}}, + [{cons, Line, Call, B}]}; +concat_lists({record_field, Line, _Var, _Record, _Field} = Rec, B) -> + %% concatenating a record_field with a cons + {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}}, + [{cons, Line, Rec, B}]}; +concat_lists({nil, _Line}, B) -> B; -concat_clauses(L,{cons, _Line, {tuple,_L,[K,V]}, Tail}, B) -> - [ trace_clause(K, {atom,L,'*'}), - trace_clause(K,V) | concat_clauses(L,Tail, B)]; -concat_clauses(L, _, _) -> - Err = {"*current*",[{L,?MODULE,"traces must be key value pairs"}]}, - throw({error,[Err],[]}). - -format_error(Str) -> - io_lib:format("~s", [Str]). +concat_lists({cons, Line, Element, Tail}, B) -> + {cons, Line, Element, concat_lists(Tail, B)}. -trace_clause(K,V) -> - Kn = erl_parse:normalise(K), - Vn = erl_parse:normalise(V), - {{Kn,Vn},{K,V}}. +stash_record(Record) -> + Records = case erlang:get(records) of + undefined -> + []; + R -> + R + end, + erlang:put(records, [Record|Records]). +insert_record_attribute(AST) -> + lists:foldl(fun({attribute, Line, module, _}=E, Acc) -> + [E, {attribute, Line, lager_records, erlang:get(records)}|Acc]; + (E, Acc) -> + [E|Acc] + end, [], AST). -new_var(Base,L) -> - N = case get(lager_transform_var) of - undefined -> - 1; - N0 -> - N0 - end, - put(lager_transform_var, N+1), - Name = list_to_atom("_lager_transform_"++Base++integer_to_list(N)), - {var, L, Name}. +guess_application(Dirname, Attr) when Dirname /= undefined -> + case find_app_file(Dirname) of + no_idea -> + %% try it based on source file directory (app.src most likely) + guess_application(undefined, Attr); + _ -> + ok + end; +guess_application(undefined, {attribute, _, file, {Filename, _}}) -> + Dir = filename:dirname(Filename), + find_app_file(Dir); +guess_application(_, _) -> + ok. +find_app_file(Dir) -> + case filelib:wildcard(Dir++"/*.{app,app.src}") of + [] -> + no_idea; + [File] -> + case file:consult(File) of + {ok, [{application, Appname, _Attributes}|_]} -> + erlang:put(application, Appname); + _ -> + no_idea + end; + _ -> + %% multiple files, uh oh + no_idea + end. |