%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2017. 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_server). -behaviour(gen_server). %% API -export([start_link/0, add_handler/3, remove_handler/1, add_filter/2, remove_filter/2, set_module_level/2, reset_module_level/1, cache_module_level/1, set_config/2, set_config/3]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). -include("logger_internal.hrl"). -define(SERVER, logger). -record(state, {tid}). %%%=================================================================== %%% API %%%=================================================================== start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). add_handler(Id,Module,Config0) -> case sanity_check(logger,handlers,[Id]) of ok -> try check_mod(Module) of ok -> case sanity_check(Id,Config0) of ok -> Default = default_config(Id), Config = maps:merge(Default,Config0), call({add_handler,Id,Module,Config}); Error -> Error end catch throw:Error -> {error,Error} end; Error -> Error end. remove_handler(HandlerId) -> call({remove_handler,HandlerId}). add_filter(Owner,Filter) -> case sanity_check(Owner,filters,[Filter]) of ok -> call({add_filter,Owner,Filter}); Error -> Error end. remove_filter(Owner,FilterId) -> call({remove_filter,Owner,FilterId}). set_module_level(Module,Level) when is_atom(Module) -> case sanity_check(logger,level,Level) of ok -> call({set_module_level,Module,Level}); Error -> Error end; set_module_level(Module,_) -> {error,{not_a_module,Module}}. reset_module_level(Module) when is_atom(Module) -> call({reset_module_level,Module}); reset_module_level(Module) -> {error,{not_a_module,Module}}. cache_module_level(Module) -> gen_server:cast(?SERVER,{cache_module_level,Module}). set_config(Owner,Key,Value) -> case sanity_check(Owner,Key,Value) of ok -> call({update_config,Owner,#{Key=>Value}}); Error -> Error end. set_config(Owner,Config0) -> case sanity_check(Owner,Config0) of ok -> Config = maps:merge(default_config(Owner),Config0), call({set_config,Owner,Config}); Error -> Error end. %%%=================================================================== %%% gen_server callbacks %%%=================================================================== init([]) -> process_flag(trap_exit, true), Tid = logger_config:new(?LOGGER_TABLE), LoggerConfig = maps:merge(default_config(logger), #{handlers=>[logger_simple]}), logger_config:create(Tid,logger,LoggerConfig), SimpleConfig0 = maps:merge(default_config(logger_simple), #{filter_default=>stop, filters=>?DEFAULT_HANDLER_FILTERS, logger_simple=>#{buffer=>true}}), %% If this fails, then the node should crash {ok,SimpleConfig} = logger_simple:adding_handler(logger_simple,SimpleConfig0), logger_config:create(Tid,logger_simple,logger_simple,SimpleConfig), {ok, #state{tid=Tid}}. handle_call({add_handler,Id,Module,HConfig}, _From, #state{tid=Tid}=State) -> Reply = case logger_config:exist(Tid,Id) of true -> {error,{already_exist,Id}}; false -> %% inform the handler case call_h(Module,adding_handler,[Id,HConfig],{ok,HConfig}) of {ok,HConfig1} -> logger_config:create(Tid,Id,Module,HConfig1), {ok,Config} = do_get_config(Tid,logger), Handlers = maps:get(handlers,Config,[]), do_set_config(Tid,logger, Config#{handlers=>[Id|Handlers]}), ok; {error,HReason} -> {error,{handler_not_added,HReason}} end end, {reply,Reply,State}; handle_call({remove_handler,HandlerId}, _From, #state{tid=Tid}=State) -> Reply = case logger_config:get(Tid,HandlerId) of {ok,{Module,_}} -> {ok,Config} = do_get_config(Tid,logger), Handlers0 = maps:get(handlers,Config,[]), Handlers = lists:delete(HandlerId,Handlers0), %% inform the handler _ = call_h(Module,removing_handler,[HandlerId,Config],ok), do_set_config(Tid,logger,Config#{handlers=>Handlers}), logger_config:delete(Tid,HandlerId), ok; _ -> {error,{not_found,HandlerId}} end, {reply,Reply,State}; handle_call({add_filter,Id,Filter}, _From,#state{tid=Tid}=State) -> Reply = do_add_filter(Tid,Id,Filter), {reply,Reply,State}; handle_call({remove_filter,Id,FilterId}, _From, #state{tid=Tid}=State) -> Reply = do_remove_filter(Tid,Id,FilterId), {reply,Reply,State}; handle_call({update_config,Id,NewConfig}, _From, #state{tid=Tid}=State) -> Reply = case logger_config:get(Tid,Id) of {ok,{Module,OldConfig}} -> Config = maps:merge(OldConfig,NewConfig), case call_h(Module,changing_config,[Id,OldConfig,Config], {ok,Config}) of {ok,Config1} -> do_set_config(Tid,Id,Config1); Error -> Error end; {ok,OldConfig} -> Config = maps:merge(OldConfig,NewConfig), do_set_config(Tid,Id,Config); Error -> Error end, {reply,Reply,State}; handle_call({set_config,logger,Config}, _From, #state{tid=Tid}=State) -> Reply = do_set_config(Tid,logger,Config), {reply,Reply,State}; handle_call({set_config,HandlerId,Config}, _From, #state{tid=Tid}=State) -> Reply = case logger_config:get(Tid,HandlerId) of {ok,{Module,OldConfig}} -> case call_h(Module,changing_config,[HandlerId,OldConfig,Config], {ok,Config}) of {ok,Config1} -> do_set_config(Tid,HandlerId,Config1); Error -> Error end; _ -> {error,{not_found,HandlerId}} end, {reply,Reply,State}; handle_call({set_module_level,Module,Level}, _From, #state{tid=Tid}=State) -> Reply = logger_config:set_module_level(Tid,Module,Level), {reply,Reply,State}; handle_call({reset_module_level,Module}, _From, #state{tid=Tid}=State) -> Reply = logger_config:reset_module_level(Tid,Module), {reply,Reply,State}. handle_cast({cache_module_level,Module}, #state{tid=Tid}=State) -> logger_config:cache_module_level(Tid,Module), {noreply, State}. %% Interface for those who can't call the API - e.g. the emulator, or %% places related to code loading. %% %% This can also be log events from remote nodes which are sent from %% logger.erl when the group leader of the client process is on a %% same node as the client process itself. handle_info({log,Level,Format,Args,Meta}, State) -> logger:log(Level,Format,Args,Meta), {noreply, State}; handle_info({log,Level,Report,Meta}, State) -> logger:log(Level,Report,Meta), {noreply, State}; handle_info({Ref,_Reply},State) when is_reference(Ref) -> %% Assuming this is a timed-out gen_server reply - ignoring {noreply, State}; handle_info(Unexpected,State) -> ?LOG_INTERNAL(debug, [{logger,got_unexpected_message}, {process,?SERVER}, {message,Unexpected}]), {noreply,State}. terminate(_Reason, _State) -> ok. %%%=================================================================== %%% Internal functions %%%=================================================================== call(Request) -> case whereis(?SERVER) of Pid when Pid==self() -> {error,{attempting_syncronous_call_to_self,Request}}; _ -> gen_server:call(?SERVER,Request,?DEFAULT_LOGGER_CALL_TIMEOUT) end. do_add_filter(Tid,Id,{FId,_} = Filter) -> case do_get_config(Tid,Id) of {ok,Config} -> Filters = maps:get(filters,Config,[]), case lists:keymember(FId,1,Filters) of true -> {error,{already_exist,FId}}; false -> do_set_config(Tid,Id,Config#{filters=>[Filter|Filters]}) end; Error -> Error end. do_remove_filter(Tid,Id,FilterId) -> case do_get_config(Tid,Id) of {ok,Config} -> Filters0 = maps:get(filters,Config,[]), case lists:keytake(FilterId,1,Filters0) of {value,_,Filters} -> do_set_config(Tid,Id,Config#{filters=>Filters}); false -> {error,{not_found,FilterId}} end; Error -> Error end. do_get_config(Tid,Id) -> case logger_config:get(Tid,Id) of {ok,{_,Config}} -> {ok,Config}; {ok,Config} -> {ok,Config}; Error -> Error end. do_set_config(Tid,Id,Config) -> logger_config:set(Tid,Id,Config), ok. default_config(logger) -> #{level=>info, filters=>[], filter_default=>log, handlers=>[]}; default_config(_) -> #{level=>info, filters=>[], filter_default=>log, formatter=>{?DEFAULT_FORMATTER,?DEFAULT_FORMAT_CONFIG}}. sanity_check(Owner,Key,Value) -> sanity_check_1(Owner,[{Key,Value}]). sanity_check(HandlerId,Config) when is_map(Config) -> sanity_check_1(HandlerId,maps:to_list(Config)); sanity_check(_,Config) -> {error,{invalid_handler_config,Config}}. sanity_check_1(Owner,Config) when is_list(Config) -> try Type = get_type(Owner), check_config(Type,Config) catch throw:Error -> {error,Error} end. get_type(logger) -> logger; get_type(Id) -> check_id(Id), handler. check_config(Owner,[{level,Level}|Config]) -> check_level(Level), check_config(Owner,Config); check_config(logger,[{handlers,Handlers}|Config]) -> check_handlers(Handlers), check_config(logger,Config); check_config(Owner,[{filters,Filters}|Config]) -> check_filters(Filters), check_config(Owner,Config); check_config(Owner,[{filter_default,FD}|Config]) -> check_filter_default(FD), check_config(Owner,Config); check_config(handler,[{formatter,Formatter}|Config]) -> check_formatter(Formatter), check_config(handler,Config); check_config(logger,[C|_]) -> throw({invalid_logger_config,C}); check_config(handler,[{_,_}|Config]) -> %% Arbitrary config elements are allowed for handlers check_config(handler,Config); check_config(_,[]) -> ok. check_id(Id) when is_atom(Id) -> ok; check_id(Id) -> throw({invalid_id,Id}). check_mod(Mod) when is_atom(Mod) -> ok; check_mod(Mod) -> throw({invalid_module,Mod}). check_level({LevelInt,cached}) when LevelInt>=?EMERGENCY, LevelInt= ok; check_level(Level) -> case lists:member(Level,?LEVELS) of true -> ok; false -> throw({invalid_level,Level}) end. check_handlers([Id|Handlers]) -> check_id(Id), check_handlers(Handlers); check_handlers([]) -> ok; check_handlers(Handlers) -> throw({invalid_handlers,Handlers}). check_filters([{Id,{Fun,_Args}}|Filters]) when is_atom(Id), is_function(Fun,2) -> check_filters(Filters); check_filters([Filter|_]) -> throw({invalid_filter,Filter}); check_filters([]) -> ok; check_filters(Filters) -> throw({invalid_filters,Filters}). check_filter_default(FD) when FD==stop; FD==log -> ok; check_filter_default(FD) -> throw({invalid_filter_default,FD}). check_formatter({logger_formatter,Config}) when is_map(Config) -> check_logger_formatter_config(maps:to_list(Config)); check_formatter({logger_formatter,Config}) -> throw({invalid_formatter_config,Config}); check_formatter({Mod,_}) -> %% no knowledge of other formatters check_mod(Mod); check_formatter(Formatter) -> throw({invalid_formatter,Formatter}). check_logger_formatter_config([{template,T}|Config]) when is_list(T) -> case lists:all(fun(X) when is_atom(X) -> true; (X) when is_tuple(X), is_atom(element(1,X)) -> true; (X) when is_list(X) -> io_lib:printable_unicode_list(X); (_) -> false end, T) of true -> check_logger_formatter_config(Config); false -> throw({invalid_formatter_template,T}) end; check_logger_formatter_config([{legacy_header,LH}|Config]) when is_boolean(LH) -> check_logger_formatter_config(Config); check_logger_formatter_config([{single_line,SL}|Config]) when is_boolean(SL) -> check_logger_formatter_config(Config); check_logger_formatter_config([{utc,Utc}|Config]) when is_boolean(Utc) -> check_logger_formatter_config(Config); check_logger_formatter_config([C|_]) -> throw({invalid_formatter_config,C}); check_logger_formatter_config([]) -> ok. call_h(Module, Function, Args, DefRet) -> %% Not calling code:ensure_loaded + erlang:function_exported here, %% since in some rare terminal cases, the code_server might not %% exist and we'll get a deadlock in removing the handler. try apply(Module, Function, Args) catch C:R:S -> case {C,R,S} of {error,undef,[{Module,Function,Args,_}|_]} -> DefRet; _ -> {error,{callback_crashed, {C,R,logger:filter_stacktrace(?MODULE,S)}}} end end.