1-module(rabbit_prelaunch_errors).
2
3-include_lib("kernel/include/logger.hrl").
4
5-include_lib("rabbit_common/include/logging.hrl").
6
7-export([format_error/1,
8         format_exception/3,
9         log_error/1,
10         log_exception/3]).
11
12-define(BOOT_FAILED_HEADER,
13        "\n"
14        "BOOT FAILED\n"
15        "===========\n").
16
17-define(BOOT_FAILED_FOOTER,
18        "\n").
19
20log_error(Error) ->
21    Message = format_error(Error),
22    log_message(Message).
23
24format_error({error, {duplicate_node_name, NodeName, NodeHost}}) ->
25    rabbit_misc:format(
26      "ERROR: node with name ~p is already running on host ~p",
27      [NodeName, NodeHost]);
28format_error({error, {epmd_error, NodeHost, EpmdReason}}) ->
29    rabbit_misc:format(
30      "ERROR: epmd error for host ~s: ~s",
31      [NodeHost, rabbit_misc:format_inet_error(EpmdReason)]);
32format_error({error, {invalid_dist_port_range, DistTcpPort}}) ->
33    rabbit_misc:format(
34      "Invalid Erlang distribution TCP port: ~b", [DistTcpPort]);
35format_error({error, {dist_port_already_used, Port, not_erlang, Host}}) ->
36    rabbit_misc:format(
37      "ERROR: could not bind to distribution port ~b on host ~s. It could "
38      "be in use by another process or cannot be bound to (e.g. due to a "
39      "security policy)", [Port, Host]);
40format_error({error, {dist_port_already_used, Port, Name, Host}}) ->
41    rabbit_misc:format(
42      "ERROR: could not bind to distribution port ~b, it is in use by "
43      "another node: ~s@~s", [Port, Name, Host]);
44format_error({error, {erlang_dist_running_with_unexpected_nodename,
45                      Unexpected, Node}}) ->
46    rabbit_misc:format(
47      "Erlang distribution running with another node name (~s) "
48      "than the configured one (~s)",
49      [Unexpected, Node]);
50format_error({bad_config_entry_decoder, missing_passphrase}) ->
51    rabbit_misc:format(
52      "Missing passphrase or missing passphrase read method in "
53      "`config_entry_decoder`", []);
54format_error({config_decryption_error, {key, Key}, _Msg}) ->
55    rabbit_misc:format(
56      "Error while decrypting key '~p'. Please check encrypted value, "
57      "passphrase, and encryption configuration~n",
58      [Key]);
59format_error({error, {timeout_waiting_for_tables, AllNodes, _}}) ->
60    Suffix =
61    "~nBACKGROUND~n==========~n~n"
62    "This cluster node was shut down while other nodes were still running.~n"
63    "To avoid losing data, you should start the other nodes first, then~n"
64    "start this one. To force this node to start, first invoke~n"
65    "\"rabbitmqctl force_boot\". If you do so, any changes made on other~n"
66    "cluster nodes after this one was shut down may be lost.",
67    {Message, Nodes} =
68    case AllNodes -- [node()] of
69        [] -> {rabbit_misc:format(
70                 "Timeout contacting cluster nodes. Since RabbitMQ was"
71                 " shut down forcefully~nit cannot determine which nodes"
72                 " are timing out.~n" ++ Suffix, []),
73               []};
74        Ns -> {rabbit_misc:format(
75                 "Timeout contacting cluster nodes: ~p.~n" ++ Suffix,
76                 [Ns]),
77               Ns}
78    end,
79    Message ++ "\n" ++ rabbit_nodes_common:diagnostics(Nodes);
80format_error({error, {cannot_log_to_file, unknown, Reason}}) ->
81    rabbit_misc:format(
82      "failed to initialised logger: ~p~n",
83      [Reason]);
84format_error({error, {cannot_log_to_file, LogFile,
85                      {cannot_create_parent_dirs, _, Reason}}}) ->
86    rabbit_misc:format(
87      "failed to create parent directory for log file at '~s', reason: ~s~n",
88      [LogFile, file:format_error(Reason)]);
89format_error({error, {cannot_log_to_file, LogFile, Reason}}) ->
90    rabbit_misc:format(
91      "failed to open log file at '~s', reason: ~s",
92      [LogFile, file:format_error(Reason)]);
93format_error(Error) ->
94    rabbit_misc:format("Error during startup: ~p", [Error]).
95
96log_exception(Class, Exception, Stacktrace) ->
97    Message = format_exception(Class, Exception, Stacktrace),
98    log_message(Message).
99
100format_exception(Class, Exception, Stacktrace) ->
101    StacktraceStrs = [begin
102                          case proplists:get_value(line, Props) of
103                              undefined when is_list(ArgListOrArity) ->
104                                  io_lib:format(
105                                    "    ~ts:~ts/~b~n"
106                                    "        args: ~p",
107                                    [Mod, Fun, length(ArgListOrArity),
108                                     ArgListOrArity]);
109                              undefined when is_integer(ArgListOrArity) ->
110                                  io_lib:format(
111                                    "    ~ts:~ts/~b",
112                                    [Mod, Fun, ArgListOrArity]);
113                              Line when is_list(ArgListOrArity) ->
114                                  io_lib:format(
115                                    "    ~ts:~ts/~b, line ~b~n"
116                                    "        args: ~p",
117                                    [Mod, Fun, length(ArgListOrArity), Line,
118                                     ArgListOrArity]);
119                              Line when is_integer(ArgListOrArity) ->
120                                  io_lib:format(
121                                    "    ~ts:~ts/~b, line ~b",
122                                    [Mod, Fun, ArgListOrArity, Line])
123                          end
124                      end
125                      || {Mod, Fun, ArgListOrArity, Props} <- Stacktrace],
126    ExceptionStr = io_lib:format("~ts:~0p", [Class, Exception]),
127    rabbit_misc:format(
128      "Exception during startup:~n~n~s~n~n~s",
129      [ExceptionStr, string:join(StacktraceStrs, "\n")]).
130
131log_message(Message) ->
132    Lines = string:split(
133              ?BOOT_FAILED_HEADER ++
134              Message ++
135              ?BOOT_FAILED_FOOTER,
136              [$\n],
137              all),
138    ?LOG_ERROR(
139       "~s", [string:join(Lines, "\n")],
140       #{domain => ?RMQLOG_DOMAIN_PRELAUNCH}),
141    lists:foreach(
142      fun(Line) ->
143              io:format(standard_error, "~s~n", [Line])
144      end, Lines),
145    timer:sleep(1000),
146    ok.
147