Current section

Files

Jump to
mysql test logger_acc.erl
Raw

test/logger_acc.erl

%% @doc A 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(logger_acc).
%% Public API
-export([capture/1]).
%% @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 :: [{logger:level(), [atom()], string()|logger:report()}].
capture(Fun) when is_function(Fun, 0) ->
Tag = make_ref(),
Self = self(),
AccPid = spawn_link(fun() -> log_acc_loop(Tag, Self) end),
logger:add_primary_filter(?MODULE, {fun(Event, _)-> AccPid ! {Tag, Event}, stop end, undefined}),
try
Fun()
of
Result ->
Events = flush_logs(AccPid, Tag),
{ok, Result, Events}
catch
Class:Error:Stacktrace ->
Events = flush_logs(AccPid, Tag),
{Class, Error, Stacktrace, Events}
after
ok = logger:remove_primary_filter(?MODULE)
end.
flush_logs(AccPid, Tag) ->
AccMon = monitor(process, AccPid),
AccPid ! {Tag, flush},
receive
{Tag, Events} ->
demonitor(AccMon, [flush]),
Events;
{'DOWN', AccMon, process, AccPid, Reason} ->
error({accumulator_process, Reason})
end.
log_acc_loop(Tag, Parent) ->
log_acc_loop(Tag, Parent, open, []).
log_acc_loop(Tag, Parent, Status, Acc) ->
Timeout = case Status of
open -> infinity;
closing -> 1000
end,
receive
{Tag, flush} when Status =:= open ->
log_acc_loop(Tag, Parent, closing, Acc);
{Tag, #{level := Level, msg := Msg} = Event} ->
Domain = case Event of
#{meta := #{domain := Dom}} -> Dom;
#{} -> []
end,
Message = case Msg of
{string, Str} -> unicode:characters_to_list(Str);
{report, Report} -> Report;
{Fmt, Args} -> lists:flatten(io_lib:format(Fmt, Args))
end,
log_acc_loop(Tag, Parent, Status, [{Level, Domain, Message} | Acc]);
Other ->
error({unexpected_message, Other})
after Timeout ->
Parent ! {Tag, lists:reverse(Acc)},
unlink(Parent),
ok
end.
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
capture_success_test() ->
Result = ?MODULE:capture(fun() ->
logger:notice("Hello ~p", [world]),
logger:notice("Hello ~p", [again]),
foo
end),
?assertEqual({ok, foo, [{notice, [], "Hello world"}, {notice, [], "Hello again"}]}, Result).
capture_failure_test() ->
Result = ?MODULE:capture(fun() ->
logger:notice("Hello ~p", [world]),
throw(foo)
end),
?assertMatch({throw, foo, _Trace, [{notice, [], "Hello world"}]}, Result).
-endif.