summaryrefslogtreecommitdiff
path: root/deps/lager/src/lager_transform.erl
diff options
context:
space:
mode:
Diffstat (limited to 'deps/lager/src/lager_transform.erl')
-rw-r--r--deps/lager/src/lager_transform.erl442
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.