| %% Copyright (c) 2011-2012 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 A process that does a gen_event:add_sup_handler and attempts to re-add |
| %% event handlers when they exit. |
| |
| %% @private |
| |
| -module(lager_handler_watcher). |
| |
| -behaviour(gen_server). |
| |
| -include("lager.hrl"). |
| |
| -ifdef(TEST). |
| -include_lib("eunit/include/eunit.hrl"). |
| -endif. |
| |
| %% callbacks |
| -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, |
| code_change/3]). |
| |
| -export([start_link/3, start/3]). |
| |
| -record(state, { |
| module :: atom(), |
| config :: any(), |
| event :: pid() | atom() |
| }). |
| |
| start_link(Event, Module, Config) -> |
| gen_server:start_link(?MODULE, [Event, Module, Config], []). |
| |
| start(Event, Module, Config) -> |
| gen_server:start(?MODULE, [Event, Module, Config], []). |
| |
| init([Event, Module, Config]) -> |
| install_handler(Event, Module, Config), |
| {ok, #state{event=Event, module=Module, config=Config}}. |
| |
| handle_call(_Call, _From, State) -> |
| {reply, ok, State}. |
| |
| handle_cast(_Request, State) -> |
| {noreply, State}. |
| |
| handle_info({gen_event_EXIT, Module, normal}, #state{module=Module} = State) -> |
| {stop, normal, State}; |
| handle_info({gen_event_EXIT, Module, shutdown}, #state{module=Module} = State) -> |
| {stop, normal, State}; |
| handle_info({gen_event_EXIT, Module, Reason}, #state{module=Module, |
| config=Config, event=Event} = State) -> |
| case lager:log(error, self(), "Lager event handler ~p exited with reason ~s", |
| [Module, error_logger_lager_h:format_reason(Reason)]) of |
| ok -> |
| install_handler(Event, Module, Config); |
| {error, _} -> |
| %% lager is not working, so installing a handler won't work |
| ok |
| end, |
| {noreply, State}; |
| handle_info(reinstall_handler, #state{module=Module, config=Config, event=Event} = State) -> |
| install_handler(Event, Module, Config), |
| {noreply, State}; |
| handle_info(stop, State) -> |
| {stop, normal, State}; |
| handle_info(_Info, State) -> |
| {noreply, State}. |
| |
| terminate(_Reason, _State) -> |
| ok. |
| |
| code_change(_OldVsn, State, _Extra) -> |
| {ok, State}. |
| |
| %% internal |
| |
| install_handler(Event, Module, Config) -> |
| case gen_event:add_sup_handler(Event, Module, Config) of |
| ok -> |
| ?INT_LOG(debug, "Lager installed handler ~p into ~p", [Module, Event]), |
| lager:update_loglevel_config(), |
| ok; |
| {error, {fatal, Reason}} -> |
| ?INT_LOG(error, "Lager fatally failed to install handler ~p into" |
| " ~p, NOT retrying: ~p", [Module, Event, Reason]), |
| %% tell ourselves to stop |
| self() ! stop, |
| ok; |
| Error -> |
| %% try to reinstall it later |
| ?INT_LOG(error, "Lager failed to install handler ~p into" |
| " ~p, retrying later : ~p", [Module, Event, Error]), |
| erlang:send_after(5000, self(), reinstall_handler), |
| ok |
| end. |
| |
| -ifdef(TEST). |
| |
| from_now(Seconds) -> |
| {Mega, Secs, Micro} = os:timestamp(), |
| {Mega, Secs + Seconds, Micro}. |
| |
| reinstall_on_initial_failure_test_() -> |
| {timeout, 60000, |
| [ |
| fun() -> |
| error_logger:tty(false), |
| application:load(lager), |
| application:set_env(lager, handlers, [{lager_test_backend, info}, {lager_crash_backend, [from_now(2), undefined]}]), |
| application:set_env(lager, error_logger_redirect, false), |
| application:unset_env(lager, crash_log), |
| lager:start(), |
| try |
| ?assertEqual(1, lager_test_backend:count()), |
| {_Level, _Time, Message, _Metadata} = lager_test_backend:pop(), |
| ?assertMatch("Lager failed to install handler lager_crash_backend into lager_event, retrying later :"++_, lists:flatten(Message)), |
| ?assertEqual(0, lager_test_backend:count()), |
| timer:sleep(6000), |
| ?assertEqual(0, lager_test_backend:count()), |
| ?assert(lists:member(lager_crash_backend, gen_event:which_handlers(lager_event))) |
| after |
| application:stop(lager), |
| error_logger:tty(true) |
| end |
| end |
| ] |
| }. |
| |
| reinstall_on_runtime_failure_test_() -> |
| {timeout, 60000, |
| [ |
| fun() -> |
| error_logger:tty(false), |
| application:load(lager), |
| application:set_env(lager, handlers, [{lager_test_backend, info}, {lager_crash_backend, [undefined, from_now(5)]}]), |
| application:set_env(lager, error_logger_redirect, false), |
| application:unset_env(lager, crash_log), |
| lager:start(), |
| try |
| ?assertEqual(0, lager_test_backend:count()), |
| ?assert(lists:member(lager_crash_backend, gen_event:which_handlers(lager_event))), |
| timer:sleep(6000), |
| ?assertEqual(2, lager_test_backend:count()), |
| {_Severity, _Date, Msg, _Metadata} = lager_test_backend:pop(), |
| ?assertEqual("Lager event handler lager_crash_backend exited with reason crash", lists:flatten(Msg)), |
| {_Severity2, _Date2, Msg2, _Metadata2} = lager_test_backend:pop(), |
| ?assertMatch("Lager failed to install handler lager_crash_backend into lager_event, retrying later :"++_, lists:flatten(Msg2)), |
| ?assertEqual(false, lists:member(lager_crash_backend, gen_event:which_handlers(lager_event))) |
| after |
| application:stop(lager), |
| error_logger:tty(true) |
| end |
| end |
| ] |
| }. |
| |
| |
| -endif. |