解析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! node

相關文章
相關標籤/搜索