./ct_report/coverage/mongoose_json_formatter.COVER.html

1 %%%-------------------------------------------------------------------
2 %%% @copyright 2020, Erlang Solutions Ltd.
3 %%% @doc
4 %%% A module formatting logger reports into JSON objects.
5 %%%
6 %%% @end
7 %%%-------------------------------------------------------------------
8 -module(mongoose_json_formatter).
9
10 %% API
11 -export([format/2]).
12
13 -ignore_xref([format/2]).
14
15 -spec format(logger:log_event(), logger:formatter_config()) -> unicode:chardata().
16 format(Event, FConfig) ->
17 7813 try do_format(Event, FConfig)
18 catch
19 %% Errors during log formatting can lead to a death spiral of recursive error logging, so
20 %% format the formatter error in a safe way and don't allow the exception to propagate.
21
:-(
error:Reason:Stacktrace -> format_log_formatter_error(error, Reason, Stacktrace, Event, FConfig)
22 end.
23
24
25 format_log_formatter_error(Class, Reason, Stacktrace, #{meta := #{time := Time}} = Event, FConfig) ->
26
:-(
IoData = jiffy:encode(
27 #{
28 'when' => format_time(Time),
29 level => error,
30 what => log_format_failed,
31 class => Class,
32 reason => Reason,
33 stacktrace => format_item(Stacktrace, FConfig),
34 formatter_module => ?MODULE,
35 original_event => unicode:characters_to_binary(io_lib:format("~0p", [Event]))
36 }
37 ),
38
:-(
[IoData, <<"\n">>].
39
40
41 -spec do_format(logger:log_event(), logger:formatter_config()) -> unicode:chardata().
42 do_format(E = #{msg := {report, #{label := {error_logger, _}, format := Format, args := Terms}}}, FConfig) ->
43 65 do_format(E#{msg := {report,
44 #{unstructured_log =>
45 unicode:characters_to_binary(io_lib:format(Format, Terms))}}},
46 FConfig);
47 do_format(E = #{msg := {string, String}}, FConfig) ->
48
:-(
do_format(E#{msg := {report,
49 #{unstructured_log =>
50 unicode:characters_to_binary(io_lib:format(String, []))}}}, FConfig);
51 do_format(E = #{msg := {report, Report}}, FConfig) when is_map(Report) ->
52 7813 NewMap = process_metadata(E),
53 7813 NewReport = maps:merge(Report, NewMap),
54 7813 NewConfig = maps:merge(default_config(), config_correct_depth(FConfig)),
55 7813 Formatted = format_item(NewReport, NewConfig),
56 7813 IoData = jiffy:encode(Formatted),
57 7813 [IoData, <<"\n">>];
58 do_format(Map = #{msg := {Format, Terms}}, FConfig) ->
59 612 do_format(Map#{msg := {report,
60 #{unstructured_log =>
61 unicode:characters_to_binary(io_lib:format(Format, Terms))}}},
62 FConfig).
63
64
65 format_item(_Item, _FConfig = #{depth := 0}) ->
66
:-(
<<"...">>;
67 format_item(Item, FConfig = #{depth := D}) when is_map(Item) ->
68 17918 ML = [{all_to_binary(Key, FConfig),
69 17918 format_item(Val, FConfig#{depth := D - 1})} || {Key, Val} <- maps:to_list(Item)],
70 17918 maps:from_list(ML);
71 format_item(Item, FConfig = #{depth := Depth}) when is_list(Item) ->
72 15991 case io_lib:printable_unicode_list(Item) of
73 true ->
74 % We want to print strings as strings
75 12180 format_str(Item, FConfig);
76 false ->
77 % And pass lists of objects as lists of objects
78 3811 [format_item(I, FConfig#{depth := Depth - 1}) || I <- Item]
79 end;
80 format_item(Item, FConfig) ->
81 87061 all_to_binary(Item, FConfig).
82
83 format_time(T) ->
84 7813 TimeString = calendar:system_time_to_rfc3339(T, [{unit, microsecond}]),
85 7813 unicode:characters_to_binary(TimeString).
86
87 all_to_binary(Full, FConfig) when is_binary(Full) ->
88 23155 Short = shorten_binary(Full, FConfig),
89 23155 ShortUnicode = unicode:characters_to_binary(Short, utf8, utf8),
90 23155 FullUnicode = unicode:characters_to_binary(Short, utf8, utf8),
91 23155 case {ShortUnicode, FullUnicode} of
92 23134 {<<_/binary>>, <<_/binary>>} -> ShortUnicode;
93
:-(
{{incomplete,Incomplete,_}, <<_/binary>>} -> Incomplete;
94 21 _ -> format_non_unicode(Full, FConfig)
95 end;
96 all_to_binary(Something, FConfig) ->
97 165590 format_non_unicode(Something, FConfig).
98
99 format_non_unicode(Something, FConfig) ->
100 165611 Chars = format_str(Something, FConfig),
101 165611 unicode:characters_to_binary(Chars, utf8).
102
103 shorten_binary(S, #{format_chars_limit := unlimited}) ->
104
:-(
S;
105 shorten_binary(S, #{format_chars_limit := L}) ->
106 160152 binary_part(S, 0, min(size(S), L)).
107
108
:-(
format_chars_limit_to_opts(unlimited) -> [];
109 40794 format_chars_limit_to_opts(CharsLimit) -> [{chars_limit, CharsLimit}].
110
111 format_str(S, FConfig) when is_list(S) ->
112 136997 case io_lib:printable_unicode_list(S) of
113 true ->
114 136997 B = unicode:characters_to_binary(S, utf8),
115 136997 shorten_binary(B, FConfig);
116 false ->
117
:-(
do_format_str(S, FConfig)
118 end;
119 format_str(S, FConfig) when is_atom(S) ->
120 124817 format_str(atom_to_list(S), FConfig);
121 format_str(S, FConfig) ->
122 40794 do_format_str(S, FConfig).
123
124 do_format_str(S, #{format_depth := unlimited, format_chars_limit := L}) ->
125
:-(
io_lib:format("~0tp", [S], format_chars_limit_to_opts(L));
126 do_format_str(S, #{format_depth := D, format_chars_limit := L}) ->
127 40794 io_lib:format("~0tP", [S, D], format_chars_limit_to_opts(L)).
128
129 process_metadata(#{meta := #{time := T} = M, level := L}) ->
130 7813 DiscardKeys = [time, gl, report_cb],
131 7813 #{level => L,
132 'when' => format_time(T),
133 meta => maps:without(DiscardKeys, M)}.
134
135 config_correct_depth(C = #{depth := unlimited}) ->
136
:-(
C#{depth := -1};
137 config_correct_depth(C) ->
138 7813 C.
139
140 default_config() ->
141 7813 #{format_chars_limit => unlimited,
142 format_depth => unlimited,
143 depth => -1
144 }.
Line Hits Source