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_text_fmt).
9
10-export([format/2]).
11
12format(#{msg := Msg, meta := Meta} = LogEvent, Config) ->
13    Prefix = format_prefix(LogEvent, Config),
14    Color = pick_color(LogEvent, Config),
15    FormattedMsg = rabbit_logger_fmt_helpers:format_msg(Msg, Meta, Config),
16    prepend_prefix_to_msg_and_add_color(
17      Prefix, Color, FormattedMsg, LogEvent, Config).
18
19format_prefix(LogEvent, #{prefix_format := PrefixFormat} = Config) ->
20    format_prefix(PrefixFormat, LogEvent, Config, []);
21format_prefix(LogEvent, Config) ->
22    %% Default prefix format.
23    format_prefix([time, " [", level, "] ", pid, " "], LogEvent, Config, []).
24
25format_prefix([String | Rest], LogEvent, Config, Prefix)
26  when is_list(String) ->
27    format_prefix(Rest, LogEvent, Config, [String | Prefix]);
28format_prefix([Var | Rest], LogEvent, Config, Prefix)
29  when is_atom(Var) ->
30    String = format_var(Var, LogEvent, Config),
31    format_prefix(Rest, LogEvent, Config, [String | Prefix]);
32format_prefix([], _, _, Prefix) ->
33    lists:reverse(Prefix).
34
35format_var(level, #{level := Level}, Config) ->
36    rabbit_logger_fmt_helpers:format_level(Level, Config);
37format_var(time, #{meta := #{time := Timestamp}}, Config) ->
38    rabbit_logger_fmt_helpers:format_time(Timestamp, Config);
39format_var(Var, #{meta := Meta}, _) ->
40    case maps:get(Var, Meta, undefined) of
41        undefined ->
42            io_lib:format("<unknown ~s>", [Var]);
43        Value ->
44            case io_lib:char_list(Value) of
45                true  -> io_lib:format("~s", [Value]);
46                false -> io_lib:format("~p", [Value])
47            end
48    end.
49
50pick_color(#{level := Level}, #{use_colors := true} = Config) ->
51    ColorStart = level_to_color(Level, Config),
52    ColorEnd = "\033[0m",
53    {ColorStart, ColorEnd};
54pick_color(_, _) ->
55    {"", ""}.
56
57level_to_color(Level, #{color_esc_seqs := ColorEscSeqs}) ->
58    maps:get(Level, ColorEscSeqs);
59level_to_color(debug, _)     -> "\033[38;5;246m";
60level_to_color(info, _)      -> "";
61level_to_color(notice, _)    -> "\033[38;5;87m";
62level_to_color(warning, _)   -> "\033[38;5;214m";
63level_to_color(error, _)     -> "\033[38;5;160m";
64level_to_color(critical, _)  -> "\033[1;37m\033[48;5;20m";
65level_to_color(alert, _)     -> "\033[1;37m\033[48;5;93m";
66level_to_color(emergency, _) -> "\033[1;37m\033[48;5;196m".
67
68prepend_prefix_to_msg_and_add_color(
69  Prefix, {ColorStart, ColorEnd}, FormattedMsg, LogEvent, Config) ->
70    Lines = split_lines(FormattedMsg, Config),
71    [[ColorStart,
72      format_line(Prefix, Line, LogEvent, Config),
73      ColorEnd,
74      $\n]
75     || Line <- Lines].
76
77split_lines(FormattedMsg, _) ->
78    FlattenMsg = lists:flatten(FormattedMsg),
79    string:split(FlattenMsg, [$\n], all).
80
81format_line(Prefix, Msg, LogEvent, #{line_format := Format} = Config) ->
82    format_line(Format, Msg, LogEvent, Config, [Prefix]);
83format_line(Prefix, Msg, LogEvent, Config) ->
84    format_line([msg], Msg, LogEvent, Config, [Prefix]).
85
86format_line([msg | Rest], Msg, LogEvent, Config, Line) ->
87    format_line(Rest, Msg, LogEvent, Config, [Msg | Line]);
88format_line([String | Rest], Msg, LogEvent, Config, Line)
89  when is_list(String) ->
90    format_line(Rest, Msg, LogEvent, Config, [String | Line]);
91format_line([Var | Rest], Msg, LogEvent, Config, Line)
92  when is_atom(Var) ->
93    String = format_var(Var, LogEvent, Config),
94    format_line(Rest, Msg, LogEvent, Config, [String | Line]);
95format_line([], _, _, _, Line) ->
96    remove_trailing_whitespaces(Line).
97
98remove_trailing_whitespaces([Tail | Line]) ->
99    Tail1 = string:strip(Tail, right),
100    lists:reverse([Tail1 | Line]).
101