Current section
Files
Jump to
Current section
Files
src/jsonlog.erl
-module(jsonlog).
-export([format/2]).
-type template() :: [metakey() | {metakey(), template(), template()} | string()].
-type metakey() :: atom() | [atom()].
%%====================================================================
%% API functions
%%====================================================================
-spec format(LogEvent, Config) -> unicode:chardata() when
LogEvent :: logger:log_event(),
Config :: logger:formatter_config().
format(Map = #{msg := {report, #{label := {error_logger, _}, format := Format, args := Terms}}}, UsrConfig) ->
format(Map#{msg := {report,
#{body =>
unicode:characters_to_binary(io_lib:format(Format, Terms))}}},
UsrConfig);
format(#{level := Level, msg := {report, Msg}, meta := Meta}, UserConfig) when is_map(Msg) ->
Config = apply_defaults(UserConfig),
NewMeta = maps:merge(Meta, #{level => Level}),
format_log(maps:get(template, Config), Config, Msg, NewMeta);
format(Map = #{msg := {string, String}}, UsrConfig) ->
format(Map#{msg := {report,
#{body =>
unicode:characters_to_binary(String)}}}, UsrConfig);
format(Map = #{msg := {Format, Terms}}, UserConfig) ->
format(Map#{msg := {report,
#{body =>
unicode:characters_to_binary(io_lib:format(Format, Terms))}}},
UserConfig).
-spec format_log(template(), Config, Msg, Meta) -> unicode:chardata() when
Config :: logger:formatter_config(),
Msg :: Data,
Meta :: Data,
Data :: #{string() | binary() | atom() => term()}.
format_log(Tpl, Config=#{json_encode := JsonEncode}, Msg, Meta) ->
%% need `native_forward_slash' to not get like `mod:fun\/1' for mfa
[JsonEncode(format_log(Tpl, Config, Msg, Meta, #{}), Config), "\n"].
format_log([], _Config, _Msg, _Meta, Acc) ->
Acc;
format_log([{JsonKey, msg} | Rest], Config, Msg, Meta, Acc) ->
format_log(Rest, Config, Msg, Meta, Acc#{JsonKey => format_msg(Msg, Config)});
format_log([msg | Rest], Config, Msg, Meta, Acc) ->
format_log(Rest, Config, Msg, Meta, maps:merge(Acc, format_msg(Msg, Config)));
format_log([{JsonKey, TemplateKey} | Rest], Config, Msg, Meta, Acc) when is_atom(TemplateKey) ->
case maps:find(TemplateKey, Meta) of
error ->
format_log(Rest, Config, Msg, Meta, Acc);
{ok, Val} ->
format_log(Rest, Config, Msg, Meta, Acc#{JsonKey => format_val(TemplateKey, Val, Config)})
end;
format_log([TemplateKey | Rest], Config, Msg, Meta, Acc) ->
case maps:find(TemplateKey, Meta) of
error ->
format_log(Rest, Config, Msg, Meta, Acc);
{ok, Val} ->
format_log(Rest, Config, Msg, Meta, Acc#{TemplateKey => format_val(TemplateKey, Val, Config)})
end.
%%
apply_defaults(Map) ->
maps:merge(
#{time_offset => 0,
time_designator => $T,
template => [{time, time},
{level, level},
{pid, pid},
{in, mfa},
{line, line},
{body, msg}]},
Map).
format_msg(Data, Config) ->
format_msg("", Data, Config).
format_msg(_Parents, Data, _Config) ->
Data.
format_val(time, Time, Config) ->
format_time(Time, Config);
format_val(mfa, MFA, Config) ->
format_mfa(MFA, Config);
format_val(_Key, Val, _Config) ->
jsonlog_utils:to_string(Val).
format_time(N, #{time_offset := O, time_designator := D}) when is_integer(N) ->
list_to_binary(calendar:system_time_to_rfc3339(N, [{unit, microsecond},
{offset, O},
{time_designator, D}])).
format_mfa({M, F, A}, _) when is_atom(M), is_atom(F), is_integer(A) ->
<<(atom_to_binary(M, utf8))/binary, $:, (atom_to_binary(F, utf8))/binary, $/, (integer_to_binary(A))/binary>>;
format_mfa({M, F, A}, Config) when is_atom(M), is_atom(F), is_list(A) ->
%% arguments are passed as a literal list ({mod, fun, [a, b, c]})
format_mfa({M, F, length(A)}, Config);
format_mfa(MFAStr, _Config) -> % passing in a pre-formatted string value
jsonlog_utils:to_string(MFAStr).