25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

374 lines
18 KiB

14 년 전
14 년 전
14 년 전
14 년 전
14 년 전
14 년 전
14 년 전
14 년 전
14 년 전
14 년 전
14 년 전
14 년 전
14 년 전
14 년 전
14 년 전
6 년 전
6 년 전
6 년 전
  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. Functions = proplists:get_value(lager_function_transforms, Options, []),
  31. UseLogger = proplists:get_value(lager_use_logger, Options, false),
  32. put(print_records_flag, Enable),
  33. put(truncation_size, TruncSize),
  34. put(sinks, Sinks),
  35. put(functions, lists:keysort(1, Functions)),
  36. put(use_logger, UseLogger),
  37. erlang:put(records, []),
  38. %% .app file should either be in the outdir, or the same dir as the source file
  39. guess_application(proplists:get_value(outdir, Options), hd(AST)),
  40. walk_ast([], AST).
  41. walk_ast(Acc, []) ->
  42. case get(print_records_flag) of
  43. true ->
  44. insert_record_attribute(Acc);
  45. false ->
  46. lists:reverse(Acc)
  47. end;
  48. walk_ast(Acc, [{attribute, _, module, {Module, _PmodArgs}}=H|T]) ->
  49. %% A wild parameterized module appears!
  50. put(module, Module),
  51. walk_ast([H|Acc], T);
  52. walk_ast(Acc, [{attribute, _, module, Module}=H|T]) ->
  53. put(module, Module),
  54. walk_ast([H|Acc], T);
  55. walk_ast(Acc, [{attribute, _, lager_function_transforms, FromModule }=H|T]) ->
  56. %% Merge transform options from the module over the compile options
  57. FromOptions = get(functions),
  58. put(functions, orddict:merge(fun(_Key, _V1, V2) -> V2 end, FromOptions, lists:keysort(1, FromModule))),
  59. walk_ast([H|Acc], T);
  60. walk_ast(Acc, [{function, Line, Name, Arity, Clauses}|T]) ->
  61. put(function, Name),
  62. put(arity, Arity),
  63. walk_ast([{function, Line, Name, Arity,
  64. walk_clauses([], Clauses)}|Acc], T);
  65. walk_ast(Acc, [{attribute, _, record, {Name, Fields}}=H|T]) ->
  66. FieldNames = lists:map(fun record_field_name/1, Fields),
  67. stash_record({Name, FieldNames}),
  68. walk_ast([H|Acc], T);
  69. walk_ast(Acc, [H|T]) ->
  70. walk_ast([H|Acc], T).
  71. record_field_name({record_field, _, {atom, _, FieldName}}) ->
  72. FieldName;
  73. record_field_name({record_field, _, {atom, _, FieldName}, _Default}) ->
  74. FieldName;
  75. record_field_name({typed_record_field, Field, _Type}) ->
  76. record_field_name(Field).
  77. walk_clauses(Acc, []) ->
  78. lists:reverse(Acc);
  79. walk_clauses(Acc, [{clause, Line, Arguments, Guards, Body}|T]) ->
  80. walk_clauses([{clause, Line, Arguments, Guards, walk_body([], Body)}|Acc], T).
  81. walk_body(Acc, []) ->
  82. lists:reverse(Acc);
  83. walk_body(Acc, [H|T]) ->
  84. walk_body([transform_statement(H, get(sinks))|Acc], T).
  85. transform_statement({call, Line, {remote, _Line1, {atom, _Line2, Module},
  86. {atom, _Line3, Function}}, Arguments0} = Stmt,
  87. Sinks) ->
  88. case lists:member(Module, Sinks) of
  89. true ->
  90. case lists:member(Function, ?LEVELS) of
  91. true ->
  92. SinkName = lager_util:make_internal_sink_name(Module),
  93. do_transform(Line, SinkName, Function, Arguments0);
  94. false ->
  95. case lists:keyfind(Function, 1, ?LEVELS_UNSAFE) of
  96. {Function, Severity} ->
  97. SinkName = lager_util:make_internal_sink_name(Module),
  98. do_transform(Line, SinkName, Severity, Arguments0, unsafe);
  99. false ->
  100. Stmt
  101. end
  102. end;
  103. false ->
  104. list_to_tuple(transform_statement(tuple_to_list(Stmt), Sinks))
  105. end;
  106. transform_statement(Stmt, Sinks) when is_tuple(Stmt) ->
  107. list_to_tuple(transform_statement(tuple_to_list(Stmt), Sinks));
  108. transform_statement(Stmt, Sinks) when is_list(Stmt) ->
  109. [transform_statement(S, Sinks) || S <- Stmt];
  110. transform_statement(Stmt, _Sinks) ->
  111. Stmt.
  112. add_function_transforms(_Line, DefaultAttrs, []) ->
  113. DefaultAttrs;
  114. add_function_transforms(Line, DefaultAttrs, [{Atom, on_emit, {Module, Function}}|Remainder]) ->
  115. NewFunction = {tuple, Line, [
  116. {atom, Line, Atom},
  117. {'fun', Line, {
  118. function, {atom, Line, Module}, {atom, Line, Function}, {integer, Line, 0}
  119. }}
  120. ]},
  121. add_function_transforms(Line, {cons, Line, NewFunction, DefaultAttrs}, Remainder);
  122. add_function_transforms(Line, DefaultAttrs, [{Atom, on_log, {Module, Function}}|Remainder]) ->
  123. NewFunction = {tuple, Line, [
  124. {atom, Line, Atom},
  125. {call, Line, {remote, Line, {atom, Line, Module}, {atom, Line, Function}}, []}
  126. ]},
  127. add_function_transforms(Line, {cons, Line, NewFunction, DefaultAttrs}, Remainder).
  128. do_transform(Line, SinkName, Severity, Arguments0) ->
  129. do_transform(Line, SinkName, Severity, Arguments0, safe).
  130. do_transform(Line, SinkName, Severity, Arguments0, Safety) ->
  131. DefaultAttrs0 = case get(use_logger) of
  132. true ->
  133. {cons, Line, {tuple, Line, [
  134. {atom, Line, pid}, {call, Line, {atom, Line, self}, []}]},
  135. {cons, Line, {tuple, Line, [
  136. {atom, Line, gl}, {call, Line, {atom, Line, group_leader}, []}]},
  137. {cons, Line, {tuple, Line, [
  138. {atom, Line, time}, {call, Line, {remote, Line, {atom, Line, erlang}, {atom, Line, system_time}}, [{atom, Line, microsecond}]}]},
  139. {cons, Line, {tuple, Line, [
  140. {atom, Line, mfa}, {tuple, Line, [{atom, Line, get(module)}, {atom, Line, get(function)}, {atom, Line, get(arity)}]}]},
  141. {cons, Line, {tuple, Line, [
  142. {atom, Line, file}, {string, Line, get(filename)}]},
  143. {cons, Line, {tuple, Line, [
  144. {atom, Line, line}, {integer, Line, Line}]},
  145. %% get the metadata with lager:md(), this will always return a list so we can use it as the tail here
  146. {call, Line, {remote, Line, {atom, Line, lager}, {atom, Line, md}}, []}}}}}}};
  147. false ->
  148. {cons, Line, {tuple, Line, [
  149. {atom, Line, module}, {atom, Line, get(module)}]},
  150. {cons, Line, {tuple, Line, [
  151. {atom, Line, function}, {atom, Line, get(function)}]},
  152. {cons, Line, {tuple, Line, [
  153. {atom, Line, line},
  154. {integer, Line, Line}]},
  155. {cons, Line, {tuple, Line, [
  156. {atom, Line, pid},
  157. {call, Line, {atom, Line, pid_to_list}, [
  158. {call, Line, {atom, Line ,self}, []}]}]},
  159. {cons, Line, {tuple, Line, [
  160. {atom, Line, node},
  161. {call, Line, {atom, Line, node}, []}]},
  162. %% get the metadata with lager:md(), this will always return a list so we can use it as the tail here
  163. {call, Line, {remote, Line, {atom, Line, lager}, {atom, Line, md}}, []}}}}}}
  164. end,
  165. Functions = get(functions),
  166. DefaultAttrs1 = add_function_transforms(Line, DefaultAttrs0, Functions),
  167. DefaultAttrs = case erlang:get(application) of
  168. undefined ->
  169. DefaultAttrs1;
  170. App ->
  171. %% stick the application in the attribute list
  172. concat_lists({cons, Line, {tuple, Line, [
  173. {atom, Line, application},
  174. {atom, Line, App}]},
  175. {nil, Line}}, DefaultAttrs1)
  176. end,
  177. {Meta, Message, Arguments} = handle_args(DefaultAttrs, Line, Arguments0),
  178. case get(use_logger) of
  179. true ->
  180. case Arguments of
  181. {atom, _, none} ->
  182. %% logger:log(Level, Format, Args, Metadata)
  183. {call,Line,{remote, Line, {atom, Line, logger}, {atom, Line, log}},
  184. [{atom,Line,Severity}, Message, {call, Line, {remote, Line, {atom, Line, maps}, {atom, Line, from_list}}, [Meta]}]};
  185. _ ->
  186. %% logger:log(Level, String, Metadata)
  187. {call,Line,{remote, Line, {atom, Line, logger}, {atom, Line, log}},
  188. [{atom,Line,Severity}, Message, Arguments, {call, Line, {remote, Line, {atom, Line, maps}, {atom, Line, from_list}}, [Meta]}]}
  189. end;
  190. false ->
  191. SeverityAsInt=lager_util:level_to_num(Severity),
  192. %% Generate some unique variable names so we don't accidentally export from case clauses.
  193. %% Note that these are not actual atoms, but the AST treats variable names as atoms.
  194. LevelVar = make_varname("__Level", Line),
  195. TracesVar = make_varname("__Traces", Line),
  196. PidVar = make_varname("__Pid", Line),
  197. LogFun = case Safety of
  198. safe ->
  199. do_log;
  200. unsafe ->
  201. do_log_unsafe
  202. end,
  203. %% Wrap the call to lager:dispatch_log/6 in case that will avoid doing any work if this message is not elegible for logging
  204. %% See lager.erl (lines 89-100) for lager:dispatch_log/6
  205. %% case {whereis(Sink), whereis(?DEFAULT_SINK), lager_config:get({Sink, loglevel}, {?LOG_NONE, []})} of
  206. {'case',Line,
  207. {tuple,Line,
  208. [{call,Line,{atom,Line,whereis},[{atom,Line,SinkName}]},
  209. {call,Line,{atom,Line,whereis},[{atom,Line,?DEFAULT_SINK}]},
  210. {call,Line,
  211. {remote,Line,{atom,Line,lager_config},{atom,Line,get}},
  212. [{tuple,Line,[{atom,Line,SinkName},{atom,Line,loglevel}]},
  213. {tuple,Line,[{integer,Line,0},{nil,Line}]}]}]},
  214. %% {undefined, undefined, _} -> {error, lager_not_running};
  215. [{clause,Line,
  216. [{tuple,Line,
  217. [{atom,Line,undefined},{atom,Line,undefined},{var,Line,'_'}]}],
  218. [],
  219. %% trick the linter into avoiding a 'term constructed but not used' error:
  220. %% (fun() -> {error, lager_not_running} end)()
  221. [{call, Line, {'fun', Line, {clauses, [{clause, Line, [],[], [{tuple, Line, [{atom, Line, error},{atom, Line, lager_not_running}]}]}]}}, []}]
  222. },
  223. %% {undefined, _, _} -> {error, {sink_not_configured, Sink}};
  224. {clause,Line,
  225. [{tuple,Line,
  226. [{atom,Line,undefined},{var,Line,'_'},{var,Line,'_'}]}],
  227. [],
  228. %% same trick as above to avoid linter error
  229. [{call, Line, {'fun', Line, {clauses, [{clause, Line, [],[], [{tuple,Line, [{atom,Line,error}, {tuple,Line,[{atom,Line,sink_not_configured},{atom,Line,SinkName}]}]}]}]}}, []}]
  230. },
  231. %% {SinkPid, _, {Level, Traces}} when ... -> lager:do_log/9;
  232. {clause,Line,
  233. [{tuple,Line,
  234. [{var,Line,PidVar},
  235. {var,Line,'_'},
  236. {tuple,Line,[{var,Line,LevelVar},{var,Line,TracesVar}]}]}],
  237. [[{op, Line, 'orelse',
  238. {op, Line, '/=', {op, Line, 'band', {var, Line, LevelVar}, {integer, Line, SeverityAsInt}}, {integer, Line, 0}},
  239. {op, Line, '/=', {var, Line, TracesVar}, {nil, Line}}}]],
  240. [{call,Line,{remote, Line, {atom, Line, lager}, {atom, Line, LogFun}},
  241. [{atom,Line,Severity},
  242. Meta,
  243. Message,
  244. Arguments,
  245. {integer, Line, get(truncation_size)},
  246. {integer, Line, SeverityAsInt},
  247. {var, Line, LevelVar},
  248. {var, Line, TracesVar},
  249. {atom, Line, SinkName},
  250. {var, Line, PidVar}]}]},
  251. %% _ -> ok
  252. {clause,Line,[{var,Line,'_'}],[],[{atom,Line,ok}]}]}
  253. end.
  254. handle_args(DefaultAttrs, Line, [{cons, LineNum, {tuple, _, _}, _} = Attrs]) ->
  255. {concat_lists(DefaultAttrs, Attrs), {string, LineNum, ""}, {atom, Line, none}};
  256. handle_args(DefaultAttrs, Line, [Format]) ->
  257. {DefaultAttrs, Format, {atom, Line, none}};
  258. handle_args(DefaultAttrs, Line, [Arg1, Arg2]) ->
  259. %% some ambiguity here, figure out if these arguments are
  260. %% [Format, Args] or [Attr, Format].
  261. %% The trace attributes will be a list of tuples, so check
  262. %% for that.
  263. case {element(1, Arg1), Arg1} of
  264. {_, {cons, _, {tuple, _, _}, _}} ->
  265. {concat_lists(Arg1, DefaultAttrs),
  266. Arg2, {atom, Line, none}};
  267. {Type, _} when Type == var;
  268. Type == lc;
  269. Type == call;
  270. Type == record_field ->
  271. %% crap, its not a literal. look at the second
  272. %% argument to see if it is a string
  273. case Arg2 of
  274. {string, _, _} ->
  275. {concat_lists(Arg1, DefaultAttrs),
  276. Arg2, {atom, Line, none}};
  277. _ ->
  278. %% not a string, going to have to guess
  279. %% it's the argument list
  280. {DefaultAttrs, Arg1, Arg2}
  281. end;
  282. _ ->
  283. {DefaultAttrs, Arg1, Arg2}
  284. end;
  285. handle_args(DefaultAttrs, _Line, [Attrs, Format, Args]) ->
  286. {concat_lists(Attrs, DefaultAttrs), Format, Args}.
  287. make_varname(Prefix, Line) ->
  288. list_to_atom(Prefix ++ atom_to_list(get(module)) ++ integer_to_list(Line)).
  289. %% concat 2 list ASTs by replacing the terminating [] in A with the contents of B
  290. concat_lists({var, Line, _Name}=Var, B) ->
  291. %% concatenating a var with a cons
  292. {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
  293. [{cons, Line, Var, B}]};
  294. concat_lists({lc, Line, _Body, _Generator} = LC, B) ->
  295. %% concatenating a LC with a cons
  296. {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
  297. [{cons, Line, LC, B}]};
  298. concat_lists({call, Line, _Function, _Args} = Call, B) ->
  299. %% concatenating a call with a cons
  300. {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
  301. [{cons, Line, Call, B}]};
  302. concat_lists({record_field, Line, _Var, _Record, _Field} = Rec, B) ->
  303. %% concatenating a record_field with a cons
  304. {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
  305. [{cons, Line, Rec, B}]};
  306. concat_lists({nil, _Line}, B) ->
  307. B;
  308. concat_lists({cons, Line, Element, Tail}, B) ->
  309. {cons, Line, Element, concat_lists(Tail, B)}.
  310. stash_record(Record) ->
  311. Records = case erlang:get(records) of
  312. undefined ->
  313. [];
  314. R ->
  315. R
  316. end,
  317. erlang:put(records, [Record|Records]).
  318. insert_record_attribute(AST) ->
  319. lists:foldl(fun({attribute, Line, module, _}=E, Acc) ->
  320. [E, {attribute, Line, lager_records, erlang:get(records)}|Acc];
  321. (E, Acc) ->
  322. [E|Acc]
  323. end, [], AST).
  324. guess_application(Dirname, Attr) when Dirname /= undefined ->
  325. case find_app_file(Dirname) of
  326. no_idea ->
  327. %% try it based on source file directory (app.src most likely)
  328. guess_application(undefined, Attr);
  329. _ ->
  330. ok
  331. end;
  332. guess_application(undefined, {attribute, _, file, {Filename, _}}) ->
  333. put(filename, Filename),
  334. Dir = filename:dirname(Filename),
  335. find_app_file(Dir);
  336. guess_application(_, _) ->
  337. ok.
  338. find_app_file(Dir) ->
  339. case filelib:wildcard(Dir++"/*.{app,app.src}") of
  340. [] ->
  341. no_idea;
  342. [File] ->
  343. case file:consult(File) of
  344. {ok, [{application, Appname, _Attributes}|_]} ->
  345. erlang:put(application, Appname);
  346. _ ->
  347. no_idea
  348. end;
  349. _ ->
  350. %% multiple files, uh oh
  351. no_idea
  352. end.