-module(lgBkdConsole). %% 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 %%`fmtTer' - the module to use when formatting log messages. Defaults to `lgFormatTer' %%`fmtCfg' - the format configuration string. Defaults to `time [ severity ] message' -behaviour(gen_emm). -include("lgDef.hrl"). -compile(inline). -compile({inline_size, 128}). -define(TERSE_FORMAT, [time, " ", color, "[", severity, "] ", message]). -define(LgDefConsoleFmtCfg, ?TERSE_FORMAT ++ [eol()]). -define(LgDefConsoleOpts, [{use_stderr, false}, {group_leader, false}, {id, ?MODULE}, {fmtTer, ?LgDefFmtTer}, {fmtCfg, ?LgDefConsoleFmtCfg}]). -export([ init/1 , handleCall/2 , handleEvent/2 , handleInfo/2 , terminate/2 , code_change/3 ]). -record(state, { id :: atom() | {atom(), any()} , level :: lgMaskLevel() , out = user :: user | standard_error | pid() , fmtTer :: atom() , fmtCfg :: any() , colors = [] :: list() }). -spec init([lgConsoleOpt(), ...]) -> {ok, #state{}} | {error, atom()}. init(Opts) -> case isNewStyleConsole() of false -> Msg = "eLog'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), CfgColors = ?IIF(lgUtil:get_env(colored, true), lgUtil:get_env(colors, []), []), Colors = [{lgUtil:levelToNum(Level), ColorStr} || {Level, ColorStr} <- CfgColors], Level = lgUtil:get_opt(level, Opts, undefined), LevelMask = lgUtil:configToMask(Level), [UseErr, GroupLeader, Id, FmtTer, FmtCfg] = [lgUtil:get_opt(Key, Opts, Def) || {Key, Def} <- ?LgDefConsoleOpts], Out = ?IIF(UseErr, standard_error, ?IIF(GroupLeader == false, user, begin erlang:monitor(process, GroupLeader), GroupLeader end)), {ok, #state{level = LevelMask, id = Id, out = Out, fmtTer = FmtTer, fmtCfg = FmtCfg, colors = Colors}} end. checkOpts([]) -> true; checkOpts([{id, {?MODULE, _}} | T]) -> checkOpts(T); checkOpts([{level, Level} | T]) -> ?IIF(lgUtil:validateLogLevel(Level) =/= false, checkOpts(T), {error, {bad_level, Level}}); checkOpts([{use_stderr, Flag} | T]) when is_boolean(Flag) -> checkOpts(T); checkOpts([{fmtTer, M} | T]) when is_atom(M) -> checkOpts(T); checkOpts([{fmtCfg, C} | T]) when is_list(C) -> checkOpts(T); checkOpts([{group_leader, L} | T]) when is_pid(L) -> checkOpts(T); checkOpts([H | _]) -> {error, {invalid_opt, H}}. handleCall(mGetLogLevel, State) -> {reply, State#state.level, State}; handleCall({mSetLogLevel, Level}, State) -> case lgUtil:validateLogLevel(Level) of false -> {reply, {error, bad_loglevel}, State}; LevelMask -> {reply, ok, State#state{level = LevelMask}} end; handleCall(_Msg, State) -> ?ERR("~p call receive unexpect msg ~p ~n ", [?MODULE, _Msg]), {reply, ok, State}. handleEvent({mWriteLog, Message}, #state{level = Level, out = Out, fmtTer = FmtTer, fmtCfg = FmtCfg, colors = Colors, id = ID}) -> case lgUtil:isLoggAble(Message, Level, ID) of true -> io:put_chars(Out, FmtTer:format(Message, FmtCfg, Colors)), kpS; _ -> kpS end; handleEvent(_Msg, _State) -> ?ERR("~p event receive unexpect msg ~p ~n ", [?MODULE, _Msg]), kpS. handleInfo({'DOWN', _, process, Out, _}, #state{out = Out}) -> removeEpm; handleInfo(_Msg, _State) -> ?ERR("~p info receive unexpect msg ~p ~n", [?MODULE, _Msg]), kpS. terminate(removeEpm, State) -> %% have to do this asynchronously because we're in the event handlr spawn(fun() -> eLog:clearTraceByDest(State#state.id) end), ok; terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. eol() -> ?IIF(lgUtil:get_env(colored, true), "\e[0m\r\n", "\r\n"). 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)).