rewrite from lager
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

678 rivejä
22 KiB

4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
  1. -module(eRum1).
  2. -include("rumDef.hrl").
  3. -include("rumMsg.hrl").
  4. -include("rumCom.hrl").
  5. -ifdef(TEST).
  6. -include_lib("eunit/include/eunit.hrl").
  7. -endif.
  8. %% API
  9. -export([
  10. %% start stop
  11. start/0
  12. , stop/0
  13. %% log and log param
  14. , dispatch_log/13
  15. , do_log/16
  16. , do_log_unsafe/16
  17. , safe_format/3
  18. , safe_format_chop/3
  19. , unsafe_format/2
  20. , md/0
  21. , md/1
  22. , getLogLevel/1
  23. , getLogLevel/2
  24. , setLogLevel/2
  25. , setLogLevel/3
  26. , setLogLevel/4
  27. , getLogLevels/1
  28. , updateLogevelCfg/1
  29. , setLogHwm/2
  30. , setLogHwm/3
  31. , setLogHwm/4
  32. , rotate_handler/1
  33. , rotate_handler/2
  34. , rotate_sink/1
  35. , rotate_all/0
  36. , posix_error/1
  37. %% stack
  38. , pr/2
  39. , pr/3
  40. , pr_stacktrace/1
  41. , pr_stacktrace/2
  42. %% trace
  43. , trace/2
  44. , trace/3
  45. , trace_file/2
  46. , trace_file/3
  47. , trace_file/4
  48. , trace_console/1
  49. , trace_console/2
  50. , install_trace/2
  51. , install_trace/3
  52. , remove_trace/1
  53. , trace_state/3
  54. , trace_func/3
  55. , list_all_sinks/0
  56. , clear_all_traces/0
  57. , clear_trace_by_destination/1
  58. , stop_trace/1
  59. , stop_trace/3
  60. , status/0
  61. ]).
  62. -record(trace_func_state_v1, {
  63. pid :: undefined | pid(),
  64. level :: rumAtomLevel(),
  65. count :: infinity | pos_integer(),
  66. format_string :: string(),
  67. timeout :: infinity | pos_integer(),
  68. started = os:timestamp() :: erlang:timestamp() %% use os:timestamp for compatability
  69. }).
  70. start() ->
  71. application:ensure_all_started(eRum).
  72. stop() ->
  73. application:stop(eRum).
  74. -spec dispatch_log(atom(), rumAtomLevel(), pid(), node(), atom(), atom(), atom(), integer(), list(), string(), list() | none, pos_integer(), safe | unsafe) -> ok | {error, lager_not_running} | {error, {sink_not_configured, atom()}}.
  75. %% this is the same check that the parse transform bakes into the module at compile time see rumTransform (lines 173-216)
  76. dispatch_log(Sink, Severity, Pid, Node, Module, Function, File, Line, Metadata, Format, Args, Size, Safety) ->
  77. SeverityAsInt = rumUtil:levelToNum(Severity),
  78. case {whereis(Sink), rumConfig:get({Sink, loglevel}, {?none, []})} of
  79. {undefined, _} -> {error, lager_not_running};
  80. {SinkPid, {Level, Traces}} when Safety =:= safe andalso ((Level band SeverityAsInt) /= 0 orelse Traces /= []) ->
  81. do_log(Severity, Pid, Node, Module, Function, File, Line, Metadata, Format, Args, Size, SeverityAsInt, Level, Traces, Sink, SinkPid);
  82. {SinkPid, {Level, Traces}} when Safety =:= unsafe andalso ((Level band SeverityAsInt) /= 0 orelse Traces /= []) ->
  83. do_log_unsafe(Severity, Pid, Node, Module, Function, File, Line, Metadata, Format, Args, Size, SeverityAsInt, Level, Traces, Sink, SinkPid);
  84. _ -> ok
  85. end.
  86. %% Should only be called externally from code generated from the parse transform
  87. do_log(Severity, Pid, Node, Module, Function, File, Line, Metadata, Format, Args, Size, SeverityAsInt, LevelThreshold, TraceFilters, Sink, SinkPid) ->
  88. FormatFun = fun() -> safe_format_chop(Format, Args, Size) end,
  89. do_log_impl(Severity, Pid, Node, Module, Function, File, Line, Metadata, Format, Args, SeverityAsInt, LevelThreshold, TraceFilters, Sink, SinkPid, FormatFun).
  90. %% Should only be called externally from code generated from the parse transform Specifically, it would be level ++ `_unsafe' as in `info_unsafe'.
  91. do_log_unsafe(Severity, Pid, Node, Module, Function, File, Line, Metadata, Format, Args, _Size, SeverityAsInt, LevelThreshold, TraceFilters, Sink, SinkPid) ->
  92. FormatFun = fun() -> unsafe_format(Format, Args) end,
  93. do_log_impl(Severity, Pid, Node, Module, Function, File, Line, Metadata, Format, Args, SeverityAsInt, LevelThreshold, TraceFilters, Sink, SinkPid, FormatFun).
  94. do_log_impl(Severity, Pid, Node, Module, Function, _File, Line, Metadata, Format, Args, SeverityAsInt, LevelThreshold, TraceFilters, Sink, SinkPid, FormatFun) ->
  95. {Destinations, TraceSinkPid} =
  96. case TraceFilters of
  97. [] ->
  98. {[], undefined};
  99. _ ->
  100. {rumUtil:check_traces(Metadata, SeverityAsInt, TraceFilters, []), whereis(?RumTrackSink)}
  101. end,
  102. case (LevelThreshold band SeverityAsInt) /= 0 orelse Destinations /= [] of
  103. true ->
  104. Msg =
  105. case Args of
  106. A when is_list(A) ->
  107. FormatFun();
  108. _ ->
  109. Format
  110. end,
  111. LagerMsg = ?newMsg(Severity, Pid, Node, Module, Function, Line, Metadata, Destinations, rumUtil:nowMs(), Msg),
  112. case rumConfig:get({Sink, async}, false) of
  113. true ->
  114. gen_emm:info_notify(SinkPid, {mWriteLog, LagerMsg});
  115. false ->
  116. gen_emm:call_notify(SinkPid, {mWriteLog, LagerMsg})
  117. end,
  118. case TraceSinkPid /= undefined of
  119. true ->
  120. gen_emm:info_notify(TraceSinkPid, {mWriteLog, LagerMsg});
  121. false ->
  122. ok
  123. end;
  124. false ->
  125. ok
  126. end.
  127. %% @doc Get lager metadata for current process
  128. -spec md() -> [{atom(), any()}].
  129. md() ->
  130. case erlang:get(?PdMdKey) of
  131. undefined -> [];
  132. MD -> MD
  133. end.
  134. %% @doc Set lager metadata for current process.
  135. %% Will badarg if you don't supply a list of {key, value} tuples keyed by atoms.
  136. -spec md([{atom(), any()}, ...]) -> ok.
  137. md(NewMD) when is_list(NewMD) ->
  138. %% make sure its actually a real proplist
  139. case lists:all(
  140. fun({Key, _Value}) when is_atom(Key) -> true;
  141. (_) -> false
  142. end, NewMD) of
  143. true ->
  144. erlang:put(?PdMdKey, NewMD),
  145. ok;
  146. false ->
  147. erlang:error(badarg)
  148. end;
  149. md(_) ->
  150. erlang:error(badarg).
  151. validate_trace_filters(Filters, Level, Backend) ->
  152. Sink = proplists:get_value(sink, Filters, ?RumDefSink),
  153. {Sink,
  154. rumUtil:validate_trace({
  155. proplists:delete(sink, Filters),
  156. Level,
  157. Backend
  158. })
  159. }.
  160. %% @doc Set the loglevel for a particular backend.
  161. setLogLevel(Handler, Level) when is_atom(Level) ->
  162. setLogLevel(?RumDefSink, Handler, undefined, Level).
  163. %% @doc Set the loglevel for a particular backend that has multiple identifiers
  164. %% (eg. the file backend).
  165. setLogLevel(Handler, Ident, Level) when is_atom(Level) ->
  166. setLogLevel(?RumDefSink, Handler, Ident, Level).
  167. %% @doc Set the loglevel for a particular sink's backend that potentially has
  168. %% multiple identifiers. (Use `undefined' if it doesn't have any.)
  169. setLogLevel(Sink, Handler, Ident, Level) when is_atom(Level) ->
  170. HandlerArg =
  171. case Ident of
  172. undefined -> Handler;
  173. _ -> {Handler, Ident}
  174. end,
  175. Reply = gen_emm:call(Sink, HandlerArg, {mSetLogLevel, Level}, infinity),
  176. updateLogevelCfg(Sink),
  177. Reply.
  178. %% @doc Get the loglevel for a particular backend on the default sink. In the case that the backend has multiple identifiers, the lowest is returned.
  179. getLogLevel(Handler) ->
  180. getLogLevel(?RumDefSink, Handler).
  181. %% @doc Get the loglevel for a particular sink's backend. In the case that the backend
  182. %% has multiple identifiers, the lowest is returned.
  183. getLogLevel(Sink, Handler) ->
  184. case gen_emm:call(Sink, Handler, mGetLogLevel, infinity) of
  185. Mask when is_integer(Mask) ->
  186. case rumUtil:maskToLevels(Mask) of
  187. [] -> none;
  188. Levels -> hd(Levels)
  189. end;
  190. Y -> Y
  191. end.
  192. getLogLevels(Sink) ->
  193. [gen_emm:call(Sink, Handler, mGetLogLevel, infinity) || Handler <- gen_emm:which_epm(Sink)].
  194. %% @doc Set the loghwm for the default sink.
  195. setLogHwm(Handler, Hwm) when is_integer(Hwm) ->
  196. setLogHwm(?RumDefSink, Handler, Hwm).
  197. %% @doc Set the loghwm for a particular backend.
  198. setLogHwm(Sink, Handler, Hwm) when is_integer(Hwm) ->
  199. gen_emm:call(Sink, Handler, {mSetLogHwm, Hwm}, infinity).
  200. %% @doc Set the loghwm (log high water mark) for file backends with multiple identifiers
  201. setLogHwm(Sink, Handler, Ident, Hwm) when is_integer(Hwm) ->
  202. gen_emm:call(Sink, {Handler, Ident}, {mSetLogHwm, Hwm}, infinity).
  203. %% @doc Print stacktrace in human readable form
  204. pr_stacktrace(Stacktrace) ->
  205. Stacktrace1 =
  206. case rumUtil:get_env(reversePrettyStacktrace, true) of
  207. true ->
  208. lists:reverse(Stacktrace);
  209. _ ->
  210. Stacktrace
  211. end,
  212. pr_stacktrace_(Stacktrace1).
  213. pr_stacktrace_(Stacktrace) ->
  214. Indent = "\n ",
  215. lists:foldl(
  216. fun(Entry, Acc) ->
  217. Acc ++ Indent ++ binary_to_list(rumErrLoggerH:formatMfa(Entry))
  218. end,
  219. [],
  220. Stacktrace).
  221. pr_stacktrace(Stacktrace, {Class, Reason}) ->
  222. case rumUtil:get_env(reversePrettyStacktrace, true) of
  223. true ->
  224. lists:flatten(
  225. pr_stacktrace_(lists:reverse(Stacktrace)) ++ "\n" ++ io_lib:format("~s:~p", [Class, Reason]));
  226. _ ->
  227. lists:flatten(
  228. io_lib:format("~s:~p", [Class, Reason]) ++ pr_stacktrace_(Stacktrace))
  229. end.
  230. rotate_sink(Sink) ->
  231. Handlers = rumConfig:global_get(handlers),
  232. RotateHandlers = lists:filtermap(
  233. fun({Handler, _, S}) when S == Sink -> {true, {Handler, Sink}};
  234. (_) -> false
  235. end,
  236. Handlers),
  237. rotate_handlers(RotateHandlers).
  238. rotate_all() ->
  239. rotate_handlers(lists:map(fun({H, _, S}) -> {H, S} end,
  240. rumConfig:global_get(handlers))).
  241. rotate_handlers(Handlers) ->
  242. [rotate_handler(Handler, Sink) || {Handler, Sink} <- Handlers].
  243. rotate_handler(Handler) ->
  244. Handlers = rumConfig:global_get(handlers),
  245. case lists:keyfind(Handler, 1, Handlers) of
  246. {Handler, _, Sink} -> rotate_handler(Handler, Sink);
  247. false -> ok
  248. end.
  249. rotate_handler(Handler, Sink) ->
  250. gen_emm:call(Sink, Handler, mRotate, ?RumRotateTimeout).
  251. trace_file(File, Filter) ->
  252. trace_file(File, Filter, debug, []).
  253. trace_file(File, Filter, Level) when is_atom(Level) ->
  254. trace_file(File, Filter, Level, []);
  255. trace_file(File, Filter, Options) when is_list(Options) ->
  256. trace_file(File, Filter, debug, Options).
  257. trace_file(File, Filter, Level, Options) ->
  258. FileName = rumUtil:parsePath(File),
  259. case validate_trace_filters(Filter, Level, {rumBkdFile, FileName}) of
  260. {Sink, {ok, Trace}} ->
  261. Handlers = rumConfig:global_get(handlers, []),
  262. %% check if this file backend is already installed
  263. Res =
  264. case rumUtil:find_file(FileName, Handlers) of
  265. false ->
  266. %% install the handler
  267. LogFileConfig =
  268. lists:keystore(level, 1,
  269. lists:keystore(file, 1,
  270. Options,
  271. {file, FileName}),
  272. {level, none}),
  273. HandlerInfo =
  274. eRum_app:startHandler(Sink, {rumBkdFile, FileName}, LogFileConfig),
  275. rumConfig:global_set(handlers, [HandlerInfo | Handlers]),
  276. {ok, installed};
  277. {_Watcher, _Handler, Sink} ->
  278. {ok, exists};
  279. {_Watcher, _Handler, _OtherSink} ->
  280. {error, file_in_use}
  281. end,
  282. case Res of
  283. {ok, _} ->
  284. add_trace_to_loglevel_config(Trace, Sink),
  285. {ok, {{rumBkdFile, FileName}, Filter, Level}};
  286. {error, _} = E ->
  287. E
  288. end;
  289. {_Sink, Error} ->
  290. Error
  291. end.
  292. trace_console(Filter) ->
  293. trace_console(Filter, debug).
  294. trace_console(Filter, Level) ->
  295. trace(rumBkdConsole, Filter, Level).
  296. trace(Backend, Filter) ->
  297. trace(Backend, Filter, debug).
  298. trace({rumBkdFile, File}, Filter, Level) ->
  299. trace_file(File, Filter, Level);
  300. trace(Backend, Filter, Level) ->
  301. case validate_trace_filters(Filter, Level, Backend) of
  302. {Sink, {ok, Trace}} ->
  303. add_trace_to_loglevel_config(Trace, Sink),
  304. {ok, {Backend, Filter, Level}};
  305. {_Sink, Error} ->
  306. Error
  307. end.
  308. stop_trace(Backend, Filter, Level) ->
  309. case validate_trace_filters(Filter, Level, Backend) of
  310. {Sink, {ok, Trace}} ->
  311. stop_trace_int(Trace, Sink);
  312. {_Sink, Error} ->
  313. Error
  314. end.
  315. stop_trace({Backend, Filter, Level}) ->
  316. stop_trace(Backend, Filter, Level).
  317. %% Important: validate_trace_filters orders the arguments of
  318. %% trace tuples differently than the way outside callers have
  319. %% the trace tuple.
  320. %%
  321. %% That is to say, outside they are represented as
  322. %% `{Backend, Filter, Level}'
  323. %%
  324. %% and when they come back from validation, they're
  325. %% `{Filter, Level, Backend}'
  326. stop_trace_int({_Filter, _Level, Backend} = Trace, Sink) ->
  327. {Level, Traces} = rumConfig:get({Sink, loglevel}),
  328. NewTraces = lists:delete(Trace, Traces),
  329. _ = rumUtil:trace_filter([element(1, T) || T <- NewTraces]),
  330. %MinLevel = minimum_loglevel(get_loglevels() ++ get_trace_levels(NewTraces)),
  331. rumConfig:set({Sink, loglevel}, {Level, NewTraces}),
  332. case getLogLevel(Sink, Backend) of
  333. none ->
  334. %% check no other traces point here
  335. case lists:keyfind(Backend, 3, NewTraces) of
  336. false ->
  337. gen_emm:delEpm(Sink, Backend, []),
  338. rumConfig:global_set(handlers,
  339. lists:keydelete(Backend, 1,
  340. rumConfig:global_get(handlers)));
  341. _ ->
  342. ok
  343. end;
  344. _ ->
  345. ok
  346. end,
  347. ok.
  348. %% @doc installs a lager trace handler into the target process (using sys:install) at the specified level.
  349. -spec install_trace(pid(), rumAtomLevel()) -> ok.
  350. install_trace(Pid, Level) ->
  351. install_trace(Pid, Level, []).
  352. -spec install_trace(pid(), rumAtomLevel(), [{count, infinity | pos_integer()} | {format_string, string()} | {timeout, timeout()}]) -> ok.
  353. install_trace(Pid, Level, Options) ->
  354. sys:install(Pid, {fun ?MODULE:trace_func/3, trace_state(Pid, Level, Options)}).
  355. %% @doc remove a previously installed lager trace handler from the target process.
  356. -spec remove_trace(pid()) -> ok.
  357. remove_trace(Pid) ->
  358. sys:remove(Pid, fun ?MODULE:trace_func/3).
  359. list_all_sinks() ->
  360. sets:to_list(
  361. lists:foldl(fun({_Watcher, _Handler, Sink}, Set) ->
  362. sets:add_element(Sink, Set)
  363. end,
  364. sets:new(),
  365. rumConfig:global_get(handlers, []))).
  366. clear_traces_by_sink(Sinks) ->
  367. lists:foreach(fun(S) ->
  368. {Level, _Traces} =
  369. rumConfig:get({S, loglevel}),
  370. rumConfig:set({S, loglevel},
  371. {Level, []})
  372. end,
  373. Sinks).
  374. clear_trace_by_destination(ID) ->
  375. Sinks = lists:sort(list_all_sinks()),
  376. Traces = find_traces(Sinks),
  377. [stop_trace_int({Filter, Level, Destination}, Sink) || {Sink, {Filter, Level, Destination}} <- Traces, Destination == ID].
  378. clear_all_traces() ->
  379. Handlers = rumConfig:global_get(handlers, []),
  380. clear_traces_by_sink(list_all_sinks()),
  381. _ = rumUtil:trace_filter(none),
  382. rumConfig:global_set(handlers,
  383. lists:filter(
  384. fun({Handler, _Watcher, Sink}) ->
  385. case getLogLevel(Sink, Handler) of
  386. none ->
  387. gen_emm:delEpm(Sink, Handler, []),
  388. false;
  389. _ ->
  390. true
  391. end
  392. end, Handlers)).
  393. find_traces(Sinks) ->
  394. lists:foldl(fun(S, Acc) ->
  395. {_Level, Traces} = rumConfig:get({S, loglevel}),
  396. Acc ++ lists:map(fun(T) -> {S, T} end, Traces)
  397. end,
  398. [],
  399. Sinks).
  400. status() ->
  401. Handlers = rumConfig:global_get(handlers, []),
  402. Sinks = lists:sort(list_all_sinks()),
  403. Traces = find_traces(Sinks),
  404. TraceCount = case length(Traces) of
  405. 0 -> 1;
  406. N -> N
  407. end,
  408. Status = ["Lager status:\n",
  409. [begin
  410. Level = getLogLevel(Sink, Handler),
  411. get_sink_handler_status(Sink, Handler, Level)
  412. end || {Handler, _Watcher, Sink} <- lists:sort(fun({_, _, S1},
  413. {_, _, S2}) -> S1 =< S2 end,
  414. Handlers)],
  415. "Active Traces:\n",
  416. [begin
  417. LevelName =
  418. case rumUtil:maskToLevels(Level) of
  419. [] -> none;
  420. Levels -> hd(Levels)
  421. end,
  422. io_lib:format("Tracing messages matching ~p (sink ~s) at level ~p to ~p\n",
  423. [Filter, Sink, LevelName, Destination])
  424. end || {Sink, {Filter, Level, Destination}} <- Traces],
  425. [
  426. "Tracing Reductions:\n",
  427. case ?RumDefTracer:info('query') of
  428. {null, false} -> "";
  429. Query -> io_lib:format("~p~n", [Query])
  430. end
  431. ],
  432. [
  433. "Tracing Statistics:\n ",
  434. [begin
  435. [" ", atom_to_list(Table), ": ",
  436. integer_to_list(?RumDefTracer:info(Table) div TraceCount),
  437. "\n"]
  438. end || Table <- [input, output, filter]]
  439. ]],
  440. io:put_chars(Status).
  441. get_sink_handler_status(Sink, Handler, Level) ->
  442. case Handler of
  443. {rumBkdFile, File} ->
  444. io_lib:format("File ~ts (~s) at level ~p\n", [File, Sink, Level]);
  445. rumBkdConsole ->
  446. io_lib:format("Console (~s) at level ~p\n", [Sink, Level]);
  447. _ ->
  448. []
  449. end.
  450. %% @doc Try to convert an atom to a posix error, but fall back on printing the
  451. %% term if its not a valid posix error code.
  452. posix_error(Error) when is_atom(Error) ->
  453. case erl_posix_msg:message(Error) of
  454. "unknown POSIX error" -> atom_to_list(Error);
  455. Message -> Message
  456. end;
  457. posix_error(Error) ->
  458. safe_format_chop("~p", [Error], ?RumDefTruncation).
  459. %% @private
  460. add_trace_to_loglevel_config(Trace, Sink) ->
  461. {MinLevel, Traces} = rumConfig:get({Sink, loglevel}),
  462. case lists:member(Trace, Traces) of
  463. false ->
  464. NewTraces = [Trace | Traces],
  465. _ = rumUtil:trace_filter([element(1, T) || T <- NewTraces]),
  466. rumConfig:set({Sink, loglevel}, {MinLevel, [Trace | Traces]});
  467. _ ->
  468. ok
  469. end.
  470. %% @doc recalculate min log level
  471. updateLogevelCfg(error_logger) ->
  472. %% Not a sink under our control, part of the Erlang logging
  473. %% utility that error_logger_lager_h attaches to
  474. true;
  475. updateLogevelCfg(Sink) ->
  476. {_, Traces} = rumConfig:get({Sink, loglevel}, {ignore_me, []}),
  477. MinLog = minLogLevel(getLogLevels(Sink)),
  478. rumConfig:set({Sink, loglevel}, {MinLog, Traces}).
  479. minLogLevel(Levels) ->
  480. lists:foldl(
  481. fun(Mask, Acc) ->
  482. Mask bor Acc
  483. end, 0, Levels).
  484. %% @doc Print the format string `Fmt' with `Args' safely with a size
  485. %% limit of `Limit'. If the format string is invalid, or not enough
  486. %% arguments are supplied 'FORMAT ERROR' is printed with the offending
  487. %% arguments. The caller is NOT crashed.
  488. safe_format(Fmt, Args, Limit) ->
  489. safe_format(Fmt, Args, Limit, []).
  490. safe_format(Fmt, Args, Limit, Options) ->
  491. try rumTruncIo:format(Fmt, Args, Limit, Options)
  492. catch
  493. _:_ -> rumTruncIo:format("FORMAT ERROR: ~p ~p", [Fmt, Args], Limit)
  494. end.
  495. %% @private
  496. safe_format_chop(Fmt, Args, Limit) ->
  497. safe_format(Fmt, Args, Limit, [{chomp, true}]).
  498. %% @private Print the format string `Fmt' with `Args' without a size limit.
  499. %% This is unsafe because the output of this function is unbounded.
  500. %%
  501. %% Log messages with unbounded size will kill your application dead as
  502. %% OTP mechanisms stuggle to cope with them. So this function is
  503. %% intended <b>only</b> for messages which have a reasonable bounded
  504. %% size before they're formatted.
  505. %%
  506. %% If the format string is invalid or not enough arguments are
  507. %% supplied a 'FORMAT ERROR' message is printed instead with the
  508. %% offending arguments. The caller is NOT crashed.
  509. unsafe_format(Fmt, Args) ->
  510. try io_lib:format(Fmt, Args)
  511. catch
  512. _:_ -> io_lib:format("FORMAT ERROR: ~p ~p", [Fmt, Args])
  513. end.
  514. %% @doc Print a record or a list of records lager found during parse transform
  515. pr(Record, Module) when is_tuple(Record), is_atom(element(1, Record)) ->
  516. pr(Record, Module, []);
  517. pr(List, Module) when is_list(List) ->
  518. pr(List, Module, []);
  519. pr(Record, _) ->
  520. Record.
  521. %% @doc Print a record or a list of records lager found during parse transform
  522. pr(Record, Module, Options) when is_tuple(Record), is_atom(element(1, Record)), is_list(Options) ->
  523. try
  524. case is_record_known(Record, Module) of
  525. false ->
  526. Record;
  527. {RecordName, RecordFields} ->
  528. {'$lager_record', RecordName,
  529. zip(RecordFields, tl(tuple_to_list(Record)), Module, Options, [])}
  530. end
  531. catch
  532. error:undef ->
  533. Record
  534. end;
  535. pr([Head | Tail], Module, Options) when is_list(Options) ->
  536. [pr(Head, Module, Options) | pr(Tail, Module, Options)];
  537. pr(Record, _, _) ->
  538. Record.
  539. zip([FieldName | RecordFields], [FieldValue | Record], Module, Options, ToReturn) when is_list(FieldValue) ->
  540. zip(RecordFields, Record, Module, Options,
  541. [{FieldName, pr(FieldValue, Module, Options)} | ToReturn]);
  542. zip([FieldName | RecordFields], [FieldValue | Record], Module, Options, ToReturn) ->
  543. Compress = lists:member(compress, Options),
  544. case is_tuple(FieldValue) andalso
  545. tuple_size(FieldValue) > 0 andalso
  546. is_atom(element(1, FieldValue)) andalso
  547. is_record_known(FieldValue, Module) of
  548. false when Compress andalso FieldValue =:= undefined ->
  549. zip(RecordFields, Record, Module, Options, ToReturn);
  550. false ->
  551. zip(RecordFields, Record, Module, Options, [{FieldName, FieldValue} | ToReturn]);
  552. _Else ->
  553. F = {FieldName, pr(FieldValue, Module, Options)},
  554. zip(RecordFields, Record, Module, Options, [F | ToReturn])
  555. end;
  556. zip([], [], _Module, _Compress, ToReturn) ->
  557. lists:reverse(ToReturn).
  558. is_record_known(Record, Module) ->
  559. Name = element(1, Record),
  560. Attrs = Module:module_info(attributes),
  561. case lists:keyfind(lager_records, 1, Attrs) of
  562. false -> false;
  563. {lager_records, Records} ->
  564. case lists:keyfind(Name, 1, Records) of
  565. false -> false;
  566. {Name, RecordFields} ->
  567. case (tuple_size(Record) - 1) =:= length(RecordFields) of
  568. false -> false;
  569. true -> {Name, RecordFields}
  570. end
  571. end
  572. end.
  573. %% @private
  574. trace_func(#trace_func_state_v1{pid = Pid, level = Level, format_string = Fmt} = FuncState, Event, ProcState) ->
  575. _ = eRum:log(Level, Pid, Fmt, [Event, ProcState]),
  576. check_timeout(decrement_count(FuncState)).
  577. %% @private
  578. trace_state(Pid, Level, Options) ->
  579. #trace_func_state_v1{pid = Pid,
  580. level = Level,
  581. count = proplists:get_value(count, Options, infinity),
  582. timeout = proplists:get_value(timeout, Options, infinity),
  583. format_string = proplists:get_value(format_string, Options, "TRACE ~p ~p")}.
  584. decrement_count(#trace_func_state_v1{count = infinity} = FuncState) ->
  585. FuncState;
  586. decrement_count(#trace_func_state_v1{count = 1}) ->
  587. %% hit the counter limit
  588. done;
  589. decrement_count(#trace_func_state_v1{count = Count} = FuncState) ->
  590. FuncState#trace_func_state_v1{count = Count - 1}.
  591. check_timeout(#trace_func_state_v1{timeout = infinity} = FuncState) ->
  592. FuncState;
  593. check_timeout(#trace_func_state_v1{timeout = Timeout, started = Started} = FuncState) ->
  594. case (timer:now_diff(os:timestamp(), Started) / 1000) > Timeout of
  595. true ->
  596. done;
  597. false ->
  598. FuncState
  599. end.