summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--deps/rabbit/Makefile3
-rw-r--r--deps/rabbit/apps/rabbitmq_prelaunch/Makefile3
-rw-r--r--deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_logger_fmt_helpers.erl187
-rw-r--r--deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_logger_json_fmt.erl118
-rw-r--r--deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_logger_text_fmt.erl201
-rw-r--r--deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_prelaunch_early_logging.erl361
-rw-r--r--deps/rabbit/priv/schema/rabbit.schema168
-rw-r--r--deps/rabbit/src/rabbit_prelaunch_logging.erl293
-rw-r--r--deps/rabbit/test/logging_SUITE.erl1080
9 files changed, 1934 insertions, 480 deletions
diff --git a/deps/rabbit/Makefile b/deps/rabbit/Makefile
index 259a7c9123..0e7f98815a 100644
--- a/deps/rabbit/Makefile
+++ b/deps/rabbit/Makefile
@@ -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
diff --git a/deps/rabbit/apps/rabbitmq_prelaunch/Makefile b/deps/rabbit/apps/rabbitmq_prelaunch/Makefile
index 54d4e199f7..c95d097907 100644
--- a/deps/rabbit/apps/rabbitmq_prelaunch/Makefile
+++ b/deps/rabbit/apps/rabbitmq_prelaunch/Makefile
@@ -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
diff --git a/deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_logger_fmt_helpers.erl b/deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_logger_fmt_helpers.erl
new file mode 100644
index 0000000000..b4976d2962
--- /dev/null
+++ b/deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_logger_fmt_helpers.erl
@@ -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]}.
diff --git a/deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_logger_json_fmt.erl b/deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_logger_json_fmt.erl
index d621de13ea..46863754ad 100644
--- a/deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_logger_json_fmt.erl
+++ b/deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_logger_json_fmt.erl
@@ -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).
-
-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]}.
+level_to_verbosity(_, _) ->
+ undefined.
-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).
diff --git a/deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_logger_text_fmt.erl b/deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_logger_text_fmt.erl
index 30cf16fb34..5e703d2fc2 100644
--- a/deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_logger_text_fmt.erl
+++ b/deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_logger_text_fmt.erl
@@ -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).
-
-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]).
-
-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.
-
-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_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) ->
+ FormattedMsg = rabbit_logger_fmt_helpers:format_msg(Msg, Meta, Config),
+ prepend_prefix_to_msg_and_add_color(
+ Prefix, Color, FormattedMsg, LogEvent, Config).
+
+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, []).
+
+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).
+
+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.
+
+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]).
diff --git a/deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_prelaunch_early_logging.erl b/deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_prelaunch_early_logging.erl
index 7d9d7f263a..89c5c6c3d5 100644
--- a/deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_prelaunch_early_logging.erl
+++ b/deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_prelaunch_early_logging.erl
@@ -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].
diff --git a/deps/rabbit/priv/schema/rabbit.schema b/deps/rabbit/priv/schema/rabbit.schema
index 62368486e6..a32157b370 100644
--- a/deps/rabbit/priv/schema/rabbit.schema
+++ b/deps/rabbit/priv/schema/rabbit.schema
@@ -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
diff --git a/deps/rabbit/src/rabbit_prelaunch_logging.erl b/deps/rabbit/src/rabbit_prelaunch_logging.erl
index b70c881582..8195bfed4b 100644
--- a/deps/rabbit/src/rabbit_prelaunch_logging.erl
+++ b/deps/rabbit/src/rabbit_prelaunch_logging.erl
@@ -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) ->
- 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) ->
+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_output(Rest, Output1, Outputs);
-normalize_main_file_output([{level, Level} | Rest],
- Output, Outputs) ->
+ normalize_main_file_output1(Rest, Output1, Outputs);
+normalize_main_file_output1(
+ [{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) ->
+ 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_output(Rest, Output1, Outputs);
-normalize_main_file_output([{size, Size} | Rest],
- #{config := Config} = Output, Outputs) ->
+ 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_output(Rest, Output1, Outputs);
-normalize_main_file_output([{count, Count} | Rest],
- #{config := Config} = Output, Outputs) ->
+ 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_output(Rest, Output1, Outputs);
-normalize_main_file_output([], Output, Outputs) ->
+ 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).
+
-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() ->
diff --git a/deps/rabbit/test/logging_SUITE.erl b/deps/rabbit/test/logging_SUITE.erl
index e5c865bed8..eb0d907385 100644
--- a/deps/rabbit/test/logging_SUITE.erl
+++ b/deps/rabbit/test/logging_SUITE.erl
@@ -14,7 +14,9 @@
-include_lib("rabbit_common/include/logging.hrl").
-include_lib("amqp_client/include/amqp_client.hrl").
--export([all/0,
+-export([suite/0,
+ all/0,
+ groups/0,
init_per_suite/1,
end_per_suite/1,
init_per_group/2,
@@ -23,25 +25,76 @@
end_per_testcase/2,
logging_with_default_config_works/1,
+ setting_log_levels_in_env_works/1,
+ setting_log_levels_in_config_works/1,
+ setting_log_levels_in_config_with_output_overridden_in_env_works/1,
+ setting_message_format_works/1,
+ setting_level_format_works/1,
+ setting_time_format_works/1,
+ logging_as_single_line_works/1,
+ logging_as_multi_line_works/1,
+ formatting_as_json_configured_in_env_works/1,
+ formatting_as_json_configured_in_config_works/1,
+ renaming_json_fields_works/1,
+ removing_specific_json_fields_works/1,
+ removing_non_mentionned_json_fields_works/1,
+ configuring_verbosity_works/1,
+
logging_to_stdout_configured_in_env_works/1,
logging_to_stdout_configured_in_config_works/1,
logging_to_stderr_configured_in_env_works/1,
+ logging_to_stderr_configured_in_config_works/1,
+ formatting_with_colors_works/1,
+ formatting_without_colors_works/1,
+
logging_to_exchange_works/1,
- setting_log_levels_in_env_works/1,
- setting_log_levels_in_config_works/1,
- setting_log_levels_in_config_with_output_overridden_in_env_works/1,
- format_messages_as_json_works/1]).
+
+ logging_to_syslog_works/1]).
+
+suite() ->
+ [{timetrap, {minutes, 1}}].
all() ->
- [logging_with_default_config_works,
- logging_to_stdout_configured_in_env_works,
- logging_to_stdout_configured_in_config_works,
- logging_to_stderr_configured_in_env_works,
- logging_to_exchange_works,
- setting_log_levels_in_env_works,
- setting_log_levels_in_config_works,
- setting_log_levels_in_config_with_output_overridden_in_env_works,
- format_messages_as_json_works].
+ [
+ {group, file_output},
+ {group, console_output},
+ {group, exchange_output},
+ {group, syslog_output}
+ ].
+
+groups() ->
+ [
+ {file_output, [],
+ [logging_with_default_config_works,
+ setting_log_levels_in_env_works,
+ setting_log_levels_in_config_works,
+ setting_log_levels_in_config_with_output_overridden_in_env_works,
+ setting_message_format_works,
+ setting_level_format_works,
+ setting_time_format_works,
+ logging_as_single_line_works,
+ logging_as_multi_line_works,
+ formatting_as_json_configured_in_env_works,
+ formatting_as_json_configured_in_config_works,
+ renaming_json_fields_works,
+ removing_specific_json_fields_works,
+ removing_non_mentionned_json_fields_works,
+ configuring_verbosity_works]},
+
+ {console_output, [],
+ [logging_to_stdout_configured_in_env_works,
+ logging_to_stdout_configured_in_config_works,
+ logging_to_stderr_configured_in_env_works,
+ logging_to_stderr_configured_in_config_works,
+ formatting_with_colors_works,
+ formatting_without_colors_works]},
+
+ {exchange_output, [],
+ [logging_to_exchange_works]},
+
+ {syslog_output, [],
+ [logging_to_syslog_works]}
+ ].
init_per_suite(Config) ->
rabbit_ct_helpers:log_environment(),
@@ -50,50 +103,91 @@ init_per_suite(Config) ->
end_per_suite(Config) ->
Config.
+init_per_group(syslog_output, Config) ->
+ Config1 = start_fake_syslogd(Config),
+ TcpPort = ?config(syslogd_tcp_port, Config1),
+ ok = application:set_env(
+ syslog, logger, [],
+ [{persistent, true}]),
+ ok = application:set_env(
+ syslog, syslog_error_logger, false,
+ [{persistent, true}]),
+ ok = application:set_env(
+ syslog, protocol, {rfc3164, tcp},
+ [{persistent, true}]),
+ ok = application:set_env(
+ syslog, dest_port, TcpPort,
+ [{persistent, true}]),
+ {ok, _} = application:ensure_all_started(syslog),
+ Config1;
init_per_group(_, Config) ->
Config.
+end_per_group(syslog_output, Config) ->
+ ok = application:stop(syslog),
+ stop_fake_syslogd(Config);
end_per_group(_, Config) ->
Config.
-init_per_testcase(logging_to_exchange_works = Testcase, Config) ->
- rabbit_ct_helpers:testcase_started(Config, Testcase),
- Config1 = rabbit_ct_helpers:set_config(
- Config,
- [{rmq_nodes_count, 1},
- {rmq_nodename_suffix, Testcase}]),
- Config2 = rabbit_ct_helpers:merge_app_env(
- Config1,
- {rabbit, [{log, [{exchange, [{enabled, true},
- {level, info}]},
- {file, [{level, info}]}]}]}),
- rabbit_ct_helpers:run_steps(
- Config2,
- rabbit_ct_broker_helpers:setup_steps() ++
- rabbit_ct_client_helpers:setup_steps());
init_per_testcase(Testcase, Config) ->
- remove_all_handlers(),
- application:unset_env(rabbit, log),
- LogBaseDir = filename:join(
- ?config(priv_dir, Config),
- atom_to_list(Testcase)),
- Config1 = rabbit_ct_helpers:set_config(
- Config, {log_base_dir, LogBaseDir}),
+ rabbit_ct_helpers:testcase_started(Config, Testcase),
+ GroupProps = ?config(tc_group_properties, Config),
+ Group = proplists:get_value(name, GroupProps),
+ case Group of
+ %% The exchange output requires RabbitMQ to run. All testcases in this
+ %% group will run in the context of that RabbitMQ node.
+ exchange_output ->
+ ExchProps = case Testcase of
+ logging_to_exchange_works ->
+ [{enabled, true},
+ {level, info}]
+ end,
+ Config1 = rabbit_ct_helpers:set_config(
+ Config,
+ [{rmq_nodes_count, 1},
+ {rmq_nodename_suffix, Testcase}]),
+ Config2 = rabbit_ct_helpers:merge_app_env(
+ Config1,
+ {rabbit, [{log, [{exchange, ExchProps},
+ {file, [{level, info}]}]}]}),
+ rabbit_ct_helpers:run_steps(
+ Config2,
+ rabbit_ct_broker_helpers:setup_steps() ++
+ rabbit_ct_client_helpers:setup_steps());
+
+ %% Other groups and testcases runs the tested code directly without a
+ %% RabbitMQ node running.
+ _ ->
+ remove_all_handlers(),
+ application:unset_env(rabbit, log),
+ LogBaseDir = filename:join(
+ ?config(priv_dir, Config),
+ atom_to_list(Testcase)),
+ rabbit_ct_helpers:set_config(
+ Config, {log_base_dir, LogBaseDir})
+ end.
+
+end_per_testcase(Testcase, Config) ->
+ Config1 = case rabbit_ct_helpers:get_config(Config, rmq_nodes_count) of
+ undefined ->
+ application:unset_env(rabbit, log),
+ Config;
+ _ ->
+ rabbit_ct_helpers:run_steps(
+ Config,
+ rabbit_ct_client_helpers:teardown_steps() ++
+ rabbit_ct_broker_helpers:teardown_steps())
+ end,
rabbit_ct_helpers:testcase_finished(Config1, Testcase).
-end_per_testcase(logging_to_exchange_works, Config) ->
- rabbit_ct_helpers:run_steps(
- Config,
- rabbit_ct_client_helpers:teardown_steps() ++
- rabbit_ct_broker_helpers:teardown_steps());
-end_per_testcase(_, Config) ->
- application:unset_env(rabbit, log),
- Config.
-
remove_all_handlers() ->
_ = [logger:remove_handler(Id)
|| #{id := Id} <- logger:get_handler_config()].
+%% -------------------------------------------------------------------
+%% Testcases.
+%% -------------------------------------------------------------------
+
logging_with_default_config_works(Config) ->
Context = default_context(Config),
rabbit_prelaunch_logging:clear_config_run_number(),
@@ -143,156 +237,6 @@ logging_with_default_config_works(Config) ->
#{domain => ?RMQLOG_DOMAIN_GLOBAL})),
ok.
-logging_to_stdout_configured_in_env_works(Config) ->
- #{var_origins := Origins0} = Context0 = default_context(Config),
- Context = Context0#{main_log_file => "-",
- var_origins => Origins0#{
- main_log_file => environment}},
- logging_to_stddev_works(standard_io, rmq_1_stdout, Config, Context).
-
-logging_to_stdout_configured_in_config_works(Config) ->
- Context = default_context(Config),
- ok = application:set_env(
- rabbit, log,
- [{console, [{enabled, true}]}],
- [{persistent, true}]),
- logging_to_stddev_works(standard_io, rmq_1_stdout, Config, Context).
-
-logging_to_stderr_configured_in_env_works(Config) ->
- #{var_origins := Origins0} = Context0 = default_context(Config),
- Context = Context0#{main_log_file => "-stderr",
- var_origins => Origins0#{
- main_log_file => environment}},
- logging_to_stddev_works(standard_error, rmq_1_stderr, Config, Context).
-
-logging_to_stddev_works(Stddev, Id, Config, Context) ->
- rabbit_prelaunch_logging:clear_config_run_number(),
- rabbit_prelaunch_logging:setup(Context),
-
- Handlers = logger:get_handler_config(),
-
- StddevHandler = get_handler_by_id(Handlers, Id),
- ?assertNotEqual(undefined, StddevHandler),
- ?assertMatch(
- #{level := info,
- module := rabbit_logger_std_h,
- filter_default := log,
- filters := [{progress_reports, {_, stop}},
- {rmqlog_filter, {_, #{global := info,
- upgrade := none}}}],
- formatter := {rabbit_logger_text_fmt, _},
- config := #{type := Stddev}},
- StddevHandler),
-
- UpgradeFileHandler = get_handler_by_id(Handlers, rmq_1_file_1),
- UpgradeFile = upgrade_log_file_in_context(Context),
- ?assertNotEqual(undefined, UpgradeFileHandler),
- ?assertMatch(
- #{level := info,
- module := rabbit_logger_std_h,
- filter_default := stop,
- filters := [{rmqlog_filter, {_, #{upgrade := info}}}],
- formatter := {rabbit_logger_text_fmt, _},
- config := #{type := file,
- file := UpgradeFile}},
- UpgradeFileHandler),
-
- ?assert(ping_log(Id, info, Config)),
- ?assert(ping_log(Id, info,
- #{domain => ?RMQLOG_DOMAIN_GLOBAL}, Config)),
- ?assert(ping_log(Id, info,
- #{domain => ['3rd_party']}, Config)),
- ?assertNot(ping_log(Id, info,
- #{domain => ?RMQLOG_DOMAIN_UPGRADE}, Config)),
-
- ?assert(ping_log(rmq_1_file_1, info,
- #{domain => ?RMQLOG_DOMAIN_UPGRADE})),
- ?assertNot(ping_log(rmq_1_file_1, info,
- #{domain => ?RMQLOG_DOMAIN_GLOBAL})),
- ok.
-
-logging_to_exchange_works(Config) ->
- Context = rabbit_ct_broker_helpers:rpc(
- Config, 0,
- rabbit_prelaunch, get_context, []),
- Handlers = rabbit_ct_broker_helpers:rpc(
- Config, 0,
- logger, get_handler_config, []),
-
- ExchangeHandler = get_handler_by_id(Handlers, rmq_1_exchange),
- ?assertNotEqual(undefined, ExchangeHandler),
- ?assertMatch(
- #{level := info,
- module := rabbit_logger_exchange_h,
- filter_default := log,
- filters := [{progress_reports, {_, stop}},
- {rmqlog_filter, {_, #{global := info,
- upgrade := none}}}],
- formatter := {rabbit_logger_text_fmt, _},
- config := #{exchange := _}},
- ExchangeHandler),
- #{config :=
- #{exchange := #resource{name = XName} = Exchange}} = ExchangeHandler,
-
- UpgradeFileHandler = get_handler_by_id(Handlers, rmq_1_file_2),
- UpgradeFile = upgrade_log_file_in_context(Context),
- ?assertNotEqual(undefined, UpgradeFileHandler),
- ?assertMatch(
- #{level := info,
- module := rabbit_logger_std_h,
- filter_default := stop,
- filters := [{rmqlog_filter, {_, #{upgrade := info}}}],
- formatter := {rabbit_logger_text_fmt, _},
- config := #{type := file,
- file := UpgradeFile}},
- UpgradeFileHandler),
-
- %% Wait for the expected exchange to be automatically declared.
- lists:any(
- fun(_) ->
- Ret = rabbit_ct_broker_helpers:rpc(
- Config, 0,
- rabbit_exchange, lookup, [Exchange]),
- case Ret of
- {ok, _} -> true;
- _ -> timer:sleep(500),
- false
- end
- end, lists:seq(1, 20)),
-
- %% Declare a queue to collect all logged messages.
- {Conn, Chan} = rabbit_ct_client_helpers:open_connection_and_channel(
- Config),
- QName = <<"log-messages">>,
- ?assertMatch(
- #'queue.declare_ok'{},
- amqp_channel:call(Chan, #'queue.declare'{queue = QName,
- durable = false})),
- ?assertMatch(
- #'queue.bind_ok'{},
- amqp_channel:call(Chan, #'queue.bind'{queue = QName,
- exchange = XName,
- routing_key = <<"#">>})),
- Config1 = rabbit_ct_helpers:set_config(
- Config, {test_channel_and_queue, {Chan, QName}}),
-
- ?assert(ping_log(rmq_1_exchange, info, Config1)),
- ?assert(ping_log(rmq_1_exchange, info,
- #{domain => ?RMQLOG_DOMAIN_GLOBAL}, Config1)),
- ?assert(ping_log(rmq_1_exchange, info,
- #{domain => ['3rd_party']}, Config1)),
- ?assertNot(ping_log(rmq_1_exchange, info,
- #{domain => ?RMQLOG_DOMAIN_UPGRADE}, Config1)),
-
- ?assert(ping_log(rmq_1_file_2, info,
- #{domain => ?RMQLOG_DOMAIN_UPGRADE}, Config)),
- ?assertNot(ping_log(rmq_1_file_2, info,
- #{domain => ?RMQLOG_DOMAIN_GLOBAL}, Config)),
-
- amqp_channel:call(Chan, #'queue.delete'{queue = QName}),
- rabbit_ct_client_helpers:close_connection_and_channel(Conn, Chan),
- ok.
-
setting_log_levels_in_env_works(Config) ->
GlobalLevel = warning,
PrelaunchLevel = error,
@@ -443,8 +387,7 @@ setting_log_levels_in_config_with_output_overridden_in_env_works(Config) ->
var_origins => Origins0#{
main_log_file => environment}},
ok = application:set_env(
- rabbit, log,
- [{console, [{level, debug}]}],
+ rabbit, log, [{console, [{level, debug}]}],
[{persistent, true}]),
rabbit_prelaunch_logging:clear_config_run_number(),
rabbit_prelaunch_logging:setup(Context),
@@ -486,10 +429,177 @@ setting_log_levels_in_config_with_output_overridden_in_env_works(Config) ->
#{domain => ?RMQLOG_DOMAIN_UPGRADE}, Config)),
ok.
-format_messages_as_json_works(Config) ->
+setting_message_format_works(Config) ->
+ Context = default_context(Config),
+ Format = ["level=", level, " ",
+ "md_key=", md_key, " ",
+ "unknown_field=", unknown_field, " ",
+ "msg=", msg],
+ {PrefixFormat, LineFormat} =
+ rabbit_prelaunch_early_logging:determine_prefix(Format),
+ ok = application:set_env(
+ rabbit, log,
+ [{file, [{formatter, {rabbit_logger_text_fmt,
+ #{prefix_format => PrefixFormat,
+ line_format => LineFormat}}}]}],
+ [{persistent, true}]),
+ rabbit_prelaunch_logging:clear_config_run_number(),
+ rabbit_prelaunch_logging:setup(Context),
+
+ Metadata = #{md_key => "md_value"},
+ {RandomMsg, Line} = log_and_return_line(Context, Metadata),
+
+ RandomMsgBin = list_to_binary(RandomMsg),
+ ?assertEqual(
+ <<"level=warn ",
+ "md_key=md_value ",
+ "unknown_field=<unknown unknown_field> "
+ "msg=", RandomMsgBin/binary>>,
+ Line).
+
+setting_level_format_works(Config) ->
+ LevelFormats = #{lc => "warning",
+ uc => "WARNING",
+ lc3 => "wrn",
+ uc3 => "WRN",
+ lc4 => "warn",
+ uc4 => "WARN"},
+ maps:fold(
+ fun(LevelFormat, LevelName, Acc) ->
+ remove_all_handlers(),
+ setting_level_format_works(
+ LevelFormat, list_to_binary(LevelName), Config),
+ Acc
+ end, ok, LevelFormats).
+
+setting_level_format_works(LevelFormat, LevelName, Config) ->
+ Context = default_context(Config),
+ Format = [level, " ", msg],
+ {PrefixFormat, LineFormat} =
+ rabbit_prelaunch_early_logging:determine_prefix(Format),
+ ok = application:set_env(
+ rabbit, log,
+ [{file, [{formatter, {rabbit_logger_text_fmt,
+ #{level_format => LevelFormat,
+ prefix_format => PrefixFormat,
+ line_format => LineFormat}}}]}],
+ [{persistent, true}]),
+ rabbit_prelaunch_logging:clear_config_run_number(),
+ rabbit_prelaunch_logging:setup(Context),
+
+ {RandomMsg, Line} = log_and_return_line(Context, #{}),
+
+ RandomMsgBin = list_to_binary(RandomMsg),
+ ?assertEqual(
+ <<LevelName/binary, " ", RandomMsgBin/binary>>,
+ Line).
+
+setting_time_format_works(Config) ->
+ DateTime = "2018-02-01T16:17:58.123456+01:00",
+ Timestamp = calendar:rfc3339_to_system_time(
+ DateTime, [{unit, microsecond}]),
+ TimeFormats =
+ #{{rfc3339, $T} => DateTime,
+ {rfc3339, $\s} => "2018-02-01 16:17:58.123456+01:00",
+ {epoch, usecs, binary} => integer_to_list(Timestamp),
+ {epoch, secs, binary} => io_lib:format("~.6.0f", [Timestamp / 1000000]),
+ {"~4..0b-~2..0b-~2..0b "
+ "~2..0b:~2..0b:~2..0b.~3..0b",
+ [year, month, day,
+ hour, minute, second,
+ {second_fractional, 3}]} => "2018-02-01 16:17:58.123"},
+ maps:fold(
+ fun(TimeFormat, TimeValue, Acc) ->
+ remove_all_handlers(),
+ setting_time_format_works(
+ Timestamp, TimeFormat, list_to_binary(TimeValue), Config),
+ Acc
+ end, ok, TimeFormats).
+
+setting_time_format_works(Timestamp, TimeFormat, TimeValue, Config) ->
+ Context = default_context(Config),
+ Format = [time, " ", msg],
+ {PrefixFormat, LineFormat} =
+ rabbit_prelaunch_early_logging:determine_prefix(Format),
+ ok = application:set_env(
+ rabbit, log,
+ [{file, [{formatter, {rabbit_logger_text_fmt,
+ #{time_format => TimeFormat,
+ prefix_format => PrefixFormat,
+ line_format => LineFormat}}}]}],
+ [{persistent, true}]),
+ rabbit_prelaunch_logging:clear_config_run_number(),
+ rabbit_prelaunch_logging:setup(Context),
+
+ Metadata = #{time => Timestamp},
+ {RandomMsg, Line} = log_and_return_line(Context, Metadata),
+
+ RandomMsgBin = list_to_binary(RandomMsg),
+ ?assertEqual(
+ <<TimeValue/binary, " ", RandomMsgBin/binary>>,
+ Line).
+
+logging_as_single_line_works(Config) ->
+ logging_as_single_or_multi_line_works(false, Config).
+
+logging_as_multi_line_works(Config) ->
+ logging_as_single_or_multi_line_works(true, Config).
+
+logging_as_single_or_multi_line_works(AsMultiline, Config) ->
+ Context = default_context(Config),
+ Format = [time, " ", msg],
+ {PrefixFormat, LineFormat} =
+ rabbit_prelaunch_early_logging:determine_prefix(Format),
+ ok = application:set_env(
+ rabbit, log,
+ [{file, [{formatter, {rabbit_logger_text_fmt,
+ #{single_line => not AsMultiline,
+ prefix_format => PrefixFormat,
+ line_format => LineFormat}}}]}],
+ [{persistent, true}]),
+ rabbit_prelaunch_logging:clear_config_run_number(),
+ rabbit_prelaunch_logging:setup(Context),
+
+ RandomMsg1 = get_random_string(
+ 32,
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"),
+ RandomMsg2 = get_random_string(
+ 32,
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"),
+ ?LOG_WARNING(RandomMsg1 ++ "\n" ++ RandomMsg2, #{}),
+
+ rabbit_logger_std_h:filesync(rmq_1_file_1),
+ MainFile = main_log_file_in_context(Context),
+ {ok, Content} = file:read_file(MainFile),
+ ReOpts = [{capture, none}, multiline],
+ case AsMultiline of
+ true ->
+ match = re:run(Content, RandomMsg1 ++ "$", ReOpts),
+ match = re:run(Content, RandomMsg2 ++ "$", ReOpts);
+ false ->
+ match = re:run(
+ Content,
+ RandomMsg1 ++ ", " ++ RandomMsg2 ++ "$",
+ ReOpts)
+ end.
+
+formatting_as_json_configured_in_env_works(Config) ->
#{var_origins := Origins0} = Context0 = default_context(Config),
Context = Context0#{log_levels => #{json => true},
var_origins => Origins0#{log_levels => environment}},
+ formatting_as_json_works(Config, Context).
+
+formatting_as_json_configured_in_config_works(Config) ->
+ Context = default_context(Config),
+ ok = application:set_env(
+ rabbit, log,
+ [{file, [{formatter, {rabbit_logger_json_fmt, #{}}}]}],
+ [{persistent, true}]),
+ formatting_as_json_works(Config, Context).
+
+formatting_as_json_works(_, Context) ->
rabbit_prelaunch_logging:clear_config_run_number(),
rabbit_prelaunch_logging:setup(Context),
@@ -512,10 +622,6 @@ format_messages_as_json_works(Config) ->
?assertNot(ping_log(rmq_1_file_1, info)),
- RandomMsg = get_random_string(
- 32,
- "abcdefghijklmnopqrstuvwxyz"
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ"),
Metadata = #{atom => rabbit,
integer => 1,
float => 1.42,
@@ -526,37 +632,383 @@ format_messages_as_json_works(Config) ->
pid => self(),
port => hd(erlang:ports()),
ref => erlang:make_ref()},
- ?LOG_INFO(RandomMsg, Metadata),
-
- rabbit_logger_std_h:filesync(rmq_1_file_1),
- {ok, Content} = file:read_file(MainFile),
- ReOpts = [{capture, first, binary}, multiline],
- {match, [Line]} = re:run(
- Content,
- "^.+\"" ++ RandomMsg ++ "\".+$",
- ReOpts),
- Term = jsx:decode(Line, [return_maps, {labels, attempt_atom}]),
+ {RandomMsg, Term} = log_and_return_json_object(
+ Context, Metadata, [return_maps]),
RandomMsgBin = list_to_binary(RandomMsg),
?assertMatch(#{time := _}, Term),
?assertMatch(#{level := <<"info">>}, Term),
?assertMatch(#{msg := RandomMsgBin}, Term),
- Meta = maps:get(meta, Term),
FunBin = list_to_binary(erlang:fun_to_list(maps:get(function, Metadata))),
PidBin = list_to_binary(erlang:pid_to_list(maps:get(pid, Metadata))),
PortBin = list_to_binary(erlang:port_to_list(maps:get(port, Metadata))),
RefBin = list_to_binary(erlang:ref_to_list(maps:get(ref, Metadata))),
- ?assertMatch(#{atom := <<"rabbit">>}, Meta),
- ?assertMatch(#{integer := 1}, Meta),
- ?assertMatch(#{float := 1.42}, Meta),
- ?assertMatch(#{string := <<"string">>}, Meta),
- ?assertMatch(#{list := [<<"s">>, <<"a">>, 3]}, Meta),
- ?assertMatch(#{map := #{key := <<"value">>}}, Meta),
- ?assertMatch(#{function := FunBin}, Meta),
- ?assertMatch(#{pid := PidBin}, Meta),
- ?assertMatch(#{port := PortBin}, Meta),
- ?assertMatch(#{ref := RefBin}, Meta).
+ ?assertMatch(#{atom := <<"rabbit">>}, Term),
+ ?assertMatch(#{integer := 1}, Term),
+ ?assertMatch(#{float := 1.42}, Term),
+ ?assertMatch(#{string := <<"string">>}, Term),
+ ?assertMatch(#{list := [<<"s">>, <<"a">>, 3]}, Term),
+ ?assertMatch(#{map := #{key := <<"value">>}}, Term),
+ ?assertMatch(#{function := FunBin}, Term),
+ ?assertMatch(#{pid := PidBin}, Term),
+ ?assertMatch(#{port := PortBin}, Term),
+ ?assertMatch(#{ref := RefBin}, Term).
+
+renaming_json_fields_works(Config) ->
+ Context = default_context(Config),
+ FieldMap = [{integer, int},
+ {msg, m},
+ {unknown_field, still_unknown_field},
+ {'$REST', false}],
+ ok = application:set_env(
+ rabbit, log,
+ [{file, [{formatter, {rabbit_logger_json_fmt,
+ #{field_map => FieldMap}}}]}],
+ [{persistent, true}]),
+ rabbit_prelaunch_logging:clear_config_run_number(),
+ rabbit_prelaunch_logging:setup(Context),
+
+ Metadata = #{atom => rabbit,
+ integer => 1,
+ string => "string",
+ list => ["s", a, 3]},
+ {RandomMsg, Term} = log_and_return_json_object(Context, Metadata, []),
+
+ RandomMsgBin = list_to_binary(RandomMsg),
+ ?assertEqual(
+ [{int, 1},
+ {m, RandomMsgBin}],
+ Term).
+
+removing_specific_json_fields_works(Config) ->
+ Context = default_context(Config),
+ FieldMap = [{integer, integer},
+ {msg, msg},
+ {list, false}],
+ ok = application:set_env(
+ rabbit, log,
+ [{file, [{formatter, {rabbit_logger_json_fmt,
+ #{field_map => FieldMap}}}]}],
+ [{persistent, true}]),
+ rabbit_prelaunch_logging:clear_config_run_number(),
+ rabbit_prelaunch_logging:setup(Context),
+
+ Metadata = #{atom => rabbit,
+ integer => 1,
+ string => "string",
+ list => ["s", a, 3]},
+ {RandomMsg, Term} = log_and_return_json_object(Context, Metadata, []),
+
+ RandomMsgBin = list_to_binary(RandomMsg),
+ ?assertMatch(
+ [{integer, 1},
+ {msg, RandomMsgBin} | _],
+ Term),
+ ?assertMatch(<<"string">>, proplists:get_value(string, Term)).
+
+removing_non_mentionned_json_fields_works(Config) ->
+ Context = default_context(Config),
+ FieldMap = [{integer, integer},
+ {msg, msg},
+ {'$REST', false}],
+ ok = application:set_env(
+ rabbit, log,
+ [{file, [{formatter, {rabbit_logger_json_fmt,
+ #{field_map => FieldMap}}}]}],
+ [{persistent, true}]),
+ rabbit_prelaunch_logging:clear_config_run_number(),
+ rabbit_prelaunch_logging:setup(Context),
+
+ Metadata = #{atom => rabbit,
+ integer => 1,
+ string => "string",
+ list => ["s", a, 3]},
+ {RandomMsg, Term} = log_and_return_json_object(Context, Metadata, []),
+
+ RandomMsgBin = list_to_binary(RandomMsg),
+ ?assertEqual(
+ [{integer, 1},
+ {msg, RandomMsgBin}],
+ Term).
+
+configuring_verbosity_works(Config) ->
+ Context = default_context(Config),
+ FieldMap = [{verbosity, v},
+ {msg, msg},
+ {'$REST', false}],
+ VerbMap = #{debug => 2,
+ info => 1,
+ '$REST' => 0},
+ ok = application:set_env(
+ rabbit, log,
+ [{file, [{formatter, {rabbit_logger_json_fmt,
+ #{field_map => FieldMap,
+ verbosity_map => VerbMap}}}]}],
+ [{persistent, true}]),
+ rabbit_prelaunch_logging:clear_config_run_number(),
+ rabbit_prelaunch_logging:setup(Context),
+
+ {RandomMsg, Term} = log_and_return_json_object(Context, #{}, []),
+
+ RandomMsgBin = list_to_binary(RandomMsg),
+ ?assertEqual(
+ [{v, 1},
+ {msg, RandomMsgBin}],
+ Term).
+
+logging_to_stdout_configured_in_env_works(Config) ->
+ #{var_origins := Origins0} = Context0 = default_context(Config),
+ Context = Context0#{main_log_file => "-",
+ var_origins => Origins0#{
+ main_log_file => environment}},
+ logging_to_stddev_works(standard_io, rmq_1_stdout, Config, Context).
+
+logging_to_stdout_configured_in_config_works(Config) ->
+ Context = default_context(Config),
+ ok = application:set_env(
+ rabbit, log, [{console, [{enabled, true}]}],
+ [{persistent, true}]),
+ logging_to_stddev_works(standard_io, rmq_1_stdout, Config, Context).
+
+logging_to_stderr_configured_in_env_works(Config) ->
+ #{var_origins := Origins0} = Context0 = default_context(Config),
+ Context = Context0#{main_log_file => "-stderr",
+ var_origins => Origins0#{
+ main_log_file => environment}},
+ logging_to_stddev_works(standard_error, rmq_1_stderr, Config, Context).
+
+logging_to_stderr_configured_in_config_works(Config) ->
+ Context = default_context(Config),
+ ok = application:set_env(
+ rabbit, log, [{console, [{enabled, true},
+ {stdio, stderr}]}],
+ [{persistent, true}]),
+ logging_to_stddev_works(standard_error, rmq_1_stderr, Config, Context).
+
+logging_to_stddev_works(Stddev, Id, Config, Context) ->
+ rabbit_prelaunch_logging:clear_config_run_number(),
+ rabbit_prelaunch_logging:setup(Context),
+
+ Handlers = logger:get_handler_config(),
+
+ StddevHandler = get_handler_by_id(Handlers, Id),
+ ?assertNotEqual(undefined, StddevHandler),
+ ?assertMatch(
+ #{level := info,
+ module := rabbit_logger_std_h,
+ filter_default := log,
+ filters := [{progress_reports, {_, stop}},
+ {rmqlog_filter, {_, #{global := info,
+ upgrade := none}}}],
+ formatter := {rabbit_logger_text_fmt, _},
+ config := #{type := Stddev}},
+ StddevHandler),
+
+ UpgradeFileHandler = get_handler_by_id(Handlers, rmq_1_file_1),
+ UpgradeFile = upgrade_log_file_in_context(Context),
+ ?assertNotEqual(undefined, UpgradeFileHandler),
+ ?assertMatch(
+ #{level := info,
+ module := rabbit_logger_std_h,
+ filter_default := stop,
+ filters := [{rmqlog_filter, {_, #{upgrade := info}}}],
+ formatter := {rabbit_logger_text_fmt, _},
+ config := #{type := file,
+ file := UpgradeFile}},
+ UpgradeFileHandler),
+
+ ?assert(ping_log(Id, info, Config)),
+ ?assert(ping_log(Id, info,
+ #{domain => ?RMQLOG_DOMAIN_GLOBAL}, Config)),
+ ?assert(ping_log(Id, info,
+ #{domain => ['3rd_party']}, Config)),
+ ?assertNot(ping_log(Id, info,
+ #{domain => ?RMQLOG_DOMAIN_UPGRADE}, Config)),
+
+ ?assert(ping_log(rmq_1_file_1, info,
+ #{domain => ?RMQLOG_DOMAIN_UPGRADE})),
+ ?assertNot(ping_log(rmq_1_file_1, info,
+ #{domain => ?RMQLOG_DOMAIN_GLOBAL})),
+ ok.
+
+formatting_with_colors_works(Config) ->
+ EscSeqs = make_color_esc_seqs_map(),
+ Context = default_context(Config),
+ ok = application:set_env(
+ rabbit, log, [{console, [{level, debug},
+ {formatter,
+ {rabbit_logger_text_fmt,
+ #{use_colors => true,
+ color_esc_seqs => EscSeqs}}}]}],
+ [{persistent, true}]),
+ formatting_maybe_with_colors_works(Config, Context, EscSeqs).
+
+formatting_without_colors_works(Config) ->
+ EscSeqs = make_color_esc_seqs_map(),
+ Context = default_context(Config),
+ ok = application:set_env(
+ rabbit, log, [{console, [{level, debug},
+ {formatter,
+ {rabbit_logger_text_fmt,
+ #{use_colors => false,
+ color_esc_seqs => EscSeqs}}}]}],
+ [{persistent, true}]),
+ formatting_maybe_with_colors_works(Config, Context, EscSeqs).
+
+make_color_esc_seqs_map() ->
+ lists:foldl(
+ fun(Lvl, Acc) ->
+ EscSeq = "[" ++ atom_to_list(Lvl) ++ " color]",
+ Acc#{Lvl => EscSeq}
+ end, #{}, rabbit_prelaunch_early_logging:levels()).
+
+formatting_maybe_with_colors_works(Config, Context, _EscSeqs) ->
+ rabbit_prelaunch_logging:clear_config_run_number(),
+ rabbit_prelaunch_logging:setup(Context),
+
+ ?assert(ping_log(rmq_1_stdout, debug, Config)),
+ ?assert(ping_log(rmq_1_stdout, info, Config)),
+ ?assert(ping_log(rmq_1_stdout, notice, Config)),
+ ?assert(ping_log(rmq_1_stdout, warning, Config)),
+ ?assert(ping_log(rmq_1_stdout, error, Config)),
+ ?assert(ping_log(rmq_1_stdout, critical, Config)),
+ ?assert(ping_log(rmq_1_stdout, alert, Config)),
+ ?assert(ping_log(rmq_1_stdout, emergency, Config)),
+ ok.
+
+logging_to_exchange_works(Config) ->
+ Context = rabbit_ct_broker_helpers:rpc(
+ Config, 0,
+ rabbit_prelaunch, get_context, []),
+ Handlers = rabbit_ct_broker_helpers:rpc(
+ Config, 0,
+ logger, get_handler_config, []),
+
+ ExchangeHandler = get_handler_by_id(Handlers, rmq_1_exchange),
+ ?assertNotEqual(undefined, ExchangeHandler),
+ ?assertMatch(
+ #{level := info,
+ module := rabbit_logger_exchange_h,
+ filter_default := log,
+ filters := [{progress_reports, {_, stop}},
+ {rmqlog_filter, {_, #{global := info,
+ upgrade := none}}}],
+ formatter := {rabbit_logger_text_fmt, _},
+ config := #{exchange := _}},
+ ExchangeHandler),
+ #{config :=
+ #{exchange := #resource{name = XName} = Exchange}} = ExchangeHandler,
+
+ UpgradeFileHandler = get_handler_by_id(Handlers, rmq_1_file_2),
+ UpgradeFile = upgrade_log_file_in_context(Context),
+ ?assertNotEqual(undefined, UpgradeFileHandler),
+ ?assertMatch(
+ #{level := info,
+ module := rabbit_logger_std_h,
+ filter_default := stop,
+ filters := [{rmqlog_filter, {_, #{upgrade := info}}}],
+ formatter := {rabbit_logger_text_fmt, _},
+ config := #{type := file,
+ file := UpgradeFile}},
+ UpgradeFileHandler),
+
+ %% Wait for the expected exchange to be automatically declared.
+ lists:any(
+ fun(_) ->
+ Ret = rabbit_ct_broker_helpers:rpc(
+ Config, 0,
+ rabbit_exchange, lookup, [Exchange]),
+ case Ret of
+ {ok, _} -> true;
+ _ -> timer:sleep(500),
+ false
+ end
+ end, lists:seq(1, 20)),
+
+ %% Declare a queue to collect all logged messages.
+ {Conn, Chan} = rabbit_ct_client_helpers:open_connection_and_channel(
+ Config),
+ QName = <<"log-messages">>,
+ ?assertMatch(
+ #'queue.declare_ok'{},
+ amqp_channel:call(Chan, #'queue.declare'{queue = QName,
+ durable = false})),
+ ?assertMatch(
+ #'queue.bind_ok'{},
+ amqp_channel:call(Chan, #'queue.bind'{queue = QName,
+ exchange = XName,
+ routing_key = <<"#">>})),
+ Config1 = rabbit_ct_helpers:set_config(
+ Config, {test_channel_and_queue, {Chan, QName}}),
+
+ ?assert(ping_log(rmq_1_exchange, info, Config1)),
+ ?assert(ping_log(rmq_1_exchange, info,
+ #{domain => ?RMQLOG_DOMAIN_GLOBAL}, Config1)),
+ ?assert(ping_log(rmq_1_exchange, info,
+ #{domain => ['3rd_party']}, Config1)),
+ ?assertNot(ping_log(rmq_1_exchange, info,
+ #{domain => ?RMQLOG_DOMAIN_UPGRADE}, Config1)),
+
+ ?assert(ping_log(rmq_1_file_2, info,
+ #{domain => ?RMQLOG_DOMAIN_UPGRADE}, Config)),
+ ?assertNot(ping_log(rmq_1_file_2, info,
+ #{domain => ?RMQLOG_DOMAIN_GLOBAL}, Config)),
+
+ amqp_channel:call(Chan, #'queue.delete'{queue = QName}),
+ rabbit_ct_client_helpers:close_connection_and_channel(Conn, Chan),
+ ok.
+
+logging_to_syslog_works(Config) ->
+ Context = default_context(Config),
+ ok = application:set_env(
+ rabbit, log, [{syslog, [{enabled, true}]}],
+ [{persistent, true}]),
+ rabbit_prelaunch_logging:clear_config_run_number(),
+ rabbit_prelaunch_logging:setup(Context),
+ clear_syslogd_messages(Config),
+
+ Handlers = logger:get_handler_config(),
+
+ SyslogHandler = get_handler_by_id(Handlers, rmq_1_syslog),
+ ?assertNotEqual(undefined, SyslogHandler),
+ ?assertMatch(
+ #{level := info,
+ module := syslog_logger_h,
+ filter_default := log,
+ filters := [{progress_reports, {_, stop}},
+ {rmqlog_filter, {_, #{global := info,
+ upgrade := none}}}],
+ formatter := {rabbit_logger_text_fmt, _},
+ config := #{}},
+ SyslogHandler),
+
+ UpgradeFileHandler = get_handler_by_id(Handlers, rmq_1_file_1),
+ UpgradeFile = upgrade_log_file_in_context(Context),
+ ?assertNotEqual(undefined, UpgradeFileHandler),
+ ?assertMatch(
+ #{level := info,
+ module := rabbit_logger_std_h,
+ filter_default := stop,
+ filters := [{rmqlog_filter, {_, #{upgrade := info}}}],
+ formatter := {rabbit_logger_text_fmt, _},
+ config := #{type := file,
+ file := UpgradeFile}},
+ UpgradeFileHandler),
+
+ ?assert(ping_log(rmq_1_syslog, info, Config)),
+ ?assert(ping_log(rmq_1_syslog, info,
+ #{domain => ?RMQLOG_DOMAIN_GLOBAL}, Config)),
+ ?assert(ping_log(rmq_1_syslog, info,
+ #{domain => ['3rd_party']}, Config)),
+ ?assertNot(ping_log(rmq_1_syslog, info,
+ #{domain => ?RMQLOG_DOMAIN_UPGRADE}, Config)),
+
+ ?assert(ping_log(rmq_1_file_1, info,
+ #{domain => ?RMQLOG_DOMAIN_UPGRADE})),
+ ?assertNot(ping_log(rmq_1_file_1, info,
+ #{domain => ?RMQLOG_DOMAIN_GLOBAL})),
+ ok.
%% -------------------------------------------------------------------
%% Internal functions.
@@ -610,25 +1062,26 @@ ping_log(Id, Level, Metadata, Config) ->
Config, 0,
logger, log, [Level, RandomMsg, Metadata])
end,
- check_log(Id, RandomMsg, Config).
+ check_log(Id, Level, RandomMsg, Config).
need_rpc(Config) ->
rabbit_ct_helpers:get_config(
Config, rmq_nodes_count) =/= undefined.
-check_log(Id, RandomMsg, Config) ->
+check_log(Id, Level, RandomMsg, Config) ->
{ok, Handler} = case need_rpc(Config) of
false -> logger:get_handler_config(Id);
true -> rabbit_ct_broker_helpers:rpc(
Config, 0,
logger, get_handler_config, [Id])
end,
- check_log1(Handler, RandomMsg, Config).
+ check_log1(Handler, Level, RandomMsg, Config).
check_log1(#{id := Id,
module := rabbit_logger_std_h,
config := #{type := file,
file := Filename}},
+ _Level,
RandomMsg,
Config) ->
ok = case need_rpc(Config) of
@@ -641,22 +1094,27 @@ check_log1(#{id := Id,
ReOpts = [{capture, none}, multiline],
match =:= re:run(Content, RandomMsg ++ "$", ReOpts);
check_log1(#{module := Mod,
- config := #{type := Stddev}},
+ config := #{type := Stddev}} = Handler,
+ Level,
RandomMsg,
Config)
when ?IS_STD_H_COMPAT(Mod) andalso ?IS_STDDEV(Stddev) ->
Filename = html_report_filename(Config),
+ {ColorStart, ColorEnd} = get_color_config(Handler, Level),
ReOpts = [{capture, none}, multiline],
lists:any(
fun(_) ->
{ok, Content} = file:read_file(Filename),
- case re:run(Content, RandomMsg ++ "$", ReOpts) of
+ Regex =
+ "^" ++ ColorStart ++ ".+" ++ RandomMsg ++ ColorEnd ++ "$",
+ case re:run(Content, Regex, ReOpts) of
match -> true;
_ -> timer:sleep(500),
false
end
end, lists:seq(1, 10));
check_log1(#{module := rabbit_logger_exchange_h},
+ _Level,
RandomMsg,
Config) ->
{Chan, QName} = ?config(test_channel_and_queue, Config),
@@ -680,6 +1138,20 @@ check_log1(#{module := rabbit_logger_exchange_h},
timer:sleep(500),
false
end
+ end, lists:seq(1, 10));
+check_log1(#{module := syslog_logger_h},
+ _Level,
+ RandomMsg,
+ Config) ->
+ ReOpts = [{capture, none}, multiline],
+ lists:any(
+ fun(_) ->
+ Buffer = get_syslogd_messages(Config),
+ case re:run(Buffer, RandomMsg ++ "$", ReOpts) of
+ match -> true;
+ _ -> timer:sleep(500),
+ false
+ end
end, lists:seq(1, 10)).
get_random_string(Length, AllowedChars) ->
@@ -691,3 +1163,155 @@ get_random_string(Length, AllowedChars) ->
html_report_filename(Config) ->
?config(tc_logfile, Config).
+
+get_color_config(
+ #{formatter := {rabbit_logger_text_fmt,
+ #{use_colors := true,
+ color_esc_seqs := EscSeqs}}}, Level) ->
+ ColorStart = maps:get(Level, EscSeqs),
+ ColorEnd = "\033[0m",
+ {escape_for_re(ColorStart), escape_for_re(ColorEnd)};
+get_color_config(_, _) ->
+ {"", ""}.
+
+escape_for_re(String) ->
+ String1 = string:replace(String, "[", "\\[", all),
+ string:replace(String1, "]", "\\]", all).
+
+log_and_return_line(Context, Metadata) ->
+ RandomMsg = get_random_string(
+ 32,
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"),
+ logger:warning(RandomMsg, Metadata),
+
+ rabbit_logger_std_h:filesync(rmq_1_file_1),
+ MainFile = main_log_file_in_context(Context),
+ {ok, Content} = file:read_file(MainFile),
+ ReOpts = [{capture, first, binary}, multiline],
+ {match, [Line]} = re:run(
+ Content,
+ "^.+" ++ RandomMsg ++ ".*$",
+ ReOpts),
+ {RandomMsg, Line}.
+
+log_and_return_json_object(Context, Metadata, DecodeOpts) ->
+ RandomMsg = get_random_string(
+ 32,
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"),
+ ?LOG_INFO(RandomMsg, Metadata),
+
+ rabbit_logger_std_h:filesync(rmq_1_file_1),
+ MainFile = main_log_file_in_context(Context),
+ {ok, Content} = file:read_file(MainFile),
+ ReOpts = [{capture, first, binary}, multiline],
+ {match, [Line]} = re:run(
+ Content,
+ "^.+\"" ++ RandomMsg ++ "\".+$",
+ ReOpts),
+ Term = jsx:decode(Line, [{labels, attempt_atom} | DecodeOpts]),
+
+ {RandomMsg, Term}.
+
+%% -------------------------------------------------------------------
+%% Fake syslog server.
+%% -------------------------------------------------------------------
+
+start_fake_syslogd(Config) ->
+ Self = self(),
+ Pid = spawn(fun() -> syslogd_init(Self) end),
+ TcpPort = receive {syslogd_ready, P} -> P end,
+
+ rabbit_ct_helpers:set_config(
+ Config, [{syslogd_pid, Pid},
+ {syslogd_tcp_port, TcpPort}]).
+
+stop_fake_syslogd(Config) ->
+ Pid = ?config(syslogd_pid, Config),
+ Pid ! stop,
+ Config1 = rabbit_ct_helpers:delete_config(Config, syslogd_pid),
+ rabbit_ct_helpers:delete_config(Config1, syslogd_tcp_port).
+
+get_syslogd_messages(Config) ->
+ Pid = ?config(syslogd_pid, Config),
+ Pid ! {get_messages, self()},
+ receive {syslogd_messages, Buffer} -> Buffer end.
+
+clear_syslogd_messages(Config) ->
+ Pid = ?config(syslogd_pid, Config),
+ Pid ! clear_messages.
+
+syslogd_init(Parent) ->
+ {ok, TcpPort, LSock} = open_tcp_listening_sock(22000),
+ ct:pal(
+ "Fake syslogd ready (~p), listening on TCP port ~p",
+ [self(), TcpPort]),
+ Parent ! {syslogd_ready, TcpPort},
+ syslogd_start_loop(LSock).
+
+open_tcp_listening_sock(TcpPort) ->
+ Options = [binary,
+ {active, true}],
+ case gen_tcp:listen(TcpPort, Options) of
+ {ok, LSock} -> {ok, TcpPort, LSock};
+ {error, eaddrinuse} -> open_tcp_listening_sock(TcpPort + 1)
+ end.
+
+syslogd_start_loop(LSock) ->
+ ct:pal("Fake syslogd: accepting new connection", []),
+ {ok, Sock} = gen_tcp:accept(LSock),
+ ct:pal("Fake syslogd: accepted new connection!", []),
+ syslogd_loop(LSock, Sock, [], <<>>).
+
+syslogd_loop(LSock, Sock, Messages, Buffer) ->
+ try
+ receive
+ {tcp, Sock, NewData} ->
+ Buffer1 = <<Buffer/binary, NewData/binary>>,
+ {NewMessages, Buffer2} = parse_messages(Buffer1),
+ syslogd_loop(LSock, Sock, Messages ++ NewMessages, Buffer2);
+ {get_messages, From} ->
+ ct:pal(
+ "Fake syslogd: sending messages to ~p:~n~p",
+ [From, Messages]),
+ From ! {syslogd_messages, Messages},
+ syslogd_loop(LSock, Sock, Messages, Buffer);
+ clear_messages ->
+ ct:pal("Fake syslogd: clearing buffer", []),
+ syslogd_loop(LSock, Sock, [], Buffer);
+ {tcp_closed, Sock} ->
+ ct:pal("Fake syslogd: socket closed, restarting loop", []),
+ syslogd_start_loop(LSock);
+ stop ->
+ ct:pal("Fake syslogd: exiting", []),
+ _ = gen_tcp:close(Sock),
+ _ = gen_tcp:close(LSock);
+ Other ->
+ ct:pal("Fake syslogd: unhandled message: ~p", [Other]),
+ syslogd_loop(LSock, Sock, Messages, Buffer)
+ end
+ catch
+ C:R:S ->
+ ct:pal("~p ~p ~p", [C, R, S]),
+ throw(R)
+ end.
+
+parse_messages(Buffer) ->
+ parse_messages(Buffer, []).
+
+parse_messages(Buffer, Messages) ->
+ ReOpts = [{capture, all_but_first, binary}],
+ case re:run(Buffer, "^([0-9]+) (.*)", ReOpts) of
+ {match, [Length0, Buffer1]} ->
+ Length = list_to_integer(binary_to_list(Length0)),
+ case Buffer1 of
+ <<Message:Length/binary, Buffer2/binary>> ->
+ parse_messages(
+ Buffer2, [<<Message/binary, $\n>> | Messages]);
+ _ ->
+ {lists:reverse(Messages), Buffer}
+ end;
+ _ ->
+ {lists:reverse(Messages), Buffer}
+ end.