Current section
Files
Jump to
Current section
Files
src/logger_debug_h.erl
-module(logger_debug_h).
%% API
-export([start/1]).
%% logger callbacks
-export([
log/2,
adding_handler/1,
removing_handler/1,
changing_config/3,
filter_config/1
]).
-ignore_xref([
start/1,
log/2,
adding_handler/1,
removing_handler/1,
changing_config/3,
filter_config/1
]).
%% Adds a new handler, waits for it to be added.
%% There is a bug in the logger, when add_handler/remove_handler
%% are not consistent. So, we do all the synchronisation here instead.
start(#{id := Id}) ->
Pid = self(),
Name = reg_cleaner_name(Id),
case whereis(Name) of
undefined ->
ok;
OldPid ->
OldPid ! stop,
wait_for_down(OldPid)
end,
{Cleaner, Mon} = spawn_monitor(fun() ->
erlang:monitor(process, Pid),
erlang:register(Name, self()),
trans(fun() ->
wait_for_handler_removed(Id),
ok = logger:add_handler(
Id,
?MODULE,
#{config => #{forward_to_pid => Pid}}
)
end),
wait_for(fun() -> lists:member(Id, logger:get_handler_ids()) end, true),
Pid ! {done_starting, self()},
receive
{'DOWN', _Ref, process, Pid, _Reason} ->
ok;
stop ->
ok
end,
trans(fun() ->
wait_for_handler_removed(Id)
end)
end),
receive
{'DOWN', Mon, process, Cleaner, Reason} ->
error({failed_to_add_handler, Reason});
{done_starting, Cleaner} ->
ok
end.
reg_cleaner_name(Id) when is_atom(Id) ->
list_to_atom(atom_to_list(Id) ++ "_cleaner").
wait_for_down(Pid) ->
Mon = erlang:monitor(process, Pid),
receive
{'DOWN', Mon, process, Pid, _Reason} ->
ok
after 5000 ->
error({timeout, wait_for_down})
end.
wait_for(F, Cond) ->
wait_for(F, Cond, 50).
wait_for(_F, Cond, 0) ->
error({timeout, wait_for, Cond});
wait_for(F, Cond, Retries) ->
case F() of
Cond ->
ok;
_ ->
timer:sleep(100),
wait_for(F, Cond, Retries - 1)
end.
wait_for_handler_removed(Id) ->
F = fun() ->
_ = logger:remove_handler(Id),
lists:member(Id, logger:get_handler_ids())
end,
extra_info(
fun() -> wait_for(F, false) end,
fun() -> {logger:remove_handler(Id), logger:get_handler_ids()} end
).
extra_info(Try, Extra) ->
try
Try()
catch
Class:Reason:Stacktrace ->
erlang:raise(Class, {Reason, Extra()}, Stacktrace)
end.
%% Logger has a race condition when starting/stopping several handlers at the same
%% time. Logger fails to update the configuration atomically.
trans(F) ->
LockRequest = {?MODULE, self()},
Nodes = [node()],
Retries = 10,
global:trans(LockRequest, F, Nodes, Retries).
%%%===================================================================
%%% logger callbacks
%%%===================================================================
%%%-----------------------------------------------------------------
%%% Handler being added
-spec adding_handler(Config) -> {ok, Config} when
Config :: logger:handler_config().
adding_handler(Config) ->
{ok, Config}.
%%%-----------------------------------------------------------------
%%% Updating handler config
-spec changing_config(SetOrUpdate, OldConfig, NewConfig) ->
{ok, Config}
when
SetOrUpdate :: set | update,
OldConfig :: logger:handler_config(),
NewConfig :: logger:handler_config(),
Config :: logger:handler_config().
changing_config(_SetOrUpdate, _OldConfig, NewConfig) ->
{ok, NewConfig}.
%%%-----------------------------------------------------------------
%%% Handler being removed
-spec removing_handler(Config) -> ok when
Config :: logger:handler_config().
removing_handler(_Config) ->
ok.
%%%-----------------------------------------------------------------
%%% Log a string or report
-spec log(LogEvent, Config) -> ok when
LogEvent :: logger:log_event(),
Config :: logger:handler_config().
log(LogEvent, #{id := Id, config := #{forward_to_pid := Pid}}) ->
Pid ! {log, Id, LogEvent},
ok.
%%%-----------------------------------------------------------------
%%% Remove internal fields from configuration
-spec filter_config(Config) -> Config when
Config :: logger:handler_config().
filter_config(Config) ->
Config.