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.

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