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

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