| %% Copyright (c) 2011-2012, 2014 Basho Technologies, Inc. All Rights Reserved. |
| %% |
| %% This file is provided to you under the Apache License, |
| %% Version 2.0 (the "License"); you may not use this file |
| %% except in compliance with the License. You may obtain |
| %% a copy of the License at |
| %% |
| %% http://www.apache.org/licenses/LICENSE-2.0 |
| %% |
| %% Unless required by applicable law or agreed to in writing, |
| %% software distributed under the License is distributed on an |
| %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| %% KIND, either express or implied. See the License for the |
| %% specific language governing permissions and limitations |
| %% under the License. |
| |
| %% @doc Console backend for lager. Configured with a single option, the loglevel |
| %% desired. |
| |
| -module(lager_console_backend). |
| |
| -behaviour(gen_event). |
| |
| -export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2, |
| code_change/3]). |
| |
| -record(state, {level :: {'mask', integer()}, |
| formatter :: atom(), |
| format_config :: any(), |
| colors=[] :: list()}). |
| |
| -ifdef(TEST). |
| -include_lib("eunit/include/eunit.hrl"). |
| -compile([{parse_transform, lager_transform}]). |
| -endif. |
| |
| -include("lager.hrl"). |
| -define(TERSE_FORMAT,[time, " ", color, "[", severity,"] ", message]). |
| |
| %% @private |
| init([Level, true]) -> % for backwards compatibility |
| init([Level,{lager_default_formatter,[{eol, eol()}]}]); |
| init([Level,false]) -> % for backwards compatibility |
| init([Level,{lager_default_formatter,?TERSE_FORMAT ++ [eol()]}]); |
| init([Level,{Formatter,FormatterConfig}]) when is_atom(Formatter) -> |
| Colors = case application:get_env(lager, colored) of |
| {ok, true} -> |
| {ok, LagerColors} = application:get_env(lager, colors), |
| LagerColors; |
| _ -> [] |
| end, |
| |
| try {is_new_style_console_available(), lager_util:config_to_mask(Level)} 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, Levels} -> |
| {ok, #state{level=Levels, |
| formatter=Formatter, |
| format_config=FormatterConfig, |
| colors=Colors}} |
| catch |
| _:_ -> |
| {error, {fatal, bad_log_level}} |
| end; |
| init(Level) -> |
| init([Level,{lager_default_formatter,?TERSE_FORMAT ++ [eol()]}]). |
| |
| |
| %% @private |
| handle_call(get_loglevel, #state{level=Level} = State) -> |
| {ok, Level, State}; |
| handle_call({set_loglevel, Level}, State) -> |
| try lager_util:config_to_mask(Level) of |
| Levels -> |
| {ok, ok, State#state{level=Levels}} |
| catch |
| _:_ -> |
| {ok, {error, bad_log_level}, State} |
| end; |
| handle_call(_Request, State) -> |
| {ok, ok, State}. |
| |
| %% @private |
| handle_event({log, Message}, |
| #state{level=L,formatter=Formatter,format_config=FormatConfig,colors=Colors} = State) -> |
| case lager_util:is_loggable(Message, L, ?MODULE) of |
| true -> |
| io:put_chars(user, Formatter:format(Message,FormatConfig,Colors)), |
| {ok, State}; |
| false -> |
| {ok, State} |
| end; |
| handle_event(_Event, State) -> |
| {ok, State}. |
| |
| %% @private |
| handle_info(_Info, State) -> |
| {ok, State}. |
| |
| %% @private |
| terminate(_Reason, _State) -> |
| ok. |
| |
| %% @private |
| code_change(_OldVsn, State, _Extra) -> |
| {ok, State}. |
| |
| eol() -> |
| case application:get_env(lager, colored) of |
| {ok, true} -> |
| "\e[0m\r\n"; |
| _ -> |
| "\r\n" |
| end. |
| |
| -ifdef(TEST). |
| is_new_style_console_available() -> |
| true. |
| -else. |
| is_new_style_console_available() -> |
| %% 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)). |
| -endif. |
| |
| -ifdef(TEST). |
| 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, error_logger_redirect, false), |
| lager:start(), |
| whereis(user) |
| end, |
| fun(User) -> |
| unregister(user), |
| register(user, User), |
| application:stop(lager), |
| error_logger:tty(true) |
| end, |
| [ |
| {"regular console logging", |
| fun() -> |
| Pid = spawn(F(self())), |
| unregister(user), |
| register(user, Pid), |
| erlang:group_leader(Pid, whereis(lager_event)), |
| gen_event:add_handler(lager_event, lager_console_backend, info), |
| lager_config:set(loglevel, {element(2, lager_util:config_to_mask(info)), []}), |
| lager: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(lager_event)), |
| gen_event:add_handler(lager_event, lager_console_backend, [info, true]), |
| lager_config:set(loglevel, {element(2, lager_util:config_to_mask(info)), []}), |
| lager: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(lager_event)), |
| gen_event:add_handler(lager_event, lager_console_backend, |
| [info, {lager_default_formatter, [date,"#",time,"#",severity,"#",node,"#",pid,"#", |
| module,"#",function,"#",file,"#",line,"#",message,"\r\n"]}]), |
| lager_config:set(loglevel, {?INFO, []}), |
| lager: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(lager_event, lager_console_backend, info), |
| erlang:group_leader(Pid, whereis(lager_event)), |
| lager_config:set(loglevel, {element(2, lager_util:config_to_mask(info)), []}), |
| lager: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, _} = lager:trace_console([{module, ?MODULE}]), |
| lager: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(lager_event, lager_console_backend, info), |
| lager_config:set(loglevel, {element(2, lager_util:config_to_mask(info)), []}), |
| erlang:group_leader(Pid, whereis(lager_event)), |
| lager: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, _} = lager:trace_console([{module, ?MODULE}]), |
| lager: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(lager_event, lager_console_backend, info), |
| lager_config:set(loglevel, {element(2, lager_util:config_to_mask(info)), []}), |
| lager:set_loglevel(lager_console_backend, '!=info'), |
| erlang:group_leader(Pid, whereis(lager_event)), |
| lager: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 |
| lager: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(lager_event, lager_console_backend, info), |
| lager_config:set(loglevel, {element(2, lager_util:config_to_mask(info)), []}), |
| lager:set_loglevel(lager_console_backend, '=debug'), |
| erlang:group_leader(Pid, whereis(lager_event)), |
| lager: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 |
| lager: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 |
| } |
| ] |
| }. |
| |
| set_loglevel_test_() -> |
| {foreach, |
| fun() -> |
| error_logger:tty(false), |
| application:load(lager), |
| application:set_env(lager, handlers, [{lager_console_backend, info}]), |
| application:set_env(lager, error_logger_redirect, false), |
| lager:start() |
| end, |
| fun(_) -> |
| application:stop(lager), |
| error_logger:tty(true) |
| end, |
| [ |
| {"Get/set loglevel test", |
| fun() -> |
| ?assertEqual(info, lager:get_loglevel(lager_console_backend)), |
| lager:set_loglevel(lager_console_backend, debug), |
| ?assertEqual(debug, lager:get_loglevel(lager_console_backend)), |
| lager:set_loglevel(lager_console_backend, '!=debug'), |
| ?assertEqual(info, lager:get_loglevel(lager_console_backend)), |
| lager:set_loglevel(lager_console_backend, '!=info'), |
| ?assertEqual(debug, lager:get_loglevel(lager_console_backend)), |
| ok |
| end |
| }, |
| {"Get/set invalid loglevel test", |
| fun() -> |
| ?assertEqual(info, lager:get_loglevel(lager_console_backend)), |
| ?assertEqual({error, bad_log_level}, |
| lager:set_loglevel(lager_console_backend, fatfinger)), |
| ?assertEqual(info, lager:get_loglevel(lager_console_backend)) |
| end |
| } |
| |
| ] |
| }. |
| |
| -endif. |