330 lines
11 KiB
Erlang
330 lines
11 KiB
Erlang
%%--------------------------------------------------------------------
|
|
%% Copyright (c) 2020 EMQ Technologies Co., Ltd. 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.
|
|
%%--------------------------------------------------------------------
|
|
|
|
-module(emqx_logger).
|
|
|
|
-compile({no_auto_import, [error/1]}).
|
|
|
|
%% Logs
|
|
-export([ debug/1
|
|
, debug/2
|
|
, debug/3
|
|
, info/1
|
|
, info/2
|
|
, info/3
|
|
, warning/1
|
|
, warning/2
|
|
, warning/3
|
|
, error/1
|
|
, error/2
|
|
, error/3
|
|
, critical/1
|
|
, critical/2
|
|
, critical/3
|
|
]).
|
|
|
|
%% Configs
|
|
-export([ set_metadata_peername/1
|
|
, set_metadata_clientid/1
|
|
, set_proc_metadata/1
|
|
, set_primary_log_level/1
|
|
, set_log_handler_level/2
|
|
, set_log_level/1
|
|
, set_all_log_handlers_level/1
|
|
]).
|
|
|
|
-export([ get_primary_log_level/0
|
|
, get_log_handlers/0
|
|
, get_log_handlers/1
|
|
, get_log_handler/1
|
|
]).
|
|
|
|
-export([ start_log_handler/1
|
|
, stop_log_handler/1
|
|
]).
|
|
|
|
-export([parse_transform/2]).
|
|
|
|
-type(peername_str() :: list()).
|
|
-type(logger_dst() :: file:filename() | console | unknown).
|
|
-type(logger_handler_info() :: #{
|
|
id := logger:handler_id(),
|
|
level := logger:level(),
|
|
dst := logger_dst(),
|
|
status := started | stopped
|
|
}).
|
|
|
|
-define(stopped_handlers, {?MODULE, stopped_handlers}).
|
|
|
|
%%--------------------------------------------------------------------
|
|
%% APIs
|
|
%%--------------------------------------------------------------------
|
|
-spec(debug(unicode:chardata()) -> ok).
|
|
debug(Msg) ->
|
|
logger:debug(Msg).
|
|
|
|
-spec(debug(io:format(), [term()]) -> ok).
|
|
debug(Format, Args) ->
|
|
logger:debug(Format, Args).
|
|
|
|
-spec(debug(logger:metadata(), io:format(), [term()]) -> ok).
|
|
debug(Metadata, Format, Args) when is_map(Metadata) ->
|
|
logger:debug(Format, Args, Metadata).
|
|
|
|
|
|
-spec(info(unicode:chardata()) -> ok).
|
|
info(Msg) ->
|
|
logger:info(Msg).
|
|
|
|
-spec(info(io:format(), [term()]) -> ok).
|
|
info(Format, Args) ->
|
|
logger:info(Format, Args).
|
|
|
|
-spec(info(logger:metadata(), io:format(), [term()]) -> ok).
|
|
info(Metadata, Format, Args) when is_map(Metadata) ->
|
|
logger:info(Format, Args, Metadata).
|
|
|
|
|
|
-spec(warning(unicode:chardata()) -> ok).
|
|
warning(Msg) ->
|
|
logger:warning(Msg).
|
|
|
|
-spec(warning(io:format(), [term()]) -> ok).
|
|
warning(Format, Args) ->
|
|
logger:warning(Format, Args).
|
|
|
|
-spec(warning(logger:metadata(), io:format(), [term()]) -> ok).
|
|
warning(Metadata, Format, Args) when is_map(Metadata) ->
|
|
logger:warning(Format, Args, Metadata).
|
|
|
|
|
|
-spec(error(unicode:chardata()) -> ok).
|
|
error(Msg) ->
|
|
logger:error(Msg).
|
|
-spec(error(io:format(), [term()]) -> ok).
|
|
error(Format, Args) ->
|
|
logger:error(Format, Args).
|
|
-spec(error(logger:metadata(), io:format(), [term()]) -> ok).
|
|
error(Metadata, Format, Args) when is_map(Metadata) ->
|
|
logger:error(Format, Args, Metadata).
|
|
|
|
|
|
-spec(critical(unicode:chardata()) -> ok).
|
|
critical(Msg) ->
|
|
logger:critical(Msg).
|
|
|
|
-spec(critical(io:format(), [term()]) -> ok).
|
|
critical(Format, Args) ->
|
|
logger:critical(Format, Args).
|
|
|
|
-spec(critical(logger:metadata(), io:format(), [term()]) -> ok).
|
|
critical(Metadata, Format, Args) when is_map(Metadata) ->
|
|
logger:critical(Format, Args, Metadata).
|
|
|
|
-spec(set_metadata_clientid(emqx_types:clientid()) -> ok).
|
|
set_metadata_clientid(<<>>) ->
|
|
ok;
|
|
set_metadata_clientid(ClientId) ->
|
|
set_proc_metadata(#{clientid => ClientId}).
|
|
|
|
-spec(set_metadata_peername(peername_str()) -> ok).
|
|
set_metadata_peername(Peername) ->
|
|
set_proc_metadata(#{peername => Peername}).
|
|
|
|
-spec(set_proc_metadata(logger:metadata()) -> ok).
|
|
set_proc_metadata(Meta) ->
|
|
logger:update_process_metadata(Meta).
|
|
|
|
-spec(get_primary_log_level() -> logger:level()).
|
|
get_primary_log_level() ->
|
|
#{level := Level} = logger:get_primary_config(),
|
|
Level.
|
|
|
|
-spec(set_primary_log_level(logger:level()) -> ok | {error, term()}).
|
|
set_primary_log_level(Level) ->
|
|
logger:set_primary_config(level, Level).
|
|
|
|
-spec(get_log_handlers() -> [logger_handler_info()]).
|
|
get_log_handlers() ->
|
|
get_log_handlers(started) ++ get_log_handlers(stopped).
|
|
|
|
-spec(get_log_handlers(started | stopped) -> [logger_handler_info()]).
|
|
get_log_handlers(started) ->
|
|
[log_hanlder_info(Conf, started) || Conf <- logger:get_handler_config()];
|
|
get_log_handlers(stopped) ->
|
|
[log_hanlder_info(Conf, stopped) || Conf <- list_stopped_handler_config()].
|
|
|
|
-spec(get_log_handler(logger:handler_id()) -> logger_handler_info()).
|
|
get_log_handler(HandlerId) ->
|
|
case logger:get_handler_config(HandlerId) of
|
|
{ok, Conf} ->
|
|
log_hanlder_info(Conf, started);
|
|
{error, _} ->
|
|
case read_stopped_handler_config(HandlerId) of
|
|
error -> {error, {not_found, HandlerId}};
|
|
{ok, Conf} -> log_hanlder_info(Conf, stopped)
|
|
end
|
|
end.
|
|
|
|
-spec(start_log_handler(logger:handler_id()) -> ok | {error, term()}).
|
|
start_log_handler(HandlerId) ->
|
|
case lists:member(HandlerId, logger:get_handler_ids()) of
|
|
true -> ok;
|
|
false ->
|
|
case read_stopped_handler_config(HandlerId) of
|
|
error -> {error, {not_found, HandlerId}};
|
|
{ok, Conf = #{module := Mod}} ->
|
|
case logger:add_handler(HandlerId, Mod, Conf) of
|
|
ok -> remove_stopped_handler_config(HandlerId);
|
|
{error, _} = Error -> Error
|
|
end
|
|
end
|
|
end.
|
|
|
|
-spec(stop_log_handler(logger:handler_id()) -> ok | {error, term()}).
|
|
stop_log_handler(HandlerId) ->
|
|
case logger:get_handler_config(HandlerId) of
|
|
{ok, Conf} ->
|
|
case logger:remove_handler(HandlerId) of
|
|
ok -> save_stopped_handler_config(HandlerId, Conf);
|
|
Error -> Error
|
|
end;
|
|
{error, _} ->
|
|
{error, {not_started, HandlerId}}
|
|
end.
|
|
|
|
-spec(set_log_handler_level(logger:handler_id(), logger:level()) -> ok | {error, term()}).
|
|
set_log_handler_level(HandlerId, Level) ->
|
|
case logger:set_handler_config(HandlerId, level, Level) of
|
|
ok -> ok;
|
|
{error, _} ->
|
|
case read_stopped_handler_config(HandlerId) of
|
|
error -> {error, {not_found, HandlerId}};
|
|
{ok, Conf} ->
|
|
save_stopped_handler_config(HandlerId, Conf#{level => Level})
|
|
end
|
|
end.
|
|
|
|
%% @doc Set both the primary and all handlers level in one command
|
|
-spec(set_log_level(logger:handler_id()) -> ok | {error, term()}).
|
|
set_log_level(Level) ->
|
|
case set_primary_log_level(Level) of
|
|
ok -> set_all_log_handlers_level(Level);
|
|
{error, Error} -> {error, {primary_logger_level, Error}}
|
|
end.
|
|
|
|
%% @doc The parse transform for prefixing a module-specific logger header to the logs.
|
|
%% The logger header can be specified by "-logger_header(Header)", where Header
|
|
%% must be a string (list).
|
|
%% @end
|
|
parse_transform(AST, _Opts) ->
|
|
trans(AST, "", []).
|
|
|
|
%%--------------------------------------------------------------------
|
|
%% Internal Functions
|
|
%%--------------------------------------------------------------------
|
|
|
|
log_hanlder_info(#{id := Id, level := Level, module := logger_std_h,
|
|
config := #{type := Type}}, Status) when
|
|
Type =:= standard_io;
|
|
Type =:= standard_error ->
|
|
#{id => Id, level => Level, dst => console, status => Status};
|
|
log_hanlder_info(#{id := Id, level := Level, module := logger_std_h,
|
|
config := Config = #{type := file}}, Status) ->
|
|
#{id => Id, level => Level, status => Status,
|
|
dst => maps:get(file, Config, atom_to_list(Id))};
|
|
|
|
log_hanlder_info(#{id := Id, level := Level, module := logger_disk_log_h,
|
|
config := #{file := Filename}}, Status) ->
|
|
#{id => Id, level => Level, dst => Filename, status => Status};
|
|
log_hanlder_info(#{id := Id, level := Level, module := _OtherModule}, Status) ->
|
|
#{id => Id, level => Level, dst => unknown, status => Status}.
|
|
|
|
%% set level for all log handlers in one command
|
|
set_all_log_handlers_level(Level) ->
|
|
set_all_log_handlers_level(get_log_handlers(), Level, []).
|
|
|
|
set_all_log_handlers_level([#{id := ID, level := Level} | List], NewLevel, ChangeHistory) ->
|
|
case set_log_handler_level(ID, NewLevel) of
|
|
ok -> set_all_log_handlers_level(List, NewLevel, [{ID, Level} | ChangeHistory]);
|
|
{error, Error} ->
|
|
rollback(ChangeHistory),
|
|
{error, {handlers_logger_level, {ID, Error}}}
|
|
end;
|
|
set_all_log_handlers_level([], _NewLevel, _NewHanlder) ->
|
|
ok.
|
|
|
|
rollback([{ID, Level} | List]) ->
|
|
set_log_handler_level(ID, Level),
|
|
rollback(List);
|
|
rollback([]) -> ok.
|
|
|
|
save_stopped_handler_config(HandlerId, Config) ->
|
|
case persistent_term:get(?stopped_handlers, undefined) of
|
|
undefined ->
|
|
persistent_term:put(?stopped_handlers, #{HandlerId => Config});
|
|
ConfList ->
|
|
persistent_term:put(?stopped_handlers, ConfList#{HandlerId => Config})
|
|
end.
|
|
read_stopped_handler_config(HandlerId) ->
|
|
case persistent_term:get(?stopped_handlers, undefined) of
|
|
undefined -> error;
|
|
ConfList -> maps:find(HandlerId, ConfList)
|
|
end.
|
|
remove_stopped_handler_config(HandlerId) ->
|
|
case persistent_term:get(?stopped_handlers, undefined) of
|
|
undefined -> ok;
|
|
ConfList ->
|
|
case maps:find(HandlerId, ConfList) of
|
|
error -> ok;
|
|
{ok, _} ->
|
|
persistent_term:put(?stopped_handlers, maps:remove(HandlerId, ConfList))
|
|
end
|
|
end.
|
|
list_stopped_handler_config() ->
|
|
case persistent_term:get(?stopped_handlers, undefined) of
|
|
undefined -> [];
|
|
ConfList -> maps:values(ConfList)
|
|
end.
|
|
|
|
%% @doc The following parse-transforms stripped off the module attribute named
|
|
%% `-logger_header(Header)` (if there's one) from the source code, and then
|
|
%% generate a function named '$logger_header'/0, which returns the logger header.
|
|
%% @end
|
|
trans([], LogHeader, ResAST) ->
|
|
lists:reverse([header_fun(LogHeader) | ResAST]);
|
|
trans([{eof, L} | AST], LogHeader, ResAST) ->
|
|
lists:reverse([{eof, L}, header_fun(LogHeader) | ResAST]) ++ AST;
|
|
trans([{attribute, _, module, _Mod} = M | AST], Header, ResAST) ->
|
|
trans(AST, Header, [export_header_fun(), M | ResAST]);
|
|
trans([{attribute, _, logger_header, Header} | AST], _, ResAST) ->
|
|
io_lib:printable_list(Header) orelse erlang:error({invalid_string, Header}),
|
|
trans(AST, Header, ResAST);
|
|
trans([F | AST], LogHeader, ResAST) ->
|
|
trans(AST, LogHeader, [F | ResAST]).
|
|
|
|
export_header_fun() ->
|
|
{attribute,erl_anno:new(0),export,[{'$logger_header',0}]}.
|
|
|
|
header_fun(LogHeader) ->
|
|
L = erl_anno:new(0),
|
|
{function,L,'$logger_header',0,
|
|
[{clause,L,
|
|
[], [], [{string,L,pad(LogHeader)}]}]}.
|
|
|
|
pad("") -> "";
|
|
pad(Str) -> Str ++ " ".
|