1%% This Source Code Form is subject to the terms of the Mozilla Public 2%% License, v. 2.0. If a copy of the MPL was not distributed with this 3%% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4%% 5%% Copyright (c) 2019-2021 VMware, Inc. or its affiliates. All rights reserved. 6%% 7 8%% @author The RabbitMQ team 9%% @copyright 2019-2021 VMware, Inc. or its affiliates. 10%% 11%% @doc 12%% This module manages the configuration of the Erlang Logger facility. In 13%% other words, it translates the RabbitMQ logging configuration (in the 14%% Cuttlefish format or classic Erlang-term-based configuration) into Erlang 15%% Logger handler setups. 16%% 17%% Configuring the Erlang Logger is done in two steps: 18%% <ol> 19%% <li>Logger handler configurations are created based on the configuration 20%% and the context (see {@link //rabbit_common/rabbit_env}).</li> 21%% <li>Created handlers are installed (i.e. they become active). Any handlers 22%% previously installed by this module are removed.</li> 23%% </ol> 24%% 25%% It also takes care of setting the `$ERL_CRASH_DUMP' variable to enable 26%% Erlang core dumps. 27%% 28%% Note that before this module handles the Erlang Logger, {@link 29%% //rabbitmq_prelaunch/rabbit_prelaunch_early_logging} configures basic 30%% logging to have messages logged as soon as possible during RabbitMQ 31%% startup. 32%% 33%% == How to configure RabbitMQ logging == 34%% 35%% RabbitMQ supports a main/default logging output and per-category outputs. 36%% An output is a combination of a destination (a text file or stdout for 37%% example) and a message formatted (e.g. plain text or JSON). 38%% 39%% Here is the Erlang-term-based configuration expected and supported by this 40%% module: 41%% 42%% ``` 43%% {rabbit, [ 44%% {log_root, string()}, 45%% {log, [ 46%% {categories, [ 47%% {default, [ 48%% {level, Level} 49%% ]}, 50%% {CategoryName, [ 51%% {level, Level}, 52%% {file, Filename} 53%% ]} 54%% ]}, 55%% 56%% {console, [ 57%% {level, Level}, 58%% {enabled, boolean()} 59%% ]}, 60%% 61%% {exchange, [ 62%% {level, Level}, 63%% {enabled, boolean()} 64%% ]}, 65%% 66%% {file, [ 67%% {level, Level}, 68%% {file, Filename | false}, 69%% {date, RotationDateSpec}, 70%% {size, RotationSize}, 71%% {count, RotationCount}, 72%% ]}, 73%% 74%% {journald, [ 75%% {level, Level}, 76%% {enabled, boolean()}, 77%% {fields, proplists:proplist()} 78%% ]} 79%% 80%% {syslog, [ 81%% {level, Level}, 82%% {enabled, boolean()} 83%% ]} 84%% ]} 85%% ]}. 86%% 87%% Level = logger:level(). 88%% Filename = file:filename(). 89%% RotationDateSpec = string(). % Pattern format used by newsyslog.conf(5). 90%% RotationSize = non_neg_integer() | infinity. 91%% RotationCount = non_neg_integer(). 92%% ''' 93%% 94%% See `priv/schema/rabbit.schema' for the definition of the Cuttlefish 95%% configuration schema. 96 97-module(rabbit_prelaunch_logging). 98 99-include_lib("kernel/include/logger.hrl"). 100-include_lib("rabbit_common/include/logging.hrl"). 101 102-export([setup/1, 103 set_log_level/1, 104 log_locations/0]). 105 106-ifdef(TEST). 107-export([clear_config_run_number/0, 108 get_less_severe_level/2]). 109-endif. 110 111-export_type([log_location/0]). 112 113-type log_location() :: file:filename() | string(). 114%% A short description of an output. 115%% 116%% If the output is the console, the location is either `"<stdout>"' or 117%% `"<stderr>"'. 118%% 119%% If the output is an exchange, the location is the string `"exchange:"' with 120%% the exchange name appended. 121%% 122%% If the output is a file, the location is the absolute filename. 123%% 124%% If the output is journald, the location is `"<journald>"'. 125%% 126%% If the output is syslog, the location is the string `"syslog:"' with the 127%% syslog server hostname appended. 128 129-type category_name() :: atom(). 130%% The name of a log category. 131%% Erlang Logger uses the concept of "domain" which is an ordered list of 132%% atoms. A category is mapped to the domain `[?RMQLOG_SUPER_DOMAIN_NAME, 133%% Category]'. In other words, a category is a subdomain of the `rabbitmq' 134%% domain. 135 136-type console_props() :: [{level, logger:level()} | 137 {enabled, boolean()} | 138 {stdio, stdout | stderr} | 139 {formatter, {atom(), term()}}]. 140%% Console properties are the parameters in the configuration file for a 141%% console-based handler. 142 143-type exchange_props() :: [{level, logger:level()} | 144 {enabled, boolean()} | 145 {formatter, {atom(), term()}}]. 146%% Exchange properties are the parameters in the configuration file for an 147%% exchange-based handler. 148 149-type file_props() :: [{level, logger:level()} | 150 {file, file:filename() | false} | 151 {date, string()} | 152 {size, non_neg_integer()} | 153 {count, non_neg_integer()} | 154 {formatter, {atom(), term()}}]. 155%% File properties are the parameters in the configuration file for a 156%% file-based handler. 157 158-type journald_props() :: [{level, logger:level()} | 159 {enabled, boolean()} | 160 {fields, proplists:proplist()}]. 161%% journald properties are the parameters in the configuration file for a 162%% journald-based handler. 163 164-type syslog_props() :: [{level, logger:level()} | 165 {enabled, boolean()} | 166 {formatter, {atom(), term()}}]. 167%% Syslog properties are the parameters in the configuration file for a 168%% syslog-based handler. 169 170-type main_log_env() :: [{console, console_props()} | 171 {exchange, exchange_props()} | 172 {file, file_props()} | 173 {journald, journald_props()} | 174 {syslog, syslog_props()}]. 175%% The main log environment is the parameters in the configuration file for 176%% the main log handler (i.e. where all messages go by default). 177 178-type per_cat_env() :: [{level, logger:level()} | 179 {file, file:filename()}]. 180%% A per-category log environment is the parameters in the configuration file 181%% for a specific category log handler. There can be one per category. 182 183-type default_cat_env() :: [{level, logger:level()}]. 184%% The `default' category log environment is special (read: awkward) in the 185%% configuration file. It is used to change the log level of the main log 186%% handler. 187 188-type log_app_env() :: [main_log_env() | 189 {categories, [{default, default_cat_env()} | 190 {category_name(), per_cat_env()}]}]. 191%% The value for the `log' key in the `rabbit' application environment. 192 193-type global_log_config() :: #{level => logger:level() | all | none, 194 outputs := [logger:handler_config()]}. 195%% This is the internal structure used to prepare the handlers for the 196%% main/global messages (i.e. not marked with a specific category). 197 198-type per_cat_log_config() :: global_log_config(). 199%% This is the internal structure used to prepare the handlers for 200%% category-specific messages. 201 202-type log_config() :: #{global := global_log_config(), 203 per_category := #{ 204 category_name() => per_cat_log_config()}}. 205%% This is the internal structure to store the global and per-category 206%% configurations, to prepare the final handlers. 207 208-type handler_key() :: atom(). 209%% Key used to deduplicate handlers before they are installed in Logger. 210 211-type id_assignment_state() :: #{config_run_number := pos_integer(), 212 next_file := pos_integer()}. 213%% State used while assigning IDs to handlers. 214 215-spec setup(rabbit_env:context()) -> ok. 216%% @doc 217%% Configures or reconfigures logging. 218%% 219%% The logging framework is the builtin Erlang Logger API. The configuration 220%% is based on the configuration file and the environment. 221%% 222%% In addition to logging, it sets the `$ERL_CRASH_DUMP' environment variable 223%% to enable Erlang crash dumps. 224%% 225%% @param Context the RabbitMQ context (see {@link 226%% //rabbitmq_prelaunch/rabbit_prelaunch:get_context/0}). 227 228setup(Context) -> 229 ?LOG_DEBUG("\n== Logging ==", 230 #{domain => ?RMQLOG_DOMAIN_PRELAUNCH}), 231 ok = compute_config_run_number(), 232 ok = set_ERL_CRASH_DUMP_envvar(Context), 233 ok = configure_logger(Context). 234 235-spec set_log_level(logger:level()) -> ok | {error, term()}. 236%% @doc 237%% Changes the log level. 238%% 239%% The log level is changed for the following layers of filtering: 240%% <ul> 241%% <li>the primary log level</li> 242%% <li>the module log level(s)</li> 243%% <li>the handler log level(s)</li> 244%% </ul> 245%% 246%% @param Level the new log level. 247 248set_log_level(Level) -> 249 %% Primary log level. 250 ?LOG_DEBUG( 251 "Logging: changing primary log level to ~s", [Level], 252 #{domain => ?RMQLOG_DOMAIN_GLOBAL}), 253 logger:set_primary_config(level, Level), 254 255 %% Per-module log level. 256 lists:foreach( 257 fun({Module, _}) -> 258 ?LOG_DEBUG( 259 "Logging: changing '~s' module log level to ~s", 260 [Module, Level], 261 #{domain => ?RMQLOG_DOMAIN_GLOBAL}), 262 _ = logger:set_module_level(Module, Level) 263 end, logger:get_module_level()), 264 265 %% Per-handler log level. 266 %% In the handler, we have the "top-level" log level for that handler, plus 267 %% per-domain (per-category) levels inside a filter. We need to change all 268 %% of them to the new level. 269 lists:foreach( 270 fun 271 (#{id := Id, filters := Filters, config := Config}) -> 272 ?LOG_DEBUG( 273 "Logging: changing '~s' handler log level to ~s", 274 [Id, Level], 275 #{domain => ?RMQLOG_DOMAIN_GLOBAL}), 276 Filters1 = lists:map( 277 fun 278 %% We only modify the filter we know about. 279 %% The argument to that filter is of the form: 280 %% #{CatName => Level} 281 %% (where CatName if an atom()) 282 %% 283 %% We don't change the level for a category if 284 %% it is set to 'none' because it means the 285 %% category is filtered out by this filter. 286 ({?FILTER_NAME, {Fun, Arg}}) when is_map(Arg) -> 287 Arg1 = maps:map( 288 fun 289 (_, none) -> none; 290 (_, _) -> Level 291 end, Arg), 292 {?FILTER_NAME, {Fun, Arg1}}; 293 %% We also change what we do with Erlang 294 %% progress reports. 295 ({progress_reports, {Fun, _}}) -> 296 Action = case Level of 297 debug -> log; 298 _ -> stop 299 end, 300 {progress_reports, {Fun, Action}}; 301 %% Other filters are left untouched. 302 (Filter) -> 303 Filter 304 end, Filters), 305 %% If the log level is set to `debug', we turn off burst limit to 306 %% make sure all debug messages make it. 307 Config1 = adjust_burst_limit(Config, Level), 308 logger:set_handler_config(Id, filters, Filters1), 309 logger:set_handler_config(Id, config, Config1), 310 logger:set_handler_config(Id, level, Level), 311 ok; 312 (#{id := Id, config := Config}) -> 313 ?LOG_DEBUG( 314 "Logging: changing '~s' handler log level to ~s", 315 [Id, Level], 316 #{domain => ?RMQLOG_DOMAIN_GLOBAL}), 317 %% If the log level is set to `debug', we turn off burst limit to 318 %% make sure all debug messages make it. 319 Config1 = adjust_burst_limit(Config, Level), 320 logger:set_handler_config(Id, config, Config1), 321 logger:set_handler_config(Id, level, Level), 322 ok 323 end, logger:get_handler_config()), 324 ok. 325 326-spec log_locations() -> [file:filename() | string()]. 327%% @doc 328%% Returns the list of output locations. 329%% 330%% For file-based handlers, the absolute filename is returned. 331%% 332%% For console-based handlers, a string literal is returned; either 333%% `"<stdout>"' or `"<stderr>"'. 334%% 335%% For exchange-based handlers, a string of the form `"exchange:Exchange"' is 336%% returned, where `Exchange' is the name of the exchange. 337%% 338%% For journald-based handlers, a string literal is returned; `"<journald>"'. 339%% 340%% For syslog-based handlers, a string of the form `"syslog:Hostname"' is 341%% returned, where `Hostname' is either the hostname of the remote syslog 342%% server, or an empty string if none were configured (which means log to 343%% localhost). 344%% 345%% @returns the list of output locations. 346%% 347%% @see log_location() 348 349log_locations() -> 350 Handlers = logger:get_handler_config(), 351 log_locations(Handlers, []). 352 353log_locations([#{module := Mod, 354 config := #{type := file, 355 file := Filename}} | Rest], 356 Locations) 357 when ?IS_STD_H_COMPAT(Mod) -> 358 Locations1 = add_once(Locations, Filename), 359 log_locations(Rest, Locations1); 360log_locations([#{module := Mod, 361 config := #{type := standard_io}} | Rest], 362 Locations) 363 when ?IS_STD_H_COMPAT(Mod) -> 364 Locations1 = add_once(Locations, "<stdout>"), 365 log_locations(Rest, Locations1); 366log_locations([#{module := Mod, 367 config := #{type := standard_error}} | Rest], 368 Locations) 369 when ?IS_STD_H_COMPAT(Mod) -> 370 Locations1 = add_once(Locations, "<stderr>"), 371 log_locations(Rest, Locations1); 372log_locations([#{module := systemd_journal_h} | Rest], 373 Locations) -> 374 Locations1 = add_once(Locations, "<journald>"), 375 log_locations(Rest, Locations1); 376log_locations([#{module := syslog_logger_h} | Rest], 377 Locations) -> 378 Host = application:get_env(syslog, dest_host, ""), 379 Locations1 = add_once( 380 Locations, 381 rabbit_misc:format("syslog:~s", [Host])), 382 log_locations(Rest, Locations1); 383log_locations([#{module := rabbit_logger_exchange_h, 384 config := #{exchange := Exchange}} | Rest], 385 Locations) -> 386 Locations1 = add_once( 387 Locations, 388 rabbit_misc:format("exchange:~p", [Exchange])), 389 log_locations(Rest, Locations1); 390log_locations([_ | Rest], Locations) -> 391 log_locations(Rest, Locations); 392log_locations([], Locations) -> 393 lists:sort(Locations). 394 395add_once(Locations, Location) -> 396 case lists:member(Location, Locations) of 397 false -> [Location | Locations]; 398 true -> Locations 399 end. 400 401%% ------------------------------------------------------------------- 402%% ERL_CRASH_DUMP setting. 403%% ------------------------------------------------------------------- 404 405-spec set_ERL_CRASH_DUMP_envvar(rabbit_env:context()) -> ok. 406 407set_ERL_CRASH_DUMP_envvar(Context) -> 408 case os:getenv("ERL_CRASH_DUMP") of 409 false -> 410 LogBaseDir = get_log_base_dir(Context), 411 ErlCrashDump = filename:join(LogBaseDir, "erl_crash.dump"), 412 ?LOG_DEBUG( 413 "Setting $ERL_CRASH_DUMP environment variable to \"~ts\"", 414 [ErlCrashDump], 415 #{domain => ?RMQLOG_DOMAIN_PRELAUNCH}), 416 os:putenv("ERL_CRASH_DUMP", ErlCrashDump), 417 ok; 418 ErlCrashDump -> 419 ?LOG_DEBUG( 420 "$ERL_CRASH_DUMP environment variable already set to \"~ts\"", 421 [ErlCrashDump], 422 #{domain => ?RMQLOG_DOMAIN_PRELAUNCH}), 423 ok 424 end. 425 426-spec get_log_base_dir(rabbit_env:context()) -> file:filename(). 427%% @doc 428%% Returns the log base directory. 429%% 430%% The precedence is: 431%% <ol> 432%% <li>the $RABBITMQ_LOG_BASE variable if overriden in the environment</li> 433%% <li>the value of `log_root' in the application environment</li> 434%% <li>the default value</li> 435%% </ol> 436%% 437%% @param Context the RabbitMQ context (see {@link 438%% //rabbitmq_prelaunch/rabbit_prelaunch:get_context/0}). 439%% @returns an absolute path to a directory. 440 441get_log_base_dir(#{log_base_dir := LogBaseDirFromEnv} = Context) -> 442 case rabbit_env:has_var_been_overridden(Context, log_base_dir) of 443 false -> application:get_env(rabbit, log_root, LogBaseDirFromEnv); 444 true -> LogBaseDirFromEnv 445 end. 446 447%% ------------------------------------------------------------------- 448%% Logger's handlers configuration. 449%% ------------------------------------------------------------------- 450 451-define(CONFIG_RUN_NUMBER_KEY, {?MODULE, config_run_number}). 452 453-spec compute_config_run_number() -> ok. 454%% @doc 455%% Compute the next configuration run number. 456%% 457%% We use a "configuration run number" to distinguish previously installed 458%% handlers (if any) from the ones we want to install in a subsequent call of 459%% {@link setup/1}. 460%% 461%% The configuration run number appears in the generated IDs for handlers. 462%% This is how we can filter old ones. 463%% 464%% @returns an positive integer. 465 466compute_config_run_number() -> 467 RunNum = persistent_term:get(?CONFIG_RUN_NUMBER_KEY, 0), 468 ok = persistent_term:put(?CONFIG_RUN_NUMBER_KEY, RunNum + 1). 469 470-spec get_config_run_number() -> pos_integer(). 471 472get_config_run_number() -> 473 persistent_term:get(?CONFIG_RUN_NUMBER_KEY). 474 475-ifdef(TEST). 476-spec clear_config_run_number() -> ok. 477%% @doc 478%% Clears the recorded configuration run number. 479%% 480%% In testsuites, we want to be able to reset that number to 1 because 481%% testcases have expectations on handler IDs. 482 483clear_config_run_number() -> 484 _ = persistent_term:erase(?CONFIG_RUN_NUMBER_KEY), 485 ok. 486-endif. 487 488-spec configure_logger(rabbit_env:context()) -> ok. 489 490configure_logger(Context) -> 491 %% Configure main handlers. 492 %% We distinguish them by their type and possibly other 493 %% parameters (file name, syslog settings, etc.). 494 LogConfig0 = get_log_configuration_from_app_env(), 495 LogConfig1 = handle_default_and_overridden_outputs(LogConfig0, Context), 496 LogConfig2 = apply_log_levels_from_env(LogConfig1, Context), 497 LogConfig3 = make_filenames_absolute(LogConfig2, Context), 498 LogConfig4 = configure_formatters(LogConfig3, Context), 499 500 %% At this point, the log configuration is complete: we know the global 501 %% parameters as well as the per-category settings. 502 %% 503 %% Now, we turn that into a map of handlers. We use a map to deduplicate 504 %% handlers. For instance, if the same file is used for global logging and 505 %% a specific category. 506 %% 507 %% The map is then converted to a list, once the deduplication is done and 508 %% IDs are assigned to handlers. 509 Handlers = create_logger_handlers_conf(LogConfig4), 510 ?LOG_DEBUG( 511 "Logging: logger handlers:~n ~p", [Handlers], 512 #{domain => ?RMQLOG_DOMAIN_PRELAUNCH}), 513 514 %% We can now install the new handlers. The function takes care of 515 %% removing previously configured handlers (after installing the new 516 %% ones to ensure we don't loose a message). 517 ok = install_handlers(Handlers), 518 519 %% Let's log a message per log level (if debug logging is enabled). This 520 %% is handy if the user wants to verify the configuration is what he 521 %% expects. 522 ok = maybe_log_test_messages(LogConfig3). 523 524-spec get_log_configuration_from_app_env() -> log_config(). 525 526get_log_configuration_from_app_env() -> 527 %% The log configuration in the Cuttlefish configuration file or the 528 %% application environment is not structured logically. This functions is 529 %% responsible for extracting the configuration and organize it. If one day 530 %% we decide to fix the configuration structure, we just have to modify 531 %% this function and normalize_*(). 532 Env = get_log_app_env(), 533 EnvWithoutCats = proplists:delete(categories, Env), 534 DefaultAndCatProps = proplists:get_value(categories, Env, []), 535 DefaultProps = proplists:get_value(default, DefaultAndCatProps, []), 536 CatProps = proplists:delete(default, DefaultAndCatProps), 537 538 %% This "normalization" turns the RabbitMQ-specific configuration into a 539 %% structure which stores Logger handler configurations. That structure is 540 %% later modified to reach the final handler configurations. 541 PerCatConfig = maps:from_list( 542 [{Cat, normalize_per_cat_log_config(Props)} 543 || {Cat, Props} <- CatProps]), 544 GlobalConfig = normalize_main_log_config(EnvWithoutCats, DefaultProps), 545 #{global => GlobalConfig, 546 per_category => PerCatConfig}. 547 548-spec get_log_app_env() -> log_app_env(). 549 550get_log_app_env() -> 551 application:get_env(rabbit, log, []). 552 553-spec normalize_main_log_config(main_log_env(), default_cat_env()) -> 554 global_log_config(). 555 556normalize_main_log_config(Props, DefaultProps) -> 557 Outputs = case proplists:get_value(level, DefaultProps) of 558 undefined -> #{outputs => []}; 559 Level -> #{outputs => [], 560 level => Level} 561 end, 562 Props1 = compute_implicitly_enabled_output(Props), 563 normalize_main_log_config1(Props1, Outputs). 564 565compute_implicitly_enabled_output(Props) -> 566 {ConsoleEnabled, Props1} = compute_implicitly_enabled_output( 567 console, Props), 568 {ExchangeEnabled, Props2} = compute_implicitly_enabled_output( 569 exchange, Props1), 570 {JournaldEnabled, Props3} = compute_implicitly_enabled_output( 571 journald, Props2), 572 {SyslogEnabled, Props4} = compute_implicitly_enabled_output( 573 syslog, Props3), 574 FileDisabledByDefault = 575 ConsoleEnabled orelse 576 ExchangeEnabled orelse 577 JournaldEnabled orelse 578 SyslogEnabled, 579 580 FileProps = proplists:get_value(file, Props4, []), 581 case is_output_explicitely_enabled(FileProps) of 582 true -> 583 Props4; 584 false -> 585 case FileDisabledByDefault of 586 true -> 587 FileProps1 = lists:keystore( 588 file, 1, FileProps, {file, false}), 589 lists:keystore( 590 file, 1, Props4, {file, FileProps1}); 591 false -> 592 Props4 593 end 594 end. 595 596compute_implicitly_enabled_output(PropName, Props) -> 597 SubProps = proplists:get_value(PropName, Props, []), 598 {Enabled, SubProps1} = compute_implicitly_enabled_output1(SubProps), 599 {Enabled, 600 lists:keystore(PropName, 1, Props, {PropName, SubProps1})}. 601 602compute_implicitly_enabled_output1(SubProps) -> 603 %% We consider the output enabled or disabled if: 604 %% * it is explicitely marked as such, or 605 %% * the level is set to a log level (enabled) or `none' (disabled) 606 Enabled = proplists:get_value( 607 enabled, SubProps, 608 proplists:get_value(level, SubProps, none) =/= none), 609 {Enabled, 610 lists:keystore(enabled, 1, SubProps, {enabled, Enabled})}. 611 612is_output_explicitely_enabled(FileProps) -> 613 %% We consider the output enabled or disabled if: 614 %% * the file is explicitely set, or 615 %% * the level is set to a log level (enabled) or `none' (disabled) 616 File = proplists:get_value(file, FileProps), 617 Level = proplists:get_value(level, FileProps), 618 is_list(File) orelse (Level =/= undefined andalso Level =/= none). 619 620normalize_main_log_config1([{Type, Props} | Rest], 621 #{outputs := Outputs} = LogConfig) -> 622 Outputs1 = normalize_main_output(Type, Props, Outputs), 623 LogConfig1 = LogConfig#{outputs => Outputs1}, 624 normalize_main_log_config1(Rest, LogConfig1); 625normalize_main_log_config1([], LogConfig) -> 626 LogConfig. 627 628-spec normalize_main_output 629(console, console_props(), [logger:handler_config()]) -> 630 [logger:handler_config()]; 631(exchange, exchange_props(), [logger:handler_config()]) -> 632 [logger:handler_config()]; 633(file, file_props(), [logger:handler_config()]) -> 634 [logger:handler_config()]; 635(journald, journald_props(), [logger:handler_config()]) -> 636 [logger:handler_config()]; 637(syslog, syslog_props(), [logger:handler_config()]) -> 638 [logger:handler_config()]. 639 640normalize_main_output(console, Props, Outputs) -> 641 normalize_main_console_output( 642 Props, 643 #{module => rabbit_logger_std_h, 644 config => #{type => standard_io}}, 645 Outputs); 646normalize_main_output(exchange, Props, Outputs) -> 647 normalize_main_exchange_output( 648 Props, 649 #{module => rabbit_logger_exchange_h, 650 config => #{}}, 651 Outputs); 652normalize_main_output(file, Props, Outputs) -> 653 normalize_main_file_output( 654 Props, 655 #{module => rabbit_logger_std_h, 656 config => #{type => file}}, 657 Outputs); 658normalize_main_output(journald, Props, Outputs) -> 659 normalize_main_journald_output( 660 Props, 661 #{module => systemd_journal_h, 662 config => #{}}, 663 Outputs); 664normalize_main_output(syslog, Props, Outputs) -> 665 normalize_main_syslog_output( 666 Props, 667 #{module => syslog_logger_h, 668 config => #{}}, 669 Outputs). 670 671-spec normalize_main_file_output(file_props(), logger:handler_config(), 672 [logger:handler_config()]) -> 673 [logger:handler_config()]. 674 675normalize_main_file_output(Props, Output, Outputs) -> 676 Enabled = case proplists:get_value(file, Props) of 677 false -> false; 678 _ -> true 679 end, 680 case Enabled of 681 true -> normalize_main_file_output1(Props, Output, Outputs); 682 false -> remove_main_file_output(Outputs) 683 end. 684 685normalize_main_file_output1( 686 [{file, Filename} | Rest], 687 #{config := Config} = Output, Outputs) -> 688 Output1 = Output#{config => Config#{file => Filename}}, 689 normalize_main_file_output1(Rest, Output1, Outputs); 690normalize_main_file_output1( 691 [{level, Level} | Rest], 692 Output, Outputs) -> 693 Output1 = Output#{level => Level}, 694 normalize_main_file_output1(Rest, Output1, Outputs); 695normalize_main_file_output1( 696 [{date, DateSpec} | Rest], 697 #{config := Config} = Output, Outputs) -> 698 Output1 = Output#{config => Config#{rotate_on_date => DateSpec}}, 699 normalize_main_file_output1(Rest, Output1, Outputs); 700normalize_main_file_output1( 701 [{size, Size} | Rest], 702 #{config := Config} = Output, Outputs) -> 703 Output1 = Output#{config => Config#{max_no_bytes => Size}}, 704 normalize_main_file_output1(Rest, Output1, Outputs); 705normalize_main_file_output1( 706 [{count, Count} | Rest], 707 #{config := Config} = Output, Outputs) -> 708 Output1 = Output#{config => Config#{max_no_files => Count}}, 709 normalize_main_file_output1(Rest, Output1, Outputs); 710normalize_main_file_output1( 711 [{formatter, undefined} | Rest], 712 Output, Outputs) -> 713 normalize_main_file_output1(Rest, Output, Outputs); 714normalize_main_file_output1( 715 [{formatter, Formatter} | Rest], 716 Output, Outputs) -> 717 Output1 = Output#{formatter => Formatter}, 718 normalize_main_file_output1(Rest, Output1, Outputs); 719normalize_main_file_output1([], Output, Outputs) -> 720 [Output | Outputs]. 721 722remove_main_file_output(Outputs) -> 723 lists:filter( 724 fun 725 (#{module := rabbit_logger_std_h, 726 config := #{type := file}}) -> false; 727 (_) -> true 728 end, Outputs). 729 730-spec normalize_main_console_output(console_props(), logger:handler_config(), 731 [logger:handler_config()]) -> 732 [logger:handler_config()]. 733 734normalize_main_console_output(Props, Output, Outputs) -> 735 Enabled = proplists:get_value(enabled, Props), 736 case Enabled of 737 true -> normalize_main_console_output1(Props, Output, Outputs); 738 false -> remove_main_console_output(Output, Outputs) 739 end. 740 741normalize_main_console_output1( 742 [{enabled, true} | Rest], 743 Output, Outputs) -> 744 normalize_main_console_output1(Rest, Output, Outputs); 745normalize_main_console_output1( 746 [{level, Level} | Rest], 747 Output, Outputs) -> 748 Output1 = Output#{level => Level}, 749 normalize_main_console_output1(Rest, Output1, Outputs); 750normalize_main_console_output1( 751 [{stdio, stdout} | Rest], 752 #{config := Config} = Output, Outputs) -> 753 Config1 = Config#{type => standard_io}, 754 Output1 = Output#{config => Config1}, 755 normalize_main_console_output1(Rest, Output1, Outputs); 756normalize_main_console_output1( 757 [{stdio, stderr} | Rest], 758 #{config := Config} = Output, Outputs) -> 759 Config1 = Config#{type => standard_error}, 760 Output1 = Output#{config => Config1}, 761 normalize_main_console_output1(Rest, Output1, Outputs); 762normalize_main_console_output1( 763 [{formatter, undefined} | Rest], 764 Output, Outputs) -> 765 normalize_main_console_output1(Rest, Output, Outputs); 766normalize_main_console_output1( 767 [{formatter, Formatter} | Rest], 768 Output, Outputs) -> 769 Output1 = Output#{formatter => Formatter}, 770 normalize_main_console_output1(Rest, Output1, Outputs); 771normalize_main_console_output1([], Output, Outputs) -> 772 [Output | Outputs]. 773 774remove_main_console_output( 775 #{module := Mod1, config := #{type := Stddev}}, 776 Outputs) 777 when ?IS_STD_H_COMPAT(Mod1) andalso 778 ?IS_STDDEV(Stddev) -> 779 lists:filter( 780 fun 781 (#{module := Mod2, 782 config := #{type := standard_io}}) 783 when ?IS_STD_H_COMPAT(Mod2) -> 784 false; 785 (#{module := Mod2, 786 config := #{type := standard_error}}) 787 when ?IS_STD_H_COMPAT(Mod2) -> 788 false; 789 (_) -> 790 true 791 end, Outputs). 792 793-spec normalize_main_exchange_output( 794 exchange_props(), logger:handler_config(), 795 [logger:handler_config()]) -> 796 [logger:handler_config()]. 797 798normalize_main_exchange_output(Props, Output, Outputs) -> 799 Enabled = proplists:get_value(enabled, Props), 800 case Enabled of 801 true -> normalize_main_exchange_output1(Props, Output, Outputs); 802 false -> remove_main_exchange_output(Output, Outputs) 803 end. 804 805normalize_main_exchange_output1( 806 [{enabled, true} | Rest], 807 Output, Outputs) -> 808 normalize_main_exchange_output1(Rest, Output, Outputs); 809normalize_main_exchange_output1( 810 [{level, Level} | Rest], 811 Output, Outputs) -> 812 Output1 = Output#{level => Level}, 813 normalize_main_exchange_output1(Rest, Output1, Outputs); 814normalize_main_exchange_output1( 815 [{formatter, undefined} | Rest], 816 Output, Outputs) -> 817 normalize_main_exchange_output1(Rest, Output, Outputs); 818normalize_main_exchange_output1( 819 [{formatter, Formatter} | Rest], 820 Output, Outputs) -> 821 Output1 = Output#{formatter => Formatter}, 822 normalize_main_exchange_output1(Rest, Output1, Outputs); 823normalize_main_exchange_output1([], Output, Outputs) -> 824 [Output | Outputs]. 825 826remove_main_exchange_output( 827 #{module := rabbit_logger_exchange_h}, Outputs) -> 828 lists:filter( 829 fun 830 (#{module := rabbit_logger_exchange_h}) -> false; 831 (_) -> true 832 end, Outputs). 833 834-spec normalize_main_journald_output(journald_props(), logger:handler_config(), 835 [logger:handler_config()]) -> 836 [logger:handler_config()]. 837 838normalize_main_journald_output(Props, Output, Outputs) -> 839 Enabled = proplists:get_value(enabled, Props), 840 case Enabled of 841 true -> normalize_main_journald_output1(Props, Output, Outputs); 842 false -> remove_main_journald_output(Output, Outputs) 843 end. 844 845normalize_main_journald_output1( 846 [{enabled, true} | Rest], 847 Output, Outputs) -> 848 normalize_main_journald_output1(Rest, Output, Outputs); 849normalize_main_journald_output1( 850 [{level, Level} | Rest], 851 Output, Outputs) -> 852 Output1 = Output#{level => Level}, 853 normalize_main_journald_output1(Rest, Output1, Outputs); 854normalize_main_journald_output1( 855 [{fields, FieldMapping} | Rest], 856 #{config := Config} = Output, Outputs) -> 857 Config1 = Config#{fields => FieldMapping}, 858 Output1 = Output#{config => Config1}, 859 normalize_main_journald_output1(Rest, Output1, Outputs); 860normalize_main_journald_output1( 861 [{formatter, undefined} | Rest], 862 Output, Outputs) -> 863 normalize_main_journald_output1(Rest, Output, Outputs); 864normalize_main_journald_output1( 865 [{formatter, Formatter} | Rest], 866 Output, Outputs) -> 867 Output1 = Output#{formatter => Formatter}, 868 normalize_main_journald_output1(Rest, Output1, Outputs); 869normalize_main_journald_output1([], Output, Outputs) -> 870 [Output | Outputs]. 871 872remove_main_journald_output( 873 #{module := systemd_journal_h}, 874 Outputs) -> 875 lists:filter( 876 fun 877 (#{module := systemd_journal_h}) -> false; 878 (_) -> true 879 end, Outputs). 880 881-spec normalize_main_syslog_output( 882 syslog_props(), logger:handler_config(), 883 [logger:handler_config()]) -> 884 [logger:handler_config()]. 885 886normalize_main_syslog_output(Props, Output, Outputs) -> 887 Enabled = proplists:get_value(enabled, Props), 888 case Enabled of 889 true -> normalize_main_syslog_output1(Props, Output, Outputs); 890 false -> remove_main_syslog_output(Output, Outputs) 891 end. 892 893normalize_main_syslog_output1( 894 [{enabled, true} | Rest], 895 Output, Outputs) -> 896 normalize_main_syslog_output1(Rest, Output, Outputs); 897normalize_main_syslog_output1( 898 [{level, Level} | Rest], 899 Output, Outputs) -> 900 Output1 = Output#{level => Level}, 901 normalize_main_syslog_output1(Rest, Output1, Outputs); 902normalize_main_syslog_output1( 903 [{formatter, undefined} | Rest], 904 Output, Outputs) -> 905 normalize_main_syslog_output1(Rest, Output, Outputs); 906normalize_main_syslog_output1( 907 [{formatter, Formatter} | Rest], 908 Output, Outputs) -> 909 Output1 = Output#{formatter => Formatter}, 910 normalize_main_syslog_output1(Rest, Output1, Outputs); 911normalize_main_syslog_output1([], Output, Outputs) -> 912 [Output | Outputs]. 913 914remove_main_syslog_output( 915 #{module := syslog_logger_h}, Outputs) -> 916 lists:filter( 917 fun 918 (#{module := syslog_logger_h}) -> false; 919 (_) -> true 920 end, Outputs). 921 922-spec normalize_per_cat_log_config(per_cat_env()) -> per_cat_log_config(). 923 924normalize_per_cat_log_config(Props) -> 925 normalize_per_cat_log_config(Props, #{outputs => []}). 926 927normalize_per_cat_log_config([{level, Level} | Rest], LogConfig) -> 928 LogConfig1 = LogConfig#{level => Level}, 929 normalize_per_cat_log_config(Rest, LogConfig1); 930normalize_per_cat_log_config([{file, Filename} | Rest], 931 #{outputs := Outputs} = LogConfig) -> 932 %% Caution: The `file' property in the per-category configuration only 933 %% accepts a filename. It doesn't support the properties of the `file' 934 %% property at the global configuration level. 935 Output = #{module => rabbit_logger_std_h, 936 config => #{type => file, 937 file => Filename}}, 938 LogConfig1 = LogConfig#{outputs => [Output | Outputs]}, 939 normalize_per_cat_log_config(Rest, LogConfig1); 940normalize_per_cat_log_config([], LogConfig) -> 941 LogConfig. 942 943-spec handle_default_and_overridden_outputs(log_config(), 944 rabbit_env:context()) -> 945 log_config(). 946 947handle_default_and_overridden_outputs(LogConfig, Context) -> 948 LogConfig1 = handle_default_main_output(LogConfig, Context), 949 LogConfig2 = handle_default_upgrade_cat_output(LogConfig1, Context), 950 LogConfig2. 951 952-spec handle_default_main_output(log_config(), rabbit_env:context()) -> 953 log_config(). 954 955handle_default_main_output( 956 #{global := #{outputs := Outputs} = GlobalConfig} = LogConfig, 957 #{main_log_file := MainLogFile} = Context) -> 958 NoOutputsConfigured = Outputs =:= [], 959 Overridden = rabbit_env:has_var_been_overridden(Context, main_log_file), 960 Outputs1 = if 961 NoOutputsConfigured orelse Overridden -> 962 Output0 = log_file_var_to_output(MainLogFile), 963 Output1 = keep_log_level_from_equivalent_output( 964 Output0, Outputs), 965 [Output1]; 966 true -> 967 [case Output of 968 #{module := Mod, 969 config := #{type := file, file := _}} 970 when ?IS_STD_H_COMPAT(Mod) -> 971 Output; 972 #{module := Mod, 973 config := #{type := file} = Config} 974 when ?IS_STD_H_COMPAT(Mod) -> 975 Output#{config => 976 Config#{file => MainLogFile}}; 977 _ -> 978 Output 979 end || Output <- Outputs] 980 end, 981 case Outputs1 of 982 Outputs -> LogConfig; 983 _ -> LogConfig#{ 984 global => GlobalConfig#{ 985 outputs => Outputs1}} 986 end. 987 988-spec handle_default_upgrade_cat_output(log_config(), rabbit_env:context()) -> 989 log_config(). 990 991handle_default_upgrade_cat_output( 992 #{per_category := PerCatConfig} = LogConfig, 993 #{upgrade_log_file := UpgLogFile} = Context) -> 994 UpgCatConfig = case PerCatConfig of 995 #{upgrade := CatConfig} -> CatConfig; 996 _ -> #{outputs => []} 997 end, 998 #{outputs := Outputs} = UpgCatConfig, 999 NoOutputsConfigured = Outputs =:= [], 1000 Overridden = rabbit_env:has_var_been_overridden( 1001 Context, upgrade_log_file), 1002 Outputs1 = if 1003 NoOutputsConfigured orelse Overridden -> 1004 Output0 = log_file_var_to_output(UpgLogFile), 1005 Output1 = keep_log_level_from_equivalent_output( 1006 Output0, Outputs), 1007 [Output1]; 1008 true -> 1009 Outputs 1010 end, 1011 case Outputs1 of 1012 Outputs -> LogConfig; 1013 _ -> LogConfig#{ 1014 per_category => PerCatConfig#{ 1015 upgrade => UpgCatConfig#{ 1016 outputs => Outputs1}}} 1017 end. 1018 1019-spec log_file_var_to_output(file:filename() | string()) -> 1020 logger:handler_config(). 1021 1022log_file_var_to_output("-") -> 1023 #{module => rabbit_logger_std_h, 1024 config => #{type => standard_io}}; 1025log_file_var_to_output("-stderr") -> 1026 #{module => rabbit_logger_std_h, 1027 config => #{type => standard_error}}; 1028log_file_var_to_output("exchange:" ++ _) -> 1029 #{module => rabbit_logger_exchange_h, 1030 config => #{}}; 1031log_file_var_to_output("journald:" ++ _) -> 1032 #{module => systemd_journal_h, 1033 config => #{}}; 1034log_file_var_to_output("syslog:" ++ _) -> 1035 #{module => syslog_logger_h, 1036 config => #{}}; 1037log_file_var_to_output(Filename) -> 1038 #{module => rabbit_logger_std_h, 1039 config => #{type => file, 1040 file => Filename}}. 1041 1042-spec keep_log_level_from_equivalent_output( 1043 logger:handler_config(), [logger:handler_config()]) -> 1044 logger:handler_config(). 1045%% @doc 1046%% Keeps the log level from the equivalent output if found in the given list of 1047%% outputs. 1048%% 1049%% If the output is overridden from the environment, or if no output is 1050%% configured at all (and the default output is used), we should still keep the 1051%% log level set in the configuration. The idea is that the $RABBITMQ_LOGS 1052%% environment variable only overrides the output, not its log level (which 1053%% would be set in $RABBITMQ_LOG). 1054%% 1055%% Here is an example of when it is used: 1056%% * "$RABBITMQ_LOGS=-" is set in the environment 1057%% * "log.console.level = debug" is set in the configuration file 1058 1059keep_log_level_from_equivalent_output( 1060 #{module := Mod, config := #{type := Type}} = Output, 1061 [#{module := Mod, config := #{type := Type}} = OverridenOutput | _]) 1062 when ?IS_STD_H_COMPAT(Mod) -> 1063 keep_log_level_from_equivalent_output1(Output, OverridenOutput); 1064keep_log_level_from_equivalent_output( 1065 #{module := Mod} = Output, 1066 [#{module := Mod} = OverridenOutput | _]) -> 1067 keep_log_level_from_equivalent_output1(Output, OverridenOutput); 1068keep_log_level_from_equivalent_output(Output, [_ | Rest]) -> 1069 keep_log_level_from_equivalent_output(Output, Rest); 1070keep_log_level_from_equivalent_output(Output, []) -> 1071 Output. 1072 1073-spec keep_log_level_from_equivalent_output1( 1074 logger:handler_config(), logger:handler_config()) -> 1075 logger:handler_config(). 1076 1077keep_log_level_from_equivalent_output1(Output, #{level := Level}) -> 1078 Output#{level => Level}; 1079keep_log_level_from_equivalent_output1(Output, _) -> 1080 Output. 1081 1082-spec apply_log_levels_from_env(log_config(), rabbit_env:context()) -> 1083 log_config(). 1084 1085apply_log_levels_from_env(LogConfig, #{log_levels := LogLevels}) 1086 when is_map(LogLevels) -> 1087 %% `LogLevels' comes from the `$RABBITMQ_LOG' environment variable. It has 1088 %% the following form: 1089 %% RABBITMQ_LOG=$cat1=$level1,$cat2=$level2,+color,-json 1090 %% I.e. it contains either `category=level' or `+flag'/`-flag'. 1091 %% 1092 %% Here we want to apply the log levels set from that variable, but we 1093 %% need to filter out any flags. 1094 maps:fold( 1095 fun 1096 (_, Value, LC) when is_boolean(Value) -> 1097 %% Ignore the '+color' and '+json' flags. 1098 LC; 1099 (global, Level, #{global := GlobalConfig} = LC) -> 1100 GlobalConfig1 = GlobalConfig#{level => Level}, 1101 LC#{global => GlobalConfig1}; 1102 (CatString, Level, #{per_category := PerCatConfig} = LC) -> 1103 CatAtom = list_to_atom(CatString), 1104 CatConfig0 = maps:get(CatAtom, PerCatConfig, #{outputs => []}), 1105 CatConfig1 = CatConfig0#{level => Level}, 1106 PerCatConfig1 = PerCatConfig#{CatAtom => CatConfig1}, 1107 LC#{per_category => PerCatConfig1} 1108 end, LogConfig, LogLevels); 1109apply_log_levels_from_env(LogConfig, _) -> 1110 LogConfig. 1111 1112-spec make_filenames_absolute(log_config(), rabbit_env:context()) -> 1113 log_config(). 1114 1115make_filenames_absolute( 1116 #{global := GlobalConfig, per_category := PerCatConfig} = LogConfig, 1117 Context) -> 1118 LogBaseDir = get_log_base_dir(Context), 1119 GlobalConfig1 = make_filenames_absolute1(GlobalConfig, LogBaseDir), 1120 PerCatConfig1 = maps:map( 1121 fun(_, CatConfig) -> 1122 make_filenames_absolute1(CatConfig, LogBaseDir) 1123 end, PerCatConfig), 1124 LogConfig#{global => GlobalConfig1, per_category => PerCatConfig1}. 1125 1126make_filenames_absolute1(#{outputs := Outputs} = Config, LogBaseDir) -> 1127 Outputs1 = lists:map( 1128 fun 1129 (#{module := Mod, 1130 config := #{type := file, 1131 file := Filename} = Cfg} = Output) 1132 when ?IS_STD_H_COMPAT(Mod) -> 1133 Cfg1 = Cfg#{file => filename:absname( 1134 Filename, LogBaseDir)}, 1135 Output#{config => Cfg1}; 1136 (Output) -> 1137 Output 1138 end, Outputs), 1139 Config#{outputs => Outputs1}. 1140 1141-spec configure_formatters(log_config(), rabbit_env:context()) -> 1142 log_config(). 1143 1144configure_formatters( 1145 #{global := GlobalConfig, per_category := PerCatConfig} = LogConfig, 1146 Context) -> 1147 GlobalConfig1 = configure_formatters1(GlobalConfig, Context), 1148 PerCatConfig1 = maps:map( 1149 fun(_, CatConfig) -> 1150 configure_formatters1(CatConfig, Context) 1151 end, PerCatConfig), 1152 LogConfig#{global => GlobalConfig1, per_category => PerCatConfig1}. 1153 1154configure_formatters1(#{outputs := Outputs} = Config, Context) -> 1155 %% TODO: Add ability to configure formatters from the Cuttlefish 1156 %% configuration file. For now, it is only possible from the 1157 %% `$RABBITMQ_LOG' environment variable. 1158 ConsFormatter = 1159 rabbit_prelaunch_early_logging:default_console_formatter(Context), 1160 FileFormatter = 1161 rabbit_prelaunch_early_logging:default_file_formatter(Context), 1162 JournaldFormatter = 1163 rabbit_prelaunch_early_logging:default_journald_formatter(Context), 1164 SyslogFormatter = 1165 rabbit_prelaunch_early_logging:default_syslog_formatter(Context), 1166 Outputs1 = lists:map( 1167 fun 1168 (#{module := Mod, 1169 config := #{type := Stddev}} = Output) 1170 when ?IS_STD_H_COMPAT(Mod) andalso 1171 ?IS_STDDEV(Stddev) -> 1172 case maps:is_key(formatter, Output) of 1173 true -> Output; 1174 false -> Output#{formatter => ConsFormatter} 1175 end; 1176 (#{module := systemd_journal_h} = Output) -> 1177 case maps:is_key(formatter, Output) of 1178 true -> Output; 1179 false -> Output#{formatter => JournaldFormatter} 1180 end; 1181 (#{module := syslog_logger_h} = Output) -> 1182 case maps:is_key(formatter, Output) of 1183 true -> Output; 1184 false -> Output#{formatter => SyslogFormatter} 1185 end; 1186 (Output) -> 1187 case maps:is_key(formatter, Output) of 1188 true -> Output; 1189 false -> Output#{formatter => FileFormatter} 1190 end 1191 end, Outputs), 1192 Config#{outputs => Outputs1}. 1193 1194-spec create_logger_handlers_conf(log_config()) -> 1195 [logger:handler_config()]. 1196 1197create_logger_handlers_conf( 1198 #{global := GlobalConfig, per_category := PerCatConfig}) -> 1199 Handlers0 = create_global_handlers_conf(GlobalConfig), 1200 Handlers1 = create_per_cat_handlers_conf(PerCatConfig, Handlers0), 1201 Handlers2 = adjust_log_levels(Handlers1), 1202 1203 %% assign_handler_ids/1 is also responsible for transforming the map of 1204 %% handlers into a list. The map was only used to deduplicate handlers. 1205 assign_handler_ids(Handlers2). 1206 1207-spec create_global_handlers_conf(global_log_config()) -> 1208 #{handler_key() := logger:handler_config()}. 1209 1210create_global_handlers_conf(#{outputs := Outputs} = GlobalConfig) -> 1211 Handlers = ensure_handlers_conf(Outputs, global, GlobalConfig, #{}), 1212 maps:map( 1213 fun(_, Handler) -> 1214 add_erlang_specific_filters(Handler) 1215 end, Handlers). 1216 1217-spec add_erlang_specific_filters(logger:handler_config()) -> 1218 logger:handler_config(). 1219 1220add_erlang_specific_filters(#{filters := Filters} = Handler) -> 1221 %% We only log progress reports (from application master and supervisor) 1222 %% only if the handler level is set to debug. 1223 Action = case Handler of 1224 #{level := debug} -> log; 1225 _ -> stop 1226 end, 1227 Filters1 = [{progress_reports, {fun logger_filters:progress/2, Action}} 1228 | Filters], 1229 Handler#{filters => Filters1}. 1230 1231-spec create_per_cat_handlers_conf( 1232 #{category_name() => per_cat_log_config()}, 1233 #{handler_key() => logger:handler_config()}) -> 1234 #{handler_key() => logger:handler_config()}. 1235 1236create_per_cat_handlers_conf(PerCatConfig, Handlers) -> 1237 maps:fold( 1238 fun 1239 (CatName, #{outputs := []} = CatConfig, Hdls) -> 1240 %% That category has no outputs defined. It means its messages 1241 %% will go to the global handlers. We still need to update 1242 %% global handlers to filter the level of the messages from that 1243 %% category. 1244 filter_cat_in_global_handlers(Hdls, CatName, CatConfig); 1245 (CatName, #{outputs := Outputs} = CatConfig, Hdls) -> 1246 %% That category has specific outputs (i.e. in addition to the 1247 %% global handlers). 1248 Hdls1 = ensure_handlers_conf(Outputs, CatName, CatConfig, Hdls), 1249 %% We need to filter the messages from that category out in the 1250 %% global handlers. 1251 filter_out_cat_in_global_handlers(Hdls1, CatName) 1252 end, Handlers, PerCatConfig). 1253 1254-spec ensure_handlers_conf( 1255 [logger:handler_config()], global | category_name(), 1256 global_log_config() | per_cat_log_config(), 1257 #{handler_key() => logger:handler_config()}) -> 1258 #{handler_key() => logger:handler_config()}. 1259 1260ensure_handlers_conf([Output | Rest], CatName, Config, Handlers) -> 1261 Key = create_handler_key(Output), 1262 %% This is where the deduplication happens: either we update the existing 1263 %% handler (based on the key computed above) or we create a new one. 1264 Handler = case maps:is_key(Key, Handlers) of 1265 false -> create_handler_conf(Output, CatName, Config); 1266 true -> update_handler_conf(maps:get(Key, Handlers), 1267 CatName, Output) 1268 end, 1269 Handlers1 = Handlers#{Key => Handler}, 1270 ensure_handlers_conf(Rest, CatName, Config, Handlers1); 1271ensure_handlers_conf([], _, _, Handlers) -> 1272 Handlers. 1273 1274-spec create_handler_key(logger:handler_config()) -> handler_key(). 1275 1276create_handler_key( 1277 #{module := Mod, config := #{type := file, file := Filename}}) 1278 when ?IS_STD_H_COMPAT(Mod) -> 1279 {file, Filename}; 1280create_handler_key( 1281 #{module := Mod, config := #{type := standard_io}}) 1282 when ?IS_STD_H_COMPAT(Mod) -> 1283 {console, standard_io}; 1284create_handler_key( 1285 #{module := Mod, config := #{type := standard_error}}) 1286 when ?IS_STD_H_COMPAT(Mod) -> 1287 {console, standard_error}; 1288create_handler_key( 1289 #{module := rabbit_logger_exchange_h}) -> 1290 exchange; 1291create_handler_key( 1292 #{module := systemd_journal_h}) -> 1293 journald; 1294create_handler_key( 1295 #{module := syslog_logger_h}) -> 1296 syslog. 1297 1298-spec create_handler_conf( 1299 logger:handler_config(), global | category_name(), 1300 global_log_config() | per_cat_log_config()) -> 1301 logger:handler_config(). 1302 1303%% The difference between a global handler and a category handler is the value 1304%% of `filter_default'. In a global hanler, if a message was not stopped or 1305%% explicitely accepted by a filter, the message is logged. In a category 1306%% handler, it is dropped. 1307 1308create_handler_conf(Output, global, Config) -> 1309 Level = compute_level_from_config_and_output(Config, Output), 1310 Output#{level => Level, 1311 filter_default => log, 1312 filters => [{?FILTER_NAME, 1313 {fun filter_log_event/2, #{global => Level}}}]}; 1314create_handler_conf(Output, CatName, Config) -> 1315 Level = compute_level_from_config_and_output(Config, Output), 1316 Output#{level => Level, 1317 filter_default => stop, 1318 filters => [{?FILTER_NAME, 1319 {fun filter_log_event/2, #{CatName => Level}}}]}. 1320 1321-spec update_handler_conf( 1322 logger:handler_config(), global | category_name(), 1323 logger:handler_config()) -> 1324 logger:handler_config(). 1325 1326update_handler_conf( 1327 #{level := ConfiguredLevel} = Handler, global, Output) -> 1328 case Output of 1329 #{level := NewLevel} -> 1330 Handler#{level => 1331 get_less_severe_level(NewLevel, ConfiguredLevel)}; 1332 _ -> 1333 Handler 1334 end; 1335update_handler_conf(Handler, CatName, Output) -> 1336 add_cat_filter(Handler, CatName, Output). 1337 1338-spec compute_level_from_config_and_output( 1339 global_log_config() | per_cat_log_config(), 1340 logger:handler_config()) -> 1341 logger:level(). 1342%% @doc 1343%% Compute the debug level for a handler. 1344%% 1345%% The precedence is: 1346%% <ol> 1347%% <li>the level of the output</li> 1348%% <li>the level of the category (or the global level)</li> 1349%% <li>the default value</li> 1350%% </ol> 1351 1352compute_level_from_config_and_output(Config, Output) -> 1353 case Output of 1354 #{level := Level} -> 1355 Level; 1356 _ -> 1357 case Config of 1358 #{level := Level} -> Level; 1359 _ -> ?DEFAULT_LOG_LEVEL 1360 end 1361 end. 1362 1363-spec filter_cat_in_global_handlers( 1364 #{handler_key() => logger:handler_config()}, category_name(), 1365 per_cat_log_config()) -> 1366 #{handler_key() => logger:handler_config()}. 1367 1368filter_cat_in_global_handlers(Handlers, CatName, CatConfig) -> 1369 maps:map( 1370 fun 1371 (_, #{filter_default := log} = Handler) -> 1372 add_cat_filter(Handler, CatName, CatConfig); 1373 (_, Handler) -> 1374 Handler 1375 end, Handlers). 1376 1377-spec filter_out_cat_in_global_handlers( 1378 #{handler_key() => logger:handler_config()}, category_name()) -> 1379 #{handler_key() => logger:handler_config()}. 1380 1381filter_out_cat_in_global_handlers(Handlers, CatName) -> 1382 maps:map( 1383 fun 1384 (_, #{filter_default := log, filters := Filters} = Handler) -> 1385 {_, FilterConfig} = proplists:get_value(?FILTER_NAME, Filters), 1386 case maps:is_key(CatName, FilterConfig) of 1387 true -> Handler; 1388 false -> add_cat_filter(Handler, CatName, #{level => none, 1389 outputs => []}) 1390 end; 1391 (_, Handler) -> 1392 Handler 1393 end, Handlers). 1394 1395-spec add_cat_filter( 1396 logger:handler_config(), category_name(), 1397 per_cat_log_config() | logger:handler_config()) -> 1398 logger:handler_config(). 1399 1400add_cat_filter(Handler, CatName, CatConfigOrOutput) -> 1401 Level = case CatConfigOrOutput of 1402 #{level := L} -> L; 1403 _ -> maps:get(level, Handler) 1404 end, 1405 do_add_cat_filter(Handler, CatName, Level). 1406 1407do_add_cat_filter(#{filters := Filters} = Handler, CatName, Level) -> 1408 {Fun, FilterConfig} = proplists:get_value(?FILTER_NAME, Filters), 1409 FilterConfig1 = FilterConfig#{CatName => Level}, 1410 Filters1 = lists:keystore(?FILTER_NAME, 1, Filters, 1411 {?FILTER_NAME, {Fun, FilterConfig1}}), 1412 Handler#{filters => Filters1}. 1413 1414-spec filter_log_event(logger:log_event(), term()) -> logger:filter_return(). 1415 1416filter_log_event(LogEvent, FilterConfig) -> 1417 rabbit_prelaunch_early_logging:filter_log_event(LogEvent, FilterConfig). 1418 1419-spec adjust_log_levels(#{handler_key() => logger:handler_config()}) -> 1420 #{handler_key() => logger:handler_config()}. 1421%% @doc 1422%% Adjust handler log level based on the filters' level. 1423%% 1424%% If a filter is more permissive, we need to adapt the handler log level so 1425%% the message makes it to the filter. 1426%% 1427%% Also, if the log level is set to `debug', we turn off burst limit to make 1428%% sure all debug messages make it. 1429 1430adjust_log_levels(Handlers) -> 1431 maps:map( 1432 fun(_, #{level := GeneralLevel, filters := Filters} = Handler) -> 1433 {_, FilterConfig} = proplists:get_value(?FILTER_NAME, Filters), 1434 Level = maps:fold( 1435 fun(_, LvlA, LvlB) -> 1436 get_less_severe_level(LvlA, LvlB) 1437 end, GeneralLevel, FilterConfig), 1438 Handler1 = Handler#{level => Level}, 1439 adjust_burst_limit(Handler1) 1440 end, Handlers). 1441 1442adjust_burst_limit(#{config := #{burst_limit_enable := _}} = Handler) -> 1443 Handler; 1444adjust_burst_limit(#{level := debug, config := Config} = Handler) -> 1445 Config1 = Config#{burst_limit_enable => false}, 1446 Handler#{config => Config1}; 1447adjust_burst_limit(Handler) when is_map(Handler) -> 1448 Handler. 1449 1450adjust_burst_limit(Config, Level) -> 1451 Config#{burst_limit_enable => Level =/= debug}. 1452 1453-spec assign_handler_ids(#{handler_key() => logger:handler_config()}) -> 1454 [logger:handler_config()]. 1455 1456assign_handler_ids(Handlers) -> 1457 Handlers1 = [maps:get(Key, Handlers) 1458 || Key <- lists:sort(maps:keys(Handlers))], 1459 assign_handler_ids(Handlers1, 1460 #{config_run_number => get_config_run_number(), 1461 next_file => 1}, 1462 []). 1463 1464-spec assign_handler_ids( 1465 [logger:handler_config()], id_assignment_state(), 1466 [logger:handler_config()]) -> 1467 [logger:handler_config()]. 1468 1469assign_handler_ids( 1470 [#{module := Mod, config := #{type := file}} = Handler | Rest], 1471 #{next_file := NextFile} = State, 1472 Result) 1473 when ?IS_STD_H_COMPAT(Mod) -> 1474 Id = format_id("file_~b", [NextFile], State), 1475 Handler1 = Handler#{id => Id}, 1476 assign_handler_ids( 1477 Rest, State#{next_file => NextFile + 1}, [Handler1 | Result]); 1478assign_handler_ids( 1479 [#{module := Mod, config := #{type := standard_io}} = Handler | Rest], 1480 State, 1481 Result) 1482 when ?IS_STD_H_COMPAT(Mod) -> 1483 Id = format_id("stdout", [], State), 1484 Handler1 = Handler#{id => Id}, 1485 assign_handler_ids(Rest, State, [Handler1 | Result]); 1486assign_handler_ids( 1487 [#{module := Mod, config := #{type := standard_error}} = Handler | Rest], 1488 State, 1489 Result) 1490 when ?IS_STD_H_COMPAT(Mod) -> 1491 Id = format_id("stderr", [], State), 1492 Handler1 = Handler#{id => Id}, 1493 assign_handler_ids(Rest, State, [Handler1 | Result]); 1494assign_handler_ids( 1495 [#{module := rabbit_logger_exchange_h} = Handler 1496 | Rest], 1497 State, 1498 Result) -> 1499 Id = format_id("exchange", [], State), 1500 Handler1 = Handler#{id => Id}, 1501 assign_handler_ids(Rest, State, [Handler1 | Result]); 1502assign_handler_ids( 1503 [#{module := systemd_journal_h} = Handler 1504 | Rest], 1505 State, 1506 Result) -> 1507 Id = format_id("journald", [], State), 1508 Handler1 = Handler#{id => Id}, 1509 assign_handler_ids(Rest, State, [Handler1 | Result]); 1510assign_handler_ids( 1511 [#{module := syslog_logger_h} = Handler 1512 | Rest], 1513 State, 1514 Result) -> 1515 Id = format_id("syslog", [], State), 1516 Handler1 = Handler#{id => Id}, 1517 assign_handler_ids(Rest, State, [Handler1 | Result]); 1518assign_handler_ids([], _, Result) -> 1519 lists:reverse(Result). 1520 1521-spec format_id(io:format(), [term()], id_assignment_state()) -> 1522 logger:handler_id(). 1523 1524format_id(Format, Args, #{config_run_number := RunNum}) -> 1525 list_to_atom(rabbit_misc:format("rmq_~b_" ++ Format, [RunNum | Args])). 1526 1527-spec install_handlers([logger:handler_config()]) -> ok | no_return(). 1528 1529install_handlers([]) -> 1530 ok; 1531install_handlers(Handlers) -> 1532 case adjust_running_dependencies(Handlers) of 1533 ok -> 1534 ?LOG_NOTICE( 1535 "Logging: switching to configured handler(s); following " 1536 "messages may not be visible in this log output", 1537 #{domain => ?RMQLOG_DOMAIN_PRELAUNCH}), 1538 ok = do_install_handlers(Handlers), 1539 ok = remove_old_handlers(), 1540 ok = define_primary_level(Handlers), 1541 ?LOG_NOTICE( 1542 "Logging: configured log handlers are now ACTIVE", 1543 #{domain => ?RMQLOG_DOMAIN_PRELAUNCH}); 1544 _ -> 1545 ?LOG_NOTICE( 1546 "Logging: failed to configure log handlers; keeping existing " 1547 "handlers", 1548 #{domain => ?RMQLOG_DOMAIN_PRELAUNCH}), 1549 ok 1550 end. 1551 1552-spec adjust_running_dependencies([logger:handler_config()]) -> ok | error. 1553 1554adjust_running_dependencies(Handlers) -> 1555 %% Based on the log handlers' module, we determine the list of applications 1556 %% they depend on. 1557 %% 1558 %% The DefaultDeps lists all possible dependencies and marked them as 1559 %% unneeded. Then, if we have a log handler which depends on one of them, 1560 %% it is marked as needed. This way, we know what needs to be started AND 1561 %% stopped. 1562 %% 1563 %% DefaultDeps is of the form `#{ApplicationName => Needed}'. 1564 DefaultDeps = #{syslog => false}, 1565 Deps = lists:foldl( 1566 fun 1567 (#{module := syslog_logger_h}, Acc) -> Acc#{syslog => true}; 1568 (_, Acc) -> Acc 1569 end, DefaultDeps, Handlers), 1570 adjust_running_dependencies1(maps:to_list(Deps)). 1571 1572-spec adjust_running_dependencies1([{atom(), boolean()}]) -> ok | error. 1573 1574adjust_running_dependencies1([{App, true} | Rest]) -> 1575 ?LOG_DEBUG( 1576 "Logging: ensure log handler dependency '~s' is started", [App], 1577 #{domain => ?RMQLOG_DOMAIN_PRELAUNCH}), 1578 case application:ensure_all_started(App) of 1579 {ok, _} -> 1580 adjust_running_dependencies1(Rest); 1581 {error, Reason} -> 1582 ?LOG_ERROR( 1583 "Failed to start log handlers dependency '~s': ~p", 1584 [App, Reason], 1585 #{domain => ?RMQLOG_DOMAIN_PRELAUNCH}), 1586 error 1587 end; 1588adjust_running_dependencies1([{App, false} | Rest]) -> 1589 ?LOG_DEBUG( 1590 "Logging: ensure log handler dependency '~s' is stopped", [App], 1591 #{domain => ?RMQLOG_DOMAIN_PRELAUNCH}), 1592 case application:stop(App) of 1593 ok -> 1594 ok; 1595 {error, Reason} -> 1596 ?LOG_NOTICE( 1597 "Logging: failed to stop log handlers dependency '~s': ~p", 1598 [App, Reason], 1599 #{domain => ?RMQLOG_DOMAIN_PRELAUNCH}) 1600 end, 1601 adjust_running_dependencies1(Rest); 1602adjust_running_dependencies1([]) -> 1603 ok. 1604 1605-spec do_install_handlers([logger:handler_config()]) -> ok | no_return(). 1606 1607do_install_handlers([#{id := Id, module := Module} = Handler | Rest]) -> 1608 case logger:add_handler(Id, Module, Handler) of 1609 ok -> 1610 ok = remove_syslog_logger_h_hardcoded_filters(Handler), 1611 do_install_handlers(Rest); 1612 {error, {handler_not_added, {open_failed, Filename, Reason}}} -> 1613 throw({error, {cannot_log_to_file, Filename, Reason}}); 1614 {error, {handler_not_added, Reason}} -> 1615 throw({error, {cannot_log_to_file, unknown, Reason}}) 1616 end; 1617do_install_handlers([]) -> 1618 ok. 1619 1620remove_syslog_logger_h_hardcoded_filters( 1621 #{id := Id, module := syslog_logger_h}) -> 1622 _ = logger:remove_handler_filter(Id, progress), 1623 _ = logger:remove_handler_filter(Id, remote_gl), 1624 ok; 1625remove_syslog_logger_h_hardcoded_filters(_) -> 1626 ok. 1627 1628-spec remove_old_handlers() -> ok. 1629 1630remove_old_handlers() -> 1631 _ = logger:remove_handler(default), 1632 RunNum = get_config_run_number(), 1633 lists:foreach( 1634 fun(Id) -> 1635 Ret = re:run(atom_to_list(Id), "^rmq_([0-9]+)_", 1636 [{capture, all_but_first, list}]), 1637 case Ret of 1638 {match, [NumStr]} -> 1639 Num = erlang:list_to_integer(NumStr), 1640 if 1641 Num < RunNum -> 1642 ?LOG_DEBUG( 1643 "Logging: removing old logger handler ~s", 1644 [Id], 1645 #{domain => ?RMQLOG_DOMAIN_PRELAUNCH}), 1646 ok = logger:remove_handler(Id); 1647 true -> 1648 ok 1649 end; 1650 _ -> 1651 ok 1652 end 1653 end, lists:sort(logger:get_handler_ids())), 1654 ok. 1655 1656-spec define_primary_level([logger:handler_config()]) -> 1657 ok | {error, term()}. 1658 1659define_primary_level(Handlers) -> 1660 define_primary_level(Handlers, emergency). 1661 1662-spec define_primary_level([logger:handler_config()], logger:level()) -> 1663 ok | {error, term()}. 1664 1665define_primary_level([#{level := Level} | Rest], PrimaryLevel) -> 1666 NewLevel = get_less_severe_level(Level, PrimaryLevel), 1667 define_primary_level(Rest, NewLevel); 1668define_primary_level([], PrimaryLevel) -> 1669 logger:set_primary_config(level, PrimaryLevel). 1670 1671-spec get_less_severe_level(logger:level(), logger:level()) -> logger:level(). 1672%% @doc 1673%% Compares two log levels and returns the less severe one. 1674%% 1675%% @param LevelA the log level to compare to LevelB. 1676%% @param LevelB the log level to compare to LevelA. 1677%% 1678%% @returns the less severe log level. 1679 1680get_less_severe_level(LevelA, LevelB) -> 1681 case logger:compare_levels(LevelA, LevelB) of 1682 lt -> LevelA; 1683 _ -> LevelB 1684 end. 1685 1686-spec maybe_log_test_messages(log_config()) -> ok. 1687 1688maybe_log_test_messages( 1689 #{per_category := #{prelaunch := #{level := debug}}}) -> 1690 log_test_messages(); 1691maybe_log_test_messages( 1692 #{global := #{level := debug}}) -> 1693 log_test_messages(); 1694maybe_log_test_messages(_) -> 1695 ok. 1696 1697-spec log_test_messages() -> ok. 1698 1699log_test_messages() -> 1700 ?LOG_DEBUG("Logging: testing debug log level", 1701 #{domain => ?RMQLOG_DOMAIN_PRELAUNCH}), 1702 ?LOG_INFO("Logging: testing info log level", 1703 #{domain => ?RMQLOG_DOMAIN_PRELAUNCH}), 1704 ?LOG_NOTICE("Logging: testing notice log level", 1705 #{domain => ?RMQLOG_DOMAIN_PRELAUNCH}), 1706 ?LOG_WARNING("Logging: testing warning log level", 1707 #{domain => ?RMQLOG_DOMAIN_PRELAUNCH}), 1708 ?LOG_ERROR("Logging: testing error log level", 1709 #{domain => ?RMQLOG_DOMAIN_PRELAUNCH}), 1710 ?LOG_CRITICAL("Logging: testing critical log level", 1711 #{domain => ?RMQLOG_DOMAIN_PRELAUNCH}), 1712 ?LOG_ALERT("Logging: testing alert log level", 1713 #{domain => ?RMQLOG_DOMAIN_PRELAUNCH}), 1714 ?LOG_EMERGENCY("Logging: testing emergency log level", 1715 #{domain => ?RMQLOG_DOMAIN_PRELAUNCH}). 1716