%% @doc A error_logger report handler that be used to capture expected errors in %% tests. The current error report handlers are disabled during the execution %% of a function. Afterwards, they are restored and the errors that occered are %% returned along with the return value of the fun. -module(error_logger_acc). %% Public API -export([capture/1]). -behaviour(gen_event). -export([init/1, handle_event/2, handle_call/2, handle_info/2, terminate/2, code_change/3]). %% @doc Executes `Fun' and captures all logged errors returns as well as %% uncaught errors in `Fun'. -spec capture(fun (() -> ResultOfFun)) -> {ok, ResultOfFun, AccumulatedErrors} | {throw | error | exit, Reason, Trace, AccumulatedErrors} when ResultOfFun :: term(), Reason :: term(), Trace :: list(), AccumulatedErrors :: [{error|warning_msg|info_msg, string()} | {error_report|warning_report|info_report, term()}]. capture(Fun) when is_function(Fun, 0) -> OldHandlers = gen_event:which_handlers(error_logger), error_logger:add_report_handler(?MODULE), lists:foreach(fun error_logger:delete_report_handler/1, OldHandlers), try Fun() of Result -> lists:foreach(fun error_logger:add_report_handler/1, OldHandlers), {ok, Result, error_logger:delete_report_handler(?MODULE)} catch Class:Error -> Trace = erlang:get_stacktrace(), lists:foreach(fun error_logger:add_report_handler/1, OldHandlers), AccumulatedErrors = error_logger:delete_report_handler(?MODULE), {Class, Error, Trace, AccumulatedErrors} end. %% --- gen_event callbacks --- init([]) -> {ok, []}. handle_event({ErrorType, _Gleader, {_Pid, Format, Data}}, State) -> ShortError = if ErrorType == error; ErrorType == warning_msg; ErrorType == info_msg -> {ErrorType, lists:flatten(io_lib:format(Format, Data))}; true -> {ErrorType, {Format, Data}} end, {ok, [ShortError | State]}; handle_event(_OtherEvent, State) -> {ok, State}. handle_call(_Call, State) -> {ok, ignored, State}. handle_info(_Info, State) -> {ok, State}. terminate([], State) -> %% error_logger:delete_report_handler/1 called lists:reverse(State); terminate(_Arg, _State) -> %% terminating for some other reason. error_logger:info_msg("Accumulating error handler shutting down"), ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). capture_success_test() -> Result = ?MODULE:capture(fun () -> error_logger:info_msg("Hello ~p", [world]), error_logger:info_msg("Hello ~p", [again]), foo end), ?assertEqual({ok, foo, [{info_msg, "Hello world"}, {info_msg, "Hello again"}]}, Result). capture_failure_test() -> Result = ?MODULE:capture(fun () -> error_logger:info_msg("Hello ~p", [world]), throw(foo) end), ?assertMatch({throw, foo, _Trace, [{info_msg, "Hello world"}]}, Result). -endif.