%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2017-2018. All Rights Reserved.
%%
%% Licensed 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.
%%
%% %CopyrightEnd%
%%
-module(logger_proxy).
%% API
-export([start_link/0, restart/0, log/1, child_spec/0, get_default_config/0]).
%% logger_olp callbacks
-export([init/1, handle_load/2, handle_info/2, terminate/2,
notify/2]).
-include("logger_internal.hrl").
-define(SERVER,?MODULE).
%%%-----------------------------------------------------------------
%%% API
-spec log(RemoteLog) -> ok when
RemoteLog :: {remote,node(),LogEvent},
LogEvent :: {log,Level,Format,Args,Meta} |
{log,Level,StringOrReport,Meta},
Level :: logger:level(),
Format :: io:format(),
Args :: list(term()),
StringOrReport :: unicode:chardata() | logger:report(),
Meta :: logger:metadata().
log(RemoteLog) ->
Olp = persistent_term:get(?MODULE),
case logger_olp:get_pid(Olp) =:= self() of
true ->
%% This happens when the log event comes from the
%% emulator, and the group leader is on a remote node.
_ = handle_load(RemoteLog, no_state),
ok;
false ->
logger_olp:load(Olp, RemoteLog)
end.
%% Called by supervisor
-spec start_link() -> {ok,pid(),logger_olp:olp_ref()} | {error,term()}.
start_link() ->
%% Notice that sync_mode is only used when logging to remote node,
%% i.e. when the log/2 API function is called.
%%
%% When receiving log events from the emulator or from a remote
%% node, the log event is sent as a message to this process, and
%% thus received directly in handle_info/2. This means that the
%% mode (async/sync/drop) is not read before the message is
%% sent. Thus sync mode is never entered, and drop mode is
%% implemented by setting the system_logger flag to undefined (see
%% notify/2)
%%
%% Burst limit is disabled, since this is only a proxy and we
%% don't want to limit bursts twice (here and in the handler).
logger_olp:start_link(?SERVER,?MODULE,[],logger:get_proxy_config()).
%% Fun used for restarting this process after it has been killed due
%% to overload (must set overload_kill_enable=>true in opts)
restart() ->
case supervisor:start_child(logger_sup, child_spec()) of
{ok,_Pid,Olp} ->
{ok,Olp};
{error,{Reason,Ch}} when is_tuple(Ch), element(1,Ch)==child ->
{error,Reason};
Error ->
Error
end.
%% Called internally and by logger_sup
child_spec() ->
Name = ?SERVER,
#{id => Name,
start => {?MODULE, start_link, []},
restart => temporary,
shutdown => 2000,
type => worker,
modules => [?MODULE]}.
get_default_config() ->
OlpDefault = logger_olp:get_default_opts(),
OlpDefault#{sync_mode_qlen=>500,
drop_mode_qlen=>1000,
flush_qlen=>5000,
burst_limit_enable=>false}.
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
process_flag(trap_exit, true),
_ = erlang:system_flag(system_logger,self()),
persistent_term:put(?MODULE,logger_olp:get_ref()),
{ok,no_state}.
%% Log event to send to the node where the group leader of it's client resides
handle_load({remote,Node,Log},State) ->
%% If the connection is overloaded (send_nosuspend returns false),
%% we drop the message.
_ = erlang:send_nosuspend({?SERVER,Node},Log),
State;
%% Log event to log on this node
handle_load({log,Level,Format,Args,Meta},State) ->
try_log([Level,Format,Args,Meta]),
State;
handle_load({log,Level,Report,Meta},State) ->
try_log([Level,Report,Meta]),
State.
%% Log event sent to this process e.g. from the emulator - it is really load
handle_info(Log,State) when is_tuple(Log), element(1,Log)==log ->
{load,State}.
terminate(overloaded, _State) ->
_ = erlang:system_flag(system_logger,undefined),
{ok,fun ?MODULE:restart/0};
terminate(_Reason, _State) ->
_ = erlang:system_flag(system_logger,whereis(logger)),
ok.
notify({mode_change,Mode0,Mode1},State) ->
_ = if Mode1=:=drop -> % entering drop mode
erlang:system_flag(system_logger,undefined);
Mode0=:=drop -> % leaving drop mode
erlang:system_flag(system_logger,self());
true ->
ok
end,
?LOG_INTERNAL(notice,"~w switched from ~w to ~w mode",[?MODULE,Mode0,Mode1]),
State;
notify({flushed,Flushed},State) ->
?LOG_INTERNAL(notice, "~w flushed ~w log events",[?MODULE,Flushed]),
State;
notify(restart,State) ->
?LOG_INTERNAL(notice, "~w restarted", [?MODULE]),
State;
notify(_Note,State) ->
State.
%%%-----------------------------------------------------------------
%%% Internal functions
try_log(Args) ->
try apply(logger,log,Args)
catch C:R:S ->
?LOG_INTERNAL(debug,[{?MODULE,log_failed},
{log,Args},
{reason,{C,R,S}}])
end.