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

564 行
18 KiB

4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
  1. -module(eRum).
  2. -include("rumDef.hrl").
  3. -include("rumCom.hrl").
  4. -compile(inline).
  5. -compile({inline_size, 150}).
  6. %% API
  7. -export([
  8. %% start stop
  9. start/0
  10. , stop/0
  11. %% log and log param
  12. , dispatchLog/12
  13. , doLogImpl/13
  14. , safeFormat/3
  15. , unsafeFormat/2
  16. , getMd/0
  17. , setMd/1
  18. , getLogLevel/1
  19. , getLogLevel/2
  20. , setLogLevel/2
  21. , setLogLevel/3
  22. , setLogLevel/4
  23. , getLogLevels/1
  24. , upLogLevelCfg/1
  25. , setLogHwm/2
  26. , setLogHwm/3
  27. , setLogHwm/4
  28. , rotateHandler/1
  29. , rotateHandler/2
  30. , rotateSink/1
  31. , rotateAll/0
  32. %% stack parse
  33. , parseStack/1
  34. , parseStack/3
  35. %% trace
  36. , trace/2
  37. , trace/3
  38. , traceFile/2
  39. , trace_file/3
  40. , trace_file/4
  41. , trace_console/1
  42. , trace_console/2
  43. , install_trace/2
  44. , install_trace/3
  45. , remove_trace/1
  46. , trace_state/3
  47. , trace_func/3
  48. , list_all_sinks/0
  49. , clear_all_traces/0
  50. , clear_trace_by_destination/1
  51. , stop_trace/1
  52. , stop_trace/3
  53. , status/0
  54. ]).
  55. -record(trace_func_state_v1, {
  56. pid :: undefined | pid(),
  57. level :: rumAtomLevel(),
  58. count :: infinity | pos_integer(),
  59. format_string :: string(),
  60. timeout :: infinity | pos_integer(),
  61. started = os:timestamp() :: erlang:timestamp() %% use os:timestamp for compatability
  62. }).
  63. start() ->
  64. application:ensure_all_started(eRum).
  65. stop() ->
  66. application:stop(eRum).
  67. -spec dispatchLog(atom(), rumAtomLevel(), pid(), node(), atom(), atom(), integer(), list(), string(), list() | none, pos_integer(), safe | unsafe) -> ok | {error, lager_not_running} | {error, {sink_not_configured, atom()}}.
  68. %% this is the same check that the parse transform bakes into the module at compile time see rumTransform (lines 173-216)
  69. dispatchLog(Sink, Severity, Pid, Node, Module, Function, Line, Metadata, Format, Args, Size, Safety) ->
  70. case ?eRumCfg:get(Sink) band Severity /= 0 of
  71. true ->
  72. doLogImpl(Severity, Pid, Node, Module, Function, Line, Metadata, Format, Args, Severity, Size, Sink, Safety);
  73. _ ->
  74. ok
  75. end.
  76. doLogImpl(Severity, Pid, Node, Module, Function, Line, Metadata, Format, Args, Severity, Size, Sink, Safety) ->
  77. TraceFilters = rumConfig:ptGet({Sink, trace}, []),
  78. Destinations = ?IIF(TraceFilters /= [], rumUtil:check_traces(Metadata, Severity, TraceFilters, []), []),
  79. MsgStr = ?IIF(Args /= [] andalso Args /= undefined, ?IIF(Safety == safe, safeFormat(Format, [Args], [{charsLimit, Size}]), unsafeFormat(Format, [Args])), Format),
  80. NowMs = rumTime:nowMs(),
  81. NowStr = rumUtil:msToBinStr(NowMs),
  82. RumMsg = #rumMsg{severity = Severity, pid = Pid, node = Node, module = Module, function = Function, line = Line, metadata = Metadata, datetime = NowStr, timestamp = NowMs, message = MsgStr, destinations = Destinations},
  83. case rumConfig:ptGet({Sink, async}, false) of
  84. true ->
  85. gen_emm:info_notify(Sink, {mWriteLog, RumMsg});
  86. false ->
  87. gen_emm:call_notify(Sink, {mWriteLog, RumMsg})
  88. end,
  89. case whereis(?RumTrackSink) of
  90. undefined ->
  91. ok;
  92. TraceSinkPid ->
  93. gen_emm:info_notify(TraceSinkPid, {mWriteLog, RumMsg})
  94. end.
  95. %% @doc Get lager metadata for current process
  96. -spec getMd() -> [{atom(), any()}].
  97. getMd() ->
  98. case erlang:get(?PdMdKey) of
  99. undefined -> [];
  100. MD -> MD
  101. end.
  102. %% @doc Set lager metadata for current process.
  103. %% Will badarg if you don't supply a list of {key, value} tuples keyed by atoms.
  104. -spec setMd([{atom(), any()}, ...]) -> ok.
  105. setMd(NewMD) when is_list(NewMD) ->
  106. %% make sure its actually a real proplist
  107. case lists:all(
  108. fun({Key, _Value}) when is_atom(Key) -> true;
  109. (_) -> false
  110. end, NewMD) of
  111. true ->
  112. erlang:put(?PdMdKey, NewMD),
  113. ok;
  114. _ ->
  115. erlang:error(badarg)
  116. end;
  117. setMd(_) ->
  118. erlang:error(badarg).
  119. %% @doc Set the loglevel for a particular backend.
  120. setLogLevel(Handler, Level) when is_atom(Level) ->
  121. setLogLevel(?RumDefSink, Handler, undefined, Level).
  122. %% @doc Set the loglevel for a particular backend that has multiple identifiers
  123. %% (eg. the file backend).
  124. setLogLevel(Handler, Ident, Level) when is_atom(Level) ->
  125. setLogLevel(?RumDefSink, Handler, Ident, Level).
  126. %% @doc Set the loglevel for a particular sink's backend that potentially has
  127. %% multiple identifiers. (Use `undefined' if it doesn't have any.)
  128. setLogLevel(Sink, Handler, Ident, Level) when is_atom(Level) ->
  129. HandlerArg =
  130. case Ident of
  131. undefined -> Handler;
  132. _ -> {Handler, Ident}
  133. end,
  134. Reply = gen_emm:call(Sink, HandlerArg, {mSetLogLevel, Level}, infinity),
  135. upLogLevelCfg(Sink),
  136. Reply.
  137. %% @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.
  138. getLogLevel(Handler) ->
  139. getLogLevel(?RumDefSink, Handler).
  140. %% @doc Get the loglevel for a particular sink's backend. In the case that the backend
  141. %% has multiple identifiers, the lowest is returned.
  142. getLogLevel(Sink, Handler) ->
  143. case gen_emm:call(Sink, Handler, mGetLogLevel, infinity) of
  144. Mask when is_integer(Mask) ->
  145. case rumUtil:maskToLevels(Mask) of
  146. [] -> none;
  147. Levels -> hd(Levels)
  148. end;
  149. Y -> Y
  150. end.
  151. getLogLevels(Sink) ->
  152. [gen_emm:call(Sink, Handler, mGetLogLevel, infinity) || Handler <- gen_emm:which_epm(Sink)].
  153. %% @doc Set the loghwm for the default sink.
  154. setLogHwm(Handler, Hwm) when is_integer(Hwm) ->
  155. setLogHwm(?RumDefSink, Handler, Hwm).
  156. %% @doc Set the loghwm for a particular backend.
  157. setLogHwm(Sink, Handler, Hwm) when is_integer(Hwm) ->
  158. gen_emm:call(Sink, Handler, {mSetLogHwm, Hwm}, infinity).
  159. %% @doc Set the loghwm (log high water mark) for file backends with multiple identifiers
  160. setLogHwm(Sink, Handler, Ident, Hwm) when is_integer(Hwm) ->
  161. gen_emm:call(Sink, {Handler, Ident}, {mSetLogHwm, Hwm}, infinity).
  162. %% @doc recalculate min log level
  163. upLogLevelCfg(error_logger) ->
  164. %% Not a sink under our control, part of the Erlang logging
  165. %% utility that error_logger_lager_h attaches to
  166. true;
  167. upLogLevelCfg(Sink) ->
  168. Traces = rumConfig:ptGet({Sink, trace}, []),
  169. AllLogLevel = allLogLevel(getLogLevels(Sink), 0),
  170. case Traces /= [] of
  171. true ->
  172. ets:insert(?eRumEts, {Sink, 16#ff}),
  173. AllSinks = ets:tab2list(?eRumEts),
  174. rumKvsToBeam:load(?eRumCfg, AllSinks);
  175. _ ->
  176. ets:insert(?eRumEts, {Sink, AllLogLevel}),
  177. AllSinks = ets:tab2list(?eRumEts),
  178. rumKvsToBeam:load(?eRumCfg, AllSinks)
  179. end.
  180. allLogLevel([], Acc) ->
  181. Acc;
  182. allLogLevel([OneLv | Levels], Acc) ->
  183. allLogLevel(Levels, OneLv bor Acc).
  184. rotateSink(Sink) ->
  185. Handlers = rumConfig:ptGet(handlers, []),
  186. RotateHandlers = lists:filtermap(
  187. fun({Handler, _, S}) when S == Sink -> {true, {Handler, Sink}};
  188. (_) -> false
  189. end,
  190. Handlers),
  191. rotateHandlers(RotateHandlers).
  192. rotateAll() ->
  193. rotateHandlers(lists:map(fun({H, _, S}) -> {H, S} end,
  194. rumConfig:ptGet(handlers, []))).
  195. rotateHandlers(Handlers) ->
  196. [rotateHandler(Handler, Sink) || {Handler, Sink} <- Handlers].
  197. rotateHandler(Handler) ->
  198. Handlers = rumConfig:ptGet(handlers, []),
  199. case lists:keyfind(Handler, 1, Handlers) of
  200. {Handler, _, Sink} -> rotateHandler(Handler, Sink);
  201. false -> ok
  202. end.
  203. rotateHandler(Handler, Sink) ->
  204. gen_emm:call(Sink, Handler, mRotate, ?RumRotateTimeout).
  205. %% @doc Print stacktrace in human readable form
  206. parseStack(Stacktrace) ->
  207. <<
  208. begin
  209. case Location of
  210. [] ->
  211. <<" ", (atom_to_binary(Mod, utf8))/binary, ":", (atom_to_binary(Func, utf8))/binary, "(", (eFmt:formatBin("~w", [Arity]))/binary, ")\n">>;
  212. [{file, File}, {line, Line}] ->
  213. <<" ", (atom_to_binary(Mod, utf8))/binary, ":", (atom_to_binary(Func, utf8))/binary, "/", (integer_to_binary(Arity))/binary, "(", (unicode:characters_to_binary(File))/binary, ":", (integer_to_binary(Line))/binary, ")\n">>;
  214. _ ->
  215. <<" ", (atom_to_binary(Mod, utf8))/binary, ":", (atom_to_binary(Func, utf8))/binary, "(", (eFmt:formatBin("~w", [Arity]))/binary, ")", (eFmt:formatBin("~w", [Location]))/binary, "\n">>
  216. end
  217. end || {Mod, Func, Arity, Location} <- Stacktrace
  218. >>.
  219. parseStack(Class, Reason, Stacktrace) ->
  220. eFmt:formatBin(<<"~n Class:~s~n Reason:~p~n Stacktrace:~s">>, [Class, Reason, parseStack(Stacktrace)]).
  221. trace(BkdMod, Filter) ->
  222. trace(BkdMod, Filter, debug).
  223. trace({rumBkdFile, File}, Filter, Level) ->
  224. trace_file(File, Filter, Level);
  225. trace(Backend, Filter, Level) ->
  226. case validateTraceFilters(Filter, Level, Backend) of
  227. {Sink, {ok, Trace}} ->
  228. add_trace_to_loglevel_config(Trace, Sink),
  229. {ok, {Backend, Filter, Level}};
  230. {_Sink, Error} ->
  231. Error
  232. end.
  233. traceFile(File, Filter) ->
  234. trace_file(File, Filter, debug, []).
  235. trace_file(File, Filter, Level) when is_atom(Level) ->
  236. trace_file(File, Filter, Level, []);
  237. trace_file(File, Filter, Options) when is_list(Options) ->
  238. trace_file(File, Filter, debug, Options).
  239. trace_file(File, Filter, Level, Options) ->
  240. FileName = rumUtil:parsePath(File),
  241. case validateTraceFilters(Filter, Level, {rumBkdFile, FileName}) of
  242. {Sink, {ok, Trace}} ->
  243. Handlers = rumConfig:ptGet(handlers, []),
  244. %% check if this file backend is already installed
  245. Res =
  246. case rumUtil:find_file(FileName, Handlers) of
  247. false ->
  248. %% install the handler
  249. LogFileConfig =
  250. lists:keystore(level, 1,
  251. lists:keystore(file, 1,
  252. Options,
  253. {file, FileName}),
  254. {level, none}),
  255. HandlerInfo =
  256. eRum_app:startHandler(Sink, {rumBkdFile, FileName}, LogFileConfig),
  257. rumConfig:ptSet(handlers, [HandlerInfo | Handlers]),
  258. {ok, installed};
  259. {_Watcher, _Handler, Sink} ->
  260. {ok, exists};
  261. {_Watcher, _Handler, _OtherSink} ->
  262. {error, file_in_use}
  263. end,
  264. case Res of
  265. {ok, _} ->
  266. add_trace_to_loglevel_config(Trace, Sink),
  267. {ok, {{rumBkdFile, FileName}, Filter, Level}};
  268. {error, _} = E ->
  269. E
  270. end;
  271. {_Sink, Error} ->
  272. Error
  273. end.
  274. trace_console(Filter) ->
  275. trace_console(Filter, debug).
  276. trace_console(Filter, Level) ->
  277. trace(rumBkdConsole, Filter, Level).
  278. stop_trace(Backend, Filter, Level) ->
  279. case validateTraceFilters(Filter, Level, Backend) of
  280. {Sink, {ok, Trace}} ->
  281. stop_trace_int(Trace, Sink);
  282. {_Sink, Error} ->
  283. Error
  284. end.
  285. stop_trace({Backend, Filter, Level}) ->
  286. stop_trace(Backend, Filter, Level).
  287. validateTraceFilters(Filters, Level, Backend) ->
  288. Sink = proplists:get_value(sink, Filters, ?RumDefSink),
  289. {Sink,
  290. rumUtil:validate_trace({
  291. proplists:delete(sink, Filters),
  292. Level,
  293. Backend
  294. })
  295. }.
  296. %% Important: validate_trace_filters orders the arguments of
  297. %% trace tuples differently than the way outside callers have
  298. %% the trace tuple.
  299. %%
  300. %% That is to say, outside they are represented as
  301. %% `{Backend, Filter, Level}'
  302. %%
  303. %% and when they come back from validation, they're
  304. %% `{Filter, Level, Backend}'
  305. stop_trace_int({_Filter, _Level, Backend} = Trace, Sink) ->
  306. Traces = rumConfig:ptGet({Sink, trace}, []),
  307. NewTraces = lists:delete(Trace, Traces),
  308. _ = rumUtil:trace_filter([element(1, T) || T <- NewTraces]),
  309. %MinLevel = minimum_loglevel(get_loglevels() ++ get_trace_levels(NewTraces)),
  310. rumConfig:ptSet({Sink, trace}, NewTraces),
  311. eRum:upLogLevelCfg(Sink),
  312. case getLogLevel(Sink, Backend) of
  313. none ->
  314. %% check no other traces point here
  315. case lists:keyfind(Backend, 3, NewTraces) of
  316. false ->
  317. gen_emm:delEpm(Sink, Backend, []),
  318. rumConfig:ptSet(handlers,
  319. lists:keydelete(Backend, 1,
  320. rumConfig:ptGet(handlers, [])));
  321. _ ->
  322. ok
  323. end;
  324. _ ->
  325. ok
  326. end,
  327. ok.
  328. %% @doc installs a lager trace handler into the target process (using sys:install) at the specified level.
  329. -spec install_trace(pid(), rumAtomLevel()) -> ok.
  330. install_trace(Pid, Level) ->
  331. install_trace(Pid, Level, []).
  332. -spec install_trace(pid(), rumAtomLevel(), [{count, infinity | pos_integer()} | {format_string, string()} | {timeout, timeout()}]) -> ok.
  333. install_trace(Pid, Level, Options) ->
  334. sys:install(Pid, {fun ?MODULE:trace_func/3, trace_state(Pid, Level, Options)}).
  335. %% @doc remove a previously installed lager trace handler from the target process.
  336. -spec remove_trace(pid()) -> ok.
  337. remove_trace(Pid) ->
  338. sys:remove(Pid, fun ?MODULE:trace_func/3).
  339. list_all_sinks() ->
  340. sets:to_list(
  341. lists:foldl(fun({_Watcher, _Handler, Sink}, Set) ->
  342. sets:add_element(Sink, Set)
  343. end,
  344. sets:new(),
  345. rumConfig:ptGet(handlers, []))).
  346. clear_traces_by_sink(Sinks) ->
  347. lists:foreach(
  348. fun(S) ->
  349. rumConfig:ptSet({S, trace}, []),
  350. eRum:upLogLevelCfg(S)
  351. end,
  352. Sinks).
  353. clear_trace_by_destination(ID) ->
  354. Sinks = lists:sort(list_all_sinks()),
  355. Traces = find_traces(Sinks),
  356. [stop_trace_int({Filter, Level, Destination}, Sink) || {Sink, {Filter, Level, Destination}} <- Traces, Destination == ID].
  357. clear_all_traces() ->
  358. Handlers = rumConfig:ptGet(handlers, []),
  359. clear_traces_by_sink(list_all_sinks()),
  360. _ = rumUtil:trace_filter(none),
  361. rumConfig:ptSet(handlers,
  362. lists:filter(
  363. fun({Handler, _Watcher, Sink}) ->
  364. case getLogLevel(Sink, Handler) of
  365. none ->
  366. gen_emm:delEpm(Sink, Handler, []),
  367. false;
  368. _ ->
  369. true
  370. end
  371. end, Handlers)).
  372. find_traces(Sinks) ->
  373. lists:foldl(fun(S, Acc) ->
  374. Traces = rumConfig:ptGet({S, trace}, []),
  375. Acc ++ lists:map(fun(T) -> {S, T} end, Traces)
  376. end,
  377. [],
  378. Sinks).
  379. status() ->
  380. Handlers = rumConfig:ptGet(handlers, []),
  381. Sinks = lists:sort(list_all_sinks()),
  382. Traces = find_traces(Sinks),
  383. TraceCount = case length(Traces) of
  384. 0 -> 1;
  385. N -> N
  386. end,
  387. Status = ["Lager status:\n",
  388. [begin
  389. Level = getLogLevel(Sink, Handler),
  390. get_sink_handler_status(Sink, Handler, Level)
  391. end || {Handler, _Watcher, Sink} <- lists:sort(fun({_, _, S1},
  392. {_, _, S2}) -> S1 =< S2 end,
  393. Handlers)],
  394. "Active Traces:\n",
  395. [begin
  396. LevelName =
  397. case rumUtil:maskToLevels(Level) of
  398. [] -> none;
  399. Levels -> hd(Levels)
  400. end,
  401. io_lib:format("Tracing messages matching ~p (sink ~s) at level ~p to ~p\n",
  402. [Filter, Sink, LevelName, Destination])
  403. end || {Sink, {Filter, Level, Destination}} <- Traces],
  404. [
  405. "Tracing Reductions:\n",
  406. case ?RumDefTracer:info('query') of
  407. {null, false} -> "";
  408. Query -> io_lib:format("~p~n", [Query])
  409. end
  410. ],
  411. [
  412. "Tracing Statistics:\n ",
  413. [begin
  414. [" ", atom_to_list(Table), ": ",
  415. integer_to_list(?RumDefTracer:info(Table) div TraceCount),
  416. "\n"]
  417. end || Table <- [input, output, filter]]
  418. ]],
  419. io:put_chars(Status).
  420. get_sink_handler_status(Sink, Handler, Level) ->
  421. case Handler of
  422. {rumBkdFile, File} ->
  423. io_lib:format("File ~ts (~s) at level ~p\n", [File, Sink, Level]);
  424. rumBkdConsole ->
  425. io_lib:format("Console (~s) at level ~p\n", [Sink, Level]);
  426. _ ->
  427. []
  428. end.
  429. %% @private
  430. add_trace_to_loglevel_config(Trace, Sink) ->
  431. Traces = rumConfig:ptGet({Sink, trace}, []),
  432. case lists:member(Trace, Traces) of
  433. false ->
  434. NewTraces = [Trace | Traces],
  435. _ = rumUtil:trace_filter([element(1, T) || T <- NewTraces]),
  436. rumConfig:ptSet({Sink, trace}, [Trace | Traces]),
  437. eRum:upLogLevelCfg(Sink);
  438. _ ->
  439. ok
  440. end.
  441. %% @doc Print the format string `Fmt' with `Args' safely with a size
  442. %% limit of `Limit'. If the format string is invalid, or not enough
  443. %% arguments are supplied 'FORMAT ERROR' is printed with the offending
  444. %% arguments. The caller is NOT crashed.
  445. unsafeFormat(Fmt, Args) ->
  446. try io_lib:format(Fmt, Args)
  447. catch
  448. _:_ -> io_lib:format("FORMAT ERROR: ~p ~p", [Fmt, Args])
  449. end.
  450. safeFormat(Fmt, Args, Limit) ->
  451. try eFmt:formatBin(Fmt, Args, [{charsLimit, Limit}])
  452. catch
  453. _:_ -> eFmt:formatBin(<<"FORMAT ERROR: ~p ~p">>, [Fmt, Args], [{charsLimit, Limit}])
  454. end.
  455. %% @private Print the format string `Fmt' with `Args' without a size limit.
  456. %% This is unsafe because the output of this function is unbounded.
  457. %%
  458. %% Log messages with unbounded size will kill your application dead as
  459. %% OTP mechanisms stuggle to cope with them. So this function is
  460. %% intended <b>only</b> for messages which have a reasonable bounded
  461. %% size before they're formatted.
  462. %%
  463. %% If the format string is invalid or not enough arguments are
  464. %% supplied a 'FORMAT ERROR' message is printed instead with the
  465. %% offending arguments. The caller is NOT crashed.
  466. %% @private
  467. trace_func(#trace_func_state_v1{pid = Pid, level = Level, format_string = Fmt} = FuncState, Event, ProcState) ->
  468. _ = eRum:log(Level, Pid, Fmt, [Event, ProcState]),
  469. check_timeout(decrement_count(FuncState)).
  470. %% @private
  471. trace_state(Pid, Level, Options) ->
  472. #trace_func_state_v1{pid = Pid,
  473. level = Level,
  474. count = proplists:get_value(count, Options, infinity),
  475. timeout = proplists:get_value(timeout, Options, infinity),
  476. format_string = proplists:get_value(format_string, Options, "TRACE ~p ~p")}.
  477. decrement_count(#trace_func_state_v1{count = infinity} = FuncState) ->
  478. FuncState;
  479. decrement_count(#trace_func_state_v1{count = 1}) ->
  480. %% hit the counter limit
  481. done;
  482. decrement_count(#trace_func_state_v1{count = Count} = FuncState) ->
  483. FuncState#trace_func_state_v1{count = Count - 1}.
  484. check_timeout(#trace_func_state_v1{timeout = infinity} = FuncState) ->
  485. FuncState;
  486. check_timeout(#trace_func_state_v1{timeout = Timeout, started = Started} = FuncState) ->
  487. case (timer:now_diff(os:timestamp(), Started) / 1000) > Timeout of
  488. true ->
  489. done;
  490. false ->
  491. FuncState
  492. end.