|
@ -1,77 +1,47 @@ |
|
|
%% @doc Console backend for lager. |
|
|
|
|
|
|
|
|
-module(rumBkdConsole). |
|
|
%% Configuration is a proplist with the following keys: |
|
|
%% Configuration is a proplist with the following keys: |
|
|
%% <ul> |
|
|
|
|
|
%% <li>`level' - log level to use</li> |
|
|
|
|
|
%% <li>`use_stderr' - either `true' or `false', defaults to false. If set to true, |
|
|
|
|
|
%% use standard error to output console log messages</li> |
|
|
|
|
|
%% <li>`formatter' - the module to use when formatting log messages. Defaults to |
|
|
|
|
|
%% `lager_default_formatter'</li> |
|
|
|
|
|
%% <li>`formatter_config' - the format configuration string. Defaults to |
|
|
|
|
|
%% `time [ severity ] message'</li> |
|
|
|
|
|
%% </ul> |
|
|
|
|
|
|
|
|
%%`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' |
|
|
|
|
|
|
|
|
-module(rumBkdConsole). |
|
|
|
|
|
|
|
|
-behaviour(gen_emm). |
|
|
|
|
|
|
|
|
|
|
|
-include("eRum.hrl"). |
|
|
|
|
|
|
|
|
-behaviour(gen_event). |
|
|
|
|
|
|
|
|
-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([ |
|
|
-export([ |
|
|
init/1 |
|
|
init/1 |
|
|
, handle_call/2 |
|
|
|
|
|
, handle_event/2 |
|
|
|
|
|
, handle_info/2 |
|
|
|
|
|
|
|
|
, handleCall/2 |
|
|
|
|
|
, handleEvent/2 |
|
|
|
|
|
, handleInfo/2 |
|
|
, terminate/2 |
|
|
, terminate/2 |
|
|
, code_change/3 |
|
|
, code_change/3 |
|
|
]). |
|
|
]). |
|
|
|
|
|
|
|
|
-record(state, {level :: {'mask', integer()}, |
|
|
|
|
|
|
|
|
-record(state, { |
|
|
|
|
|
level :: {'mask', integer()}, |
|
|
out = user :: user | standard_error | pid(), |
|
|
out = user :: user | standard_error | pid(), |
|
|
id :: atom() | {atom(), any()}, |
|
|
id :: atom() | {atom(), any()}, |
|
|
formatter :: atom(), |
|
|
formatter :: atom(), |
|
|
format_config :: any(), |
|
|
format_config :: any(), |
|
|
colors = [] :: list()}). |
|
|
|
|
|
|
|
|
|
|
|
-ifdef(TEST). |
|
|
|
|
|
-include_lib("eunit/include/eunit.hrl"). |
|
|
|
|
|
-compile([{parse_transform, lager_transform}]). |
|
|
|
|
|
-endif. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-include("eRum.hrl"). |
|
|
|
|
|
|
|
|
|
|
|
-define(TERSE_FORMAT, [time, " ", color, "[", severity, "] ", message]). |
|
|
|
|
|
-define(DEFAULT_FORMAT_CONFIG, ?TERSE_FORMAT ++ [eol()]). |
|
|
|
|
|
-define(FORMAT_CONFIG_OFF, [{eol, eol()}]). |
|
|
|
|
|
|
|
|
|
|
|
-ifdef(TEST). |
|
|
|
|
|
-define(DEPRECATED(_Msg), ok). |
|
|
|
|
|
-else. |
|
|
|
|
|
-define(DEPRECATED(Msg), |
|
|
|
|
|
io:format(user, "WARNING: This is a deprecated console configuration. Please use \"~w\" instead.~n", [Msg])). |
|
|
|
|
|
-endif. |
|
|
|
|
|
|
|
|
|
|
|
%% @private |
|
|
|
|
|
init([Level]) when is_atom(Level) -> |
|
|
|
|
|
?DEPRECATED([{level, Level}]), |
|
|
|
|
|
init([{level, Level}]); |
|
|
|
|
|
init([Level, true]) when is_atom(Level) -> % for backwards compatibility |
|
|
|
|
|
?DEPRECATED([{level, Level}, {formatter_config, [{eol, "\\r\\n\\"}]}]), |
|
|
|
|
|
init([{level, Level}, {formatter_config, ?FORMAT_CONFIG_OFF}]); |
|
|
|
|
|
init([Level, false]) when is_atom(Level) -> % for backwards compatibility |
|
|
|
|
|
?DEPRECATED([{level, Level}]), |
|
|
|
|
|
init([{level, Level}]); |
|
|
|
|
|
|
|
|
colors = [] :: list() |
|
|
|
|
|
}). |
|
|
|
|
|
|
|
|
init(Options) when is_list(Options) -> |
|
|
|
|
|
true = validate_options(Options), |
|
|
|
|
|
Colors = case application:get_env(lager, colored) of |
|
|
|
|
|
{ok, true} -> |
|
|
|
|
|
{ok, LagerColors} = application:get_env(lager, colors), |
|
|
|
|
|
LagerColors; |
|
|
|
|
|
_ -> [] |
|
|
|
|
|
end, |
|
|
|
|
|
|
|
|
|
|
|
Level = get_option(level, Options, undefined), |
|
|
|
|
|
try {is_new_style_console_available(), rumUtil:config_to_mask(Level)} of |
|
|
|
|
|
{false, _} -> |
|
|
|
|
|
|
|
|
init(Opts) -> |
|
|
|
|
|
case isNewStyleConsole() of |
|
|
|
|
|
false -> |
|
|
Msg = "Lager's console backend is incompatible with the 'old' shell, not enabling it", |
|
|
Msg = "Lager's console backend is incompatible with the 'old' shell, not enabling it", |
|
|
%% be as noisy as possible, log to every possible place |
|
|
%% be as noisy as possible, log to every possible place |
|
|
try |
|
|
try |
|
@ -83,74 +53,37 @@ init(Options) when is_list(Options) -> |
|
|
io:format("WARNING: " ++ Msg ++ "~n"), |
|
|
io:format("WARNING: " ++ Msg ++ "~n"), |
|
|
?INT_LOG(warning, Msg, []), |
|
|
?INT_LOG(warning, Msg, []), |
|
|
{error, {fatal, old_shell}}; |
|
|
{error, {fatal, old_shell}}; |
|
|
{true, L} -> |
|
|
|
|
|
[UseErr, GroupLeader, ID, Formatter, Config] = [get_option(K, Options, Default) || {K, Default} <- [ |
|
|
|
|
|
{use_stderr, false}, |
|
|
|
|
|
{group_leader, false}, |
|
|
|
|
|
{id, ?MODULE}, |
|
|
|
|
|
{formatter, lager_default_formatter}, |
|
|
|
|
|
{formatter_config, ?DEFAULT_FORMAT_CONFIG} |
|
|
|
|
|
] |
|
|
|
|
|
], |
|
|
|
|
|
Out = case UseErr of |
|
|
|
|
|
false -> |
|
|
|
|
|
case GroupLeader of |
|
|
|
|
|
false -> user; |
|
|
|
|
|
GLPid when is_pid(GLPid) -> |
|
|
|
|
|
erlang:monitor(process, GLPid), |
|
|
|
|
|
GLPid |
|
|
|
|
|
end; |
|
|
|
|
|
true -> standard_error |
|
|
|
|
|
end, |
|
|
|
|
|
{ok, #state{level = L, |
|
|
|
|
|
id = ID, |
|
|
|
|
|
out = Out, |
|
|
|
|
|
formatter = Formatter, |
|
|
|
|
|
format_config = Config, |
|
|
|
|
|
colors = Colors}} |
|
|
|
|
|
catch |
|
|
|
|
|
_:_ -> |
|
|
|
|
|
{error, {fatal, bad_log_level}} |
|
|
|
|
|
end; |
|
|
|
|
|
init(Level) when is_atom(Level) -> |
|
|
|
|
|
?DEPRECATED([{level, Level}]), |
|
|
|
|
|
init([{level, Level}]); |
|
|
|
|
|
init(Other) -> |
|
|
|
|
|
{error, {fatal, {bad_console_config, Other}}}. |
|
|
|
|
|
|
|
|
_ -> |
|
|
|
|
|
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. |
|
|
|
|
|
|
|
|
validate_options([]) -> true; |
|
|
|
|
|
validate_options([{level, L} | T]) when is_atom(L) -> |
|
|
|
|
|
case lists:member(L, ?RumLevels) of |
|
|
|
|
|
false -> |
|
|
|
|
|
throw({error, {fatal, {bad_level, L}}}); |
|
|
|
|
|
true -> |
|
|
|
|
|
validate_options(T) |
|
|
|
|
|
end; |
|
|
|
|
|
validate_options([{use_stderr, true} | T]) -> |
|
|
|
|
|
validate_options(T); |
|
|
|
|
|
validate_options([{use_stderr, false} | T]) -> |
|
|
|
|
|
validate_options(T); |
|
|
|
|
|
validate_options([{formatter, M} | T]) when is_atom(M) -> |
|
|
|
|
|
validate_options(T); |
|
|
|
|
|
validate_options([{formatter_config, C} | T]) when is_list(C) -> |
|
|
|
|
|
validate_options(T); |
|
|
|
|
|
validate_options([{group_leader, L} | T]) when is_pid(L) -> |
|
|
|
|
|
validate_options(T); |
|
|
|
|
|
validate_options([{id, {?MODULE, _}} | T]) -> |
|
|
|
|
|
validate_options(T); |
|
|
|
|
|
validate_options([H | _]) -> |
|
|
|
|
|
|
|
|
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}}}). |
|
|
throw({error, {fatal, {bad_console_config, H}}}). |
|
|
|
|
|
|
|
|
get_option(K, Options, Default) -> |
|
|
|
|
|
case lists:keyfind(K, 1, Options) of |
|
|
|
|
|
{K, V} -> V; |
|
|
|
|
|
false -> Default |
|
|
|
|
|
end. |
|
|
|
|
|
|
|
|
|
|
|
%% @private |
|
|
|
|
|
handle_call(get_loglevel, #state{level = Level} = State) -> |
|
|
|
|
|
|
|
|
handleCall(mGetLogLevel, #state{level = Level} = State) -> |
|
|
{ok, Level, State}; |
|
|
{ok, Level, State}; |
|
|
handle_call({set_loglevel, Level}, State) -> |
|
|
|
|
|
|
|
|
handleCall({mSetLogLevel, Level}, State) -> |
|
|
try rumUtil:config_to_mask(Level) of |
|
|
try rumUtil:config_to_mask(Level) of |
|
|
Levels -> |
|
|
Levels -> |
|
|
{ok, ok, State#state{level = Levels}} |
|
|
{ok, ok, State#state{level = Levels}} |
|
@ -158,11 +91,11 @@ handle_call({set_loglevel, Level}, State) -> |
|
|
_:_ -> |
|
|
_:_ -> |
|
|
{ok, {error, bad_log_level}, State} |
|
|
{ok, {error, bad_log_level}, State} |
|
|
end; |
|
|
end; |
|
|
handle_call(_Request, State) -> |
|
|
|
|
|
|
|
|
handleCall(_Request, State) -> |
|
|
{ok, ok, State}. |
|
|
{ok, ok, State}. |
|
|
|
|
|
|
|
|
%% @private |
|
|
%% @private |
|
|
handle_event({mWriteLog, Message}, |
|
|
|
|
|
|
|
|
handleEvent({mWriteLog, Message}, |
|
|
#state{level = L, out = Out, formatter = Formatter, format_config = FormatConfig, colors = Colors, id = ID} = State) -> |
|
|
#state{level = L, out = Out, formatter = Formatter, format_config = FormatConfig, colors = Colors, id = ID} = State) -> |
|
|
case rumUtil:is_loggable(Message, L, ID) of |
|
|
case rumUtil:is_loggable(Message, L, ID) of |
|
|
true -> |
|
|
true -> |
|
@ -171,16 +104,14 @@ handle_event({mWriteLog, Message}, |
|
|
false -> |
|
|
false -> |
|
|
{ok, State} |
|
|
{ok, State} |
|
|
end; |
|
|
end; |
|
|
handle_event(_Event, State) -> |
|
|
|
|
|
|
|
|
handleEvent(_Event, State) -> |
|
|
{ok, State}. |
|
|
{ok, State}. |
|
|
|
|
|
|
|
|
%% @private |
|
|
|
|
|
handle_info({'DOWN', _, process, Out, _}, #state{out = Out}) -> |
|
|
|
|
|
|
|
|
handleInfo({'DOWN', _, process, Out, _}, #state{out = Out}) -> |
|
|
remove_handler; |
|
|
remove_handler; |
|
|
handle_info(_Info, State) -> |
|
|
|
|
|
|
|
|
handleInfo(_Info, State) -> |
|
|
{ok, State}. |
|
|
{ok, State}. |
|
|
|
|
|
|
|
|
%% @private |
|
|
|
|
|
terminate(remove_handler, _State = #state{id = ID}) -> |
|
|
terminate(remove_handler, _State = #state{id = ID}) -> |
|
|
%% have to do this asynchronously because we're in the event handlr |
|
|
%% have to do this asynchronously because we're in the event handlr |
|
|
spawn(fun() -> eRum:clear_trace_by_destination(ID) end), |
|
|
spawn(fun() -> eRum:clear_trace_by_destination(ID) end), |
|
@ -188,7 +119,6 @@ terminate(remove_handler, _State = #state{id = ID}) -> |
|
|
terminate(_Reason, _State) -> |
|
|
terminate(_Reason, _State) -> |
|
|
ok. |
|
|
ok. |
|
|
|
|
|
|
|
|
%% @private |
|
|
|
|
|
code_change(_OldVsn, State, _Extra) -> |
|
|
code_change(_OldVsn, State, _Extra) -> |
|
|
{ok, State}. |
|
|
{ok, State}. |
|
|
|
|
|
|
|
@ -200,11 +130,7 @@ eol() -> |
|
|
"\r\n" |
|
|
"\r\n" |
|
|
end. |
|
|
end. |
|
|
|
|
|
|
|
|
-ifdef(TEST). |
|
|
|
|
|
is_new_style_console_available() -> |
|
|
|
|
|
true. |
|
|
|
|
|
-else. |
|
|
|
|
|
is_new_style_console_available() -> |
|
|
|
|
|
|
|
|
isNewStyleConsole() -> |
|
|
%% Criteria: |
|
|
%% Criteria: |
|
|
%% 1. If the user has specified '-noshell' on the command line, |
|
|
%% 1. If the user has specified '-noshell' on the command line, |
|
|
%% then we will pretend that the new-style console is available. |
|
|
%% then we will pretend that the new-style console is available. |
|
@ -215,10 +141,7 @@ is_new_style_console_available() -> |
|
|
%% 3. If the user_drv process is registered, all is OK. |
|
|
%% 3. If the user_drv process is registered, all is OK. |
|
|
%% 'user_drv' is a registered proc name used by the "new" |
|
|
%% 'user_drv' is a registered proc name used by the "new" |
|
|
%% console driver. |
|
|
%% console driver. |
|
|
init:get_argument(noshell) /= error orelse |
|
|
|
|
|
element(1, os:type()) /= win32 orelse |
|
|
|
|
|
is_pid(whereis(user_drv)). |
|
|
|
|
|
-endif. |
|
|
|
|
|
|
|
|
init:get_argument(noshell) /= error orelse element(1, os:type()) /= win32 orelse is_pid(whereis(user_drv)). |
|
|
|
|
|
|
|
|
-ifdef(TEST). |
|
|
-ifdef(TEST). |
|
|
console_config_validation_test_() -> |
|
|
console_config_validation_test_() -> |
|
@ -229,10 +152,10 @@ console_config_validation_test_() -> |
|
|
{formatter_config, ["blort", "garbage"]}, |
|
|
{formatter_config, ["blort", "garbage"]}, |
|
|
{use_stderr, false}], |
|
|
{use_stderr, false}], |
|
|
[ |
|
|
[ |
|
|
?_assertEqual(true, validate_options(Good)), |
|
|
|
|
|
?_assertThrow({error, {fatal, {bad_level, foo}}}, validate_options(Bad1)), |
|
|
|
|
|
?_assertThrow({error, {fatal, {bad_console_config, {use_stderr, flase}}}}, validate_options(Bad2)), |
|
|
|
|
|
?_assertEqual(true, validate_options(AllGood)) |
|
|
|
|
|
|
|
|
?_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_() -> |
|
|
console_log_test_() -> |
|
|