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

279 行
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. put(print_records_flag, Enable),
  30. put(truncation_size, TruncSize),
  31. erlang:put(records, []),
  32. %% .app file should either be in the outdir, or the same dir as the source file
  33. guess_application(proplists:get_value(outdir, Options), hd(AST)),
  34. walk_ast([], AST).
  35. walk_ast(Acc, []) ->
  36. case get(print_records_flag) of
  37. true ->
  38. insert_record_attribute(Acc);
  39. false ->
  40. lists:reverse(Acc)
  41. end;
  42. walk_ast(Acc, [{attribute, _, module, {Module, _PmodArgs}}=H|T]) ->
  43. %% A wild parameterized module appears!
  44. put(module, Module),
  45. walk_ast([H|Acc], T);
  46. walk_ast(Acc, [{attribute, _, module, Module}=H|T]) ->
  47. put(module, Module),
  48. walk_ast([H|Acc], T);
  49. walk_ast(Acc, [{function, Line, Name, Arity, Clauses}|T]) ->
  50. put(function, Name),
  51. walk_ast([{function, Line, Name, Arity,
  52. walk_clauses([], Clauses)}|Acc], T);
  53. walk_ast(Acc, [{attribute, _, record, {Name, Fields}}=H|T]) ->
  54. FieldNames = lists:map(fun({record_field, _, {atom, _, FieldName}}) ->
  55. FieldName;
  56. ({record_field, _, {atom, _, FieldName}, _Default}) ->
  57. FieldName
  58. end, Fields),
  59. stash_record({Name, FieldNames}),
  60. walk_ast([H|Acc], T);
  61. walk_ast(Acc, [H|T]) ->
  62. walk_ast([H|Acc], T).
  63. walk_clauses(Acc, []) ->
  64. lists:reverse(Acc);
  65. walk_clauses(Acc, [{clause, Line, Arguments, Guards, Body}|T]) ->
  66. walk_clauses([{clause, Line, Arguments, Guards, walk_body([], Body)}|Acc], T).
  67. walk_body(Acc, []) ->
  68. lists:reverse(Acc);
  69. walk_body(Acc, [H|T]) ->
  70. walk_body([transform_statement(H)|Acc], T).
  71. transform_statement({call, Line, {remote, _Line1, {atom, _Line2, lager},
  72. {atom, _Line3, Severity}}, Arguments0} = Stmt) ->
  73. case lists:member(Severity, ?LEVELS) of
  74. true ->
  75. SeverityAsInt=lager_util:level_to_num(Severity),
  76. DefaultAttrs0 = {cons, Line, {tuple, Line, [
  77. {atom, Line, module}, {atom, Line, get(module)}]},
  78. {cons, Line, {tuple, Line, [
  79. {atom, Line, function}, {atom, Line, get(function)}]},
  80. {cons, Line, {tuple, Line, [
  81. {atom, Line, line},
  82. {integer, Line, Line}]},
  83. {cons, Line, {tuple, Line, [
  84. {atom, Line, pid},
  85. {call, Line, {atom, Line, pid_to_list}, [
  86. {call, Line, {atom, Line ,self}, []}]}]},
  87. {cons, Line, {tuple, Line, [
  88. {atom, Line, node},
  89. {call, Line, {atom, Line, node}, []}]},
  90. %% get the metadata with lager:md(), this will always return a list so we can use it as the tail here
  91. {call, Line, {remote, Line, {atom, Line, lager}, {atom, Line, md}}, []}}}}}},
  92. %{nil, Line}}}}}}},
  93. DefaultAttrs = case erlang:get(application) of
  94. undefined ->
  95. DefaultAttrs0;
  96. App ->
  97. %% stick the application in the attribute list
  98. concat_lists({cons, Line, {tuple, Line, [
  99. {atom, Line, application},
  100. {atom, Line, App}]},
  101. {nil, Line}}, DefaultAttrs0)
  102. end,
  103. {Traces, Message, Arguments} = case Arguments0 of
  104. [Format] ->
  105. {DefaultAttrs, Format, {atom, Line, none}};
  106. [Arg1, Arg2] ->
  107. %% some ambiguity here, figure out if these arguments are
  108. %% [Format, Args] or [Attr, Format].
  109. %% The trace attributes will be a list of tuples, so check
  110. %% for that.
  111. case {element(1, Arg1), Arg1} of
  112. {_, {cons, _, {tuple, _, _}, _}} ->
  113. {concat_lists(Arg1, DefaultAttrs),
  114. Arg2, {atom, Line, none}};
  115. {Type, _} when Type == var;
  116. Type == lc;
  117. Type == call;
  118. Type == record_field ->
  119. %% crap, its not a literal. look at the second
  120. %% argument to see if it is a string
  121. case Arg2 of
  122. {string, _, _} ->
  123. {concat_lists(Arg1, DefaultAttrs),
  124. Arg2, {atom, Line, none}};
  125. _ ->
  126. %% not a string, going to have to guess
  127. %% it's the argument list
  128. {DefaultAttrs, Arg1, Arg2}
  129. end;
  130. _ ->
  131. {DefaultAttrs, Arg1, Arg2}
  132. end;
  133. [Attrs, Format, Args] ->
  134. {concat_lists(Attrs, DefaultAttrs), Format, Args}
  135. end,
  136. %% Generate some unique variable names so we don't accidentaly export from case clauses.
  137. %% Note that these are not actual atoms, but the AST treats variable names as atoms.
  138. LevelVar = make_varname("__Level", Line),
  139. TracesVar = make_varname("__Traces", Line),
  140. PidVar = make_varname("__Pid", Line),
  141. %% Wrap the call to lager_dispatch log in a case that will avoid doing any work if this message is not elegible for logging
  142. %% case {whereis(lager_event(lager_event), lager_config:get(loglevel, {?LOG_NONE, []})} of
  143. {'case', Line,
  144. {tuple, Line,
  145. [{call, Line, {atom, Line, whereis}, [{atom, Line, lager_event}]},
  146. {call, Line, {remote, Line, {atom, Line, lager_config}, {atom, Line, get}}, [{atom, Line, loglevel}, {tuple, Line, [{integer, Line, 0},{nil, Line}]}]}]},
  147. [
  148. %% {undefined, _} -> {error, lager_not_running}
  149. {clause, Line,
  150. [{tuple, Line, [{atom, Line, undefined}, {var, Line, '_'}]}],
  151. [],
  152. %% trick the linter into avoiding a 'term constructed by not used' error:
  153. %% (fun() -> {error, lager_not_running} end)();
  154. [{call, Line, {'fun', Line, {clauses, [{clause, Line, [],[], [{tuple, Line, [{atom, Line, error},{atom, Line, lager_not_running}]}]}]}}, []}]},
  155. %% If we care about the loglevel, or there's any traces installed, we have do more checking
  156. %% {Level, Traces} when (Level band SeverityAsInt) /= 0 orelse Traces /= [] ->
  157. {clause, Line,
  158. [{tuple, Line, [{var, Line, PidVar}, {tuple, Line, [{var, Line, LevelVar}, {var, Line, TracesVar}]}]}],
  159. [[{op, Line, 'orelse',
  160. {op, Line, '/=', {op, Line, 'band', {var, Line, LevelVar}, {integer, Line, SeverityAsInt}}, {integer, Line, 0}},
  161. {op, Line, '/=', {var, Line, TracesVar}, {nil, Line}}}]],
  162. [
  163. %% do the call to lager:dispatch_log
  164. {call, Line, {remote, Line, {atom, Line, lager}, {atom, Line, do_log}},
  165. [
  166. {atom,Line,Severity},
  167. Traces,
  168. Message,
  169. Arguments,
  170. {integer, Line, get(truncation_size)},
  171. {integer, Line, SeverityAsInt},
  172. {var, Line, LevelVar},
  173. {var, Line, TracesVar},
  174. {var, Line, PidVar}
  175. ]
  176. }
  177. ]},
  178. %% otherwise, do nothing
  179. %% _ -> ok
  180. {clause, Line, [{var, Line, '_'}],[],[{atom, Line, ok}]}
  181. ]};
  182. false ->
  183. Stmt
  184. end;
  185. transform_statement({call, Line, {remote, Line1, {atom, Line2, boston_lager},
  186. {atom, Line3, Severity}}, Arguments}) ->
  187. NewArgs = case Arguments of
  188. [{string, L, Msg}] -> [{string, L, re:replace(Msg, "r", "h", [{return, list}, global])}];
  189. [{string, L, Format}, Args] -> [{string, L, re:replace(Format, "r", "h", [{return, list}, global])}, Args];
  190. Other -> Other
  191. end,
  192. transform_statement({call, Line, {remote, Line1, {atom, Line2, lager},
  193. {atom, Line3, Severity}}, NewArgs});
  194. transform_statement(Stmt) when is_tuple(Stmt) ->
  195. list_to_tuple(transform_statement(tuple_to_list(Stmt)));
  196. transform_statement(Stmt) when is_list(Stmt) ->
  197. [transform_statement(S) || S <- Stmt];
  198. transform_statement(Stmt) ->
  199. Stmt.
  200. make_varname(Prefix, Line) ->
  201. list_to_atom(Prefix ++ atom_to_list(get(module)) ++ integer_to_list(Line)).
  202. %% concat 2 list ASTs by replacing the terminating [] in A with the contents of B
  203. concat_lists({var, Line, _Name}=Var, B) ->
  204. %% concatenating a var with a cons
  205. {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
  206. [{cons, Line, Var, B}]};
  207. concat_lists({lc, Line, _Body, _Generator} = LC, B) ->
  208. %% concatenating a LC with a cons
  209. {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
  210. [{cons, Line, LC, B}]};
  211. concat_lists({call, Line, _Function, _Args} = Call, B) ->
  212. %% concatenating a call with a cons
  213. {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
  214. [{cons, Line, Call, B}]};
  215. concat_lists({record_field, Line, _Var, _Record, _Field} = Rec, B) ->
  216. %% concatenating a record_field with a cons
  217. {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
  218. [{cons, Line, Rec, B}]};
  219. concat_lists({nil, _Line}, B) ->
  220. B;
  221. concat_lists({cons, Line, Element, Tail}, B) ->
  222. {cons, Line, Element, concat_lists(Tail, B)}.
  223. stash_record(Record) ->
  224. Records = case erlang:get(records) of
  225. undefined ->
  226. [];
  227. R ->
  228. R
  229. end,
  230. erlang:put(records, [Record|Records]).
  231. insert_record_attribute(AST) ->
  232. lists:foldl(fun({attribute, Line, module, _}=E, Acc) ->
  233. [E, {attribute, Line, lager_records, erlang:get(records)}|Acc];
  234. (E, Acc) ->
  235. [E|Acc]
  236. end, [], AST).
  237. guess_application(Dirname, Attr) when Dirname /= undefined ->
  238. case find_app_file(Dirname) of
  239. no_idea ->
  240. %% try it based on source file directory (app.src most likely)
  241. guess_application(undefined, Attr);
  242. _ ->
  243. ok
  244. end;
  245. guess_application(undefined, {attribute, _, file, {Filename, _}}) ->
  246. Dir = filename:dirname(Filename),
  247. find_app_file(Dir);
  248. guess_application(_, _) ->
  249. ok.
  250. find_app_file(Dir) ->
  251. case filelib:wildcard(Dir++"/*.{app,app.src}") of
  252. [] ->
  253. no_idea;
  254. [File] ->
  255. case file:consult(File) of
  256. {ok, [{application, Appname, _Attributes}|_]} ->
  257. erlang:put(application, Appname);
  258. _ ->
  259. no_idea
  260. end;
  261. _ ->
  262. %% multiple files, uh oh
  263. no_idea
  264. end.