Multi-faceted refactor of error logging
* Store errors as instances of internal #error{} record
* Move error buffer API calls to rexi.erl
* Don't badmatch if an old process exits abnormally
* Trivial rename of internal state fields
diff --git a/src/rexi.erl b/src/rexi.erl
index 9d34c51..f5a5e7e 100644
--- a/src/rexi.erl
+++ b/src/rexi.erl
@@ -17,6 +17,7 @@
-export([cast/2, cast/3, kill/2]).
-export([reply/1, sync_reply/1, sync_reply/2]).
-export([async_server_call/2, async_server_call/3]).
+-export([get_errors/0, get_last_error/0, set_error_limit/1]).
-define(SERVER, rexi_server).
@@ -29,6 +30,18 @@
restart() ->
stop(), start().
+-spec get_errors() -> {ok, list()}.
+get_errors() ->
+ gen_server:call(?SERVER, get_errors).
+
+-spec get_last_error() -> {ok, term()} | {error, empty}.
+get_last_error() ->
+ gen_server:call(?SERVER, get_last_error).
+
+-spec set_error_limit(pos_integer()) -> ok.
+set_error_limit(N) when is_integer(N), N > 0 ->
+ gen_server:call(?SERVER, {set_error_limit, N}).
+
%% @equiv cast(Node, self(), MFA)
-spec cast(node(), {atom(), atom(), list()}) -> reference().
cast(Node, MFA) ->
diff --git a/src/rexi_server.erl b/src/rexi_server.erl
index 6b01e27..ada9c94 100644
--- a/src/rexi_server.erl
+++ b/src/rexi_server.erl
@@ -15,7 +15,7 @@
-module(rexi_server).
-behaviour(gen_server).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
- code_change/3, calls/1, set_call_buffer_size/1]).
+ code_change/3]).
-export([start_link/0, init_p/2, init_p/3]).
@@ -23,10 +23,17 @@
-record(st, {
workers = ets:new(workers, [private, {keypos,2}]),
- err_cache = queue:new(),
- err_cache_max = 20,
- err_cache_len = 0
+ errors = queue:new(),
+ error_limit = 20,
+ error_count = 0
+}).
+-record(error, {
+ timestamp,
+ reason,
+ mfa,
+ nonce,
+ stack
}).
start_link() ->
@@ -35,27 +42,25 @@
init([]) ->
{ok, #st{}}.
-calls(N) ->
- {ok, gen_server:call(?MODULE,{calls, N},infinity)}.
+handle_call(get_errors, _From, #st{errors = Errors} = St) ->
+ {reply, {ok, lists:reverse(queue:to_list(Errors))}, St};
-set_call_buffer_size(N) ->
- {ok, gen_server:call(?MODULE,{cache_max, N},infinity)}.
+handle_call(get_last_error, _From, #st{errors = Errors} = St) ->
+ try
+ {reply, {ok, queue:get_r(Errors)}, St}
+ catch error:empty ->
+ {reply, {error, empty}, St}
+ end;
-handle_call({calls, N}, _From, #st{err_cache=Q}=St) ->
- ErrList = lists:sublist(lists:reverse(queue:to_list(Q)),1,N),
- {reply,ErrList,St};
+handle_call({set_error_limit, N}, _From, #st{error_count=Len, errors=Q} = St) ->
+ if N < Len ->
+ {NewQ, _} = queue:split(N, Q);
+ true ->
+ NewQ = Q
+ end,
+ NewLen = queue:len(NewQ),
+ {reply, ok, St#st{error_limit=N, error_count=NewLen, errors=NewQ}};
-handle_call({cache_max, N}, _From, #st{err_cache_len=Len,
- err_cache_max=Max, err_cache=Q}=St) ->
- {NewQ,NewLen} = case N < Len of
- true ->
- List = queue:to_list(Q),
- {queue:from_list(lists:sublist(List,(Len-N)+1,Len)),
- N};
- false ->
- {Q,Len}
- end,
- {reply, Max, St#st{err_cache_max=N, err_cache_len=NewLen, err_cache=NewQ}};
handle_call(_Request, _From, St) ->
{reply, ignored, St}.
@@ -81,28 +86,17 @@
handle_info({'DOWN', Ref, process, _, normal}, #st{workers=Workers} = St) ->
{noreply, St#st{workers = remove_worker(Ref, Workers)}};
-handle_info({'DOWN', Ref, process, Pid, Reason}, #st{workers=Workers,
- err_cache=Q,
- err_cache_max=Max,
- err_cache_len=Len} = St) ->
+handle_info({'DOWN', Ref, process, Pid, Error}, #st{workers=Workers} = St) ->
case find_worker(Ref, Workers) of
{Pid, Ref, From} ->
-
- {Error,{M,F,A},Nonce,Stack} = Reason,
-
- {NewQ,NewLen} =
- case Len >= Max of
- true ->
- Q1 = queue:drop(Q),
- {Q1,Len};
+ case Error of #error{reason = Reason, stack = Stack} ->
+ notify_caller(From, {Reason, Stack}),
+ St1 = save_error(Error, St),
+ {noreply, St1#st{workers = remove_worker(Ref, Workers)}};
_ ->
- {Q,Len+1}
- end,
- NewQ1 = queue:in([now(),From,Nonce,M,F,A],NewQ),
- notify_caller(From, {Error,Stack}),
- {noreply, St#st{workers = remove_worker(Ref, Workers),
- err_cache=NewQ1,
- err_cache_len=NewLen}};
+ notify_caller(From, Error),
+ {noreply, St#st{workers = remove_worker(Ref, Workers)}}
+ end;
false ->
{noreply, St}
end;
@@ -132,11 +126,22 @@
try apply(M, F, A) catch exit:normal -> ok; Class:Reason ->
Stack = clean_stack(),
error_logger:error_report([{?MODULE, Nonce, {Class, Reason}}, Stack]),
- exit({Reason,{M,F,A},Nonce,Stack})
+ exit(#error{
+ timestamp = now(),
+ reason = Reason,
+ mfa = {M,F,A},
+ nonce = Nonce,
+ stack = Stack
+ })
end.
%% internal
+save_error(E, #st{errors=Q, error_limit=L, error_count=C} = St) when C >= L ->
+ St#st{errors = queue:in(E, queue:drop(Q))};
+save_error(E, #st{errors=Q, error_count=C} = St) ->
+ St#st{errors = queue:in(E, Q), error_count = C+1}.
+
clean_stack() ->
lists:map(fun({M,F,A}) when is_list(A) -> {M,F,length(A)}; (X) -> X end,
erlang:get_stacktrace()).