diff options
-rw-r--r-- | lib/kernel/doc/src/logger_formatter.xml | 14 | ||||
-rw-r--r-- | lib/kernel/src/logger_formatter.erl | 102 | ||||
-rw-r--r-- | lib/kernel/test/logger_formatter_SUITE.erl | 63 |
3 files changed, 142 insertions, 37 deletions
diff --git a/lib/kernel/doc/src/logger_formatter.xml b/lib/kernel/doc/src/logger_formatter.xml index 213a592e47..6a17e3641f 100644 --- a/lib/kernel/doc/src/logger_formatter.xml +++ b/lib/kernel/doc/src/logger_formatter.xml @@ -77,13 +77,21 @@ rewritten. The format controls ~p and ~w are replaced with ~P and ~W, respectively, and the value is used as the depth parameter. For details, see - <seealso marker="stdlib:io#format-2">io:format/2</seealso> + <seealso marker="stdlib:io#format-2">io:format/2,3</seealso> in STDLIB.</p> + <p><c>chars_limit</c> is a positive integer representing the + value of the option with the same name to be used when calling + <seealso marker="stdlib:io#format-3">io:format/3</seealso>. This + value limits the total number of characters printed bu the + formatter. Notes that this is a soft limit. For a hard + truncation limit, see option <c>max_size</c>.</p> + <p><c>max_size</c> is a positive integer representing the maximum size a string returned from this formatter can - have. If the formatted string is longer, it will be - truncated.</p> + have. If the formatted string is longer, after possibly + being limited by <c>depth</c> and/or <c>chars_limit</c>, it + will be truncated.</p> <p><c>utc</c> is a boolean. If set to true, all dates are displayed in Universal Coordinated Time. Default diff --git a/lib/kernel/src/logger_formatter.erl b/lib/kernel/src/logger_formatter.erl index 91283ab299..386e7832e2 100644 --- a/lib/kernel/src/logger_formatter.erl +++ b/lib/kernel/src/logger_formatter.erl @@ -34,6 +34,7 @@ Config :: #{single_line=>boolean(), legacy_header=>boolean(), report_cb=>fun((logger:report()) -> {io:format(),[term()]}), + chars_limit=>pos_integer()| unlimited, max_size=>pos_integer() | unlimited, depth=>pos_integer() | unlimited, template=>template(), @@ -43,19 +44,43 @@ format(#{level:=Level,msg:=Msg0,meta:=Meta},Config0) when is_map(Config0) -> Config = add_default_config(Config0), Meta1 = maybe_add_legacy_header(Level,Meta,Config), - MsgStr0 = format_msg(Msg0,Meta1,Config), + Template = maps:get(template,Config), + {BT,AT0} = lists:splitwith(fun(msg) -> false; (_) -> true end, Template), + {DoMsg,AT} = + case AT0 of + [msg|Rest] -> {true,Rest}; + _ ->{false,AT0} + end, + B = do_format(Level,"",Meta1,BT,Config), + A = do_format(Level,"",Meta1,AT,Config), MsgStr = - case maps:get(single_line,Config) of - true -> - %% Trim leading and trailing whitespaces, and replace - %% newlines with ", " - re:replace(string:trim(MsgStr0),",?\r?\n\s*",", ", - [{return,list},global]); - _false2 -> - MsgStr0 + if DoMsg -> + Config1 = + case maps:get(chars_limit,Config) of + unlimited -> + Config; + Size0 -> + Size = + case Size0 - string:length([B,A]) of + S when S>=0 -> S; + _ -> 0 + end, + Config#{chars_limit=>Size} + end, + MsgStr0 = format_msg(Msg0,Meta1,Config1), + case maps:get(single_line,Config) of + true -> + %% Trim leading and trailing whitespaces, and replace + %% newlines with ", " + re:replace(string:trim(MsgStr0),",?\r?\n\s*",", ", + [{return,list},global]); + _false -> + MsgStr0 + end; + true -> + "" end, - String = do_format(Level,MsgStr,Meta1,maps:get(template,Config),Config), - truncate(String,maps:get(max_size,Config)). + truncate(B ++ MsgStr ++ A,maps:get(max_size,Config)). do_format(Level,Msg,Data,[level|Format],Config) -> [to_string(level,Level,Config)|do_format(Level,Msg,Data,Format,Config)]; @@ -124,24 +149,25 @@ format_msg({report,Report},Meta,Config) -> format_msg({report,Report}, Meta#{report_cb=>fun logger:format_report/1}, Config); -format_msg(Msg,_Meta,Config) -> - limit_depth(Msg, maps:get(depth,Config)). - -limit_depth(Msg,false) -> - Depth = logger:get_format_depth(), - limit_depth(Msg,Depth); -limit_depth({Format,Args},unlimited) -> - try io_lib:format(Format,Args) +format_msg(Msg,_Meta,#{depth:=Depth,chars_limit:=CharsLimit}) -> + limit_size(Msg, Depth, CharsLimit). + +limit_size(Msg,Depth,unlimited) -> + limit_size(Msg,Depth,[]); +limit_size(Msg,Depth,CharsLimit) when is_integer(CharsLimit) -> + limit_size(Msg,Depth,[{chars_limit,CharsLimit}]); +limit_size({Format,Args},unlimited,Opts) when is_list(Opts) -> + try io_lib:format(Format,Args,Opts) catch _:_ -> - io_lib:format("FORMAT ERROR: ~tp - ~tp",[Format,Args]) + io_lib:format("FORMAT ERROR: ~tp - ~tp",[Format,Args],Opts) end; -limit_depth({Format0,Args},Depth) -> +limit_size({Format0,Args},Depth,Opts) when is_integer(Depth) -> try Format1 = io_lib:scan_format(Format0, Args), Format = limit_format(Format1, Depth), - io_lib:build_text(Format) + io_lib:build_text(Format,Opts) catch _:_ -> - limit_depth({"FORMAT ERROR: ~tp - ~tp",[Format0,Args]},Depth) + limit_size({"FORMAT ERROR: ~tp - ~tp",[Format0,Args]},Depth,Opts) end. limit_format([#{control_char:=C0}=M0|T], Depth) when C0 =:= $p; @@ -157,13 +183,15 @@ limit_format([], _) -> truncate(String,unlimited) -> String; -truncate(String,false) -> - Size = logger:get_max_size(), - truncate(String,Size); truncate(String,Size) -> Length = string:length(String), if Length>Size -> - string:slice(String,0,Size-3)++"..."; + case lists:reverse(lists:flatten(String)) of + [$\n|_] -> + string:slice(String,0,Size-4)++"...\n"; + _ -> + string:slice(String,0,Size-3)++"..." + end; true -> String end. @@ -232,13 +260,15 @@ month(12) -> "Dec". utcstr(#{utc:=true}) -> "UTC "; utcstr(_) -> "". -add_default_config(#{utc:=_}=Config) -> +add_default_config(#{utc:=_}=Config0) -> Default = #{legacy_header=>false, single_line=>false, - max_size=>false, - depth=>false}, - add_default_template(maps:merge(Default,Config)); + chars_limit=>unlimited}, + MaxSize = get_max_size(maps:get(max_size,Config0,false)), + Depth = get_depth(maps:get(depth,Config0,false)), + add_default_template(maps:merge(Default,Config0#{max_size=>MaxSize, + depth=>Depth})); add_default_config(Config) -> add_default_config(Config#{utc=>logger:get_utc_config()}). @@ -253,3 +283,13 @@ default_template(#{single_line:=true}) -> ?DEFAULT_FORMAT_TEMPLATE_SINGLE; default_template(_) -> ?DEFAULT_FORMAT_TEMPLATE. + +get_max_size(false) -> + logger:get_max_size(); +get_max_size(S) -> + max(10,S). + +get_depth(false) -> + logger:get_format_depth(); +get_depth(S) -> + max(5,S). diff --git a/lib/kernel/test/logger_formatter_SUITE.erl b/lib/kernel/test/logger_formatter_SUITE.erl index 44e12d2d2a..ac1abba629 100644 --- a/lib/kernel/test/logger_formatter_SUITE.erl +++ b/lib/kernel/test/logger_formatter_SUITE.erl @@ -62,6 +62,7 @@ all() -> report_cb, max_size, depth, + chars_limit, format_mfa, format_time, level_or_msg_in_meta, @@ -307,12 +308,19 @@ max_size(_Config) -> []}, #{}, #{template=>Template}), - "1234567..." = + "123456789012..." = format(info,{"12345678901234567890",[]},#{},#{template=>Template, - max_size=>10}), + max_size=>15}), "12345678901234567890" = format(info,{"12345678901234567890",[]},#{},#{template=>Template, max_size=>unlimited}), + %% Check that one newline at the end of the line is kept (if it exists) + "12345678901...\n" = + format(info,{"12345678901234567890\n",[]},#{},#{template=>Template, + max_size=>15}), + "12345678901...\n" = + format(info,{"12345678901234567890",[]},#{},#{template=>[msg,"\n"], + max_size=>15}), ok. max_size(cleanup,_Config) -> application:unset_env(kernel,logger_max_size), @@ -354,6 +362,55 @@ depth(cleanup,_Config) -> application:unset_env(kernel,logger_format_depth), ok. +chars_limit(_Config) -> + FA = {"LoL: ~p~nL: ~p~nMap: ~p~n", + [lists:duplicate(10,lists:seq(1,100)), + lists:seq(1,100), + maps:from_list(lists:zip(lists:seq(1,100), + lists:duplicate(100,value)))]}, + Meta = #{time=>"2018-04-26 9:15:40.449879"}, + Template = [time," - ", msg, "\n"], + FC = #{template=>Template, + depth=>unlimited, + max_size=>unlimited, + chars_limit=>unlimited, + single_line=>true}, + CL1 = 80, + String1 = format(info,FA,Meta,FC#{chars_limit=>CL1}), + L1 = string:length(String1), + ct:log("String1: ~p~nLength1: ~p~n",[lists:flatten(String1),L1]), + true = L1 > CL1, + true = L1 < CL1 + 10, + + String2 = format(info,FA,Meta,FC#{chars_limit=>CL1,depth=>10}), + L2 = string:length(String2), + ct:log("String2: ~p~nLength2: ~p~n",[lists:flatten(String2),L2]), + String2 = String1, + + CL3 = 200, + String3 = format(info,FA,Meta,FC#{chars_limit=>CL3}), + L3 = string:length(String3), + ct:log("String3: ~p~nLength3: ~p~n",[lists:flatten(String3),L3]), + true = L3 > CL3, + true = L3 < CL3 + 10, + + String4 = format(info,FA,Meta,FC#{chars_limit=>CL3,depth=>10}), + L4 = string:length(String4), + ct:log("String4: ~p~nLength4: ~p~n",[lists:flatten(String4),L4]), + true = L4 > CL3, + true = L4 < CL3 + 10, + + %% Test that max_size truncates the string which is limited by + %% depth and chars_limit + MS5 = 150, + String5 = format(info,FA,Meta,FC#{chars_limit=>CL3,depth=>10,max_size=>MS5}), + L5 = string:length(String5), + ct:log("String5: ~p~nLength5: ~p~n",[String5,L5]), + L5 = MS5, + true = lists:prefix(lists:sublist(String5,L5-4),String4), + + ok. + format_mfa(_Config) -> Template = [mfa], @@ -443,7 +500,7 @@ faulty_config(_Config) -> faulty_msg(_Config) -> {error, function_clause, - {logger_formatter,_,[_,_],_}} = + {logger_formatter,_,_,_}} = ?TRY(logger_formatter:format(#{level=>info, msg=>term, meta=>#{time=>timestamp()}}, |