Logging: Add configuration variables to set various formats

In addition to the existing configuration variables to configure
logging, the following variables were added to extend the settings.

log.*.formatter = plaintext | json
  Selects between the plain text (default) and JSON formatters.

log.*.formatter.time_format = rfc3339_space | rfc3339_T | epoch_usecs | epoch_secs | lager_default
  Configures how the timestamp should be formatted. It has several
  values to get RFC3339 date & time, Epoch-based integers and Lager
  default format.

log.*.formatter.level_format = lc | uc | lc3 | uc3 | lc4 | uc4
  Configures how to format the level. Things like uppercase vs.
  lowercase, full vs. truncated.
  Examples:
    lc: debug
    uc: DEBUG
    lc3: dbg
    uc3: DBG
    lw4: dbug
    uc4: DBUG

log.*.formatter.single_line = on | off
  Indicates if multi-line messages should be reformatted as a
  single-line message. A multi-line message is converted to a
  single-line message by joining all lines and separating them
  with ", ".

log.*.formatter.plaintext.format
  Set to a pattern to indicate the format of the entire message. The
  format pattern is a string with $-based variables. Each variable
  corresponds to a field in the log event. Here is a non-exhaustive list
  of common fields:
    time
    level
    msg
    pid
    file
    line
  Example:
    $time [$level] $pid $msg

log.*.formatter.json.field_map
  Indicates if fields should be renamed or removed, and the ordering
  which they should appear in the final JSON object. The order is set by
  the order of fields in that coniguration variable.
  Example:
    time:ts level msg *:-
  In this example, `time` is renamed to `ts`. `*:-` tells to remove all
  fields not mentionned in the list. In the end the JSON object will
  contain the fields in the following order: ts, level, msg.

log.*.formatter.json.verbosity_map
  Indicates if a verbosity field should be added and how it should be
  derived from the level. If the verbosity map is not set, no verbosity
  field is added to the JSON object.
  Example:
    debug:2 info:1 notice:1 *:0
  In this example, debug verbosity is 2, info and notice verbosity is 1,
  other levels have a verbosity of 0.

All of them work with the console, exchange, file and syslog outputs.

The console output has specific variables too:

log.console.stdio = stdout | stderr
  Indicates if stdout or stderr should be used. The default is stdout.

log.console.use_colors = on | off
  Indicates if colors should be used in log messages. The default
  depends on the environment.

log.console.color_esc_seqs.*
  Indicates how each level is mapped to a color. The value can be any
  string but the idea is to use an ANSI escape sequence.
  Example:
    log.console.color_esc_seqs.error = \033[1;31m

V2: A custom time format pattern was introduced, first using variables,
    then a reference date & time (e.g. "Mon 2 Jan 2006"), thanks to
    @ansd. However, we decided to remove it for now until we have a
    better implementation of the reference date & time parser.

V3: The testsuite was extended to cover new settings as well as the
    syslog output. To test it, a fake syslogd server was added (Erlang
    process, part of the testsuite).

V4: The dependency to cuttlefish is moved to rabbitmq_prelaunch which
    actually uses the library. The version is updated to 3.0.1 because
    we need Kyorai/cuttlefish#25.
This commit is contained in:
Jean-Sébastien Pédron 2021-03-23 16:56:20 +01:00
parent 768a87980d
commit aca638abbb
No known key found for this signature in database
GPG Key ID: 39E99761A5FD94CC
9 changed files with 1932 additions and 478 deletions

View File

@ -133,12 +133,11 @@ APPS_DIR := $(CURDIR)/apps
LOCAL_DEPS = sasl rabbitmq_prelaunch os_mon inets compiler public_key crypto ssl syntax_tools xmerl
BUILD_DEPS = rabbitmq_cli
DEPS = cuttlefish ranch rabbit_common ra sysmon_handler stdout_formatter recon observer_cli osiris amqp10_common syslog
DEPS = ranch rabbit_common ra sysmon_handler stdout_formatter recon observer_cli osiris amqp10_common syslog
TEST_DEPS = rabbitmq_ct_helpers rabbitmq_ct_client_helpers amqp_client meck proper
PLT_APPS += mnesia
dep_cuttlefish = git https://github.com/Kyorai/cuttlefish master
dep_syslog = git https://github.com/schlagert/syslog 4.0.0
dep_osiris = git https://github.com/rabbitmq/osiris master

View File

@ -3,7 +3,8 @@ PROJECT_DESCRIPTION = RabbitMQ prelaunch setup
PROJECT_VERSION = 1.0.0
PROJECT_MOD = rabbit_prelaunch_app
DEPS = rabbit_common jsx
DEPS = rabbit_common cuttlefish jsx
dep_cuttlefish = hex 3.0.1
DEP_PLUGINS = rabbit_common/mk/rabbitmq-build.mk

View File

@ -0,0 +1,187 @@
%% This Source Code Form is subject to the terms of the Mozilla Public
%% License, v. 2.0. If a copy of the MPL was not distributed with this
%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
%%
%% Copyright (c) 2021 VMware, Inc. or its affiliates. All rights reserved.
%%
-module(rabbit_logger_fmt_helpers).
-export([format_time/2,
format_level/2,
format_msg/3]).
format_time(Timestamp, #{time_format := Format}) ->
format_time1(Timestamp, Format);
format_time(Timestamp, _) ->
format_time1(Timestamp, {rfc3339, $\s}).
format_time1(Timestamp, {rfc3339, Sep}) ->
Options = [{unit, microsecond},
{time_designator, Sep}],
calendar:system_time_to_rfc3339(Timestamp, Options);
format_time1(Timestamp, {epoch, secs, int}) ->
Timestamp / 1000000;
format_time1(Timestamp, {epoch, usecs, int}) ->
Timestamp;
format_time1(Timestamp, {epoch, secs, binary}) ->
io_lib:format("~.6.0f", [Timestamp / 1000000]);
format_time1(Timestamp, {epoch, usecs, binary}) ->
io_lib:format("~b", [Timestamp]);
format_time1(Timestamp, {Format, Args}) ->
%% The format string and the args list is prepared by
%% `rabbit_prelaunch_early_logging:translate_generic_conf()'.
{{Year, Month, Day},
{Hour, Minute, Second}} = calendar:system_time_to_local_time(
Timestamp, microsecond),
Args1 = lists:map(
fun
(year) -> Year;
(month) -> Month;
(day) -> Day;
(hour) -> Hour;
(minute) -> Minute;
(second) -> Second;
({second_fractional,
Decimals}) -> second_fractional(Timestamp, Decimals)
end, Args),
io_lib:format(Format, Args1).
second_fractional(Timestamp, Decimals) ->
(Timestamp rem 1000000) div (1000000 div round(math:pow(10, Decimals))).
format_level(Level, Config) ->
format_level1(Level, Config).
format_level1(Level, #{level_format := lc}) ->
level_lc_name(Level);
format_level1(Level, #{level_format := uc}) ->
level_uc_name(Level);
format_level1(Level, #{level_format := lc3}) ->
level_3letter_lc_name(Level);
format_level1(Level, #{level_format := uc3}) ->
level_3letter_uc_name(Level);
format_level1(Level, #{level_format := lc4}) ->
level_4letter_lc_name(Level);
format_level1(Level, #{level_format := uc4}) ->
level_4letter_uc_name(Level);
format_level1(Level, _) ->
level_4letter_lc_name(Level).
level_lc_name(debug) -> "debug";
level_lc_name(info) -> "info";
level_lc_name(notice) -> "notice";
level_lc_name(warning) -> "warning";
level_lc_name(error) -> "error";
level_lc_name(critical) -> "critical";
level_lc_name(alert) -> "alert";
level_lc_name(emergency) -> "emergency".
level_uc_name(debug) -> "DEBUG";
level_uc_name(info) -> "INFO";
level_uc_name(notice) -> "NOTICE";
level_uc_name(warning) -> "WARNING";
level_uc_name(error) -> "ERROR";
level_uc_name(critical) -> "CRITICAL";
level_uc_name(alert) -> "ALERT";
level_uc_name(emergency) -> "EMERGENCY".
level_3letter_lc_name(debug) -> "dbg";
level_3letter_lc_name(info) -> "inf";
level_3letter_lc_name(notice) -> "ntc";
level_3letter_lc_name(warning) -> "wrn";
level_3letter_lc_name(error) -> "err";
level_3letter_lc_name(critical) -> "crt";
level_3letter_lc_name(alert) -> "alt";
level_3letter_lc_name(emergency) -> "emg".
level_3letter_uc_name(debug) -> "DBG";
level_3letter_uc_name(info) -> "INF";
level_3letter_uc_name(notice) -> "NTC";
level_3letter_uc_name(warning) -> "WRN";
level_3letter_uc_name(error) -> "ERR";
level_3letter_uc_name(critical) -> "CRT";
level_3letter_uc_name(alert) -> "ALT";
level_3letter_uc_name(emergency) -> "EMG".
level_4letter_lc_name(debug) -> "dbug";
level_4letter_lc_name(info) -> "info";
level_4letter_lc_name(notice) -> "noti";
level_4letter_lc_name(warning) -> "warn";
level_4letter_lc_name(error) -> "erro";
level_4letter_lc_name(critical) -> "crit";
level_4letter_lc_name(alert) -> "alrt";
level_4letter_lc_name(emergency) -> "emgc".
level_4letter_uc_name(debug) -> "DBUG";
level_4letter_uc_name(info) -> "INFO";
level_4letter_uc_name(notice) -> "NOTI";
level_4letter_uc_name(warning) -> "WARN";
level_4letter_uc_name(error) -> "ERRO";
level_4letter_uc_name(critical) -> "CRIT";
level_4letter_uc_name(alert) -> "ALRT";
level_4letter_uc_name(emergency) -> "EMGC".
format_msg(Msg, Meta, #{single_line := true} = Config) ->
FormattedMsg = format_msg1(Msg, Meta, Config),
%% The following behavior is the same as the one in the official
%% `logger_formatter'; the code is taken from that module (as of
%% c5ed910098e9c2787e2c3f9f462c84322064e00d in the master branch).
FormattedMsg1 = string:strip(FormattedMsg, both),
re:replace(
FormattedMsg1,
",?\r?\n\s*",
", ",
[{return, list}, global, unicode]);
format_msg(Msg, Meta, Config) ->
format_msg1(Msg, Meta, Config).
format_msg1({string, Chardata}, Meta, Config) ->
format_msg1({"~ts", [Chardata]}, Meta, Config);
format_msg1({report, Report}, Meta, Config) ->
FormattedReport = format_report(Report, Meta, Config),
format_msg1(FormattedReport, Meta, Config);
format_msg1({Format, Args}, _, _) ->
io_lib:format(Format, Args).
format_report(
#{label := {application_controller, _}} = Report, Meta, Config) ->
format_application_progress(Report, Meta, Config);
format_report(
#{label := {supervisor, progress}} = Report, Meta, Config) ->
format_supervisor_progress(Report, Meta, Config);
format_report(
Report, #{report_cb := Cb} = Meta, Config) ->
try
case erlang:fun_info(Cb, arity) of
{arity, 1} -> Cb(Report);
{arity, 2} -> {"~ts", [Cb(Report, #{})]}
end
catch
_:_:_ ->
format_report(Report, maps:remove(report_cb, Meta), Config)
end;
format_report(Report, _, _) ->
logger:format_report(Report).
format_application_progress(#{label := {_, progress},
report := InternalReport}, _, _) ->
Application = proplists:get_value(application, InternalReport),
StartedAt = proplists:get_value(started_at, InternalReport),
{"Application ~w started on ~0p",
[Application, StartedAt]};
format_application_progress(#{label := {_, exit},
report := InternalReport}, _, _) ->
Application = proplists:get_value(application, InternalReport),
Exited = proplists:get_value(exited, InternalReport),
{"Application ~w exited with reason: ~0p",
[Application, Exited]}.
format_supervisor_progress(#{report := InternalReport}, _, _) ->
Supervisor = proplists:get_value(supervisor, InternalReport),
Started = proplists:get_value(started, InternalReport),
Id = proplists:get_value(id, Started),
Pid = proplists:get_value(pid, Started),
Mfa = proplists:get_value(mfargs, Started),
{"Supervisor ~w: child ~w started (~w): ~0p",
[Supervisor, Id, Pid, Mfa]}.

View File

@ -12,79 +12,42 @@
format(
#{msg := Msg,
level := Level,
meta := #{time := Timestamp} = Meta},
meta := Meta},
Config) ->
FormattedTimestamp = unicode:characters_to_binary(
format_time(Timestamp, Config)),
FormattedMsg = unicode:characters_to_binary(
format_msg(Msg, Meta, Config)),
FormattedLevel = unicode:characters_to_binary(
rabbit_logger_fmt_helpers:format_level(Level, Config)),
FormattedMeta = format_meta(Meta, Config),
Json = jsx:encode(
[{time, FormattedTimestamp},
{level, Level},
{msg, FormattedMsg},
{meta, FormattedMeta}]),
%% We need to call `unicode:characters_to_binary()' here and several other
%% places because `jsx:encode()' will format a string as a list of
%% integers (we don't blame it for that, it makes sense).
FormattedMsg = unicode:characters_to_binary(
rabbit_logger_fmt_helpers:format_msg(Msg, Meta, Config)),
InitialDoc0 = FormattedMeta#{level => FormattedLevel,
msg => FormattedMsg},
InitialDoc = case level_to_verbosity(Level, Config) of
undefined -> InitialDoc0;
Verbosity -> InitialDoc0#{verbosity => Verbosity}
end,
DocAfterMapping = apply_mapping_and_ordering(InitialDoc, Config),
Json = jsx:encode(DocAfterMapping),
[Json, $\n].
format_time(Timestamp, _) ->
Options = [{unit, microsecond}],
calendar:system_time_to_rfc3339(Timestamp, Options).
format_msg({string, Chardata}, Meta, Config) ->
format_msg({"~ts", [Chardata]}, Meta, Config);
format_msg({report, Report}, Meta, Config) ->
FormattedReport = format_report(Report, Meta, Config),
format_msg(FormattedReport, Meta, Config);
format_msg({Format, Args}, _, _) ->
io_lib:format(Format, Args).
format_report(
#{label := {application_controller, _}} = Report, Meta, Config) ->
format_application_progress(Report, Meta, Config);
format_report(
#{label := {supervisor, progress}} = Report, Meta, Config) ->
format_supervisor_progress(Report, Meta, Config);
format_report(
Report, #{report_cb := Cb} = Meta, Config) ->
try
case erlang:fun_info(Cb, arity) of
{arity, 1} -> Cb(Report);
{arity, 2} -> {"~ts", [Cb(Report, #{})]}
end
catch
_:_:_ ->
format_report(Report, maps:remove(report_cb, Meta), Config)
level_to_verbosity(Level, #{verbosity_map := Mapping}) ->
case maps:is_key(Level, Mapping) of
true -> maps:get(Level, Mapping);
false -> undefined
end;
format_report(Report, _, _) ->
logger:format_report(Report).
level_to_verbosity(_, _) ->
undefined.
format_application_progress(#{label := {_, progress},
report := InternalReport}, _, _) ->
Application = proplists:get_value(application, InternalReport),
StartedAt = proplists:get_value(started_at, InternalReport),
{"Application ~w started on ~0p",
[Application, StartedAt]};
format_application_progress(#{label := {_, exit},
report := InternalReport}, _, _) ->
Application = proplists:get_value(application, InternalReport),
Exited = proplists:get_value(exited, InternalReport),
{"Application ~w exited with reason: ~0p",
[Application, Exited]}.
format_supervisor_progress(#{report := InternalReport}, _, _) ->
Supervisor = proplists:get_value(supervisor, InternalReport),
Started = proplists:get_value(started, InternalReport),
Id = proplists:get_value(id, Started),
Pid = proplists:get_value(pid, Started),
Mfa = proplists:get_value(mfargs, Started),
{"Supervisor ~w: child ~w started (~w): ~0p",
[Supervisor, Id, Pid, Mfa]}.
format_meta(Meta, _) ->
format_meta(Meta, Config) ->
maps:fold(
fun
(time, _, Acc) ->
Acc;
(time, Timestamp, Acc) ->
FormattedTime = unicode:characters_to_binary(
rabbit_logger_fmt_helpers:format_time(
Timestamp, Config)),
Acc#{time => FormattedTime};
(domain = Key, Components, Acc) ->
Term = unicode:characters_to_binary(
string:join(
@ -125,3 +88,28 @@ convert_to_types_accepted_by_jsx(Term) when is_reference(Term) ->
unicode:characters_to_binary(String);
convert_to_types_accepted_by_jsx(Term) ->
Term.
apply_mapping_and_ordering(Doc, #{field_map := Mapping}) ->
apply_mapping_and_ordering(Mapping, Doc, []);
apply_mapping_and_ordering(Doc, _) ->
maps:to_list(Doc).
apply_mapping_and_ordering([{'$REST', false} | Rest], _, Result) ->
apply_mapping_and_ordering(Rest, #{}, Result);
apply_mapping_and_ordering([{Old, false} | Rest], Doc, Result)
when is_atom(Old) ->
Doc1 = maps:remove(Old, Doc),
apply_mapping_and_ordering(Rest, Doc1, Result);
apply_mapping_and_ordering([{Old, New} | Rest], Doc, Result)
when is_atom(Old) andalso is_atom(New) ->
case maps:is_key(Old, Doc) of
true ->
Value = maps:get(Old, Doc),
Doc1 = maps:remove(Old, Doc),
Result1 = [{New, Value} | Result],
apply_mapping_and_ordering(Rest, Doc1, Result1);
false ->
apply_mapping_and_ordering(Rest, Doc, Result)
end;
apply_mapping_and_ordering([], Doc, Result) ->
lists:reverse(Result) ++ maps:to_list(Doc).

View File

@ -12,130 +12,50 @@
format(#{msg := Msg, meta := Meta} = LogEvent, Config) ->
Prefix = format_prefix(LogEvent, Config),
Color = pick_color(LogEvent, Config),
FormattedMsg = format_msg(Msg, Meta, Config),
prepend_prefix_to_msg_and_add_color(Prefix, Color, FormattedMsg, Config).
FormattedMsg = rabbit_logger_fmt_helpers:format_msg(Msg, Meta, Config),
prepend_prefix_to_msg_and_add_color(
Prefix, Color, FormattedMsg, LogEvent, Config).
format_prefix(_, #{prefix := false}) ->
none;
format_prefix(#{level := Level,
meta := #{time := Timestamp,
pid := Pid}},
Config) ->
Time = format_time(Timestamp, Config),
LevelName = level_name(Level, Config),
io_lib:format("~ts [~ts] ~p", [Time, LevelName, Pid]).
format_prefix(LogEvent, #{prefix_format := PrefixFormat} = Config) ->
format_prefix(PrefixFormat, LogEvent, Config, []);
format_prefix(LogEvent, Config) ->
%% Default prefix format.
format_prefix([time, " [", level, "] ", pid, " "], LogEvent, Config, []).
level_name(Level, #{level_name := full}) ->
Level;
level_name(Level, #{level_name := uc3}) ->
level_3letter_uc_name(Level);
level_name(Level, #{level_name := lc3}) ->
level_3letter_lc_name(Level);
level_name(Level, #{level_name := uc4}) ->
level_4letter_uc_name(Level);
level_name(Level, #{level_name := lc4}) ->
level_4letter_lc_name(Level);
level_name(Level, _) ->
Level.
format_prefix([String | Rest], LogEvent, Config, Prefix)
when is_list(String) ->
format_prefix(Rest, LogEvent, Config, [String | Prefix]);
format_prefix([Var | Rest], LogEvent, Config, Prefix)
when is_atom(Var) ->
String = format_var(Var, LogEvent, Config),
format_prefix(Rest, LogEvent, Config, [String | Prefix]);
format_prefix([], _, _, Prefix) ->
lists:reverse(Prefix).
level_3letter_lc_name(debug) -> "dbg";
level_3letter_lc_name(info) -> "inf";
level_3letter_lc_name(notice) -> "ntc";
level_3letter_lc_name(warning) -> "wrn";
level_3letter_lc_name(error) -> "err";
level_3letter_lc_name(critical) -> "crt";
level_3letter_lc_name(alert) -> "alt";
level_3letter_lc_name(emergency) -> "emg".
format_var(level, #{level := Level}, Config) ->
rabbit_logger_fmt_helpers:format_level(Level, Config);
format_var(time, #{meta := #{time := Timestamp}}, Config) ->
rabbit_logger_fmt_helpers:format_time(Timestamp, Config);
format_var(Var, #{meta := Meta}, _) ->
case maps:get(Var, Meta, undefined) of
undefined ->
io_lib:format("<unknown ~s>", [Var]);
Value ->
case io_lib:char_list(Value) of
true -> io_lib:format("~s", [Value]);
false -> io_lib:format("~p", [Value])
end
end.
level_3letter_uc_name(debug) -> "DBG";
level_3letter_uc_name(info) -> "INF";
level_3letter_uc_name(notice) -> "NTC";
level_3letter_uc_name(warning) -> "WRN";
level_3letter_uc_name(error) -> "ERR";
level_3letter_uc_name(critical) -> "CRT";
level_3letter_uc_name(alert) -> "ALT";
level_3letter_uc_name(emergency) -> "EMG".
level_4letter_lc_name(debug) -> "dbug";
level_4letter_lc_name(info) -> "info";
level_4letter_lc_name(notice) -> "noti";
level_4letter_lc_name(warning) -> "warn";
level_4letter_lc_name(error) -> "erro";
level_4letter_lc_name(critical) -> "crit";
level_4letter_lc_name(alert) -> "alrt";
level_4letter_lc_name(emergency) -> "emgc".
level_4letter_uc_name(debug) -> "DBUG";
level_4letter_uc_name(info) -> "INFO";
level_4letter_uc_name(notice) -> "NOTI";
level_4letter_uc_name(warning) -> "WARN";
level_4letter_uc_name(error) -> "ERRO";
level_4letter_uc_name(critical) -> "CRIT";
level_4letter_uc_name(alert) -> "ALRT";
level_4letter_uc_name(emergency) -> "EMGC".
format_time(Timestamp, _) ->
Options = [{unit, microsecond},
{time_designator, $\s}],
calendar:system_time_to_rfc3339(Timestamp, Options).
format_msg({string, Chardata}, Meta, Config) ->
format_msg({"~ts", [Chardata]}, Meta, Config);
format_msg({report, Report}, Meta, Config) ->
FormattedReport = format_report(Report, Meta, Config),
format_msg(FormattedReport, Meta, Config);
format_msg({Format, Args}, _, _) ->
io_lib:format(Format, Args).
format_report(
#{label := {application_controller, _}} = Report, Meta, Config) ->
format_application_progress(Report, Meta, Config);
format_report(
#{label := {supervisor, progress}} = Report, Meta, Config) ->
format_supervisor_progress(Report, Meta, Config);
format_report(
Report, #{report_cb := Cb} = Meta, Config) ->
try
case erlang:fun_info(Cb, arity) of
{arity, 1} -> Cb(Report);
{arity, 2} -> {"~ts", [Cb(Report, #{})]}
end
catch
_:_:_ ->
format_report(Report, maps:remove(report_cb, Meta), Config)
end;
format_report(Report, _, _) ->
logger:format_report(Report).
format_application_progress(#{label := {_, progress},
report := InternalReport}, _, _) ->
Application = proplists:get_value(application, InternalReport),
StartedAt = proplists:get_value(started_at, InternalReport),
{"Application ~w started on ~0p",
[Application, StartedAt]};
format_application_progress(#{label := {_, exit},
report := InternalReport}, _, _) ->
Application = proplists:get_value(application, InternalReport),
Exited = proplists:get_value(exited, InternalReport),
{"Application ~w exited with reason: ~0p",
[Application, Exited]}.
format_supervisor_progress(#{report := InternalReport}, _, _) ->
Supervisor = proplists:get_value(supervisor, InternalReport),
Started = proplists:get_value(started, InternalReport),
Id = proplists:get_value(id, Started),
Pid = proplists:get_value(pid, Started),
Mfa = proplists:get_value(mfargs, Started),
{"Supervisor ~w: child ~w started (~w): ~0p",
[Supervisor, Id, Pid, Mfa]}.
pick_color(_, #{color := false}) ->
{"", ""};
pick_color(#{level := Level}, #{color := true} = Config) ->
pick_color(#{level := Level}, #{use_colors := true} = Config) ->
ColorStart = level_to_color(Level, Config),
ColorEnd = "\033[0m",
{ColorStart, ColorEnd}.
{ColorStart, ColorEnd};
pick_color(_, _) ->
{"", ""}.
level_to_color(Level, #{color_esc_seqs := ColorEscSeqs}) ->
maps:get(Level, ColorEscSeqs);
level_to_color(debug, _) -> "\033[38;5;246m";
level_to_color(info, _) -> "";
level_to_color(notice, _) -> "\033[38;5;87m";
@ -146,22 +66,35 @@ level_to_color(alert, _) -> "\033[1;37m\033[48;5;93m";
level_to_color(emergency, _) -> "\033[1;37m\033[48;5;196m".
prepend_prefix_to_msg_and_add_color(
none, {ColorStart, ColorEnd}, FormattedMsg, Config) ->
Prefix, {ColorStart, ColorEnd}, FormattedMsg, LogEvent, Config) ->
Lines = split_lines(FormattedMsg, Config),
[case Line of
"" -> [$\n];
_ -> [ColorStart, Line, ColorEnd, $\n]
end
|| Line <- Lines];
prepend_prefix_to_msg_and_add_color(
Prefix, {ColorStart, ColorEnd}, FormattedMsg, Config) ->
Lines = split_lines(FormattedMsg, Config),
[case Line of
"" -> [ColorStart, Prefix, ColorEnd, $\n];
_ -> [ColorStart, Prefix, " ", Line, ColorEnd, $\n]
end
[[ColorStart,
format_line(Prefix, Line, LogEvent, Config),
ColorEnd,
$\n]
|| Line <- Lines].
split_lines(FormattedMsg, _) ->
FlattenMsg = lists:flatten(FormattedMsg),
string:split(FlattenMsg, [$\n], all).
format_line(Prefix, Msg, LogEvent, #{line_format := Format} = Config) ->
format_line(Format, Msg, LogEvent, Config, [Prefix]);
format_line(Prefix, Msg, LogEvent, Config) ->
format_line([msg], Msg, LogEvent, Config, [Prefix]).
format_line([msg | Rest], Msg, LogEvent, Config, Line) ->
format_line(Rest, Msg, LogEvent, Config, [Msg | Line]);
format_line([String | Rest], Msg, LogEvent, Config, Line)
when is_list(String) ->
format_line(Rest, Msg, LogEvent, Config, [String | Line]);
format_line([Var | Rest], Msg, LogEvent, Config, Line)
when is_atom(Var) ->
String = format_var(Var, LogEvent, Config),
format_line(Rest, Msg, LogEvent, Config, [String | Line]);
format_line([], _, _, _, Line) ->
remove_trailing_whitespaces(Line).
remove_trailing_whitespaces([Tail | Line]) ->
Tail1 = string:strip(Tail, right),
lists:reverse([Tail1 | Line]).

View File

@ -18,9 +18,15 @@
default_syslog_formatter/1,
enable_quick_dbg/1,
use_colored_logging/0,
use_colored_logging/1]).
use_colored_logging/1,
translate_formatter_conf/2]).
-export([filter_log_event/2]).
-ifdef(TEST).
-export([levels/0,
determine_prefix/1]).
-endif.
-define(CONFIGURED_KEY, {?MODULE, configured}).
setup_early_logging(#{log_levels := undefined} = Context) ->
@ -107,7 +113,7 @@ default_formatter(#{log_levels := #{json := true}}) ->
{rabbit_logger_json_fmt, #{}};
default_formatter(Context) ->
Color = use_colored_logging(Context),
{rabbit_logger_text_fmt, #{color => Color}}.
{rabbit_logger_text_fmt, #{use_colors => Color}}.
default_console_formatter(Context) ->
default_formatter(Context).
@ -118,7 +124,7 @@ default_file_formatter(Context) ->
default_syslog_formatter(Context) ->
{Module, Config} = default_file_formatter(Context),
case Module of
rabbit_logger_text_fmt -> {Module, Config#{prefix => false}};
rabbit_logger_text_fmt -> {Module, Config#{prefix_format => []}};
rabbit_logger_json_fmt -> {Module, Config}
end.
@ -140,3 +146,352 @@ enable_quick_dbg(#{dbg_output := Output, dbg_mods := Mods}) ->
end,
{ok, _} = dbg:p(all, c),
lists:foreach(fun(M) -> {ok, _} = dbg:tp(M, cx) end, Mods).
%% -------------------------------------------------------------------
%% Internal function used by our Cuttlefish schema.
%% -------------------------------------------------------------------
-spec translate_formatter_conf(string(), cuttlefish_conf:conf()) ->
{rabbit_logger_text_fmt, formatter_plaintext_conf()} |
{rabbit_logger_json_fmt, formatter_json_conf()}.
%% @doc
%% Called from the Cuttlefish schema to derive the actual configuration from
%% several Cuttlefish variables.
translate_formatter_conf(Var, Conf) when is_list(Var) ->
try
%% Part of the formatter configuration is common to all the
%% formatters, the rest is formatter-specific.
GenericConfig = translate_generic_conf(Var, Conf),
case cuttlefish:conf_get(Var, Conf) of
plaintext ->
translate_plaintext_formatter_conf(Var, Conf, GenericConfig);
json ->
translate_json_formatter_conf(Var, Conf, GenericConfig)
end
catch
Class:Reason:Stacktrace ->
?LOG_ERROR(
rabbit_prelaunch_errors:format_exception(
Class, Reason, Stacktrace),
#{domain => ?RMQLOG_DOMAIN_PRELAUNCH}),
throw({configuration_translation_failure, Reason})
end.
-type time_format_string_arg() :: year | month | day |
hour | minute | second |
{second_fractional, non_neg_integer()}.
-type time_format() :: {rfc3339, char()} |
{epoch, secs | usecs, binary | int} |
{string(), [time_format_string_arg()]}.
-type level_format() :: lc | uc | lc3 | uc3 | lc4 | uc4.
-type formatter_generic_conf() :: #{time_format := time_format(),
level_format := level_format(),
single_line := boolean()}.
-spec translate_generic_conf(string(), cuttlefish_conf:conf()) ->
formatter_generic_conf().
%% @doc
%% Handles variables common to all formatters.
translate_generic_conf(Var, Conf) ->
%% log.*.formatter.time_format
%% It accepts either a "named pattern" like `rfc3339_T' or a custom
%% pattern.
Formatter = cuttlefish:conf_get(Var, Conf),
TimeFormat = case cuttlefish:conf_get(Var ++ ".time_format", Conf) of
rfc3339_T ->
{rfc3339, $T};
rfc3339_space ->
{rfc3339, $\s};
epoch_secs when Formatter =:= json ->
{epoch, secs, int};
epoch_usecs when Formatter =:= json ->
{epoch, usecs, int};
epoch_secs ->
{epoch, secs, binary};
epoch_usecs ->
{epoch, usecs, binary};
lager_default ->
{"~4..0b-~2..0b-~2..0b "
"~2..0b:~2..0b:~2..0b.~3..0b",
[year, month, day,
hour, minute, second,
{second_fractional, 3}]}
end,
%% log.*.formatter.level_format
%% It determines basically if the level should be printed in lowercase or
%% uppercase, and fully or truncated (to align messages horizontally).
LevelFormat = cuttlefish:conf_get(Var ++ ".level_format", Conf),
%% log.*.formatter.single_line
%% It tells if multi-line messages should be kept as-is or reformatted to
%% stay on a single line.
SingleLine = cuttlefish:conf_get(Var ++ ".single_line", Conf),
#{time_format => TimeFormat,
level_format => LevelFormat,
single_line => SingleLine}.
-type line_format() :: [atom() | string()].
-type color_esc_seqs() :: #{logger:level() => string()}.
-type formatter_plaintext_conf() :: #{time_format := time_format(),
level_format := level_format(),
single_line := boolean(),
prefix_format := line_format(),
line_format := line_format(),
use_colors := boolean(),
color_esc_seqs := color_esc_seqs()}.
-spec translate_plaintext_formatter_conf(
string(), cuttlefish_conf:conf(), formatter_generic_conf()) ->
{rabbit_logger_text_fmt, formatter_plaintext_conf()}.
%% @doc
%% Handles variables specific to the plaintext formatter.
translate_plaintext_formatter_conf(Var, Conf, GenericConfig) ->
%% log.*.formatter.plaintext.format
%% This is a variable-based string used to indicate the message format.
%% Here, we parse that pattern to make it easier and more efficient for
%% the formatter to format the final message.
Format0 = cuttlefish:conf_get(Var ++ ".plaintext.format", Conf),
Format = prepare_fmt_format(Format0),
{PrefixFormat, LineFormat} = determine_prefix(Format),
%% log.console.use_colors
%% log.console.color_esc_seqs
%% Those variables indicates if colors should be used and which one. They
%% are specific to the console handler.
{UseColors, ColorEscSeqs} = translate_colors_conf(Var, Conf),
Mod = rabbit_logger_text_fmt,
Config = GenericConfig#{prefix_format => PrefixFormat,
line_format => LineFormat,
use_colors => UseColors,
color_esc_seqs => ColorEscSeqs},
{Mod, Config}.
-spec prepare_fmt_format(string()) -> [atom() | string()].
%% @doc
%% Parse the pattern and prepare a list which makes it easy for the formatter
%% to format the final message.
%%
%% The initial pattern will use variables; for example:
%% `$time [$level] $pid - $msg'
%%
%% Once parsed, the pattern will look like:
%% `[time, " [", level, "] ", pid, " - ", msg]'
%%
%% Variables are taken from the log event structure: `msg' and `level' are
%% taken from the top-level, other variables come from the `meta' map.
prepare_fmt_format(Format) ->
prepare_fmt_format(Format, []).
prepare_fmt_format([$$ | Rest], Parsed) ->
{match, [Var, Rest1]} = re:run(Rest, "^([a-zA_Z0-9_]+)(.*)",
[{capture, all_but_first, list}]),
Var1 = list_to_atom(Var),
prepare_fmt_format(Rest1, [Var1 | Parsed]);
prepare_fmt_format(Rest, Parsed) when Rest =/= "" ->
%% We made sure in the guard expression that `Rest' contains at least
%% onecharacter. The following regex "eats" at least that character. This
%% avoids an infinite loop which would happen if the returned `String' was
%% empty and `Rest1' would be the same as `Rest'.
{match, [String, Rest1]} = re:run(Rest, "^(.[^$]*)(.*)",
[{capture, all_but_first, list}]),
prepare_fmt_format(Rest1, [String | Parsed]);
prepare_fmt_format("", Parsed) ->
lists:reverse(Parsed).
determine_prefix(Format) ->
%% Based on where the `msg' variable is, we determine the prefix of the
%% message. This is later used by the formatter to repeat the prefix for
%% each line making a multi-line message.
%%
%% If `msg' is not logged at all, we consider the line has no prefix.
{PrefixFormat0, LineFormat0} =
lists:foldl(
fun
(msg, {PF, LF}) -> {PF, LF ++ [msg]};
(Elem, {PF, [] = LF}) -> {PF ++ [Elem], LF};
(Elem, {PF, LF}) -> {PF, LF ++ [Elem]}
end, {[], []}, Format),
case {PrefixFormat0, LineFormat0} of
{_, []} -> {[], PrefixFormat0};
_ -> {PrefixFormat0, LineFormat0}
end.
-spec translate_colors_conf(string(), cuttlefish_conf:conf()) ->
{boolean(), map()}.
%% @doc
%% Computes the color configuration.
%%
%% The function uses the following two variables:
%% `log.console.use_colors'
%% `log.console.color_esc_seqs'
%%
%% It does not verify what escape sequences are actually configured. It is
%% entirely possible to play with the cursor position or other control
%% characters.
%%
%% This is only valid for the console output.
translate_colors_conf("log.console.formatter", Conf) ->
{
cuttlefish:conf_get("log.console.use_colors", Conf),
lists:foldl(
fun(Lvl, Acc) ->
LvlS = atom_to_list(Lvl),
Key = "log.console.color_esc_seqs." ++ LvlS,
RawVal = cuttlefish:conf_get(Key, Conf),
%% The ESC character will be escaped if the user entered the
%% string "\033" for instance. We need to convert it back to an
%% actual ESC character.
Val = re:replace(
RawVal,
"\\\\(e|033)",
"\033",
[global, {return, list}]),
Acc#{Lvl => Val}
end,
#{},
levels())
};
translate_colors_conf(_, _) ->
{false, #{}}.
-type json_field_map() :: [{atom(), atom()} | {atom() | '$REST', false}].
-type json_verbosity_map() :: #{logger:level() => non_neg_integer(),
'$REST' => non_neg_integer()}.
-type formatter_json_conf() :: #{time_format := time_format(),
level_format := level_format(),
single_line := boolean(),
field_map := json_field_map(),
verbosity_map := json_verbosity_map()}.
-spec translate_json_formatter_conf(
string(), cuttlefish_conf:conf(), map()) ->
{rabbit_logger_json_fmt, formatter_json_conf()}.
%% @doc
%% Handles variables specific to the JSON formatter.
translate_json_formatter_conf(Var, Conf, GenericConfig) ->
%% log.*.formatter.json.field_map
%% It indicates several things:
%% - the order of fields; non-mentionned fields go unordered at the end
%% of the JSON object
%% - if fields should be renamed
%% - if fields should be removed from the final object
RawFieldMapping = cuttlefish:conf_get(Var ++ ".json.field_map", Conf),
FieldMapping = parse_json_field_mapping(RawFieldMapping),
%% log.*.formatter.json.verbosity_map
%% It indicates if a `verbosity' field should be added and how its value
%% should be derived from `level'.
RawVerbMapping = cuttlefish:conf_get(
Var ++ ".json.verbosity_map", Conf),
VerbMapping = parse_json_verbosity_mapping(RawVerbMapping),
Mod = rabbit_logger_json_fmt,
Config = GenericConfig#{field_map => FieldMapping,
verbosity_map => VerbMapping},
{Mod, Config}.
-spec parse_json_field_mapping(string()) -> json_field_map().
%% @doc
%% Parses the field_map pattern.
%%
%% The pattern is of the form: `time:ts level msg *:-'.
%%
%% `time:ts' means the `time' field should be renamed to `ts'.
%%
%% `level' means that field should be kept as-is.
%%
%% `gl:-' means the `gl' field should be dropped.
%%
%% `*:-' means all non-mentionned fields should be dropped.
%%
%% The order of fields in the pattern is important: it tells the order of
%% fields in the final JSON object.
parse_json_field_mapping(RawMapping) ->
parse_json_field_mapping(string:split(RawMapping, " ", all), []).
parse_json_field_mapping([Entry | Rest], Mapping) ->
Mapping1 = case string:split(Entry, ":", leading) of
["*", "-"] ->
[{'$REST', false} | Mapping];
[OldS, "-"] ->
Old = list_to_atom(OldS),
[{Old, false} | Mapping];
["*", _] ->
throw({bad_json_mapping, Entry});
[OldS, NewS] ->
Old = list_to_atom(OldS),
New = list_to_atom(NewS),
[{Old, New} | Mapping];
[KeepS] ->
Keep = list_to_atom(KeepS),
[{Keep, Keep} | Mapping]
end,
parse_json_field_mapping(Rest, Mapping1);
parse_json_field_mapping([], Mapping) ->
%% We parsed everything. Now we want to organize fields a bit:
%% - All `{atom(), atom()}' (kept or renamed fields) go at the
%% beginning, preserving their order
%% - All `{_, false}' (removed fields) go at the end
{Renames0, Removes0} = lists:partition(
fun
({_, false}) -> false;
(_) -> true
end,
Mapping),
Renames = lists:reverse(Renames0),
%% If all non-mentionned fields are to be removed, only the `{$REST,
%% false}' entry is useful.
Removes = case lists:member({'$REST', false}, Removes0) of
true -> [{'$REST', false}];
false -> Removes0
end,
Renames ++ Removes.
-spec parse_json_verbosity_mapping(string()) -> json_verbosity_map().
%% @doc
%% Parses the verbosity_map pattern.
%%
%% The pattern is of the form: `debug=2 info=1 *=0'.
%%
%% `debug=2' means that the verbosity of the debug level is 2.
%%
%% `*=0' means that the verbosity of all non-mentionned levels is 0.
parse_json_verbosity_mapping("") ->
#{};
parse_json_verbosity_mapping(RawMapping) ->
parse_json_verbosity_mapping(string:split(RawMapping, " ", all), #{}).
parse_json_verbosity_mapping([Entry | Rest], Mapping) ->
Mapping1 = case string:split(Entry, "=", leading) of
["*", VerbS] ->
Verb = list_to_integer(VerbS),
Mapping#{'$REST' => Verb};
[LvlS, VerbS] ->
Lvl = list_to_atom(LvlS),
Verb = list_to_integer(VerbS),
Mapping#{Lvl => Verb}
end,
parse_json_verbosity_mapping(Rest, Mapping1);
parse_json_verbosity_mapping([], #{'$REST' := Default} = Mapping) ->
DefaultMapping = lists:foldl(
fun(Lvl, Acc) -> Acc#{Lvl => Default} end,
#{}, levels()),
maps:merge(
DefaultMapping,
maps:remove('$REST', Mapping));
parse_json_verbosity_mapping([], Mapping) ->
Mapping.
levels() ->
[debug, info, notice, warning, error, critical, alert, emergency].

View File

@ -1192,6 +1192,78 @@ end}.
{mapping, "log.console.level", "rabbit.log.console.level", [
{datatype, {enum, [debug, info, notice, warning, error, critical, alert, emergency, none]}}
]}.
{mapping, "log.console.stdio", "rabbit.log.console.stdio", [
{default, stdout},
{datatype, {enum, [stdout, stderr]}}
]}.
{mapping, "log.console.use_colors", "rabbit.log.console.formatter", [
{default, on},
{datatype, flag}
]}.
{mapping, "log.console.color_esc_seqs.debug", "rabbit.log.console.formatter", [
{default, "\033[38;5;246m"},
{datatype, string}
]}.
{mapping, "log.console.color_esc_seqs.info", "rabbit.log.console.formatter", [
{default, ""},
{datatype, string}
]}.
{mapping, "log.console.color_esc_seqs.notice", "rabbit.log.console.formatter", [
{default, "\033[38;5;87m"},
{datatype, string}
]}.
{mapping, "log.console.color_esc_seqs.warning", "rabbit.log.console.formatter", [
{default, "\033[38;5;214m"},
{datatype, string}
]}.
{mapping, "log.console.color_esc_seqs.error", "rabbit.log.console.formatter", [
{default, "\033[38;5;160m"},
{datatype, string}
]}.
{mapping, "log.console.color_esc_seqs.critical", "rabbit.log.console.formatter", [
{default, "\033[1;37m\033[48;5;20m"},
{datatype, string}
]}.
{mapping, "log.console.color_esc_seqs.alert", "rabbit.log.console.formatter", [
{default, "\033[1;37m\033[48;5;93m"},
{datatype, string}
]}.
{mapping, "log.console.color_esc_seqs.emergency", "rabbit.log.console.formatter", [
{default, "\033[1;37m\033[48;5;196m"},
{datatype, string}
]}.
{mapping, "log.console.formatter", "rabbit.log.console.formatter", [
{default, plaintext},
{datatype, {enum, [plaintext, json]}}
]}.
{mapping, "log.console.formatter.time_format", "rabbit.log.console.formatter", [
{default, rfc3339_space},
{datatype, {enum, [rfc3339_space, rfc3339_T, epoch_usecs, epoch_secs, lager_default]}}
]}.
{mapping, "log.console.formatter.level_format", "rabbit.log.console.formatter", [
{default, lc},
{datatype, {enum, [lc, uc, lc3, uc3, lc4, uc4]}}
]}.
{mapping, "log.console.formatter.single_line", "rabbit.log.file.formatter", [
{default, off},
{datatype, flag}
]}.
{mapping, "log.console.formatter.plaintext.format", "rabbit.log.console.formatter", [
{default, "$time [$level] $pid $msg"},
{datatype, string}
]}.
{mapping, "log.console.formatter.json.field_map", "rabbit.log.console.formatter", [
{default, "time level msg gl:- logger_formatter:- report_cb:- error_logger:-"},
{datatype, string}
]}.
{mapping, "log.console.formatter.json.verbosity_map", "rabbit.log.console.formatter", [
{default, ""},
{datatype, string}
]}.
{translation, "rabbit.log.console.formatter",
fun(Conf) ->
rabbit_prelaunch_early_logging:translate_formatter_conf("log.console.formatter", Conf)
end}.
{mapping, "log.exchange", "rabbit.log.exchange.enabled", [
{datatype, {enum, [true, false]}}
@ -1199,6 +1271,38 @@ end}.
{mapping, "log.exchange.level", "rabbit.log.exchange.level", [
{datatype, {enum, [debug, info, notice, warning, error, critical, alert, emergency, none]}}
]}.
{mapping, "log.exchange.formatter", "rabbit.log.exchange.formatter", [
{default, plaintext},
{datatype, {enum, [plaintext, json]}}
]}.
{mapping, "log.exchange.formatter.time_format", "rabbit.log.console.formatter", [
{default, rfc3339_space},
{datatype, [{enum, [rfc3339_space, rfc3339_T, epoch_usecs, epoch_secs, lager_default]}, string]}
]}.
{mapping, "log.exchange.formatter.level_format", "rabbit.log.exchange.formatter", [
{default, lc},
{datatype, {enum, [lc, uc, lc3, uc3, lc4, uc4]}}
]}.
{mapping, "log.exchange.formatter.single_line", "rabbit.log.file.formatter", [
{default, off},
{datatype, flag}
]}.
{mapping, "log.exchange.formatter.plaintext.format", "rabbit.log.exchange.formatter", [
{default, "$time [$level] $pid $msg"},
{datatype, string}
]}.
{mapping, "log.exchange.formatter.json.field_map", "rabbit.log.exchange.formatter", [
{default, "time level msg gl:- logger_formatter:- report_cb:- error_logger:-"},
{datatype, string}
]}.
{mapping, "log.exchange.formatter.json.verbosity_map", "rabbit.log.exchange.formatter", [
{default, ""},
{datatype, string}
]}.
{translation, "rabbit.log.exchange.formatter",
fun(Conf) ->
rabbit_prelaunch_early_logging:translate_formatter_conf("log.exchange.formatter", Conf)
end}.
{mapping, "log.syslog", "rabbit.log.syslog.enabled", [
{datatype, {enum, [true, false]}}
@ -1207,6 +1311,38 @@ end}.
{mapping, "log.syslog.level", "rabbit.log.syslog.level", [
{datatype, {enum, [debug, info, notice, warning, error, critical, alert, emergency, none]}}
]}.
{mapping, "log.syslog.formatter", "rabbit.log.syslog.formatter", [
{default, plaintext},
{datatype, {enum, [plaintext, json]}}
]}.
{mapping, "log.syslog.formatter.time_format", "rabbit.log.console.formatter", [
{default, rfc3339_space},
{datatype, [{enum, [rfc3339_space, rfc3339_T, epoch_usecs, epoch_secs, lager_default]}, string]}
]}.
{mapping, "log.syslog.formatter.level_format", "rabbit.log.syslog.formatter", [
{default, lc},
{datatype, {enum, [lc, uc, lc3, uc3, lc4, uc4]}}
]}.
{mapping, "log.syslog.formatter.single_line", "rabbit.log.file.formatter", [
{default, off},
{datatype, flag}
]}.
{mapping, "log.syslog.formatter.plaintext.format", "rabbit.log.syslog.formatter", [
{default, "$msg"},
{datatype, string}
]}.
{mapping, "log.syslog.formatter.json.field_map", "rabbit.log.syslog.formatter", [
{default, "time level msg gl:- logger_formatter:- report_cb:- error_logger:-"},
{datatype, string}
]}.
{mapping, "log.syslog.formatter.json.verbosity_map", "rabbit.log.syslog.formatter", [
{default, ""},
{datatype, string}
]}.
{translation, "rabbit.log.syslog.formatter",
fun(Conf) ->
rabbit_prelaunch_early_logging:translate_formatter_conf("log.syslog.formatter", Conf)
end}.
{mapping, "log.syslog.identity", "syslog.app_name", [
{datatype, string}
@ -1413,6 +1549,38 @@ end}.
{mapping, "log.file.rotation.count", "rabbit.log.file.count", [
{datatype, integer}
]}.
{mapping, "log.file.formatter", "rabbit.log.file.formatter", [
{default, plaintext},
{datatype, {enum, [plaintext, json]}}
]}.
{mapping, "log.file.formatter.time_format", "rabbit.log.console.formatter", [
{default, rfc3339_space},
{datatype, [{enum, [rfc3339_space, rfc3339_T, epoch_usecs, epoch_secs, lager_default]}, string]}
]}.
{mapping, "log.file.formatter.level_format", "rabbit.log.file.formatter", [
{default, lc},
{datatype, {enum, [lc, uc, lc3, uc3, lc4, uc4]}}
]}.
{mapping, "log.file.formatter.single_line", "rabbit.log.file.formatter", [
{default, off},
{datatype, flag}
]}.
{mapping, "log.file.formatter.plaintext.format", "rabbit.log.file.formatter", [
{default, "$time [$level] $pid $msg"},
{datatype, string}
]}.
{mapping, "log.file.formatter.json.field_map", "rabbit.log.file.formatter", [
{default, "time level msg gl:- logger_formatter:- report_cb:- error_logger:-"},
{datatype, string}
]}.
{mapping, "log.file.formatter.json.verbosity_map", "rabbit.log.file.formatter", [
{default, ""},
{datatype, string}
]}.
{translation, "rabbit.log.file.formatter",
fun(Conf) ->
rabbit_prelaunch_early_logging:translate_formatter_conf("log.file.formatter", Conf)
end}.
%% Log categories

View File

@ -98,7 +98,8 @@
log_locations/0]).
-ifdef(TEST).
-export([clear_config_run_number/0, get_less_severe_level/2]).
-export([clear_config_run_number/0,
get_less_severe_level/2]).
-endif.
-export_type([log_location/0]).
@ -125,11 +126,15 @@
%% domain.
-type console_props() :: [{level, logger:level()} |
{enabled, boolean()}].
{enabled, boolean()} |
{stdio, stdout | stderr} |
{formatter, {atom(), term()}}].
%% Console properties are the parameters in the configuration file for a
%% console-based handler.
-type exchange_props() :: console_props().
-type exchange_props() :: [{level, logger:level()} |
{enabled, boolean()} |
{formatter, {atom(), term()}}].
%% Exchange properties are the parameters in the configuration file for an
%% exchange-based handler.
@ -137,11 +142,14 @@
{file, file:filename() | false} |
{date, string()} |
{size, non_neg_integer()} |
{count, non_neg_integer()}].
{count, non_neg_integer()} |
{formatter, {atom(), term()}}].
%% File properties are the parameters in the configuration file for a
%% file-based handler.
-type syslog_props() :: console_props().
-type syslog_props() :: [{level, logger:level()} |
{enabled, boolean()} |
{formatter, {atom(), term()}}].
%% Syslog properties are the parameters in the configuration file for a
%% syslog-based handler.
@ -522,7 +530,58 @@ normalize_main_log_config(Props, DefaultProps) ->
Level -> #{outputs => [],
level => Level}
end,
normalize_main_log_config1(Props, Outputs).
Props1 = compute_implicitly_enabled_output(Props),
normalize_main_log_config1(Props1, Outputs).
compute_implicitly_enabled_output(Props) ->
{ConsoleEnabled, Props1} = compute_implicitly_enabled_output(
console, Props),
{ExchangeEnabled, Props2} = compute_implicitly_enabled_output(
exchange, Props1),
{SyslogEnabled, Props3} = compute_implicitly_enabled_output(
syslog, Props2),
FileDisabledByDefault =
ConsoleEnabled orelse ExchangeEnabled orelse SyslogEnabled,
FileProps = proplists:get_value(file, Props3, []),
case is_output_explicitely_enabled(FileProps) of
true ->
Props3;
false ->
case FileDisabledByDefault of
true ->
FileProps1 = lists:keystore(
file, 1, FileProps, {file, false}),
lists:keystore(
file, 1, Props3, {file, FileProps1});
false ->
Props3
end
end.
compute_implicitly_enabled_output(PropName, Props) ->
SubProps = proplists:get_value(PropName, Props, []),
{Enabled, SubProps1} = compute_implicitly_enabled_output1(SubProps),
{Enabled,
lists:keystore(PropName, 1, Props, {PropName, SubProps1})}.
compute_implicitly_enabled_output1(SubProps) ->
%% We consider the output enabled or disabled if:
%% * it is explicitely marked as such, or
%% * the level is set to a log level (enabled) or `none' (disabled)
Enabled = proplists:get_value(
enabled, SubProps,
proplists:get_value(level, SubProps, none) =/= none),
{Enabled,
lists:keystore(enabled, 1, SubProps, {enabled, Enabled})}.
is_output_explicitely_enabled(FileProps) ->
%% We consider the output enabled or disabled if:
%% * the file is explicitely set, or
%% * the level is set to a log level (enabled) or `none' (disabled)
File = proplists:get_value(file, FileProps),
Level = proplists:get_value(level, FileProps),
is_list(File) orelse (Level =/= undefined andalso Level =/= none).
normalize_main_log_config1([{Type, Props} | Rest],
#{outputs := Outputs} = LogConfig) ->
@ -555,13 +614,13 @@ normalize_main_output(console, Props, Outputs) ->
config => #{type => standard_io}},
Outputs);
normalize_main_output(syslog, Props, Outputs) ->
normalize_main_console_output(
normalize_main_syslog_output(
Props,
#{module => syslog_logger_h,
config => #{}},
Outputs);
normalize_main_output(exchange, Props, Outputs) ->
normalize_main_console_output(
normalize_main_exchange_output(
Props,
#{module => rabbit_logger_exchange_h,
config => #{}},
@ -571,42 +630,106 @@ normalize_main_output(exchange, Props, Outputs) ->
[logger:handler_config()]) ->
[logger:handler_config()].
normalize_main_file_output([{file, false} | _], _, Outputs) ->
normalize_main_file_output(Props, Output, Outputs) ->
Enabled = case proplists:get_value(file, Props) of
false -> false;
_ -> true
end,
case Enabled of
true -> normalize_main_file_output1(Props, Output, Outputs);
false -> remove_main_file_output(Outputs)
end.
normalize_main_file_output1(
[{file, Filename} | Rest],
#{config := Config} = Output, Outputs) ->
Output1 = Output#{config => Config#{file => Filename}},
normalize_main_file_output1(Rest, Output1, Outputs);
normalize_main_file_output1(
[{level, Level} | Rest],
Output, Outputs) ->
Output1 = Output#{level => Level},
normalize_main_file_output1(Rest, Output1, Outputs);
normalize_main_file_output1(
[{date, DateSpec} | Rest],
#{config := Config} = Output, Outputs) ->
Output1 = Output#{config => Config#{rotate_on_date => DateSpec}},
normalize_main_file_output1(Rest, Output1, Outputs);
normalize_main_file_output1(
[{size, Size} | Rest],
#{config := Config} = Output, Outputs) ->
Output1 = Output#{config => Config#{max_no_bytes => Size}},
normalize_main_file_output1(Rest, Output1, Outputs);
normalize_main_file_output1(
[{count, Count} | Rest],
#{config := Config} = Output, Outputs) ->
Output1 = Output#{config => Config#{max_no_files => Count}},
normalize_main_file_output1(Rest, Output1, Outputs);
normalize_main_file_output1(
[{formatter, undefined} | Rest],
Output, Outputs) ->
normalize_main_file_output1(Rest, Output, Outputs);
normalize_main_file_output1(
[{formatter, Formatter} | Rest],
Output, Outputs) ->
Output1 = Output#{formatter => Formatter},
normalize_main_file_output1(Rest, Output1, Outputs);
normalize_main_file_output1([], Output, Outputs) ->
[Output | Outputs].
remove_main_file_output(Outputs) ->
lists:filter(
fun
(#{module := rabbit_logger_std_h,
config := #{type := file}}) -> false;
(_) -> true
end, Outputs);
normalize_main_file_output([{file, Filename} | Rest],
#{config := Config} = Output, Outputs) ->
Output1 = Output#{config => Config#{file => Filename}},
normalize_main_file_output(Rest, Output1, Outputs);
normalize_main_file_output([{level, Level} | Rest],
Output, Outputs) ->
Output1 = Output#{level => Level},
normalize_main_file_output(Rest, Output1, Outputs);
normalize_main_file_output([{date, DateSpec} | Rest],
#{config := Config} = Output, Outputs) ->
Output1 = Output#{config => Config#{rotate_on_date => DateSpec}},
normalize_main_file_output(Rest, Output1, Outputs);
normalize_main_file_output([{size, Size} | Rest],
#{config := Config} = Output, Outputs) ->
Output1 = Output#{config => Config#{max_no_bytes => Size}},
normalize_main_file_output(Rest, Output1, Outputs);
normalize_main_file_output([{count, Count} | Rest],
#{config := Config} = Output, Outputs) ->
Output1 = Output#{config => Config#{max_no_files => Count}},
normalize_main_file_output(Rest, Output1, Outputs);
normalize_main_file_output([], Output, Outputs) ->
[Output | Outputs].
end, Outputs).
-spec normalize_main_console_output(console_props(), logger:handler_config(),
[logger:handler_config()]) ->
[logger:handler_config()].
normalize_main_console_output(
[{enabled, false} | _],
normalize_main_console_output(Props, Output, Outputs) ->
Enabled = proplists:get_value(enabled, Props),
case Enabled of
true -> normalize_main_console_output1(Props, Output, Outputs);
false -> remove_main_console_output(Output, Outputs)
end.
normalize_main_console_output1(
[{enabled, true} | Rest],
Output, Outputs) ->
normalize_main_console_output1(Rest, Output, Outputs);
normalize_main_console_output1(
[{level, Level} | Rest],
Output, Outputs) ->
Output1 = Output#{level => Level},
normalize_main_console_output1(Rest, Output1, Outputs);
normalize_main_console_output1(
[{stdio, stdout} | Rest],
#{config := Config} = Output, Outputs) ->
Config1 = Config#{type => standard_io},
Output1 = Output#{config => Config1},
normalize_main_console_output1(Rest, Output1, Outputs);
normalize_main_console_output1(
[{stdio, stderr} | Rest],
#{config := Config} = Output, Outputs) ->
Config1 = Config#{type => standard_error},
Output1 = Output#{config => Config1},
normalize_main_console_output1(Rest, Output1, Outputs);
normalize_main_console_output1(
[{formatter, undefined} | Rest],
Output, Outputs) ->
normalize_main_console_output1(Rest, Output, Outputs);
normalize_main_console_output1(
[{formatter, Formatter} | Rest],
Output, Outputs) ->
Output1 = Output#{formatter => Formatter},
normalize_main_console_output1(Rest, Output1, Outputs);
normalize_main_console_output1([], Output, Outputs) ->
[Output | Outputs].
remove_main_console_output(
#{module := Mod1, config := #{type := Stddev}},
Outputs)
when ?IS_STD_H_COMPAT(Mod1) andalso
@ -623,23 +746,90 @@ normalize_main_console_output(
false;
(_) ->
true
end, Outputs);
normalize_main_console_output(
[{enabled, false} | _],
#{module := Mod},
Outputs)
when Mod =:= syslog_logger_h orelse
Mod =:= rabbit_logger_exchange_h ->
lists:filter(fun(#{module := M}) -> M =/= Mod end, Outputs);
normalize_main_console_output([{enabled, true} | Rest], Output, Outputs) ->
normalize_main_console_output(Rest, Output, Outputs);
normalize_main_console_output([{level, Level} | Rest],
Output, Outputs) ->
end, Outputs).
-spec normalize_main_exchange_output(
exchange_props(), logger:handler_config(),
[logger:handler_config()]) ->
[logger:handler_config()].
normalize_main_exchange_output(Props, Output, Outputs) ->
Enabled = proplists:get_value(enabled, Props),
case Enabled of
true -> normalize_main_exchange_output1(Props, Output, Outputs);
false -> remove_main_exchange_output(Output, Outputs)
end.
normalize_main_exchange_output1(
[{enabled, true} | Rest],
Output, Outputs) ->
normalize_main_exchange_output1(Rest, Output, Outputs);
normalize_main_exchange_output1(
[{level, Level} | Rest],
Output, Outputs) ->
Output1 = Output#{level => Level},
normalize_main_console_output(Rest, Output1, Outputs);
normalize_main_console_output([], Output, Outputs) ->
normalize_main_exchange_output1(Rest, Output1, Outputs);
normalize_main_exchange_output1(
[{formatter, undefined} | Rest],
Output, Outputs) ->
normalize_main_exchange_output1(Rest, Output, Outputs);
normalize_main_exchange_output1(
[{formatter, Formatter} | Rest],
Output, Outputs) ->
Output1 = Output#{formatter => Formatter},
normalize_main_exchange_output1(Rest, Output1, Outputs);
normalize_main_exchange_output1([], Output, Outputs) ->
[Output | Outputs].
remove_main_exchange_output(
#{module := rabbit_logger_exchange_h}, Outputs) ->
lists:filter(
fun
(#{module := rabbit_logger_exchange_h}) -> false;
(_) -> true
end, Outputs).
-spec normalize_main_syslog_output(
syslog_props(), logger:handler_config(),
[logger:handler_config()]) ->
[logger:handler_config()].
normalize_main_syslog_output(Props, Output, Outputs) ->
Enabled = proplists:get_value(enabled, Props),
case Enabled of
true -> normalize_main_syslog_output1(Props, Output, Outputs);
false -> remove_main_syslog_output(Output, Outputs)
end.
normalize_main_syslog_output1(
[{enabled, true} | Rest],
Output, Outputs) ->
normalize_main_syslog_output1(Rest, Output, Outputs);
normalize_main_syslog_output1(
[{level, Level} | Rest],
Output, Outputs) ->
Output1 = Output#{level => Level},
normalize_main_syslog_output1(Rest, Output1, Outputs);
normalize_main_syslog_output1(
[{formatter, undefined} | Rest],
Output, Outputs) ->
normalize_main_syslog_output1(Rest, Output, Outputs);
normalize_main_syslog_output1(
[{formatter, Formatter} | Rest],
Output, Outputs) ->
Output1 = Output#{formatter => Formatter},
normalize_main_syslog_output1(Rest, Output1, Outputs);
normalize_main_syslog_output1([], Output, Outputs) ->
[Output | Outputs].
remove_main_syslog_output(
#{module := syslog_logger_h}, Outputs) ->
lists:filter(
fun
(#{module := syslog_logger_h}) -> false;
(_) -> true
end, Outputs).
-spec normalize_per_cat_log_config(per_cat_env()) -> per_cat_log_config().
normalize_per_cat_log_config(Props) ->
@ -1292,6 +1482,7 @@ adjust_running_dependencies1([]) ->
do_install_handlers([#{id := Id, module := Module} = Handler | Rest]) ->
case logger:add_handler(Id, Module, Handler) of
ok ->
ok = remove_syslog_logger_h_hardcoded_filters(Handler),
do_install_handlers(Rest);
{error, {handler_not_added, {open_failed, Filename, Reason}}} ->
throw({error, {cannot_log_to_file, Filename, Reason}});
@ -1301,6 +1492,14 @@ do_install_handlers([#{id := Id, module := Module} = Handler | Rest]) ->
do_install_handlers([]) ->
ok.
remove_syslog_logger_h_hardcoded_filters(
#{id := Id, module := syslog_logger_h}) ->
_ = logger:remove_handler_filter(Id, progress),
_ = logger:remove_handler_filter(Id, remote_gl),
ok;
remove_syslog_logger_h_hardcoded_filters(_) ->
ok.
-spec remove_old_handlers() -> ok.
remove_old_handlers() ->

File diff suppressed because it is too large Load Diff