aboutsummaryrefslogblamecommitdiffstats
path: root/lib/kernel/src/logger_proxy.erl
blob: f89891bff091cde23835c7fe08b9d36e34b4be75 (plain) (tree)























































































































































                                                                              
%%
%% %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/2, child_spec/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(Olp, RemoteLog) -> ok when
      Olp :: logger_olp:olp_ref(),
      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(Olp, RemoteLog) ->
    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).
    Opts = #{sync_mode_qlen=>500,
             drop_mode_qlen=>1000,
             flush_qlen=>5000,
             burst_limit_enable=>false},
    logger_olp:start_link(?SERVER,?MODULE,[],Opts).

%% 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]}.

%%%===================================================================
%%% gen_server callbacks
%%%===================================================================

init([]) ->
    process_flag(trap_exit, true),
    _ = erlang:system_flag(system_logger,self()),
    logger_server:set_proxy_ref(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,drop},State) ->
    _ = erlang:system_flag(system_logger,undefined),
    State;
notify({mode_change,drop,_Mode1},State) ->
    _ = erlang:system_flag(system_logger,self()),
    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.