diff options
Diffstat (limited to 'src/rabbit.erl')
| -rw-r--r-- | src/rabbit.erl | 683 |
1 files changed, 322 insertions, 361 deletions
diff --git a/src/rabbit.erl b/src/rabbit.erl index e4d2ee9808..0b002618e5 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -30,15 +30,9 @@ -export([start/2, stop/1, prep_stop/1]). -export([start_apps/1, start_apps/2, stop_apps/1]). --export([log_locations/0, config_files/0, decrypt_config/2]). %% for testing and mgmt-agent +-export([log_locations/0, config_files/0]). %% for testing and mgmt-agent -export([is_booted/1, is_booted/0, is_booting/1, is_booting/0]). --ifdef(TEST). - --export([start_logger/0]). - --endif. - %%--------------------------------------------------------------------------- %% Boot steps. -export([maybe_insert_default_data/0, boot_delegate/0, recover/0]). @@ -262,7 +256,7 @@ -include("rabbit_framing.hrl"). -include("rabbit.hrl"). --define(APPS, [os_mon, mnesia, rabbit_common, ra, sysmon_handler, rabbit]). +-define(APPS, [os_mon, mnesia, rabbit_common, rabbitmq_prelaunch, ra, sysmon_handler, rabbit]). -define(ASYNC_THREADS_WARNING_THRESHOLD, 8). @@ -282,75 +276,93 @@ %%---------------------------------------------------------------------------- -ensure_application_loaded() -> - %% We end up looking at the rabbit app's env for HiPE and log - %% handling, so it needs to be loaded. But during the tests, it - %% may end up getting loaded twice, so guard against that. - case application:load(rabbit) of - ok -> ok; - {error, {already_loaded, rabbit}} -> ok - end. - -spec start() -> 'ok'. start() -> - start_it(fun() -> - %% We do not want to upgrade mnesia after just - %% restarting the app. - ok = ensure_application_loaded(), - HipeResult = rabbit_hipe:maybe_hipe_compile(), - ok = start_logger(), - rabbit_hipe:log_hipe_result(HipeResult), - Apps = load_all_apps(), - rabbit_feature_flags:initialize_registry(), - rabbit_node_monitor:prepare_cluster_status_files(), - rabbit_mnesia:check_cluster_consistency(), - broker_start(Apps) - end). + %% start() vs. boot(): we want to throw an error in start(). + start_it(temporary). -spec boot() -> 'ok'. boot() -> - start_it(fun() -> - ensure_config(), - ok = ensure_application_loaded(), - HipeResult = rabbit_hipe:maybe_hipe_compile(), - ok = start_logger(), - rabbit_hipe:log_hipe_result(HipeResult), - Apps = load_all_apps(), - rabbit_feature_flags:initialize_registry(), - rabbit_node_monitor:prepare_cluster_status_files(), - ok = rabbit_upgrade:maybe_upgrade_mnesia(), - %% It's important that the consistency check happens after - %% the upgrade, since if we are a secondary node the - %% primary node will have forgotten us - rabbit_mnesia:check_cluster_consistency(), - broker_start(Apps) - end). - -ensure_config() -> - case rabbit_config:validate_config_files() of - ok -> ok; - {error, {ErrFmt, ErrArgs}} -> - throw({error, {check_config_file, ErrFmt, ErrArgs}}) + %% start() vs. boot(): we want the node to exit in boot(). Because + %% applications are started with `transient`, any error during their + %% startup will abort the node. + start_it(transient). + +run_prelaunch_second_phase() -> + %% Finish the prelaunch phase started by the `rabbitmq_prelaunch` + %% application. + %% + %% The first phase was handled by the `rabbitmq_prelaunch` + %% application. It was started in one of the following way: + %% - from an Erlang release boot script; + %% - from the rabbit:boot/0 or rabbit:start/0 functions. + %% + %% The `rabbitmq_prelaunch` application creates the context map from + %% the environment and the configuration files early during Erlang + %% VM startup. Once it is done, all application environments are + %% configured (in particular `mnesia` and `ra`). + %% + %% This second phase depends on other modules & facilities of + %% RabbitMQ core. That's why we need to run it now, from the + %% `rabbit` application start function. + + %% We assert Mnesia is stopped before we run the prelaunch + %% phases. See `rabbit_prelaunch` for an explanation. + %% + %% This is the second assertion, just in case Mnesia is started + %% between the two prelaunch phases. + rabbit_prelaunch:assert_mnesia_is_stopped(), + + %% Get the context created by `rabbitmq_prelaunch` then proceed + %% with all steps in this phase. + #{initial_pass := IsInitialPass} = + Context = rabbit_prelaunch:get_context(), + + case IsInitialPass of + true -> + rabbit_log_prelaunch:debug(""), + rabbit_log_prelaunch:debug( + "== Prelaunch phase [2/2] (initial pass) =="); + false -> + rabbit_log_prelaunch:debug(""), + rabbit_log_prelaunch:debug("== Prelaunch phase [2/2] =="), + ok + end, + + %% 1. Feature flags registry. + ok = rabbit_prelaunch_feature_flags:setup(Context), + + %% 2. Configuration check + loading. + ok = rabbit_prelaunch_conf:setup(Context), + + %% 3. Logging. + ok = rabbit_prelaunch_logging:setup(Context), + + case IsInitialPass of + true -> + %% 4. HiPE compilation. + ok = rabbit_prelaunch_hipe:setup(Context); + false -> + ok end, - case rabbit_config:prepare_and_use_config() of - {error, {generation_error, Error}} -> - throw({error, {generate_config_file, Error}}); - ok -> ok - end. -load_all_apps() -> - Plugins = rabbit_plugins:setup(), - ToBeLoaded = Plugins ++ ?APPS, - app_utils:load_applications(ToBeLoaded), - ToBeLoaded. + %% 5. Clustering. + ok = rabbit_prelaunch_cluster:setup(Context), -broker_start(Apps) -> - start_loaded_apps(Apps), - maybe_sd_notify(), - ok = rabbit_lager:broker_is_started(), - ok = log_broker_started(rabbit_plugins:strictly_plugins(rabbit_plugins:active())). + %% Start Mnesia now that everything is ready. + rabbit_log_prelaunch:debug("Starting Mnesia"), + ok = mnesia:start(), + + rabbit_log_prelaunch:debug(""), + rabbit_log_prelaunch:debug("== Prelaunch DONE =="), + + case IsInitialPass of + true -> rabbit_prelaunch:initial_pass_finished(); + false -> ok + end, + ok. %% Try to send systemd ready notification if it makes sense in the %% current environment. standard_error is used intentionally in all @@ -465,41 +477,89 @@ sd_wait_activation(Port, Unit, AttemptsLeft) -> false end. -start_it(StartFun) -> +start_it(StartType) -> + case spawn_boot_marker() of + {ok, Marker} -> + T0 = erlang:timestamp(), + rabbit_log:info("RabbitMQ is asked to start...", []), + try + {ok, _} = application:ensure_all_started(rabbitmq_prelaunch, + StartType), + {ok, _} = application:ensure_all_started(rabbit, + StartType), + ok = wait_for_ready_or_stopped(), + + T1 = erlang:timestamp(), + rabbit_log_prelaunch:debug( + "Time to start RabbitMQ: ~p µs", + [timer:now_diff(T1, T0)]), + stop_boot_marker(Marker), + ok + catch + error:{badmatch, Error}:_ -> + stop_boot_marker(Marker), + case StartType of + temporary -> throw(Error); + _ -> exit(Error) + end + end; + {already_booting, Marker} -> + stop_boot_marker(Marker), + ok + end. + +wait_for_ready_or_stopped() -> + ok = rabbit_prelaunch:wait_for_boot_state(ready), + case rabbit_prelaunch:get_boot_state() of + ready -> + ok; + _ -> + ok = rabbit_prelaunch:wait_for_boot_state(stopped), + rabbit_prelaunch:get_stop_reason() + end. + +spawn_boot_marker() -> + %% Compatibility with older RabbitMQ versions: + %% We register a process doing nothing to indicate that RabbitMQ is + %% booting. This is checked by `is_booting(Node)` on a remote node. Marker = spawn_link(fun() -> receive stop -> ok end end), case catch register(rabbit_boot, Marker) of - true -> try - case is_running() of - true -> ok; - false -> StartFun() - end - catch Class:Reason -> - boot_error(Class, Reason) - after - unlink(Marker), - Marker ! stop, - %% give the error loggers some time to catch up - timer:sleep(100) - end; - _ -> unlink(Marker), - Marker ! stop + true -> {ok, Marker}; + _ -> {already_booting, Marker} end. +stop_boot_marker(Marker) -> + unlink(Marker), + Marker ! stop, + ok. + -spec stop() -> 'ok'. stop() -> - case whereis(rabbit_boot) of - undefined -> ok; - _ -> - rabbit_log:info("RabbitMQ hasn't finished starting yet. Waiting for startup to finish before stopping..."), - ok = wait_for_boot_to_finish(node()) - end, - rabbit_log:info("RabbitMQ is asked to stop...~n", []), - Apps = ?APPS ++ rabbit_plugins:active(), + case wait_for_ready_or_stopped() of + ok -> + case rabbit_prelaunch:get_boot_state() of + ready -> + rabbit_log:info("RabbitMQ is asked to stop..."), + do_stop(), + rabbit_log:info( + "Successfully stopped RabbitMQ and its dependencies"), + ok; + stopped -> + ok + end; + _ -> + ok + end. + +do_stop() -> + Apps0 = ?APPS ++ rabbit_plugins:active(), + %% We ensure that Mnesia is stopped last (or more exactly, after rabbit). + Apps1 = app_utils:app_dependency_order(Apps0, true) -- [mnesia], + Apps = [mnesia | Apps1], %% this will also perform unregistration with the peer discovery backend %% as needed - stop_apps(app_utils:app_dependency_order(Apps, true)), - rabbit_log:info("Successfully stopped RabbitMQ and its dependencies~n", []). + stop_apps(Apps). -spec stop_and_halt() -> no_return(). @@ -541,57 +601,8 @@ start_apps(Apps, RestartTypes) -> ok = rabbit_feature_flags:refresh_feature_flags_after_app_load(Apps), start_loaded_apps(Apps, RestartTypes). -start_loaded_apps(Apps) -> - start_loaded_apps(Apps, #{}). - start_loaded_apps(Apps, RestartTypes) -> - ensure_sysmon_handler_app_config(), - %% make Ra use a custom logger that dispatches to lager instead of the - %% default OTP logger - application:set_env(ra, logger_module, rabbit_log_ra_shim), - %% use a larger segments size for queues - case application:get_env(ra, segment_max_entries) of - undefined -> - application:set_env(ra, segment_max_entries, 32768); - _ -> - ok - end, - case application:get_env(ra, wal_max_size_bytes) of - undefined -> - application:set_env(ra, wal_max_size_bytes, 536870912); %% 5 * 2 ^ 20 - _ -> - ok - end, - ConfigEntryDecoder = case application:get_env(rabbit, config_entry_decoder) of - undefined -> - []; - {ok, Val} -> - Val - end, - PassPhrase = case proplists:get_value(passphrase, ConfigEntryDecoder) of - prompt -> - IoDevice = get_input_iodevice(), - io:setopts(IoDevice, [{echo, false}]), - PP = lists:droplast(io:get_line(IoDevice, - "\nPlease enter the passphrase to unlock encrypted " - "configuration entries.\n\nPassphrase: ")), - io:setopts(IoDevice, [{echo, true}]), - io:format(IoDevice, "~n", []), - PP; - {file, Filename} -> - {ok, File} = file:read_file(Filename), - [PP|_] = binary:split(File, [<<"\r\n">>, <<"\n">>]), - PP; - PP -> - PP - end, - Algo = { - proplists:get_value(cipher, ConfigEntryDecoder, rabbit_pbe:default_cipher()), - proplists:get_value(hash, ConfigEntryDecoder, rabbit_pbe:default_hash()), - proplists:get_value(iterations, ConfigEntryDecoder, rabbit_pbe:default_iterations()), - PassPhrase - }, - decrypt_config(Apps, Algo), + rabbit_prelaunch_conf:decrypt_config(Apps), OrderedApps = app_utils:app_dependency_order(Apps, false), case lists:member(rabbit, Apps) of false -> rabbit_boot_steps:run_boot_steps(Apps); %% plugin activation @@ -601,102 +612,6 @@ start_loaded_apps(Apps, RestartTypes) -> handle_app_error(could_not_start), RestartTypes). -%% rabbitmq/rabbitmq-server#952 -%% This function is to be called after configuration has been optionally generated -%% and the sysmon_handler application loaded, but not started. It will ensure that -%% sane defaults are used for configuration settings that haven't been set by the -%% user -ensure_sysmon_handler_app_config() -> - Defaults = [ - {process_limit, 100}, - {port_limit, 100}, - {gc_ms_limit, 0}, - {schedule_ms_limit, 0}, - {heap_word_limit, 0}, - {busy_port, false}, - {busy_dist_port, true} - ], - lists:foreach(fun({K, V}) -> - case application:get_env(sysmon_handler, K) of - undefined -> - application:set_env(sysmon_handler, K, V); - _ -> - ok - end - end, Defaults). - -%% This function retrieves the correct IoDevice for requesting -%% input. The problem with using the default IoDevice is that -%% the Erlang shell prevents us from getting the input. -%% -%% Instead we therefore look for the io process used by the -%% shell and if it can't be found (because the shell is not -%% started e.g with -noshell) we use the 'user' process. -%% -%% This function will not work when either -oldshell or -noinput -%% options are passed to erl. -get_input_iodevice() -> - case whereis(user) of - undefined -> user; - User -> - case group:interfaces(User) of - [] -> - user; - [{user_drv, Drv}] -> - case user_drv:interfaces(Drv) of - [] -> - user; - [{current_group, IoDevice}] -> - IoDevice - end - end - end. - -decrypt_config([], _) -> - ok; -decrypt_config([App|Apps], Algo) -> - decrypt_app(App, application:get_all_env(App), Algo), - decrypt_config(Apps, Algo). - -decrypt_app(_, [], _) -> - ok; -decrypt_app(App, [{Key, Value}|Tail], Algo) -> - try begin - case decrypt(Value, Algo) of - Value -> - ok; - NewValue -> - application:set_env(App, Key, NewValue) - end - end - catch - exit:{bad_configuration, config_entry_decoder} -> - exit({bad_configuration, config_entry_decoder}); - _:Msg -> - rabbit_log:info("Error while decrypting key '~p'. Please check encrypted value, passphrase, and encryption configuration~n", [Key]), - exit({decryption_error, {key, Key}, Msg}) - end, - decrypt_app(App, Tail, Algo). - -decrypt({encrypted, _}, {_, _, _, undefined}) -> - exit({bad_configuration, config_entry_decoder}); -decrypt({encrypted, EncValue}, {Cipher, Hash, Iterations, Password}) -> - rabbit_pbe:decrypt_term(Cipher, Hash, Iterations, Password, EncValue); -decrypt(List, Algo) when is_list(List) -> - decrypt_list(List, Algo, []); -decrypt(Value, _) -> - Value. - -%% We make no distinction between strings and other lists. -%% When we receive a string, we loop through each element -%% and ultimately return the string unmodified, as intended. -decrypt_list([], _, Acc) -> - lists:reverse(Acc); -decrypt_list([{Key, Value}|Tail], Algo, Acc) when Key =/= encrypted -> - decrypt_list(Tail, Algo, [{Key, decrypt(Value, Algo)}|Acc]); -decrypt_list([Value|Tail], Algo, Acc) -> - decrypt_list(Tail, Algo, [decrypt(Value, Algo)|Acc]). - -spec stop_apps([app_name()]) -> 'ok'. stop_apps([]) -> @@ -725,11 +640,15 @@ handle_app_error(Term) -> is_booting() -> is_booting(node()). +is_booting(Node) when Node =:= node() -> + case rabbit_prelaunch:get_boot_state() of + booting -> true; + _ -> false + end; is_booting(Node) -> - case rpc:call(Node, erlang, whereis, [rabbit_boot]) of + case rpc:call(Node, rabbit, is_booting, []) of {badrpc, _} = Err -> Err; - undefined -> false; - P when is_pid(P) -> true + Ret -> Ret end. @@ -795,9 +714,6 @@ do_wait_for_boot_to_start(Node, IterationsLeft) -> ok end. -wait_for_boot_to_finish(Node) -> - wait_for_boot_to_finish(Node, false, ?BOOT_FINISH_TIMEOUT). - wait_for_boot_to_finish(Node, PrintProgressReports) -> wait_for_boot_to_finish(Node, PrintProgressReports, ?BOOT_FINISH_TIMEOUT). @@ -917,17 +833,22 @@ total_queue_count() -> end, 0, rabbit_vhost:list_names()). -%% TODO this only determines if the rabbit application has started, -%% not if it is running, never mind plugins. It would be nice to have -%% more nuance here. - -spec is_running() -> boolean(). is_running() -> is_running(node()). -spec is_running(node()) -> boolean(). -is_running(Node) -> rabbit_nodes:is_process_running(Node, rabbit). +is_running(Node) when Node =:= node() -> + case rabbit_prelaunch:get_boot_state() of + ready -> true; + _ -> false + end; +is_running(Node) -> + case rpc:call(Node, rabbit, is_running, []) of + true -> true; + _ -> false + end. is_booted() -> is_booted(node()). @@ -983,105 +904,136 @@ rotate_logs() -> {'ok',pid()}. start(normal, []) -> - case erts_version_check() of - ok -> - rabbit_log:info("~n Starting RabbitMQ ~s on Erlang ~s~n ~s~n ~s~n", - [rabbit_misc:version(), rabbit_misc:otp_release(), - ?COPYRIGHT_MESSAGE, ?INFORMATION_MESSAGE]), - {ok, SupPid} = rabbit_sup:start_link(), - true = register(rabbit, self()), - print_banner(), - log_banner(), - warn_if_kernel_config_dubious(), - warn_if_disc_io_options_dubious(), - rabbit_boot_steps:run_boot_steps(), - {ok, SupPid}; - {error, {erlang_version_too_old, - {found, OTPRel, ERTSVer}, - {required, ?OTP_MINIMUM, ?ERTS_MINIMUM}}} -> - Msg = "This RabbitMQ version cannot run on Erlang ~s (erts ~s): " - "minimum required version is ~s (erts ~s)", - Args = [OTPRel, ERTSVer, ?OTP_MINIMUM, ?ERTS_MINIMUM], - rabbit_log:error(Msg, Args), - %% also print to stderr to make this more visible - io:format(standard_error, "Error: " ++ Msg ++ "~n", Args), - {error, {erlang_version_too_old, rabbit_misc:format("Erlang ~s or later is required, started on ~s", [?OTP_MINIMUM, OTPRel])}}; - Error -> + %% Reset boot state and clear the stop reason again (it was already + %% made in rabbitmq_prelaunch). + %% + %% This is important if the previous startup attempt failed after + %% rabbitmq_prelaunch was started and the application is still + %% running. + rabbit_prelaunch:set_boot_state(booting), + rabbit_prelaunch:clear_stop_reason(), + + try + run_prelaunch_second_phase(), + + rabbit_log:info("~n Starting RabbitMQ ~s on Erlang ~s~n ~s~n ~s~n", + [rabbit_misc:version(), rabbit_misc:otp_release(), + ?COPYRIGHT_MESSAGE, ?INFORMATION_MESSAGE]), + {ok, SupPid} = rabbit_sup:start_link(), + + %% Compatibility with older RabbitMQ versions + required by + %% rabbit_node_monitor:notify_node_up/0: + %% + %% We register the app process under the name `rabbit`. This is + %% checked by `is_running(Node)` on a remote node. The process + %% is also monitord by rabbit_node_monitor. + %% + %% The process name must be registered *before* running the boot + %% steps: that's when rabbit_node_monitor will set the process + %% monitor up. + %% + %% Note that plugins were not taken care of at this point + %% either. + rabbit_log_prelaunch:debug( + "Register `rabbit` process (~p) for rabbit_node_monitor", + [self()]), + true = register(rabbit, self()), + + print_banner(), + log_banner(), + warn_if_kernel_config_dubious(), + warn_if_disc_io_options_dubious(), + %% We run `rabbit` boot steps only for now. Plugins boot steps + %% will be executed as part of the postlaunch phase after they + %% are started. + rabbit_boot_steps:run_boot_steps([rabbit]), + run_postlaunch_phase(), + {ok, SupPid} + catch + throw:{error, _} = Error -> + mnesia:stop(), + rabbit_prelaunch_errors:log_error(Error), + rabbit_prelaunch:set_stop_reason(Error), + rabbit_prelaunch:set_boot_state(stopped), + Error; + Class:Exception:Stacktrace -> + mnesia:stop(), + rabbit_prelaunch_errors:log_exception( + Class, Exception, Stacktrace), + Error = {error, Exception}, + rabbit_prelaunch:set_stop_reason(Error), + rabbit_prelaunch:set_boot_state(stopped), Error end. +run_postlaunch_phase() -> + spawn(fun() -> do_run_postlaunch_phase() end). + +do_run_postlaunch_phase() -> + %% Once RabbitMQ itself is started, we need to run a few more steps, + %% in particular start plugins. + rabbit_log_prelaunch:debug(""), + rabbit_log_prelaunch:debug("== Postlaunch phase =="), + + try + rabbit_log_prelaunch:debug(""), + rabbit_log_prelaunch:debug("== Plugins =="), + + rabbit_log_prelaunch:debug("Setting plugins up"), + Plugins = rabbit_plugins:setup(), + rabbit_log_prelaunch:debug( + "Starting the following plugins: ~p", [Plugins]), + app_utils:load_applications(Plugins), + ok = rabbit_feature_flags:refresh_feature_flags_after_app_load( + Plugins), + lists:foreach( + fun(Plugin) -> + case application:ensure_all_started(Plugin) of + {ok, _} -> rabbit_boot_steps:run_boot_steps([Plugin]); + Error -> throw(Error) + end + end, Plugins), + + maybe_sd_notify(), + + rabbit_log_prelaunch:debug("Marking RabbitMQ as running"), + rabbit_prelaunch:set_boot_state(ready), + ok = rabbit_lager:broker_is_started(), + ok = log_broker_started( + rabbit_plugins:strictly_plugins(rabbit_plugins:active())) + catch + throw:{error, _} = Error -> + rabbit_prelaunch_errors:log_error(Error), + rabbit_prelaunch:set_stop_reason(Error), + do_stop(); + Class:Exception:Stacktrace -> + rabbit_prelaunch_errors:log_exception( + Class, Exception, Stacktrace), + Error = {error, Exception}, + rabbit_prelaunch:set_stop_reason(Error), + do_stop() + end. + prep_stop(State) -> rabbit_peer_discovery:maybe_unregister(), State. -spec stop(_) -> 'ok'. -stop(_State) -> +stop(State) -> + rabbit_prelaunch:set_boot_state(stopping), ok = rabbit_alarm:stop(), ok = case rabbit_mnesia:is_clustered() of true -> ok; false -> rabbit_table:clear_ram_only_tables() end, + case State of + [] -> rabbit_prelaunch:set_stop_reason(normal); + _ -> rabbit_prelaunch:set_stop_reason(State) + end, + rabbit_prelaunch:set_boot_state(stopped), ok. --spec boot_error(term(), not_available | [tuple()]) -> no_return(). - -boot_error(_, {could_not_start, rabbit, {{timeout_waiting_for_tables, _}, _}}) -> - AllNodes = rabbit_mnesia:cluster_nodes(all), - Suffix = "~nBACKGROUND~n==========~n~n" - "This cluster node was shut down while other nodes were still running.~n" - "To avoid losing data, you should start the other nodes first, then~n" - "start this one. To force this node to start, first invoke~n" - "\"rabbitmqctl force_boot\". If you do so, any changes made on other~n" - "cluster nodes after this one was shut down may be lost.~n", - {Err, Nodes} = - case AllNodes -- [node()] of - [] -> {"Timeout contacting cluster nodes. Since RabbitMQ was" - " shut down forcefully~nit cannot determine which nodes" - " are timing out.~n" ++ Suffix, []}; - Ns -> {rabbit_misc:format( - "Timeout contacting cluster nodes: ~p.~n" ++ Suffix, [Ns]), - Ns} - end, - log_boot_error_and_exit( - timeout_waiting_for_tables, - "~n" ++ Err ++ rabbit_nodes:diagnostics(Nodes), []); -boot_error(_, {error, {cannot_log_to_file, unknown, Reason}}) -> - log_boot_error_and_exit(could_not_initialise_logger, - "failed to initialised logger: ~p~n", - [Reason]); -boot_error(_, {error, {cannot_log_to_file, LogFile, - {cannot_create_parent_dirs, _, Reason}}}) -> - log_boot_error_and_exit(could_not_initialise_logger, - "failed to create parent directory for log file at '~s', reason: ~p~n", - [LogFile, Reason]); -boot_error(_, {error, {cannot_log_to_file, LogFile, Reason}}) -> - log_boot_error_and_exit(could_not_initialise_logger, - "failed to open log file at '~s', reason: ~p~n", - [LogFile, Reason]); -boot_error(_, {error, {generate_config_file, Error}}) -> - log_boot_error_and_exit(generate_config_file, - "~nConfig file generation failed:~n~s" - "In case the setting comes from a plugin, make sure that the plugin is enabled.~n" - "Alternatively remove the setting from the config.~n", - [Error]); -boot_error(Class, Reason) -> - LogLocations = log_locations(), - log_boot_error_and_exit( - Reason, - "~nError description:~s" - "~nLog file(s) (may contain more information):~n" ++ - lists:flatten([" ~s~n" || _ <- lists:seq(1, length(LogLocations))]), - [lager:pr_stacktrace(erlang:get_stacktrace(), {Class, Reason})] ++ - LogLocations). - --spec log_boot_error_and_exit(_, _, _) -> no_return(). -log_boot_error_and_exit(Reason, Format, Args) -> - rabbit_log:error(Format, Args), - io:format(standard_error, "~nBOOT FAILED~n===========~n" ++ Format ++ "~n", Args), - timer:sleep(1000), - exit(Reason). - %%--------------------------------------------------------------------------- %% boot step functions @@ -1141,10 +1093,6 @@ insert_default_data() -> %%--------------------------------------------------------------------------- %% logging -start_logger() -> - rabbit_lager:start_logger(), - ok. - -spec log_locations() -> [rabbit_lager:log_location()]. log_locations() -> rabbit_lager:log_locations(). @@ -1176,25 +1124,29 @@ log_broker_started(Plugins) -> rabbit_log:info(Message), io:format(" completed with ~p plugins.~n", [length(Plugins)]). -erts_version_check() -> - ERTSVer = erlang:system_info(version), - OTPRel = rabbit_misc:otp_release(), - case rabbit_misc:version_compare(?ERTS_MINIMUM, ERTSVer, lte) of - true when ?ERTS_MINIMUM =/= ERTSVer -> - ok; - true when ?ERTS_MINIMUM =:= ERTSVer andalso ?OTP_MINIMUM =< OTPRel -> - %% When a critical regression or bug is found, a new OTP - %% release can be published without changing the ERTS - %% version. For instance, this is the case with R16B03 and - %% R16B03-1. - %% - %% In this case, we compare the release versions - %% alphabetically. - ok; - _ -> {error, {erlang_version_too_old, - {found, OTPRel, ERTSVer}, - {required, ?OTP_MINIMUM, ?ERTS_MINIMUM}}} - end. +-define(RABBIT_TEXT_LOGO, + "~n ## ## ~s ~s" + "~n ## ##" + "~n ########## ~s" + "~n ###### ##" + "~n ########## ~s"). +-define(FG8_START, "\033[38;5;202m"). +-define(BG8_START, "\033[48;5;202m"). +-define(FG32_START, "\033[38;2;255;102;0m"). +-define(BG32_START, "\033[48;2;255;102;0m"). +-define(C_END, "\033[0m"). +-define(RABBIT_8BITCOLOR_LOGO, + "~n " ?BG8_START " " ?C_END " " ?BG8_START " " ?C_END " \033[1m" ?FG8_START "~s" ?C_END " ~s" + "~n " ?BG8_START " " ?C_END " " ?BG8_START " " ?C_END + "~n " ?BG8_START " " ?C_END " ~s" + "~n " ?BG8_START " " ?C_END " " ?BG8_START " " ?C_END + "~n " ?BG8_START " " ?C_END " ~s"). +-define(RABBIT_32BITCOLOR_LOGO, + "~n " ?BG32_START " " ?C_END " " ?BG32_START " " ?C_END " \033[1m" ?FG32_START "~s" ?C_END " ~s" + "~n " ?BG32_START " " ?C_END " " ?BG32_START " " ?C_END + "~n " ?BG32_START " " ?C_END " ~s" + "~n " ?BG32_START " " ?C_END " " ?BG32_START " " ?C_END + "~n " ?BG32_START " " ?C_END " ~s"). print_banner() -> {ok, Product} = application:get_key(description), @@ -1205,14 +1157,23 @@ print_banner() -> (_, []) -> {"", ["(none)"]} end, + Logo = case rabbit_prelaunch:get_context() of + %% We use the colored logo only when running the + %% interactive shell and when colors are supported. + %% + %% Basically it means it will be used on Unix when + %% running "make run-broker" and that's about it. + #{os_type := {unix, darwin}, + interactive_shell := true, + output_supports_colors := true} -> ?RABBIT_8BITCOLOR_LOGO; + #{interactive_shell := true, + output_supports_colors := true} -> ?RABBIT_32BITCOLOR_LOGO; + _ -> ?RABBIT_TEXT_LOGO + end, %% padded list lines {LogFmt, LogLocations} = LineListFormatter("~n ~ts", log_locations()), {CfgFmt, CfgLocations} = LineListFormatter("~n ~ts", config_locations()), - io:format("~n ## ## ~s ~s" - "~n ## ##" - "~n ########## ~s" - "~n ###### ##" - "~n ########## ~s" + io:format(Logo ++ "~n" "~n Doc guides: https://rabbitmq.com/documentation.html" "~n Support: https://rabbitmq.com/contact.html" |
