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

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