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.

770 lines
26 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  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.