Current section
Files
Jump to
Current section
Files
src/quic_log.erl
%%% -*- erlang -*-
%%%
%%% @private
%%%
%%% QUIC structured logging – report_cb for OTP Logger.
%%%
%%% Callback for formatting QUIC log reports (maps with mandatory `what' key)
%%% into human-readable strings. Mirrors supervisor:format_log/2: merge defaults,
%%% build {Format, Args} via format_* helpers, then io_lib:format(Format, Args, IoOpts).
-module(quic_log).
-export([format_report/2]).
%% @private
%% Report is a map with mandatory key `what' and optional key-value pairs.
%% Config is report_cb_config(): depth, chars_limit, single_line (see OTP Logger).
-spec format_report(logger:report(), logger:report_cb_config()) -> unicode:chardata().
format_report(Report, Config) when is_map(Report) ->
Default = #{
chars_limit => unlimited,
depth => unlimited,
single_line => false,
encoding => utf8
},
FormatOpts = maps:merge(Default, Config),
IoOpts =
case FormatOpts of
#{chars_limit := unlimited} -> [];
#{chars_limit := Limit} -> [{chars_limit, Limit}]
end,
{Format, Args} = format_log(Report, FormatOpts),
io_lib:format("[QUIC] " ++ Format, Args, IoOpts).
%% Single-line: one "~p = ~P " (or ~p) per pair; multi-line: " ~p = ~P~n" etc.
format_log(Report, #{single_line := Single} = FormatOpts) ->
Sep =
case Single of
true -> " ";
false -> "~n "
end,
P = p(FormatOpts),
Pairs = sort_what_first(Report),
{FormatParts, Args} = lists:mapfoldl(
fun(Pair, Acc) -> part_and_args(FormatOpts, Pair, P, Sep, Acc) end,
[],
Pairs
),
{lists:flatten(FormatParts), Args}.
%% 'what' is always an atom with underscores; print value as string without underscores.
part_and_args(_FormatOpts, {what, V}, _P, Sep, Acc) when is_atom(V) ->
Part = "[~s]" ++ Sep,
ArgsHere = [what_to_string(V)],
{Part, Acc ++ ArgsHere};
part_and_args(FormatOpts, {K, V}, P, Sep, Acc) ->
Part = "~p=" ++ P ++ Sep,
ArgsHere = args_for(FormatOpts, K, V),
{Part, Acc ++ ArgsHere}.
%% Atom to readable string: underscores become spaces (e.g. udp_received -> "udp received").
what_to_string(Atom) when is_atom(Atom) ->
lists:flatten([
case C of
$_ -> " ";
_ -> C
end
|| C <- atom_to_list(Atom)
]).
args_for(#{depth := unlimited}, K, V) ->
[K, V];
args_for(#{depth := Depth}, K, V) ->
[K, V, Depth].
%% Format control for value: ~p / ~P with optional width and encoding (cf. supervisor).
p(#{single_line := Single, depth := Depth, encoding := Enc}) ->
"~" ++ single(Single) ++ mod(Enc) ++ p_depth(Depth).
p_depth(unlimited) -> "p";
p_depth(_) -> "P".
single(true) -> "0";
single(false) -> "".
mod(latin1) -> "";
mod(_) -> "t".
sort_what_first(Report) ->
Pairs = maps:to_list(Report),
case lists:keytake(what, 1, Pairs) of
{value, WhatPair, Rest} ->
[WhatPair | Rest];
false ->
Pairs
end.