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_fmt_helpers).
9
10-export([format_time/2,
11         format_level/2,
12         format_msg/3]).
13
14format_time(Timestamp, #{time_format := Format}) ->
15    format_time1(Timestamp, Format);
16format_time(Timestamp, _) ->
17    format_time1(Timestamp, {rfc3339, $\s, ""}).
18
19format_time1(Timestamp, {rfc3339, Sep, Offset}) ->
20    Options = [{unit, microsecond},
21               {offset, Offset},
22               {time_designator, Sep}],
23    calendar:system_time_to_rfc3339(Timestamp, Options);
24format_time1(Timestamp, {epoch, secs, int}) ->
25    Timestamp div 1000000;
26format_time1(Timestamp, {epoch, usecs, int}) ->
27    Timestamp;
28format_time1(Timestamp, {epoch, secs, binary}) ->
29    io_lib:format("~.6.0f", [Timestamp / 1000000]);
30format_time1(Timestamp, {epoch, usecs, binary}) ->
31    io_lib:format("~b", [Timestamp]);
32format_time1(Timestamp, {LocalOrUniversal, Format, Args}) ->
33    %% The format string and the args list is prepared by
34    %% `rabbit_prelaunch_early_logging:translate_generic_conf()'.
35    {{Year, Month, Day},
36     {Hour, Minute, Second}} = case LocalOrUniversal of
37                                   local ->
38                                       calendar:system_time_to_local_time(
39                                         Timestamp, microsecond);
40                                   universal ->
41                                       calendar:system_time_to_universal_time(
42                                         Timestamp, microsecond)
43                               end,
44    Args1 = lists:map(
45              fun
46                  (year)       -> Year;
47                  (month)      -> Month;
48                  (day)        -> Day;
49                  (hour)       -> Hour;
50                  (minute)     -> Minute;
51                  (second)     -> Second;
52                  ({second_fractional,
53                    Decimals}) -> second_fractional(Timestamp, Decimals)
54              end, Args),
55    io_lib:format(Format, Args1).
56
57second_fractional(Timestamp, Decimals) ->
58    (Timestamp rem 1000000) div (1000000 div round(math:pow(10, Decimals))).
59
60format_level(Level, Config) ->
61    format_level1(Level, Config).
62
63format_level1(Level, #{level_format := lc}) ->
64    level_lc_name(Level);
65format_level1(Level, #{level_format := uc}) ->
66    level_uc_name(Level);
67format_level1(Level, #{level_format := lc3}) ->
68    level_3letter_lc_name(Level);
69format_level1(Level, #{level_format := uc3}) ->
70    level_3letter_uc_name(Level);
71format_level1(Level, #{level_format := lc4}) ->
72    level_4letter_lc_name(Level);
73format_level1(Level, #{level_format := uc4}) ->
74    level_4letter_uc_name(Level);
75format_level1(Level, _) ->
76    level_4letter_lc_name(Level).
77
78level_lc_name(debug)     -> "debug";
79level_lc_name(info)      -> "info";
80level_lc_name(notice)    -> "notice";
81level_lc_name(warning)   -> "warning";
82level_lc_name(error)     -> "error";
83level_lc_name(critical)  -> "critical";
84level_lc_name(alert)     -> "alert";
85level_lc_name(emergency) -> "emergency".
86
87level_uc_name(debug)     -> "DEBUG";
88level_uc_name(info)      -> "INFO";
89level_uc_name(notice)    -> "NOTICE";
90level_uc_name(warning)   -> "WARNING";
91level_uc_name(error)     -> "ERROR";
92level_uc_name(critical)  -> "CRITICAL";
93level_uc_name(alert)     -> "ALERT";
94level_uc_name(emergency) -> "EMERGENCY".
95
96level_3letter_lc_name(debug)     -> "dbg";
97level_3letter_lc_name(info)      -> "inf";
98level_3letter_lc_name(notice)    -> "ntc";
99level_3letter_lc_name(warning)   -> "wrn";
100level_3letter_lc_name(error)     -> "err";
101level_3letter_lc_name(critical)  -> "crt";
102level_3letter_lc_name(alert)     -> "alt";
103level_3letter_lc_name(emergency) -> "emg".
104
105level_3letter_uc_name(debug)     -> "DBG";
106level_3letter_uc_name(info)      -> "INF";
107level_3letter_uc_name(notice)    -> "NTC";
108level_3letter_uc_name(warning)   -> "WRN";
109level_3letter_uc_name(error)     -> "ERR";
110level_3letter_uc_name(critical)  -> "CRT";
111level_3letter_uc_name(alert)     -> "ALT";
112level_3letter_uc_name(emergency) -> "EMG".
113
114level_4letter_lc_name(debug)     -> "dbug";
115level_4letter_lc_name(info)      -> "info";
116level_4letter_lc_name(notice)    -> "noti";
117level_4letter_lc_name(warning)   -> "warn";
118level_4letter_lc_name(error)     -> "erro";
119level_4letter_lc_name(critical)  -> "crit";
120level_4letter_lc_name(alert)     -> "alrt";
121level_4letter_lc_name(emergency) -> "emgc".
122
123level_4letter_uc_name(debug)     -> "DBUG";
124level_4letter_uc_name(info)      -> "INFO";
125level_4letter_uc_name(notice)    -> "NOTI";
126level_4letter_uc_name(warning)   -> "WARN";
127level_4letter_uc_name(error)     -> "ERRO";
128level_4letter_uc_name(critical)  -> "CRIT";
129level_4letter_uc_name(alert)     -> "ALRT";
130level_4letter_uc_name(emergency) -> "EMGC".
131
132format_msg(Msg, Meta, #{single_line := true} = Config) ->
133      FormattedMsg = format_msg1(Msg, Meta, Config),
134      %% The following behavior is the same as the one in the official
135      %% `logger_formatter'; the code is taken from that module (as of
136      %% c5ed910098e9c2787e2c3f9f462c84322064e00d in the master branch).
137      FormattedMsg1 = string:strip(FormattedMsg, both),
138      re:replace(
139        FormattedMsg1,
140        ",?\r?\n\s*",
141        ", ",
142        [{return, list}, global, unicode]);
143format_msg(Msg, Meta, Config) ->
144      format_msg1(Msg, Meta, Config).
145
146format_msg1({string, Chardata}, Meta, Config) ->
147    format_msg1({"~ts", [Chardata]}, Meta, Config);
148format_msg1({report, Report}, Meta, Config) ->
149    FormattedReport = format_report(Report, Meta, Config),
150    format_msg1(FormattedReport, Meta, Config);
151format_msg1({Format, Args}, _, _) ->
152    io_lib:format(Format, Args).
153
154format_report(
155  #{label := {application_controller, _}} = Report, Meta, Config) ->
156    format_application_progress(Report, Meta, Config);
157format_report(
158  #{label := {supervisor, progress}} = Report, Meta, Config) ->
159    format_supervisor_progress(Report, Meta, Config);
160format_report(
161  Report, #{report_cb := Cb} = Meta, Config) ->
162    try
163        case erlang:fun_info(Cb, arity) of
164            {arity, 1} -> Cb(Report);
165            {arity, 2} -> {"~ts", [Cb(Report, #{})]}
166        end
167    catch
168        _:_:_ ->
169            format_report(Report, maps:remove(report_cb, Meta), Config)
170    end;
171format_report(Report, _, _) ->
172    logger:format_report(Report).
173
174format_application_progress(#{label := {_, progress},
175                              report := InternalReport}, _, _) ->
176    Application = proplists:get_value(application, InternalReport),
177    StartedAt = proplists:get_value(started_at, InternalReport),
178    {"Application ~w started on ~0p",
179     [Application, StartedAt]};
180format_application_progress(#{label := {_, exit},
181                              report := InternalReport}, _, _) ->
182    Application = proplists:get_value(application, InternalReport),
183    Exited = proplists:get_value(exited, InternalReport),
184    {"Application ~w exited with reason: ~0p",
185     [Application, Exited]}.
186
187format_supervisor_progress(#{report := InternalReport}, _, _) ->
188    Supervisor = proplists:get_value(supervisor, InternalReport),
189    Started = proplists:get_value(started, InternalReport),
190    Id = proplists:get_value(id, Started),
191    Pid = proplists:get_value(pid, Started),
192    Mfa = proplists:get_value(mfargs, Started),
193    {"Supervisor ~w: child ~w started (~w): ~0p",
194     [Supervisor, Id, Pid, Mfa]}.
195