%% 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(), sink :: pid() | atom() }). start_link(Sink, Module, Config) -> gen_server:start_link(?MODULE, [Sink, Module, Config], []). start(Sink, Module, Config) -> gen_server:start(?MODULE, [Sink, Module, Config], []). init([Sink, Module, Config]) -> install_handler(Sink, Module, Config), {ok, #state{sink=Sink, 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, sink=Sink} = 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(Sink, 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, sink=Sink} = State) -> install_handler(Sink, 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(Sink, lager_backend_throttle, Config) -> %% The lager_backend_throttle needs to know to which sink it is %% attached, hence this admittedly ugly workaround. Handlers are %% sensitive to the structure of the configuration sent to `init', %% sadly, so it's not trivial to add a configuration item to be %% ignored to backends without breaking 3rd party handlers. install_handler2(Sink, lager_backend_throttle, [{sink, Sink}|Config]); install_handler(Sink, Module, Config) -> install_handler2(Sink, Module, Config). %% private install_handler2(Sink, Module, Config) -> case gen_event:add_sup_handler(Sink, Module, Config) of ok -> ?INT_LOG(debug, "Lager installed handler ~p into ~p", [Module, Sink]), lager:update_loglevel_config(Sink), ok; {error, {fatal, Reason}} -> ?INT_LOG(error, "Lager fatally failed to install handler ~p into" " ~p, NOT retrying: ~p", [Module, Sink, 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, Sink, 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), application:stop(goldrush), 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), application:stop(goldrush), error_logger:tty(true) end end ] }. -endif.