選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

283 行
13 KiB

  1. %% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved.
  2. %%
  3. %% This file is provided to you under the Apache License,
  4. %% Version 2.0 (the "License"); you may not use this file
  5. %% except in compliance with the License. You may obtain
  6. %% a copy of the License at
  7. %%
  8. %% http://www.apache.org/licenses/LICENSE-2.0
  9. %%
  10. %% Unless required by applicable law or agreed to in writing,
  11. %% software distributed under the License is distributed on an
  12. %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  13. %% KIND, either express or implied. See the License for the
  14. %% specific language governing permissions and limitations
  15. %% under the License.
  16. %% @doc The parse transform used for lager messages.
  17. %% This parse transform rewrites functions calls to lager:Severity/1,2 into
  18. %% a more complicated function that captures module, function, line, pid and
  19. %% time as well. The entire function call is then wrapped in a case that
  20. %% checks the lager_config 'loglevel' value, so the code isn't executed if
  21. %% nothing wishes to consume the message.
  22. -module(lager_transform).
  23. -include("lager.hrl").
  24. -export([parse_transform/2]).
  25. %% @private
  26. parse_transform(AST, Options) ->
  27. TruncSize = proplists:get_value(lager_truncation_size, Options, ?DEFAULT_TRUNCATION),
  28. Enable = proplists:get_value(lager_print_records_flag, Options, true),
  29. Sinks = [lager] ++ proplists:get_value(lager_extra_sinks, Options, []),
  30. put(print_records_flag, Enable),
  31. put(truncation_size, TruncSize),
  32. put(sinks, Sinks),
  33. erlang:put(records, []),
  34. %% .app file should either be in the outdir, or the same dir as the source file
  35. guess_application(proplists:get_value(outdir, Options), hd(AST)),
  36. walk_ast([], AST).
  37. walk_ast(Acc, []) ->
  38. case get(print_records_flag) of
  39. true ->
  40. insert_record_attribute(Acc);
  41. false ->
  42. lists:reverse(Acc)
  43. end;
  44. walk_ast(Acc, [{attribute, _, module, {Module, _PmodArgs}}=H|T]) ->
  45. %% A wild parameterized module appears!
  46. put(module, Module),
  47. walk_ast([H|Acc], T);
  48. walk_ast(Acc, [{attribute, _, module, Module}=H|T]) ->
  49. put(module, Module),
  50. walk_ast([H|Acc], T);
  51. walk_ast(Acc, [{function, Line, Name, Arity, Clauses}|T]) ->
  52. put(function, Name),
  53. walk_ast([{function, Line, Name, Arity,
  54. walk_clauses([], Clauses)}|Acc], T);
  55. walk_ast(Acc, [{attribute, _, record, {Name, Fields}}=H|T]) ->
  56. FieldNames = lists:map(fun({record_field, _, {atom, _, FieldName}}) ->
  57. FieldName;
  58. ({record_field, _, {atom, _, FieldName}, _Default}) ->
  59. FieldName
  60. end, Fields),
  61. stash_record({Name, FieldNames}),
  62. walk_ast([H|Acc], T);
  63. walk_ast(Acc, [H|T]) ->
  64. walk_ast([H|Acc], T).
  65. walk_clauses(Acc, []) ->
  66. lists:reverse(Acc);
  67. walk_clauses(Acc, [{clause, Line, Arguments, Guards, Body}|T]) ->
  68. walk_clauses([{clause, Line, Arguments, Guards, walk_body([], Body)}|Acc], T).
  69. walk_body(Acc, []) ->
  70. lists:reverse(Acc);
  71. walk_body(Acc, [H|T]) ->
  72. walk_body([transform_statement(H, get(sinks))|Acc], T).
  73. transform_statement({call, Line, {remote, _Line1, {atom, _Line2, Module},
  74. {atom, _Line3, Function}}, Arguments0} = Stmt,
  75. Sinks) ->
  76. case lists:member(Module, Sinks) of
  77. true ->
  78. case lists:member(Function, ?LEVELS) of
  79. true ->
  80. SinkName = list_to_atom(atom_to_list(Module) ++ "_event"),
  81. do_transform(Line, SinkName, Function, Arguments0);
  82. false ->
  83. Stmt
  84. end;
  85. false ->
  86. list_to_tuple(transform_statement(tuple_to_list(Stmt), Sinks))
  87. end;
  88. transform_statement(Stmt, Sinks) when is_tuple(Stmt) ->
  89. list_to_tuple(transform_statement(tuple_to_list(Stmt), Sinks));
  90. transform_statement(Stmt, Sinks) when is_list(Stmt) ->
  91. [transform_statement(S, Sinks) || S <- Stmt];
  92. transform_statement(Stmt, _Sinks) ->
  93. Stmt.
  94. do_transform(Line, SinkName, Severity, Arguments0) ->
  95. SeverityAsInt=lager_util:level_to_num(Severity),
  96. DefaultAttrs0 = {cons, Line, {tuple, Line, [
  97. {atom, Line, module}, {atom, Line, get(module)}]},
  98. {cons, Line, {tuple, Line, [
  99. {atom, Line, function}, {atom, Line, get(function)}]},
  100. {cons, Line, {tuple, Line, [
  101. {atom, Line, line},
  102. {integer, Line, Line}]},
  103. {cons, Line, {tuple, Line, [
  104. {atom, Line, pid},
  105. {call, Line, {atom, Line, pid_to_list}, [
  106. {call, Line, {atom, Line ,self}, []}]}]},
  107. {cons, Line, {tuple, Line, [
  108. {atom, Line, node},
  109. {call, Line, {atom, Line, node}, []}]},
  110. %% get the metadata with lager:md(), this will always return a list so we can use it as the tail here
  111. {call, Line, {remote, Line, {atom, Line, lager}, {atom, Line, md}}, []}}}}}},
  112. %{nil, Line}}}}}}},
  113. DefaultAttrs = case erlang:get(application) of
  114. undefined ->
  115. DefaultAttrs0;
  116. App ->
  117. %% stick the application in the attribute list
  118. concat_lists({cons, Line, {tuple, Line, [
  119. {atom, Line, application},
  120. {atom, Line, App}]},
  121. {nil, Line}}, DefaultAttrs0)
  122. end,
  123. {Traces, Message, Arguments} = case Arguments0 of
  124. [Format] ->
  125. {DefaultAttrs, Format, {atom, Line, none}};
  126. [Arg1, Arg2] ->
  127. %% some ambiguity here, figure out if these arguments are
  128. %% [Format, Args] or [Attr, Format].
  129. %% The trace attributes will be a list of tuples, so check
  130. %% for that.
  131. case {element(1, Arg1), Arg1} of
  132. {_, {cons, _, {tuple, _, _}, _}} ->
  133. {concat_lists(Arg1, DefaultAttrs),
  134. Arg2, {atom, Line, none}};
  135. {Type, _} when Type == var;
  136. Type == lc;
  137. Type == call;
  138. Type == record_field ->
  139. %% crap, its not a literal. look at the second
  140. %% argument to see if it is a string
  141. case Arg2 of
  142. {string, _, _} ->
  143. {concat_lists(Arg1, DefaultAttrs),
  144. Arg2, {atom, Line, none}};
  145. _ ->
  146. %% not a string, going to have to guess
  147. %% it's the argument list
  148. {DefaultAttrs, Arg1, Arg2}
  149. end;
  150. _ ->
  151. {DefaultAttrs, Arg1, Arg2}
  152. end;
  153. [Attrs, Format, Args] ->
  154. {concat_lists(Attrs, DefaultAttrs), Format, Args}
  155. end,
  156. %% Generate some unique variable names so we don't accidentaly export from case clauses.
  157. %% Note that these are not actual atoms, but the AST treats variable names as atoms.
  158. LevelVar = make_varname("__Level", Line),
  159. TracesVar = make_varname("__Traces", Line),
  160. PidVar = make_varname("__Pid", Line),
  161. %% Wrap the call to lager_dispatch log in a case that will avoid doing any work if this message is not elegible for logging
  162. %% case {whereis(Sink), lager_config:get(Sink, loglevel, {?LOG_NONE, []})} of
  163. {'case', Line,
  164. {tuple, Line,
  165. [{call, Line, {atom, Line, whereis}, [{atom, Line, SinkName}]},
  166. {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}]}]}]},
  167. [
  168. %% {undefined, _} -> {error, lager_not_running}
  169. {clause, Line,
  170. [{tuple, Line, [{atom, Line, undefined}, {var, Line, '_'}]}],
  171. [],
  172. %% trick the linter into avoiding a 'term constructed by not used' error:
  173. %% (fun() -> {error, lager_not_running} end)();
  174. [{call, Line, {'fun', Line, {clauses, [{clause, Line, [],[], [{tuple, Line, [{atom, Line, error},{atom, Line, lager_not_running}]}]}]}}, []}]},
  175. %% If we care about the loglevel, or there's any traces installed, we have do more checking
  176. %% {Level, Traces} when (Level band SeverityAsInt) /= 0 orelse Traces /= [] ->
  177. {clause, Line,
  178. [{tuple, Line, [{var, Line, PidVar}, {tuple, Line, [{var, Line, LevelVar}, {var, Line, TracesVar}]}]}],
  179. [[{op, Line, 'orelse',
  180. {op, Line, '/=', {op, Line, 'band', {var, Line, LevelVar}, {integer, Line, SeverityAsInt}}, {integer, Line, 0}},
  181. {op, Line, '/=', {var, Line, TracesVar}, {nil, Line}}}]],
  182. [
  183. %% do the call to lager:dispatch_log/9
  184. {call, Line, {remote, Line, {atom, Line, lager}, {atom, Line, do_log}},
  185. [
  186. {atom,Line,Severity},
  187. Traces,
  188. Message,
  189. Arguments,
  190. {integer, Line, get(truncation_size)},
  191. {integer, Line, SeverityAsInt},
  192. {var, Line, LevelVar},
  193. {var, Line, TracesVar},
  194. {atom, Line, SinkName},
  195. {var, Line, PidVar}
  196. ]
  197. }
  198. ]},
  199. %% otherwise, do nothing
  200. %% _ -> ok
  201. {clause, Line, [{var, Line, '_'}],[],[{atom, Line, ok}]}
  202. ]}.
  203. make_varname(Prefix, Line) ->
  204. list_to_atom(Prefix ++ atom_to_list(get(module)) ++ integer_to_list(Line)).
  205. %% concat 2 list ASTs by replacing the terminating [] in A with the contents of B
  206. concat_lists({var, Line, _Name}=Var, B) ->
  207. %% concatenating a var with a cons
  208. {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
  209. [{cons, Line, Var, B}]};
  210. concat_lists({lc, Line, _Body, _Generator} = LC, B) ->
  211. %% concatenating a LC with a cons
  212. {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
  213. [{cons, Line, LC, B}]};
  214. concat_lists({call, Line, _Function, _Args} = Call, B) ->
  215. %% concatenating a call with a cons
  216. {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
  217. [{cons, Line, Call, B}]};
  218. concat_lists({record_field, Line, _Var, _Record, _Field} = Rec, B) ->
  219. %% concatenating a record_field with a cons
  220. {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
  221. [{cons, Line, Rec, B}]};
  222. concat_lists({nil, _Line}, B) ->
  223. B;
  224. concat_lists({cons, Line, Element, Tail}, B) ->
  225. {cons, Line, Element, concat_lists(Tail, B)}.
  226. stash_record(Record) ->
  227. Records = case erlang:get(records) of
  228. undefined ->
  229. [];
  230. R ->
  231. R
  232. end,
  233. erlang:put(records, [Record|Records]).
  234. insert_record_attribute(AST) ->
  235. lists:foldl(fun({attribute, Line, module, _}=E, Acc) ->
  236. [E, {attribute, Line, lager_records, erlang:get(records)}|Acc];
  237. (E, Acc) ->
  238. [E|Acc]
  239. end, [], AST).
  240. guess_application(Dirname, Attr) when Dirname /= undefined ->
  241. case find_app_file(Dirname) of
  242. no_idea ->
  243. %% try it based on source file directory (app.src most likely)
  244. guess_application(undefined, Attr);
  245. _ ->
  246. ok
  247. end;
  248. guess_application(undefined, {attribute, _, file, {Filename, _}}) ->
  249. Dir = filename:dirname(Filename),
  250. find_app_file(Dir);
  251. guess_application(_, _) ->
  252. ok.
  253. find_app_file(Dir) ->
  254. case filelib:wildcard(Dir++"/*.{app,app.src}") of
  255. [] ->
  256. no_idea;
  257. [File] ->
  258. case file:consult(File) of
  259. {ok, [{application, Appname, _Attributes}|_]} ->
  260. erlang:put(application, Appname);
  261. _ ->
  262. no_idea
  263. end;
  264. _ ->
  265. %% multiple files, uh oh
  266. no_idea
  267. end.