1%% This Source Code Form is subject to the terms of the Mozilla Public 2%% License, v. 2.0. If a copy of the MPL was not distributed with this 3%% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4%% 5%% Copyright (c) 2021 VMware, Inc. or its affiliates. All rights reserved. 6%% 7 8-module(rabbit_logger_json_fmt). 9 10-export([format/2]). 11 12format( 13 #{msg := Msg, 14 level := Level, 15 meta := Meta}, 16 Config) -> 17 FormattedLevel = unicode:characters_to_binary( 18 rabbit_logger_fmt_helpers:format_level(Level, Config)), 19 FormattedMeta = format_meta(Meta, Config), 20 %% We need to call `unicode:characters_to_binary()' here and several other 21 %% places because `jsx:encode()' will format a string as a list of 22 %% integers (we don't blame it for that, it makes sense). 23 FormattedMsg = unicode:characters_to_binary( 24 rabbit_logger_fmt_helpers:format_msg(Msg, Meta, Config)), 25 InitialDoc0 = FormattedMeta#{level => FormattedLevel, 26 msg => FormattedMsg}, 27 InitialDoc = case level_to_verbosity(Level, Config) of 28 undefined -> InitialDoc0; 29 Verbosity -> InitialDoc0#{verbosity => Verbosity} 30 end, 31 DocAfterMapping = apply_mapping_and_ordering(InitialDoc, Config), 32 Json = jsx:encode(DocAfterMapping), 33 [Json, $\n]. 34 35level_to_verbosity(Level, #{verbosity_map := Mapping}) -> 36 case maps:is_key(Level, Mapping) of 37 true -> maps:get(Level, Mapping); 38 false -> undefined 39 end; 40level_to_verbosity(_, _) -> 41 undefined. 42 43format_meta(Meta, Config) -> 44 maps:fold( 45 fun 46 (time, Timestamp, Acc) -> 47 FormattedTime0 = rabbit_logger_fmt_helpers:format_time( 48 Timestamp, Config), 49 FormattedTime1 = case is_number(FormattedTime0) of 50 true -> FormattedTime0; 51 false -> unicode:characters_to_binary( 52 FormattedTime0) 53 end, 54 Acc#{time => FormattedTime1}; 55 (domain = Key, Components, Acc) -> 56 Term = unicode:characters_to_binary( 57 string:join( 58 [atom_to_list(Cmp) || Cmp <- Components], 59 ".")), 60 Acc#{Key => Term}; 61 (Key, Value, Acc) -> 62 case convert_to_types_accepted_by_jsx(Value) of 63 false -> Acc; 64 Term -> Acc#{Key => Term} 65 end 66 end, #{}, Meta). 67 68convert_to_types_accepted_by_jsx(Term) when is_map(Term) -> 69 maps:map( 70 fun(_, Value) -> convert_to_types_accepted_by_jsx(Value) end, 71 Term); 72convert_to_types_accepted_by_jsx(Term) when is_list(Term) -> 73 case io_lib:deep_char_list(Term) of 74 true -> 75 unicode:characters_to_binary(Term); 76 false -> 77 [convert_to_types_accepted_by_jsx(E) || E <- Term] 78 end; 79convert_to_types_accepted_by_jsx(Term) when is_tuple(Term) -> 80 convert_to_types_accepted_by_jsx(erlang:tuple_to_list(Term)); 81convert_to_types_accepted_by_jsx(Term) when is_function(Term) -> 82 String = erlang:fun_to_list(Term), 83 unicode:characters_to_binary(String); 84convert_to_types_accepted_by_jsx(Term) when is_pid(Term) -> 85 String = erlang:pid_to_list(Term), 86 unicode:characters_to_binary(String); 87convert_to_types_accepted_by_jsx(Term) when is_port(Term) -> 88 String = erlang:port_to_list(Term), 89 unicode:characters_to_binary(String); 90convert_to_types_accepted_by_jsx(Term) when is_reference(Term) -> 91 String = erlang:ref_to_list(Term), 92 unicode:characters_to_binary(String); 93convert_to_types_accepted_by_jsx(Term) -> 94 Term. 95 96apply_mapping_and_ordering(Doc, #{field_map := Mapping}) -> 97 apply_mapping_and_ordering(Mapping, Doc, []); 98apply_mapping_and_ordering(Doc, _) -> 99 maps:to_list(Doc). 100 101apply_mapping_and_ordering([{'$REST', false} | Rest], _, Result) -> 102 apply_mapping_and_ordering(Rest, #{}, Result); 103apply_mapping_and_ordering([{Old, false} | Rest], Doc, Result) 104 when is_atom(Old) -> 105 Doc1 = maps:remove(Old, Doc), 106 apply_mapping_and_ordering(Rest, Doc1, Result); 107apply_mapping_and_ordering([{Old, New} | Rest], Doc, Result) 108 when is_atom(Old) andalso is_atom(New) -> 109 case maps:is_key(Old, Doc) of 110 true -> 111 Value = maps:get(Old, Doc), 112 Doc1 = maps:remove(Old, Doc), 113 Result1 = [{New, Value} | Result], 114 apply_mapping_and_ordering(Rest, Doc1, Result1); 115 false -> 116 apply_mapping_and_ordering(Rest, Doc, Result) 117 end; 118apply_mapping_and_ordering([], Doc, Result) -> 119 lists:reverse(Result) ++ maps:to_list(Doc). 120