解析Erlang日志组件lager的lager_transform模块

使用 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!

转载于:https://my.oschina.net/astute/blog/114767

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值