rewrite from lager
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

339 rader
14 KiB

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