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.

517 lines
20 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 години
  1. -module(rumBkdConsole).
  2. %% Configuration is a proplist with the following keys:
  3. %%`level' - log level to use
  4. %%`use_stderr' - either `true' or `false', defaults to false. If set to true, use standard error to output console log messages
  5. %%`formatter' - the module to use when formatting log messages. Defaults to `rumFormatter'
  6. %%`formatter_config' - the format configuration string. Defaults to `time [ severity ] message'
  7. -behaviour(gen_emm).
  8. -include("eRum.hrl").
  9. -ifdef(TEST).
  10. -include_lib("eunit/include/eunit.hrl").
  11. -compile([{parse_transform, rumTransform}]).
  12. -endif.
  13. -define(TERSE_FORMAT,[time, " ", color, "[", severity,"] ", message]).
  14. -define(DEFAULT_FORMAT_CONFIG, ?TERSE_FORMAT ++ [eol()]).
  15. -define(FORMAT_CONFIG_OFF, [{eol, eol()}]).
  16. -define(DefOpts, [{use_stderr, false}, {group_leader, false}, {id, ?MODULE}, {formatter, rumFormatter}, {formatter_config, ?DEFAULT_FORMAT_CONFIG}]).
  17. -export([
  18. init/1
  19. , handleCall/2
  20. , handleEvent/2
  21. , handleInfo/2
  22. , terminate/2
  23. , code_change/3
  24. ]).
  25. -record(state, {
  26. level :: {'mask', integer()},
  27. out = user :: user | standard_error | pid(),
  28. id :: atom() | {atom(), any()},
  29. formatter :: atom(),
  30. format_config :: any(),
  31. colors = [] :: list()
  32. }).
  33. init(Opts) ->
  34. case isNewStyleConsole() of
  35. false ->
  36. Msg = "Lager's console backend is incompatible with the 'old' shell, not enabling it",
  37. %% be as noisy as possible, log to every possible place
  38. try
  39. alarm_handler:set_alarm({?MODULE, "WARNING: " ++ Msg})
  40. catch
  41. _:_ ->
  42. error_logger:warning_msg(Msg ++ "~n")
  43. end,
  44. io:format("WARNING: " ++ Msg ++ "~n"),
  45. ?INT_LOG(warning, Msg, []),
  46. {error, {fatal, old_shell}};
  47. _ ->
  48. true = checkOpts(Opts),
  49. Colors = ?IIF(rumUtil:get_env(colored, false), rumUtil:get_env(colors, []), []),
  50. Level = rumUtil:get_opt(level, Opts, undefined),
  51. L = rumUtil:config_to_mask(Level),
  52. [UseErr, GroupLeader, ID, Formatter, Config] = [rumUtil:get_opt(Key, Opts, Def) || {Key, Def} <- ?DefOpts],
  53. Out = ?IIF(UseErr, standard_error, ?IIF(GroupLeader == false, user, begin erlang:monitor(process, GroupLeader), GroupLeader end)),
  54. {ok, #state{level = L, id = ID, out = Out, formatter = Formatter, format_config = Config, colors = Colors}}
  55. end.
  56. checkOpts([]) -> true;
  57. checkOpts([{level, L} | T]) when is_atom(L) ->
  58. ?IIF(lists:member(L, ?RumLevels) == false, throw({error, {fatal, {bad_level, L}}}), checkOpts(T));
  59. checkOpts([{use_stderr, true} | T]) ->
  60. checkOpts(T);
  61. checkOpts([{use_stderr, false} | T]) ->
  62. checkOpts(T);
  63. checkOpts([{formatter, M} | T]) when is_atom(M) ->
  64. checkOpts(T);
  65. checkOpts([{formatter_config, C} | T]) when is_list(C) ->
  66. checkOpts(T);
  67. checkOpts([{group_leader, L} | T]) when is_pid(L) ->
  68. checkOpts(T);
  69. checkOpts([{id, {?MODULE, _}} | T]) ->
  70. checkOpts(T);
  71. checkOpts([H | _]) ->
  72. throw({error, {fatal, {bad_console_config, H}}}).
  73. handleCall(mGetLogLevel, #state{level = Level} = State) ->
  74. {ok, Level, State};
  75. handleCall({mSetLogLevel, Level}, State) ->
  76. try rumUtil:config_to_mask(Level) of
  77. Levels ->
  78. {ok, ok, State#state{level = Levels}}
  79. catch
  80. _:_ ->
  81. {ok, {error, bad_log_level}, State}
  82. end;
  83. handleCall(_Request, State) ->
  84. {ok, ok, State}.
  85. %% @private
  86. handleEvent({mWriteLog, Message},
  87. #state{level = L, out = Out, formatter = Formatter, format_config = FormatConfig, colors = Colors, id = ID} = State) ->
  88. case rumUtil:is_loggable(Message, L, ID) of
  89. true ->
  90. io:put_chars(Out, Formatter:format(Message, FormatConfig, Colors)),
  91. {ok, State};
  92. false ->
  93. {ok, State}
  94. end;
  95. handleEvent(_Event, State) ->
  96. {ok, State}.
  97. handleInfo({'DOWN', _, process, Out, _}, #state{out = Out}) ->
  98. remove_handler;
  99. handleInfo(_Info, State) ->
  100. {ok, State}.
  101. terminate(remove_handler, _State = #state{id = ID}) ->
  102. %% have to do this asynchronously because we're in the event handlr
  103. spawn(fun() -> eRum:clear_trace_by_destination(ID) end),
  104. ok;
  105. terminate(_Reason, _State) ->
  106. ok.
  107. code_change(_OldVsn, State, _Extra) ->
  108. {ok, State}.
  109. eol() ->
  110. case application:get_env(lager, colored) of
  111. {ok, true} ->
  112. "\e[0m\r\n";
  113. _ ->
  114. "\r\n"
  115. end.
  116. isNewStyleConsole() ->
  117. %% Criteria:
  118. %% 1. If the user has specified '-noshell' on the command line,
  119. %% then we will pretend that the new-style console is available.
  120. %% If there is no shell at all, then we don't have to worry
  121. %% about log events being blocked by the old-style shell.
  122. %% 2. Windows doesn't support the new shell, so all windows users
  123. %% have is the oldshell.
  124. %% 3. If the user_drv process is registered, all is OK.
  125. %% 'user_drv' is a registered proc name used by the "new"
  126. %% console driver.
  127. init:get_argument(noshell) /= error orelse element(1, os:type()) /= win32 orelse is_pid(whereis(user_drv)).
  128. -ifdef(TEST).
  129. console_config_validation_test_() ->
  130. Good = [{level, info}, {use_stderr, true}],
  131. Bad1 = [{level, foo}, {use_stderr, flase}],
  132. Bad2 = [{level, info}, {use_stderr, flase}],
  133. AllGood = [{level, info}, {formatter, my_formatter},
  134. {formatter_config, ["blort", "garbage"]},
  135. {use_stderr, false}],
  136. [
  137. ?_assertEqual(true, checkOpts(Good)),
  138. ?_assertThrow({error, {fatal, {bad_level, foo}}}, checkOpts(Bad1)),
  139. ?_assertThrow({error, {fatal, {bad_console_config, {use_stderr, flase}}}}, checkOpts(Bad2)),
  140. ?_assertEqual(true, checkOpts(AllGood))
  141. ].
  142. console_log_test_() ->
  143. %% tiny recursive fun that pretends to be a group leader
  144. F = fun(Self) ->
  145. fun() ->
  146. YComb = fun(Fun) ->
  147. receive
  148. {io_request, From, ReplyAs, {put_chars, unicode, _Msg}} = Y ->
  149. From ! {io_reply, ReplyAs, ok},
  150. Self ! Y,
  151. Fun(Fun);
  152. Other ->
  153. ?debugFmt("unexpected message ~p~n", [Other]),
  154. Self ! Other
  155. end
  156. end,
  157. YComb(YComb)
  158. end
  159. end,
  160. {foreach,
  161. fun() ->
  162. error_logger:tty(false),
  163. application:load(lager),
  164. application:set_env(lager, handlers, []),
  165. application:set_env(lager, errLoggerRedirect, false),
  166. eRum:start(),
  167. whereis(user)
  168. end,
  169. fun(User) ->
  170. unregister(user),
  171. register(user, User),
  172. application:stop(lager),
  173. application:stop(goldrush),
  174. error_logger:tty(true)
  175. end,
  176. [
  177. {"regular console logging",
  178. fun() ->
  179. Pid = spawn(F(self())),
  180. unregister(user),
  181. register(user, Pid),
  182. erlang:group_leader(Pid, whereis(rumEvent)),
  183. gen_event:add_handler(rumEvent, lager_console_backend, [{level, info}]),
  184. rumConfig:set({rumEvent, loglevel}, {element(2, rumUtil:config_to_mask(info)), []}),
  185. eRum:log(info, self(), "Test message"),
  186. receive
  187. {io_request, From, ReplyAs, {put_chars, unicode, Msg}} ->
  188. From ! {io_reply, ReplyAs, ok},
  189. TestMsg = "Test message" ++ eol(),
  190. ?assertMatch([_, "[info]", TestMsg], re:split(Msg, " ", [{return, list}, {parts, 3}]))
  191. after
  192. 500 ->
  193. ?assert(false)
  194. end
  195. end
  196. },
  197. {"verbose console logging",
  198. fun() ->
  199. Pid = spawn(F(self())),
  200. unregister(user),
  201. register(user, Pid),
  202. erlang:group_leader(Pid, whereis(rumEvent)),
  203. gen_event:add_handler(rumEvent, lager_console_backend, [info, true]),
  204. rumConfig:set({rumEvent, loglevel}, {element(2, rumUtil:config_to_mask(info)), []}),
  205. eRum:info("Test message"),
  206. PidStr = pid_to_list(self()),
  207. receive
  208. {io_request, _, _, {put_chars, unicode, Msg}} ->
  209. TestMsg = "Test message" ++ eol(),
  210. ?assertMatch([_, _, "[info]", PidStr, _, TestMsg], re:split(Msg, "[ @]", [{return, list}, {parts, 6}]))
  211. after
  212. 500 ->
  213. ?assert(false)
  214. end
  215. end
  216. },
  217. {"custom format console logging",
  218. fun() ->
  219. Pid = spawn(F(self())),
  220. unregister(user),
  221. register(user, Pid),
  222. erlang:group_leader(Pid, whereis(rumEvent)),
  223. gen_event:add_handler(rumEvent, lager_console_backend,
  224. [{level, info}, {formatter, lager_default_formatter}, {formatter_config, [date, "#", time, "#", severity, "#", node, "#", pid, "#",
  225. module, "#", function, "#", file, "#", line, "#", message, "\r\n"]}]),
  226. rumConfig:set({rumEvent, loglevel}, {?INFO, []}),
  227. eRum:info("Test message"),
  228. PidStr = pid_to_list(self()),
  229. NodeStr = atom_to_list(node()),
  230. ModuleStr = atom_to_list(?MODULE),
  231. receive
  232. {io_request, _, _, {put_chars, unicode, Msg}} ->
  233. TestMsg = "Test message" ++ eol(),
  234. ?assertMatch([_, _, "info", NodeStr, PidStr, ModuleStr, _, _, _, TestMsg],
  235. re:split(Msg, "#", [{return, list}, {parts, 10}]))
  236. after
  237. 500 ->
  238. ?assert(false)
  239. end
  240. end
  241. },
  242. {"tracing should work",
  243. fun() ->
  244. Pid = spawn(F(self())),
  245. unregister(user),
  246. register(user, Pid),
  247. gen_event:add_handler(rumEvent, lager_console_backend, [{level, info}]),
  248. erlang:group_leader(Pid, whereis(rumEvent)),
  249. rumConfig:set({rumEvent, loglevel}, {element(2, rumUtil:config_to_mask(info)), []}),
  250. eRum:debug("Test message"),
  251. receive
  252. {io_request, From, ReplyAs, {put_chars, unicode, _Msg}} ->
  253. From ! {io_reply, ReplyAs, ok},
  254. ?assert(false)
  255. after
  256. 500 ->
  257. ?assert(true)
  258. end,
  259. {ok, _} = eRum:trace_console([{module, ?MODULE}]),
  260. eRum:debug("Test message"),
  261. receive
  262. {io_request, From1, ReplyAs1, {put_chars, unicode, Msg1}} ->
  263. From1 ! {io_reply, ReplyAs1, ok},
  264. TestMsg = "Test message" ++ eol(),
  265. ?assertMatch([_, "[debug]", TestMsg], re:split(Msg1, " ", [{return, list}, {parts, 3}]))
  266. after
  267. 500 ->
  268. ?assert(false)
  269. end
  270. end
  271. },
  272. {"tracing doesn't duplicate messages",
  273. fun() ->
  274. Pid = spawn(F(self())),
  275. unregister(user),
  276. register(user, Pid),
  277. gen_event:add_handler(rumEvent, lager_console_backend, [{level, info}]),
  278. rumConfig:set({rumEvent, loglevel}, {element(2, rumUtil:config_to_mask(info)), []}),
  279. erlang:group_leader(Pid, whereis(rumEvent)),
  280. eRum:debug("Test message"),
  281. receive
  282. {io_request, From, ReplyAs, {put_chars, unicode, _Msg}} ->
  283. From ! {io_reply, ReplyAs, ok},
  284. ?assert(false)
  285. after
  286. 500 ->
  287. ?assert(true)
  288. end,
  289. {ok, _} = eRum:trace_console([{module, ?MODULE}]),
  290. eRum:error("Test message"),
  291. receive
  292. {io_request, From1, ReplyAs1, {put_chars, unicode, Msg1}} ->
  293. From1 ! {io_reply, ReplyAs1, ok},
  294. TestMsg = "Test message" ++ eol(),
  295. ?assertMatch([_, "[error]", TestMsg], re:split(Msg1, " ", [{return, list}, {parts, 3}]))
  296. after
  297. 1000 ->
  298. ?assert(false)
  299. end,
  300. %% make sure this event wasn't duplicated
  301. receive
  302. {io_request, From2, ReplyAs2, {put_chars, unicode, _Msg2}} ->
  303. From2 ! {io_reply, ReplyAs2, ok},
  304. ?assert(false)
  305. after
  306. 500 ->
  307. ?assert(true)
  308. end
  309. end
  310. },
  311. {"blacklisting a loglevel works",
  312. fun() ->
  313. Pid = spawn(F(self())),
  314. unregister(user),
  315. register(user, Pid),
  316. gen_event:add_handler(rumEvent, lager_console_backend, [{level, info}]),
  317. rumConfig:set({rumEvent, loglevel}, {element(2, rumUtil:config_to_mask(info)), []}),
  318. eRum:set_loglevel(lager_console_backend, '!=info'),
  319. erlang:group_leader(Pid, whereis(rumEvent)),
  320. eRum:debug("Test message"),
  321. receive
  322. {io_request, From1, ReplyAs1, {put_chars, unicode, Msg1}} ->
  323. From1 ! {io_reply, ReplyAs1, ok},
  324. TestMsg = "Test message" ++ eol(),
  325. ?assertMatch([_, "[debug]", TestMsg], re:split(Msg1, " ", [{return, list}, {parts, 3}]))
  326. after
  327. 1000 ->
  328. ?assert(false)
  329. end,
  330. %% info is blacklisted
  331. eRum:info("Test message"),
  332. receive
  333. {io_request, From2, ReplyAs2, {put_chars, unicode, _Msg2}} ->
  334. From2 ! {io_reply, ReplyAs2, ok},
  335. ?assert(false)
  336. after
  337. 500 ->
  338. ?assert(true)
  339. end
  340. end
  341. },
  342. {"whitelisting a loglevel works",
  343. fun() ->
  344. Pid = spawn(F(self())),
  345. unregister(user),
  346. register(user, Pid),
  347. gen_event:add_handler(rumEvent, lager_console_backend, [{level, info}]),
  348. rumConfig:set({rumEvent, loglevel}, {element(2, rumUtil:config_to_mask(info)), []}),
  349. eRum:set_loglevel(lager_console_backend, '=debug'),
  350. erlang:group_leader(Pid, whereis(rumEvent)),
  351. eRum:debug("Test message"),
  352. receive
  353. {io_request, From1, ReplyAs1, {put_chars, unicode, Msg1}} ->
  354. From1 ! {io_reply, ReplyAs1, ok},
  355. TestMsg = "Test message" ++ eol(),
  356. ?assertMatch([_, "[debug]", TestMsg], re:split(Msg1, " ", [{return, list}, {parts, 3}]))
  357. after
  358. 1000 ->
  359. ?assert(false)
  360. end,
  361. %% info is blacklisted
  362. eRum:error("Test message"),
  363. receive
  364. {io_request, From2, ReplyAs2, {put_chars, unicode, _Msg2}} ->
  365. From2 ! {io_reply, ReplyAs2, ok},
  366. ?assert(false)
  367. after
  368. 500 ->
  369. ?assert(true)
  370. end
  371. end
  372. },
  373. {"console backend with custom group leader",
  374. fun() ->
  375. Pid = spawn(F(self())),
  376. gen_event:add_handler(rumEvent, lager_console_backend, [{level, info}, {group_leader, Pid}]),
  377. rumConfig:set({rumEvent, loglevel}, {element(2, rumUtil:config_to_mask(info)), []}),
  378. eRum:info("Test message"),
  379. ?assertNotEqual({group_leader, Pid}, erlang:process_info(whereis(rumEvent), group_leader)),
  380. receive
  381. {io_request, From1, ReplyAs1, {put_chars, unicode, Msg1}} ->
  382. From1 ! {io_reply, ReplyAs1, ok},
  383. TestMsg = "Test message" ++ eol(),
  384. ?assertMatch([_, "[info]", TestMsg], re:split(Msg1, " ", [{return, list}, {parts, 3}]))
  385. after
  386. 1000 ->
  387. ?assert(false)
  388. end,
  389. %% killing the pid should prevent any new log messages (to prove we haven't accidentally redirected
  390. %% the group leader some other way
  391. exit(Pid, kill),
  392. timer:sleep(100),
  393. %% additionally, check the lager backend has been removed (because the group leader process died)
  394. ?assertNot(lists:member(lager_console_backend, gen_event:which_handlers(rumEvent))),
  395. eRum:error("Test message"),
  396. receive
  397. {io_request, From2, ReplyAs2, {put_chars, unicode, _Msg2}} ->
  398. From2 ! {io_reply, ReplyAs2, ok},
  399. ?assert(false)
  400. after
  401. 500 ->
  402. ?assert(true)
  403. end
  404. end
  405. },
  406. {"console backend with custom group leader using a trace and an ID",
  407. fun() ->
  408. Pid = spawn(F(self())),
  409. ID = {?MODULE, trace_test},
  410. Handlers = rumConfig:global_get(handlers, []),
  411. HandlerInfo = lager_app:start_handler(rumEvent, ID,
  412. [{level, none}, {group_leader, Pid},
  413. {id, ID}]),
  414. rumConfig:global_set(handlers, [HandlerInfo | Handlers]),
  415. eRum:info("Test message"),
  416. ?assertNotEqual({group_leader, Pid}, erlang:process_info(whereis(rumEvent), group_leader)),
  417. receive
  418. {io_request, From, ReplyAs, {put_chars, unicode, _Msg}} ->
  419. From ! {io_reply, ReplyAs, ok},
  420. ?assert(false)
  421. after
  422. 500 ->
  423. ?assert(true)
  424. end,
  425. eRum:trace(ID, [{module, ?MODULE}], debug),
  426. eRum:info("Test message"),
  427. receive
  428. {io_request, From1, ReplyAs1, {put_chars, unicode, Msg1}} ->
  429. From1 ! {io_reply, ReplyAs1, ok},
  430. TestMsg = "Test message" ++ eol(),
  431. ?assertMatch([_, "[info]", TestMsg], re:split(Msg1, " ", [{return, list}, {parts, 3}]))
  432. after
  433. 500 ->
  434. ?assert(false)
  435. end,
  436. ?assertNotEqual({0, []}, rumConfig:get({rumEvent, loglevel})),
  437. %% killing the pid should prevent any new log messages (to prove we haven't accidentally redirected
  438. %% the group leader some other way
  439. exit(Pid, kill),
  440. timer:sleep(100),
  441. %% additionally, check the lager backend has been removed (because the group leader process died)
  442. ?assertNot(lists:member(lager_console_backend, gen_event:which_handlers(rumEvent))),
  443. %% finally, check the trace has been removed
  444. ?assertEqual({0, []}, rumConfig:get({rumEvent, loglevel})),
  445. eRum:error("Test message"),
  446. receive
  447. {io_request, From3, ReplyAs3, {put_chars, unicode, _Msg3}} ->
  448. From3 ! {io_reply, ReplyAs3, ok},
  449. ?assert(false)
  450. after
  451. 500 ->
  452. ?assert(true)
  453. end
  454. end
  455. }
  456. ]
  457. }.
  458. set_loglevel_test_() ->
  459. {foreach,
  460. fun() ->
  461. error_logger:tty(false),
  462. application:load(lager),
  463. application:set_env(lager, handlers, [{lager_console_backend, [{level, info}]}]),
  464. application:set_env(lager, errLoggerRedirect, false),
  465. eRum:start()
  466. end,
  467. fun(_) ->
  468. application:stop(lager),
  469. application:stop(goldrush),
  470. error_logger:tty(true)
  471. end,
  472. [
  473. {"Get/set loglevel test",
  474. fun() ->
  475. ?assertEqual(info, eRum:get_loglevel(lager_console_backend)),
  476. eRum:set_loglevel(lager_console_backend, debug),
  477. ?assertEqual(debug, eRum:get_loglevel(lager_console_backend)),
  478. eRum:set_loglevel(lager_console_backend, '!=debug'),
  479. ?assertEqual(info, eRum:get_loglevel(lager_console_backend)),
  480. eRum:set_loglevel(lager_console_backend, '!=info'),
  481. ?assertEqual(debug, eRum:get_loglevel(lager_console_backend)),
  482. ok
  483. end
  484. },
  485. {"Get/set invalid loglevel test",
  486. fun() ->
  487. ?assertEqual(info, eRum:get_loglevel(lager_console_backend)),
  488. ?assertEqual({error, bad_log_level},
  489. eRum:set_loglevel(lager_console_backend, fatfinger)),
  490. ?assertEqual(info, eRum:get_loglevel(lager_console_backend))
  491. end
  492. }
  493. ]
  494. }.
  495. -endif.