|
|
- -module(rumBkdConsole).
- %% Configuration is a proplist with the following keys:
- %%`level' - log level to use
- %%`use_stderr' - either `true' or `false', defaults to false. If set to true, use standard error to output console log messages
- %%`formatter' - the module to use when formatting log messages. Defaults to `rumFormatter'
- %%`formatter_config' - the format configuration string. Defaults to `time [ severity ] message'
-
- -behaviour(gen_emm).
-
- -include("eRum.hrl").
-
- -ifdef(TEST).
- -include_lib("eunit/include/eunit.hrl").
- -compile([{parse_transform, rumTransform}]).
- -endif.
-
-
- -define(TERSE_FORMAT,[time, " ", color, "[", severity,"] ", message]).
- -define(DEFAULT_FORMAT_CONFIG, ?TERSE_FORMAT ++ [eol()]).
- -define(FORMAT_CONFIG_OFF, [{eol, eol()}]).
- -define(DefOpts, [{use_stderr, false}, {group_leader, false}, {id, ?MODULE}, {formatter, rumFormatter}, {formatter_config, ?DEFAULT_FORMAT_CONFIG}]).
-
- -export([
- init/1
- , handleCall/2
- , handleEvent/2
- , handleInfo/2
- , terminate/2
- , code_change/3
- ]).
-
- -record(state, {
- level :: {'mask', integer()},
- out = user :: user | standard_error | pid(),
- id :: atom() | {atom(), any()},
- formatter :: atom(),
- format_config :: any(),
- colors = [] :: list()
- }).
-
-
- init(Opts) ->
- case isNewStyleConsole() of
- false ->
- Msg = "Lager's console backend is incompatible with the 'old' shell, not enabling it",
- %% be as noisy as possible, log to every possible place
- try
- alarm_handler:set_alarm({?MODULE, "WARNING: " ++ Msg})
- catch
- _:_ ->
- error_logger:warning_msg(Msg ++ "~n")
- end,
- io:format("WARNING: " ++ Msg ++ "~n"),
- ?INT_LOG(warning, Msg, []),
- {error, {fatal, old_shell}};
- _ ->
- true = checkOpts(Opts),
- Colors = ?IIF(rumUtil:get_env(colored, false), rumUtil:get_env(colors, []), []),
- Level = rumUtil:get_opt(level, Opts, undefined),
- L = rumUtil:config_to_mask(Level),
- [UseErr, GroupLeader, ID, Formatter, Config] = [rumUtil:get_opt(Key, Opts, Def) || {Key, Def} <- ?DefOpts],
- Out = ?IIF(UseErr, standard_error, ?IIF(GroupLeader == false, user, begin erlang:monitor(process, GroupLeader), GroupLeader end)),
- {ok, #state{level = L, id = ID, out = Out, formatter = Formatter, format_config = Config, colors = Colors}}
- end.
-
- checkOpts([]) -> true;
- checkOpts([{level, L} | T]) when is_atom(L) ->
- ?IIF(lists:member(L, ?RumLevels) == false, throw({error, {fatal, {bad_level, L}}}), checkOpts(T));
- checkOpts([{use_stderr, true} | T]) ->
- checkOpts(T);
- checkOpts([{use_stderr, false} | T]) ->
- checkOpts(T);
- checkOpts([{formatter, M} | T]) when is_atom(M) ->
- checkOpts(T);
- checkOpts([{formatter_config, C} | T]) when is_list(C) ->
- checkOpts(T);
- checkOpts([{group_leader, L} | T]) when is_pid(L) ->
- checkOpts(T);
- checkOpts([{id, {?MODULE, _}} | T]) ->
- checkOpts(T);
- checkOpts([H | _]) ->
- throw({error, {fatal, {bad_console_config, H}}}).
-
- handleCall(mGetLogLevel, #state{level = Level} = State) ->
- {ok, Level, State};
- handleCall({mSetLogLevel, Level}, State) ->
- try rumUtil:config_to_mask(Level) of
- Levels ->
- {ok, ok, State#state{level = Levels}}
- catch
- _:_ ->
- {ok, {error, bad_log_level}, State}
- end;
- handleCall(_Request, State) ->
- {ok, ok, State}.
-
- %% @private
- handleEvent({mWriteLog, Message},
- #state{level = L, out = Out, formatter = Formatter, format_config = FormatConfig, colors = Colors, id = ID} = State) ->
- case rumUtil:is_loggable(Message, L, ID) of
- true ->
- io:put_chars(Out, Formatter:format(Message, FormatConfig, Colors)),
- {ok, State};
- false ->
- {ok, State}
- end;
- handleEvent(_Event, State) ->
- {ok, State}.
-
- handleInfo({'DOWN', _, process, Out, _}, #state{out = Out}) ->
- remove_handler;
- handleInfo(_Info, State) ->
- {ok, State}.
-
- terminate(remove_handler, _State = #state{id = ID}) ->
- %% have to do this asynchronously because we're in the event handlr
- spawn(fun() -> eRum:clear_trace_by_destination(ID) end),
- ok;
- terminate(_Reason, _State) ->
- ok.
-
- code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
- eol() ->
- case application:get_env(lager, colored) of
- {ok, true} ->
- "\e[0m\r\n";
- _ ->
- "\r\n"
- end.
-
- isNewStyleConsole() ->
- %% Criteria:
- %% 1. If the user has specified '-noshell' on the command line,
- %% then we will pretend that the new-style console is available.
- %% If there is no shell at all, then we don't have to worry
- %% about log events being blocked by the old-style shell.
- %% 2. Windows doesn't support the new shell, so all windows users
- %% have is the oldshell.
- %% 3. If the user_drv process is registered, all is OK.
- %% 'user_drv' is a registered proc name used by the "new"
- %% console driver.
- init:get_argument(noshell) /= error orelse element(1, os:type()) /= win32 orelse is_pid(whereis(user_drv)).
-
- -ifdef(TEST).
- console_config_validation_test_() ->
- Good = [{level, info}, {use_stderr, true}],
- Bad1 = [{level, foo}, {use_stderr, flase}],
- Bad2 = [{level, info}, {use_stderr, flase}],
- AllGood = [{level, info}, {formatter, my_formatter},
- {formatter_config, ["blort", "garbage"]},
- {use_stderr, false}],
- [
- ?_assertEqual(true, checkOpts(Good)),
- ?_assertThrow({error, {fatal, {bad_level, foo}}}, checkOpts(Bad1)),
- ?_assertThrow({error, {fatal, {bad_console_config, {use_stderr, flase}}}}, checkOpts(Bad2)),
- ?_assertEqual(true, checkOpts(AllGood))
- ].
-
- console_log_test_() ->
- %% tiny recursive fun that pretends to be a group leader
- F = fun(Self) ->
- fun() ->
- YComb = fun(Fun) ->
- receive
- {io_request, From, ReplyAs, {put_chars, unicode, _Msg}} = Y ->
- From ! {io_reply, ReplyAs, ok},
- Self ! Y,
- Fun(Fun);
- Other ->
- ?debugFmt("unexpected message ~p~n", [Other]),
- Self ! Other
- end
- end,
- YComb(YComb)
- end
- end,
- {foreach,
- fun() ->
- error_logger:tty(false),
- application:load(lager),
- application:set_env(lager, handlers, []),
- application:set_env(lager, errLoggerRedirect, false),
- eRum:start(),
- whereis(user)
- end,
- fun(User) ->
- unregister(user),
- register(user, User),
- application:stop(lager),
- application:stop(goldrush),
- error_logger:tty(true)
- end,
- [
- {"regular console logging",
- fun() ->
- Pid = spawn(F(self())),
- unregister(user),
- register(user, Pid),
- erlang:group_leader(Pid, whereis(rumEvent)),
- gen_event:add_handler(rumEvent, lager_console_backend, [{level, info}]),
- rumConfig:set({rumEvent, loglevel}, {element(2, rumUtil:config_to_mask(info)), []}),
- eRum:log(info, self(), "Test message"),
- receive
- {io_request, From, ReplyAs, {put_chars, unicode, Msg}} ->
- From ! {io_reply, ReplyAs, ok},
- TestMsg = "Test message" ++ eol(),
- ?assertMatch([_, "[info]", TestMsg], re:split(Msg, " ", [{return, list}, {parts, 3}]))
- after
- 500 ->
- ?assert(false)
- end
- end
- },
- {"verbose console logging",
- fun() ->
- Pid = spawn(F(self())),
- unregister(user),
- register(user, Pid),
- erlang:group_leader(Pid, whereis(rumEvent)),
- gen_event:add_handler(rumEvent, lager_console_backend, [info, true]),
- rumConfig:set({rumEvent, loglevel}, {element(2, rumUtil:config_to_mask(info)), []}),
- eRum:info("Test message"),
- PidStr = pid_to_list(self()),
- receive
- {io_request, _, _, {put_chars, unicode, Msg}} ->
- TestMsg = "Test message" ++ eol(),
- ?assertMatch([_, _, "[info]", PidStr, _, TestMsg], re:split(Msg, "[ @]", [{return, list}, {parts, 6}]))
- after
- 500 ->
- ?assert(false)
- end
- end
- },
- {"custom format console logging",
- fun() ->
- Pid = spawn(F(self())),
- unregister(user),
- register(user, Pid),
- erlang:group_leader(Pid, whereis(rumEvent)),
- gen_event:add_handler(rumEvent, lager_console_backend,
- [{level, info}, {formatter, lager_default_formatter}, {formatter_config, [date, "#", time, "#", severity, "#", node, "#", pid, "#",
- module, "#", function, "#", file, "#", line, "#", message, "\r\n"]}]),
- rumConfig:set({rumEvent, loglevel}, {?INFO, []}),
- eRum:info("Test message"),
- PidStr = pid_to_list(self()),
- NodeStr = atom_to_list(node()),
- ModuleStr = atom_to_list(?MODULE),
- receive
- {io_request, _, _, {put_chars, unicode, Msg}} ->
- TestMsg = "Test message" ++ eol(),
- ?assertMatch([_, _, "info", NodeStr, PidStr, ModuleStr, _, _, _, TestMsg],
- re:split(Msg, "#", [{return, list}, {parts, 10}]))
- after
- 500 ->
- ?assert(false)
- end
- end
- },
- {"tracing should work",
- fun() ->
- Pid = spawn(F(self())),
- unregister(user),
- register(user, Pid),
- gen_event:add_handler(rumEvent, lager_console_backend, [{level, info}]),
- erlang:group_leader(Pid, whereis(rumEvent)),
- rumConfig:set({rumEvent, loglevel}, {element(2, rumUtil:config_to_mask(info)), []}),
- eRum:debug("Test message"),
- receive
- {io_request, From, ReplyAs, {put_chars, unicode, _Msg}} ->
- From ! {io_reply, ReplyAs, ok},
- ?assert(false)
- after
- 500 ->
- ?assert(true)
- end,
- {ok, _} = eRum:trace_console([{module, ?MODULE}]),
- eRum:debug("Test message"),
- receive
- {io_request, From1, ReplyAs1, {put_chars, unicode, Msg1}} ->
- From1 ! {io_reply, ReplyAs1, ok},
- TestMsg = "Test message" ++ eol(),
- ?assertMatch([_, "[debug]", TestMsg], re:split(Msg1, " ", [{return, list}, {parts, 3}]))
- after
- 500 ->
- ?assert(false)
- end
- end
- },
- {"tracing doesn't duplicate messages",
- fun() ->
- Pid = spawn(F(self())),
- unregister(user),
- register(user, Pid),
- gen_event:add_handler(rumEvent, lager_console_backend, [{level, info}]),
- rumConfig:set({rumEvent, loglevel}, {element(2, rumUtil:config_to_mask(info)), []}),
- erlang:group_leader(Pid, whereis(rumEvent)),
- eRum:debug("Test message"),
- receive
- {io_request, From, ReplyAs, {put_chars, unicode, _Msg}} ->
- From ! {io_reply, ReplyAs, ok},
- ?assert(false)
- after
- 500 ->
- ?assert(true)
- end,
- {ok, _} = eRum:trace_console([{module, ?MODULE}]),
- eRum:error("Test message"),
- receive
- {io_request, From1, ReplyAs1, {put_chars, unicode, Msg1}} ->
- From1 ! {io_reply, ReplyAs1, ok},
- TestMsg = "Test message" ++ eol(),
- ?assertMatch([_, "[error]", TestMsg], re:split(Msg1, " ", [{return, list}, {parts, 3}]))
- after
- 1000 ->
- ?assert(false)
- end,
- %% make sure this event wasn't duplicated
- receive
- {io_request, From2, ReplyAs2, {put_chars, unicode, _Msg2}} ->
- From2 ! {io_reply, ReplyAs2, ok},
- ?assert(false)
- after
- 500 ->
- ?assert(true)
- end
- end
- },
- {"blacklisting a loglevel works",
- fun() ->
- Pid = spawn(F(self())),
- unregister(user),
- register(user, Pid),
- gen_event:add_handler(rumEvent, lager_console_backend, [{level, info}]),
- rumConfig:set({rumEvent, loglevel}, {element(2, rumUtil:config_to_mask(info)), []}),
- eRum:set_loglevel(lager_console_backend, '!=info'),
- erlang:group_leader(Pid, whereis(rumEvent)),
- eRum:debug("Test message"),
- receive
- {io_request, From1, ReplyAs1, {put_chars, unicode, Msg1}} ->
- From1 ! {io_reply, ReplyAs1, ok},
- TestMsg = "Test message" ++ eol(),
- ?assertMatch([_, "[debug]", TestMsg], re:split(Msg1, " ", [{return, list}, {parts, 3}]))
- after
- 1000 ->
- ?assert(false)
- end,
- %% info is blacklisted
- eRum:info("Test message"),
- receive
- {io_request, From2, ReplyAs2, {put_chars, unicode, _Msg2}} ->
- From2 ! {io_reply, ReplyAs2, ok},
- ?assert(false)
- after
- 500 ->
- ?assert(true)
- end
- end
- },
- {"whitelisting a loglevel works",
- fun() ->
- Pid = spawn(F(self())),
- unregister(user),
- register(user, Pid),
- gen_event:add_handler(rumEvent, lager_console_backend, [{level, info}]),
- rumConfig:set({rumEvent, loglevel}, {element(2, rumUtil:config_to_mask(info)), []}),
- eRum:set_loglevel(lager_console_backend, '=debug'),
- erlang:group_leader(Pid, whereis(rumEvent)),
- eRum:debug("Test message"),
- receive
- {io_request, From1, ReplyAs1, {put_chars, unicode, Msg1}} ->
- From1 ! {io_reply, ReplyAs1, ok},
- TestMsg = "Test message" ++ eol(),
- ?assertMatch([_, "[debug]", TestMsg], re:split(Msg1, " ", [{return, list}, {parts, 3}]))
- after
- 1000 ->
- ?assert(false)
- end,
- %% info is blacklisted
- eRum:error("Test message"),
- receive
- {io_request, From2, ReplyAs2, {put_chars, unicode, _Msg2}} ->
- From2 ! {io_reply, ReplyAs2, ok},
- ?assert(false)
- after
- 500 ->
- ?assert(true)
- end
- end
- },
- {"console backend with custom group leader",
- fun() ->
- Pid = spawn(F(self())),
- gen_event:add_handler(rumEvent, lager_console_backend, [{level, info}, {group_leader, Pid}]),
- rumConfig:set({rumEvent, loglevel}, {element(2, rumUtil:config_to_mask(info)), []}),
- eRum:info("Test message"),
- ?assertNotEqual({group_leader, Pid}, erlang:process_info(whereis(rumEvent), group_leader)),
- receive
- {io_request, From1, ReplyAs1, {put_chars, unicode, Msg1}} ->
- From1 ! {io_reply, ReplyAs1, ok},
- TestMsg = "Test message" ++ eol(),
- ?assertMatch([_, "[info]", TestMsg], re:split(Msg1, " ", [{return, list}, {parts, 3}]))
- after
- 1000 ->
- ?assert(false)
- end,
- %% killing the pid should prevent any new log messages (to prove we haven't accidentally redirected
- %% the group leader some other way
- exit(Pid, kill),
- timer:sleep(100),
- %% additionally, check the lager backend has been removed (because the group leader process died)
- ?assertNot(lists:member(lager_console_backend, gen_event:which_handlers(rumEvent))),
- eRum:error("Test message"),
- receive
- {io_request, From2, ReplyAs2, {put_chars, unicode, _Msg2}} ->
- From2 ! {io_reply, ReplyAs2, ok},
- ?assert(false)
- after
- 500 ->
- ?assert(true)
- end
- end
- },
- {"console backend with custom group leader using a trace and an ID",
- fun() ->
- Pid = spawn(F(self())),
- ID = {?MODULE, trace_test},
- Handlers = rumConfig:global_get(handlers, []),
- HandlerInfo = lager_app:start_handler(rumEvent, ID,
- [{level, none}, {group_leader, Pid},
- {id, ID}]),
- rumConfig:global_set(handlers, [HandlerInfo | Handlers]),
- eRum:info("Test message"),
- ?assertNotEqual({group_leader, Pid}, erlang:process_info(whereis(rumEvent), group_leader)),
- receive
- {io_request, From, ReplyAs, {put_chars, unicode, _Msg}} ->
- From ! {io_reply, ReplyAs, ok},
- ?assert(false)
- after
- 500 ->
- ?assert(true)
- end,
- eRum:trace(ID, [{module, ?MODULE}], debug),
- eRum:info("Test message"),
- receive
- {io_request, From1, ReplyAs1, {put_chars, unicode, Msg1}} ->
- From1 ! {io_reply, ReplyAs1, ok},
- TestMsg = "Test message" ++ eol(),
- ?assertMatch([_, "[info]", TestMsg], re:split(Msg1, " ", [{return, list}, {parts, 3}]))
- after
- 500 ->
- ?assert(false)
- end,
- ?assertNotEqual({0, []}, rumConfig:get({rumEvent, loglevel})),
- %% killing the pid should prevent any new log messages (to prove we haven't accidentally redirected
- %% the group leader some other way
- exit(Pid, kill),
- timer:sleep(100),
- %% additionally, check the lager backend has been removed (because the group leader process died)
- ?assertNot(lists:member(lager_console_backend, gen_event:which_handlers(rumEvent))),
- %% finally, check the trace has been removed
- ?assertEqual({0, []}, rumConfig:get({rumEvent, loglevel})),
- eRum:error("Test message"),
- receive
- {io_request, From3, ReplyAs3, {put_chars, unicode, _Msg3}} ->
- From3 ! {io_reply, ReplyAs3, ok},
- ?assert(false)
- after
- 500 ->
- ?assert(true)
- end
- end
- }
- ]
- }.
-
- set_loglevel_test_() ->
- {foreach,
- fun() ->
- error_logger:tty(false),
- application:load(lager),
- application:set_env(lager, handlers, [{lager_console_backend, [{level, info}]}]),
- application:set_env(lager, errLoggerRedirect, false),
- eRum:start()
- end,
- fun(_) ->
- application:stop(lager),
- application:stop(goldrush),
- error_logger:tty(true)
- end,
- [
- {"Get/set loglevel test",
- fun() ->
- ?assertEqual(info, eRum:get_loglevel(lager_console_backend)),
- eRum:set_loglevel(lager_console_backend, debug),
- ?assertEqual(debug, eRum:get_loglevel(lager_console_backend)),
- eRum:set_loglevel(lager_console_backend, '!=debug'),
- ?assertEqual(info, eRum:get_loglevel(lager_console_backend)),
- eRum:set_loglevel(lager_console_backend, '!=info'),
- ?assertEqual(debug, eRum:get_loglevel(lager_console_backend)),
- ok
- end
- },
- {"Get/set invalid loglevel test",
- fun() ->
- ?assertEqual(info, eRum:get_loglevel(lager_console_backend)),
- ?assertEqual({error, bad_log_level},
- eRum:set_loglevel(lager_console_backend, fatfinger)),
- ?assertEqual(info, eRum:get_loglevel(lager_console_backend))
- end
- }
-
- ]
- }.
-
- -endif.
|