使用 lager 的时候,在编译应用的时候,需要加入选项 {parse_transform, lager_transform}
erlc 会在编译你的项目源代码的时候,把生成的 abstract format forms 交给 lager_transform 模块的 parse_transform 函数处理,完成后,erlc 会把这些经过修改的 抽象码 编译成 bytecode。
项目中使用 lager 比如,
lager:error("Some message")
经过转换后,到底变成了什么样呢?
入口:
parse_transform(AST, Options) ->
%% AST 为源代码的abstract format形式
TruncSize = proplists:get_value(lager_truncation_size, Options, ?DEFAULT_TRUNCATION),
put(truncation_size, TruncSize),
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)),
%%
hd(AST) 结果
{attribute,1,file,{"../src/hello.erl",1} hd == head of
%%
guess_application,读取src目录下的*.app.src文件,获取应用的名字,放到进程字典中
walk_ast([], AST).
%% 遍历abstract format forms,修改 lager:error 语句。(附录一个abstract format的例子)
walk_ast
(Acc, []) ->
insert_record_attribute(Acc);
walk_ast(Acc, [{attribute, _, module, {Module, _PmodArgs}}=H|T]) -> %%处理module
%% A wild parameterized module appears!
put(module, Module),
walk_ast([H|Acc], T);
walk_ast(Acc, [{attribute, _, module, Module}=H|T]) -> %%处理module
put(module, Module),
walk_ast([H|Acc], T);
walk_ast(Acc, [{function, Line, Name, Arity, Clauses}|T]) -> %% 处理function,关键点
put(function, Name),
walk_ast([{function, Line, Name, Arity,
walk_clauses([], Clauses)}|Acc], T);
walk_ast(Acc, [{attribute, _, record, {Name, Fields}}=H|T]) -> %% 处理record
%% 把record定义,存储到进程字典中;比如一个record定义为,-record(hello, {a, b}),存储到进程字典中为{hello, [a, b]}
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)|Acc], T).
transform_statement 处理baody语句(也就是表达式列表)中的每一条,这个函数一共有5个从句
%% 处理 lager:error("log") 这种调用
%% erlc编译过程中会修改abstract format forms,改成
{call, Line, {remote, Line, {atom,Line,lager},{atom,Line,dispatch_log}} 这种形式
%% 也就是说最终应用在运行过程中会调用 lager:dispatch_log函数,并传递相关参数
transform_statement
({call, Line, {remote, _Line1, {atom, _Line2,
lager
},
{atom, _Line3,
Severity
}}, Arguments0} = Stmt) ->
case lists:member(Severity, ?LEVELS) of
true ->
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}, []}]},
{nil, Line}}}}}}, %% 这个元组的定义乍看上去恐怖,再看还是很恐怖
%% 转换成代码后,是这样的,[{module, mod},{function, fun},{line, lin},{pid,pid_to_list(self())},{node, node()}]
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, %% DefaultAttrs 最终的格式嵌套的包含后面这些信息 application-module-function-line-pid-node
{Traces, 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,
{call, Line, {remote, Line, {atom,Line,lager},{atom,Line,dispatch_log}}, %% lager:dispatch_log()
[ %% 参数
{atom,Line,Severity},
Traces,
Message,
Arguments,
{integer, Line, get(truncation_size)}
] %% 最终的调用是:
lager:dispatch_log(error, Traces, format, [], 500)
%% Traces就是上面那个复杂的列表
};
false ->
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(Stmt) when is_tuple(Stmt) ->
list_to_tuple(transform_statement(tuple_to_list(Stmt)));
transform_statement(Stmt) when is_list(Stmt) -> %% 如果碰到case语句什么的,还是要深入进去
[transform_statement(S) || S <- Stmt];
transform_statement(Stmt) ->
Stmt.
模块:
-module(hello).
-export([send_hello/1, receive_hello/0]).
send_hello(To) ->
To ! {hello, self()}.
receive_hello() ->
receive
{hello, From} ->
{ok, From};
Msg ->
{unknown_msg, Msg}
end.
生成的abstrace format forms
[{attribute,1,file,{"../src/hello.erl",1}},
{attribute,1,module,hello},
{attribute,2,export,[{send_hello,1},{receive_hello,0}]},
{function,4,send_hello,1,
[{clause,4,
[{var,4,'To'}],
[],
[{op,5,'!',
{var,5,'To'},
{tuple,5,[{atom,5,hello},{call,5,{atom,5,self},[]}]}}]}]},
{function,7,receive_hello,0,
[{clause,7,[],[],
[{'receive',8,
[{clause,9,
[{tuple,9,[{atom,9,hello},{var,9,'From'}]}],
[],
[{tuple,10,[{atom,10,ok},{var,10,'From'}]}]},
{clause,11,
[{var,11,'Msg'}],
[],
[{tuple,12,[{atom,12,unknown_msg},{var,12,'Msg'}]}]}]}]}]},
{eof,13}]
----------------------------------------------------------------------------------------------
{ok, Scan3, _} = erl_scan:string("[application, module, function].").
{ok, P} = erl_parse:parse_exprs(Scan3).
{ok,[{cons,1,
{atom,1,application},
{cons,1,
{atom,1,module},
{cons,1,{atom,1,function},{nil,1}}}}]}
----------------------------------------------------------------------------------------------
{ok, Scan2, _} = erl_scan:string("[{application, lager},{module, log},{function, dispatch_log}].").
{ok, P} = erl_parse:parse_exprs(Scan2).
{ok,[{cons,1,
{tuple,1,[{atom,1,application},{atom,1,lager}]},
{cons,1,
{tuple,1,[{atom,1,module},{atom,1,log}]},
{cons,1,
{tuple,1,[{atom,1,function},{atom,1,dispatch_log}]},
{nil,1}}}}]}
这就是Erlang的metaprogramming!