Merge pull request #13241 from zhongwencool/add-with-mfa-option-for-log

feat: add hidden option to record MFA and line in the log
This commit is contained in:
zhongwencool 2024-06-13 19:26:10 +08:00 committed by GitHub
commit 1dd4b6de5e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 71 additions and 20 deletions

View File

@ -30,7 +30,10 @@
logger:log( logger:log(
Level, Level,
(Data), (Data),
Meta maps:merge(Meta, #{
mfa => {?MODULE, ?FUNCTION_NAME, ?FUNCTION_ARITY},
line => ?LINE
})
); );
false -> false ->
ok ok

View File

@ -237,32 +237,35 @@ log_formatter(HandlerName, Conf) ->
_ -> _ ->
conf_get("formatter", Conf) conf_get("formatter", Conf)
end, end,
TsFormat = timstamp_format(Conf), TsFormat = timestamp_format(Conf),
WithMfa = conf_get("with_mfa", Conf),
do_formatter( do_formatter(
Format, CharsLimit, SingleLine, TimeOffSet, Depth, TsFormat Format, CharsLimit, SingleLine, TimeOffSet, Depth, TsFormat, WithMfa
). ).
%% auto | epoch | rfc3339 %% auto | epoch | rfc3339
timstamp_format(Conf) -> timestamp_format(Conf) ->
conf_get("timestamp_format", Conf). conf_get("timestamp_format", Conf).
%% helpers %% helpers
do_formatter(json, CharsLimit, SingleLine, TimeOffSet, Depth, TsFormat) -> do_formatter(json, CharsLimit, SingleLine, TimeOffSet, Depth, TsFormat, WithMfa) ->
{emqx_logger_jsonfmt, #{ {emqx_logger_jsonfmt, #{
chars_limit => CharsLimit, chars_limit => CharsLimit,
single_line => SingleLine, single_line => SingleLine,
time_offset => TimeOffSet, time_offset => TimeOffSet,
depth => Depth, depth => Depth,
timestamp_format => TsFormat timestamp_format => TsFormat,
with_mfa => WithMfa
}}; }};
do_formatter(text, CharsLimit, SingleLine, TimeOffSet, Depth, TsFormat) -> do_formatter(text, CharsLimit, SingleLine, TimeOffSet, Depth, TsFormat, WithMfa) ->
{emqx_logger_textfmt, #{ {emqx_logger_textfmt, #{
template => ["[", level, "] ", msg, "\n"], template => ["[", level, "] ", msg, "\n"],
chars_limit => CharsLimit, chars_limit => CharsLimit,
single_line => SingleLine, single_line => SingleLine,
time_offset => TimeOffSet, time_offset => TimeOffSet,
depth => Depth, depth => Depth,
timestamp_format => TsFormat timestamp_format => TsFormat,
with_mfa => WithMfa
}}. }}.
%% Don't record all logger message %% Don't record all logger message

View File

@ -292,6 +292,7 @@ json_obj_root(Data0, Config) ->
_ -> _ ->
json(Msg1, Config) json(Msg1, Config)
end, end,
MFA = emqx_utils:format_mfal(Data0, Config),
Data = Data =
maps:fold( maps:fold(
fun(K, V, D) -> fun(K, V, D) ->
@ -300,12 +301,12 @@ json_obj_root(Data0, Config) ->
end, end,
[], [],
maps:without( maps:without(
[time, gl, file, report_cb, msg, '$kind', level, is_trace], Data0 [time, gl, file, report_cb, msg, '$kind', level, mfa, is_trace], Data0
) )
), ),
lists:filter( lists:filter(
fun({_, V}) -> V =/= undefined end, fun({_, V}) -> V =/= undefined end,
[{time, format_ts(Time, Config)}, {level, Level}, {msg, Msg}] [{time, format_ts(Time, Config)}, {level, Level}, {msg, Msg}, {mfa, MFA}]
) ++ Data. ) ++ Data.
format_ts(Ts, #{timestamp_format := rfc3339, time_offset := Offset}) when is_integer(Ts) -> format_ts(Ts, #{timestamp_format := rfc3339, time_offset := Offset}) when is_integer(Ts) ->

View File

@ -24,7 +24,7 @@
%% Used in the other log formatters %% Used in the other log formatters
-export([evaluate_lazy_values_if_dbg_level/1, evaluate_lazy_values/1]). -export([evaluate_lazy_values_if_dbg_level/1, evaluate_lazy_values/1]).
check_config(X) -> logger_formatter:check_config(maps:without([timestamp_format], X)). check_config(X) -> logger_formatter:check_config(maps:without([timestamp_format, with_mfa], X)).
%% Principle here is to delegate the formatting to logger_formatter:format/2 %% Principle here is to delegate the formatting to logger_formatter:format/2
%% as much as possible, and only enrich the report with clientid, peername, topic, username %% as much as possible, and only enrich the report with clientid, peername, topic, username
@ -32,7 +32,7 @@ format(#{msg := {report, ReportMap0}, meta := _Meta} = Event0, Config) when is_m
#{msg := {report, ReportMap}, meta := Meta} = Event = evaluate_lazy_values_if_dbg_level(Event0), #{msg := {report, ReportMap}, meta := Meta} = Event = evaluate_lazy_values_if_dbg_level(Event0),
%% The most common case, when entering from SLOG macro %% The most common case, when entering from SLOG macro
%% i.e. logger:log(Level, #{msg => "my_msg", foo => bar}) %% i.e. logger:log(Level, #{msg => "my_msg", foo => bar})
ReportList = enrich_report(ReportMap, Meta), ReportList = enrich_report(ReportMap, Meta, Config),
Report = Report =
case is_list_report_acceptable(Meta) of case is_list_report_acceptable(Meta) of
true -> true ->
@ -40,18 +40,24 @@ format(#{msg := {report, ReportMap0}, meta := _Meta} = Event0, Config) when is_m
false -> false ->
maps:from_list(ReportList) maps:from_list(ReportList)
end, end,
fmt(Event#{msg := {report, Report}}, Config); fmt(Event#{msg := {report, Report}}, maps:remove(with_mfa, Config));
format(#{msg := {string, String}} = Event, Config) -> format(#{msg := {string, String}} = Event, Config) ->
%% copied from logger_formatter:format/2 %% copied from logger_formatter:format/2
%% unsure how this case is triggered %% unsure how this case is triggered
format(Event#{msg => {"~ts ", [String]}}, Config); format(Event#{msg => {"~ts ", [String]}}, maps:remove(with_mfa, Config));
format(#{msg := _Msg, meta := _Meta} = Event0, Config) -> format(#{msg := _Msg, meta := _Meta} = Event0, Config) ->
#{msg := Msg0, meta := Meta} = Event1 = evaluate_lazy_values_if_dbg_level(Event0), #{msg := Msg0, meta := Meta} = Event1 = evaluate_lazy_values_if_dbg_level(Event0),
%% For format strings like logger:log(Level, "~p", [Var]) %% For format strings like logger:log(Level, "~p", [Var])
%% and logger:log(Level, "message", #{key => value}) %% and logger:log(Level, "message", #{key => value})
Msg1 = enrich_client_info(Msg0, Meta), Msg1 = enrich_client_info(Msg0, Meta),
Msg2 = enrich_topic(Msg1, Meta), Msg2 = enrich_mfa(Msg1, Meta, Config),
fmt(Event1#{msg := Msg2}, Config). Msg3 = enrich_topic(Msg2, Meta),
fmt(Event1#{msg := Msg3}, maps:remove(with_mfa, Config)).
enrich_mfa({Fmt, Args}, Data, #{with_mfa := true} = Config) when is_list(Fmt) ->
{Fmt ++ " mfa: ~ts", Args ++ [emqx_utils:format_mfal(Data, Config)]};
enrich_mfa(Msg, _, _) ->
Msg.
%% Most log entries with lazy values are trace events with level debug. So to %% Most log entries with lazy values are trace events with level debug. So to
%% be more efficient we only search for lazy values to evaluate in the entries %% be more efficient we only search for lazy values to evaluate in the entries
@ -101,7 +107,7 @@ is_list_report_acceptable(#{report_cb := Cb}) ->
is_list_report_acceptable(_) -> is_list_report_acceptable(_) ->
false. false.
enrich_report(ReportRaw, Meta) -> enrich_report(ReportRaw, Meta, Config) ->
%% clientid and peername always in emqx_conn's process metadata. %% clientid and peername always in emqx_conn's process metadata.
%% topic and username can be put in meta using ?SLOG/3, or put in msg's report by ?SLOG/2 %% topic and username can be put in meta using ?SLOG/3, or put in msg's report by ?SLOG/2
Topic = Topic =
@ -117,6 +123,7 @@ enrich_report(ReportRaw, Meta) ->
ClientId = maps:get(clientid, Meta, undefined), ClientId = maps:get(clientid, Meta, undefined),
Peer = maps:get(peername, Meta, undefined), Peer = maps:get(peername, Meta, undefined),
Msg = maps:get(msg, ReportRaw, undefined), Msg = maps:get(msg, ReportRaw, undefined),
MFA = emqx_utils:format_mfal(Meta, Config),
%% TODO: move all tags to Meta so we can filter traces %% TODO: move all tags to Meta so we can filter traces
%% based on tags (currently not supported) %% based on tags (currently not supported)
Tag = maps:get(tag, ReportRaw, maps:get(tag, Meta, undefined)), Tag = maps:get(tag, ReportRaw, maps:get(tag, Meta, undefined)),
@ -131,6 +138,7 @@ enrich_report(ReportRaw, Meta) ->
{topic, try_format_unicode(Topic)}, {topic, try_format_unicode(Topic)},
{username, try_format_unicode(Username)}, {username, try_format_unicode(Username)},
{peername, Peer}, {peername, Peer},
{mfa, try_format_unicode(MFA)},
{msg, Msg}, {msg, Msg},
{clientid, try_format_unicode(ClientId)}, {clientid, try_format_unicode(ClientId)},
{tag, Tag} {tag, Tag}

View File

@ -399,7 +399,6 @@ compat_windows(Fun) when is_function(Fun, 0) ->
0.0 0.0
end; end;
compat_windows(Fun) -> compat_windows(Fun) ->
?SLOG(warning, "Invalid function: ~p", [Fun]),
error({badarg, Fun}). error({badarg, Fun}).
load(Avg) -> load(Avg) ->

View File

@ -1247,7 +1247,7 @@ tr_cluster_discovery(Conf) ->
log_handler_common_confs(Handler, Default) -> log_handler_common_confs(Handler, Default) ->
%% We rarely support dynamic defaults like this. %% We rarely support dynamic defaults like this.
%% For this one, we have build-time default the same as runtime default so it's less tricky %% For this one, we have build-time default the same as runtime default so it's less tricky
%% Buildtime default: "" (which is the same as "file") %% Build time default: "" (which is the same as "file")
%% Runtime default: "file" (because .service file sets EMQX_DEFAULT_LOG_HANDLER to "file") %% Runtime default: "file" (because .service file sets EMQX_DEFAULT_LOG_HANDLER to "file")
EnableValues = EnableValues =
case Handler of case Handler of
@ -1369,6 +1369,15 @@ log_handler_common_confs(Handler, Default) ->
desc => ?DESC("common_handler_max_depth"), desc => ?DESC("common_handler_max_depth"),
importance => ?IMPORTANCE_HIDDEN importance => ?IMPORTANCE_HIDDEN
} }
)},
{"with_mfa",
sc(
boolean(),
#{
default => false,
desc => <<"Recording MFA and line in the log(usefully).">>,
importance => ?IMPORTANCE_HIDDEN
}
)} )}
]. ].

View File

@ -133,7 +133,8 @@ log.file_handlers {
single_line => true, single_line => true,
template => ["[", level, "] ", msg, "\n"], template => ["[", level, "] ", msg, "\n"],
time_offset => TimeOffset, time_offset => TimeOffset,
timestamp_format => auto timestamp_format => auto,
with_mfa => false
}} }}
). ).

View File

@ -65,6 +65,7 @@
flattermap/2, flattermap/2,
tcp_keepalive_opts/4, tcp_keepalive_opts/4,
format/1, format/1,
format_mfal/2,
call_first_defined/1, call_first_defined/1,
ntoa/1, ntoa/1,
foldl_while/3, foldl_while/3,
@ -565,6 +566,32 @@ tcp_keepalive_opts(OS, _Idle, _Interval, _Probes) ->
format(Term) -> format(Term) ->
iolist_to_binary(io_lib:format("~0p", [Term])). iolist_to_binary(io_lib:format("~0p", [Term])).
%% @doc Helper function for log formatters.
-spec format_mfal(map(), map()) -> undefined | binary().
format_mfal(Data, #{with_mfa := true}) ->
Line =
case maps:get(line, Data, undefined) of
undefined ->
<<"">>;
Num ->
["(", integer_to_list(Num), ")"]
end,
case maps:get(mfa, Data, undefined) of
{M, F, A} ->
iolist_to_binary([
atom_to_binary(M, utf8),
$:,
atom_to_binary(F, utf8),
$/,
integer_to_binary(A),
Line
]);
_ ->
undefined
end;
format_mfal(_, _) ->
undefined.
-spec call_first_defined(list({module(), atom(), list()})) -> term() | no_return(). -spec call_first_defined(list({module(), atom(), list()})) -> term() | no_return().
call_first_defined([{Module, Function, Args} | Rest]) -> call_first_defined([{Module, Function, Args} | Rest]) ->
try try