From 571cdc3e5bd5e8a627d77c2cdd0ba21c67a117b3 Mon Sep 17 00:00:00 2001 From: Nikolay Shirokovskiy <nshirokovskiy@tarantool.org> Date: Mon, 19 Sep 2022 11:35:00 +0300 Subject: [PATCH] box: straighten log configuration code See [1] for some details on why the code for log configuration needs some care. In short log config validity checks are spread thru many places, on some code paths we use one checks and on other code paths other checks. And we make repetitive validity checks many times in runtime on single configure call. We can also reuse code for setting default values, checking options type and resetting values to default. - As a side effect of refactoring one can now reset values to default thru `log.cfg()` so now `log.cfg()` is on par with `box.cfg` in this respect. - This patch also drops conversion `log_level` from string to number. Before (shorten): tarantool> box.cfg{log_level='warn'} tarantool> box.cfg.log_level - info tarantool> log.cfg.level - 5 Also: tarantool> log.cfg{level='info'} tarantool> log.cfg.level - 5 tarantool> box.cfg{} tarantool> box.cfg.log_level - 5 After patch if `log_level`/`level` is given as string than it is saved and returned as string too. I guess it should not affect users but looks more handy. - Also fixed issue with inconsistent setting `log_nonblock` thru `box.cfg()` and `log.cfg()`. In former case `nil` means setting default depending on logger type. In the latter case `nil` meant setting `nonblock` to `false`. - Also patch fixes #7447. Closes #7447. [1] PR for this refactoring https://github.com/tarantool/tarantool/pull/7454 NO_DOC=refactoring/tiny API improvemnent --- .../gh-7447-fix-panic-on-invalid-log.md | 3 + extra/exports | 3 +- src/box/box.cc | 72 ++- src/box/box.h | 6 + src/box/lua/load_cfg.lua | 173 +++---- src/lib/core/say.c | 61 ++- src/lib/core/say.h | 15 +- src/lua/log.lua | 475 ++++-------------- src/main.cc | 11 +- .../test/static-build/exports.test.lua | 1 - .../gh-5130-panic-on-invalid-log.test.lua | 2 +- test/app-tap/logger.test.lua | 176 ++++++- test/app-tap/logmod.test.lua | 16 +- test/unit/memtx_allocator.cc | 3 +- test/unit/popen.c | 2 +- test/unit/raft_test_utils.c | 2 +- test/unit/say.c | 2 +- test/unit/swim_proto.c | 2 +- test/unit/swim_test_utils.c | 2 +- 19 files changed, 464 insertions(+), 563 deletions(-) create mode 100644 changelogs/unreleased/gh-7447-fix-panic-on-invalid-log.md diff --git a/changelogs/unreleased/gh-7447-fix-panic-on-invalid-log.md b/changelogs/unreleased/gh-7447-fix-panic-on-invalid-log.md new file mode 100644 index 0000000000..519adf4bb4 --- /dev/null +++ b/changelogs/unreleased/gh-7447-fix-panic-on-invalid-log.md @@ -0,0 +1,3 @@ +## bugfix/core + +* Fixed panic on invalid syslog log configuration (gh-7447). diff --git a/extra/exports b/extra/exports index f30e7bbfe9..e88a087672 100644 --- a/extra/exports +++ b/extra/exports @@ -236,7 +236,6 @@ lbox_socket_nonblock log_format log_level log_pid -log_type luaJIT_profile_dumpstack luaJIT_profile_start luaJIT_profile_stop @@ -426,10 +425,10 @@ prbuf_commit prbuf_iterator_create prbuf_iterator_next random_bytes +say_check_cfg say_logger_init say_logger_initialized say_logrotate -say_parse_logger_type say_set_log_format say_set_log_level SHA1internal diff --git a/src/box/box.cc b/src/box/box.cc index a722c8ed7f..aa43a9afe6 100644 --- a/src/box/box.cc +++ b/src/box/box.cc @@ -735,43 +735,53 @@ wal_stream_create(struct wal_stream *ctx) /* {{{ configuration bindings */ -static void -box_check_say(void) +/* + * Check log configuration validity. + * + * Used thru Lua FFI. + */ +extern "C" int +say_check_cfg(const char *log, + MAYBE_UNUSED int level, + int nonblock, + const char *format_str) { - enum say_logger_type type = SAY_LOGGER_STDERR; /* default */ - const char *log = cfg_gets("log"); + enum say_logger_type type = SAY_LOGGER_STDERR; if (log != NULL && say_parse_logger_type(&log, &type) < 0) { - tnt_raise(ClientError, ER_CFG, "log", - diag_last_error(diag_get())->errmsg); + diag_set(ClientError, ER_CFG, "log", + diag_last_error(diag_get())->errmsg); + return -1; } if (type == SAY_LOGGER_SYSLOG) { struct say_syslog_opts opts; if (say_parse_syslog_opts(log, &opts) < 0) { if (diag_last_error(diag_get())->type == - &type_IllegalParams) { - tnt_raise(ClientError, ER_CFG, "log", - diag_last_error(diag_get())->errmsg); - } - diag_raise(); + &type_IllegalParams) + diag_set(ClientError, ER_CFG, "log", + diag_last_error(diag_get())->errmsg); + return -1; } say_free_syslog_opts(&opts); } - const char *log_format = cfg_gets("log_format"); - enum say_format format = say_format_by_name(log_format); - if (format == say_format_MAX) - tnt_raise(ClientError, ER_CFG, "log_format", + enum say_format format = say_format_by_name(format_str); + if (format == say_format_MAX) { + diag_set(ClientError, ER_CFG, "log_format", "expected 'plain' or 'json'"); + return -1; + } if (type == SAY_LOGGER_SYSLOG && format == SF_JSON) { - tnt_raise(ClientError, ER_CFG, "log_format", - "'json' can't be used with syslog logger"); + diag_set(ClientError, ER_CFG, "log_format", + "'json' can't be used with syslog logger"); + return -1; } - int log_nonblock = cfg_getb("log_nonblock"); - if (log_nonblock == 1 && + if (nonblock == 1 && (type == SAY_LOGGER_FILE || type == SAY_LOGGER_STDERR)) { - tnt_raise(ClientError, ER_CFG, "log_nonblock", - "the option is incompatible with file/stderr logger"); + diag_set(ClientError, ER_CFG, "log_nonblock", + "the option is incompatible with file/stderr logger"); + return -1; } + return 0; } /** @@ -1399,6 +1409,26 @@ box_check_txn_isolation(void) return (enum txn_isolation_level)level; } +static void +box_check_say() +{ + if (luaT_dostring(tarantool_L, + "require('log').box_api.cfg_check()") != 0) + diag_raise(); +} + +int +box_init_say() +{ + if (luaT_dostring(tarantool_L, "require('log').box_api.cfg()") != 0) + return -1; + + if (cfg_geti("background") && say_set_background() != 0) + return -1; + + return 0; +} + void box_check_config(void) { diff --git a/src/box/box.h b/src/box/box.h index 71d72c55e5..acb76f4252 100644 --- a/src/box/box.h +++ b/src/box/box.h @@ -319,6 +319,12 @@ box_get_flightrec_cfg(struct flight_recorder_cfg *cfg); int box_configure_flightrec(void); +/** + * Initialize logger on box init. + */ +int +box_init_say(); + extern "C" { #endif /* defined(__cplusplus) */ diff --git a/src/box/lua/load_cfg.lua b/src/box/lua/load_cfg.lua index 6956cdce3e..beaf6d5dd2 100644 --- a/src/box/lua/load_cfg.lua +++ b/src/box/lua/load_cfg.lua @@ -65,9 +65,10 @@ local default_cfg = { vinyl_page_size = 8 * 1024, vinyl_bloom_fpr = 0.05, - -- logging options are covered by - -- a separate log module; they are - -- 'log_' prefixed + log = log.cfg.log, + log_nonblock = log.cfg.nonblock, + log_level = log.cfg.level, + log_format = log.cfg.format, audit_log = nil, audit_nonblock = true, @@ -133,31 +134,6 @@ local default_cfg = { txn_isolation = "best-effort", } --- cfg variables which are covered by modules -local module_cfg = { - -- logging - log = log.box_api, - log_nonblock = log.box_api, - log_level = log.box_api, - log_format = log.box_api, -} - --- cfg types for modules, probably better to --- provide some API with type enumeration or --- similar. Currently it has use for environment --- processing only. --- --- get_option_from_env() leans on the set of types --- in use: don't forget to update it when add a new --- type or a combination of types here. -local module_cfg_type = { - -- logging - log = 'string', - log_nonblock = 'boolean', - log_level = 'number, string', - log_format = 'string', -} - -- types of available options -- could be comma separated lua types or 'any' if any type is allowed -- @@ -191,10 +167,10 @@ local template_cfg = { vinyl_page_size = 'number', vinyl_bloom_fpr = 'number', - log = 'module', - log_nonblock = 'module', - log_level = 'module', - log_format = 'module', + log = 'string', + log_nonblock = 'boolean', + log_level = 'number, string', + log_format = 'string', audit_log = 'string', audit_nonblock = 'boolean', @@ -332,8 +308,6 @@ end -- fact. local dynamic_cfg = { replication = private.cfg_set_replication, - log_level = log.box_api.cfg_set_log_level, - log_format = log.box_api.cfg_set_log_format, io_collect_interval = private.cfg_set_io_collect_interval, readahead = private.cfg_set_readahead, too_long_threshold = private.cfg_set_too_long_threshold, @@ -423,6 +397,16 @@ local dynamic_cfg_modules = { flightrec_requests_max_res_size = true, }, }, + log = { + cfg = log.box_api.cfg, + options = { + log = true, + log_level = true, + log_format = true, + log_nonblock = true, + }, + skip_at_load = true, + } } ifdef_feedback = nil -- luacheck: ignore @@ -599,26 +583,25 @@ local function upgrade_cfg(cfg, translate_cfg) return result_cfg end -local function update_module_cfg(cfg, module_cfg) - local module_cfg_backup = {} - for field, api in pairs(module_cfg) do - if cfg[field] ~= nil then - module_cfg_backup[field] = api.cfg_get(field) or box.NULL - - local ok, msg = api.cfg_set(cfg, field, cfg[field]) - if not ok then - -- restore back the old values for modules - for k, v in pairs(module_cfg_backup) do - module_cfg[k].cfg_set(cfg, k, v) - end - box.error(box.error.CFG, field, msg) - end +local function check_cfg_option_type(template, name, value) + if template == 'any' then + return + elseif (string.find(template, ',') == nil) then + if type(value) ~= template then + box.error(box.error.CFG, name, "should be of type " .. + template) + end + else + local prepared_tmpl = ',' .. string.gsub(template, ' ', '') .. ',' + local prepared_type = ',' .. type(value) .. ',' + if string.find(prepared_tmpl, prepared_type) == nil then + box.error(box.error.CFG, name, "should be one of types " .. + template) end end end -local function prepare_cfg(cfg, default_cfg, template_cfg, - module_cfg, modify_cfg, prefix) +local function prepare_cfg(cfg, default_cfg, template_cfg, modify_cfg) if cfg == nil then return {} end @@ -629,39 +612,15 @@ local function prepare_cfg(cfg, default_cfg, template_cfg, if cfg.dont_check then return end - local readable_prefix = '' - if prefix ~= nil and prefix ~= '' then - readable_prefix = prefix .. '.' - end local new_cfg = {} - for k,v in pairs(cfg) do - local readable_name = readable_prefix .. k; + for k, v in pairs(cfg) do if template_cfg[k] == nil then - box.error(box.error.CFG, readable_name , "unexpected option") + box.error(box.error.CFG, k , "unexpected option") elseif v == "" or v == nil then -- "" and NULL = ffi.cast('void *', 0) set option to default value v = default_cfg[k] - elseif template_cfg[k] == 'any' then -- luacheck: ignore - -- any type is ok - elseif type(template_cfg[k]) == 'table' then - if type(v) ~= 'table' then - box.error(box.error.CFG, readable_name, "should be a table") - end - v = prepare_cfg(v, default_cfg[k], template_cfg[k], - module_cfg[k], modify_cfg[k], readable_name) - elseif template_cfg[k] ~= 'module' and - (string.find(template_cfg[k], ',') == nil) then - -- one type - if type(v) ~= template_cfg[k] then - box.error(box.error.CFG, readable_name, "should be of type ".. - template_cfg[k]) - end - elseif template_cfg[k] ~= 'module' then - local good_types = string.gsub(template_cfg[k], ' ', ''); - if (string.find(',' .. good_types .. ',', ',' .. type(v) .. ',') == nil) then - box.error(box.error.CFG, readable_name, "should be one of types ".. - template_cfg[k]) - end + else + check_cfg_option_type(template_cfg[k], k, v) end if modify_cfg ~= nil and type(modify_cfg[k]) == 'function' then v = modify_cfg[k](v) @@ -683,17 +642,12 @@ local function apply_env_cfg(cfg, env_cfg) end end -local function apply_default_cfg(cfg, default_cfg, module_cfg) +local function merge_cfg(cfg, default_cfg) for k,v in pairs(default_cfg) do if cfg[k] == nil then cfg[k] = v elseif type(v) == 'table' then - apply_default_cfg(cfg[k], v) - end - end - for k in pairs(module_cfg) do - if cfg[k] == nil then - cfg[k] = module_cfg[k].cfg_get(k) + merge_cfg(cfg[k], v) end end end @@ -786,8 +740,7 @@ end local function reload_cfg(oldcfg, cfg) cfg = upgrade_cfg(cfg, translate_cfg) - local newcfg = prepare_cfg(cfg, default_cfg, template_cfg, - module_cfg, modify_cfg) + local newcfg = prepare_cfg(cfg, default_cfg, template_cfg, modify_cfg) local module_keys = {} -- iterate over original table because prepare_cfg() may store NILs @@ -877,6 +830,12 @@ setmetatable(box, { -- Use locked() wrapper to obtain reliable results. local box_is_configured = false +-- We need to track cfg changes done thru API of distinct modules (log.cfg of +-- log module for example). We cannot use just box.cfg because it is not +-- available before box.cfg() call and other modules can be configured before +-- this moment. +local pre_load_cfg = table.copy(default_cfg) + local function load_cfg(cfg) -- A user may save box.cfg (this function) before box loading -- and call it afterwards. We should reconfigure box in the @@ -891,18 +850,14 @@ local function load_cfg(cfg) -- Set options passed through environment variables. apply_env_cfg(cfg, box.internal.cfg.env) - cfg = prepare_cfg(cfg, default_cfg, template_cfg, - module_cfg, modify_cfg) - apply_default_cfg(cfg, default_cfg, module_cfg); + cfg = prepare_cfg(cfg, default_cfg, template_cfg, modify_cfg) + merge_cfg(cfg, pre_load_cfg); + -- Save new box.cfg box.cfg = cfg local status, err = pcall(private.cfg_check) - if status then - status, err = pcall(update_module_cfg, cfg, module_cfg) - end if not status then box.cfg = locked(load_cfg) -- restore original box.cfg - -- re-throw exception from check_cfg() or update_module_cfg() return error(err) end @@ -995,11 +950,6 @@ local function get_option_from_env(option) local param_type = template_cfg[option] assert(type(param_type) == 'string') - if param_type == 'module' then - -- Parameter from module. - param_type = module_cfg_type[option] - end - local env_var_name = 'TT_' .. option:upper() local raw_value = os.getenv(env_var_name) @@ -1010,8 +960,8 @@ local function get_option_from_env(option) local err_msg_fmt = 'Environment variable %s has ' .. 'incorrect value for option "%s": should be %s' - -- This code lean on the existing set of template_cfg and - -- module_cfg_type types for simplicity. + -- This code lean on the existing set of template_cfg + -- types for simplicity. if param_type:find('table') and raw_value:find(',') then assert(not param_type:find('boolean')) local res = {} @@ -1043,9 +993,24 @@ local function get_option_from_env(option) end end --- --- Read box configuration from environment variables. --- +-- Used to propagate cfg changes done thru API of distinct modules ( +-- log.cfg of log module for example). +local function update_cfg(option, value) + if box_is_configured then + rawset(box.cfg, option, value) + else + pre_load_cfg[option] = value + end +end + +box.internal.prepare_cfg = prepare_cfg +box.internal.merge_cfg = merge_cfg +box.internal.check_cfg_option_type = check_cfg_option_type +box.internal.update_cfg = update_cfg + +--- +--- Read box configuration from environment variables. +--- box.internal.cfg = setmetatable({}, { __index = function(self, key) if key == 'env' then diff --git a/src/lib/core/say.c b/src/lib/core/say.c index 15785d6915..f45c0c642a 100644 --- a/src/lib/core/say.c +++ b/src/lib/core/say.c @@ -81,7 +81,7 @@ static const char logger_syntax_reminder[] = * True if Tarantool process runs in background mode, i.e. has no * controlling terminal. */ -static bool log_background = true; +static bool log_background; static void say_default(int level, const char *filename, int line, const char *error, @@ -722,14 +722,17 @@ say_logger_initialized(void) void say_logger_init(const char *init_str, int level, int nonblock, - const char *format, int background) + const char *format) { /* * The logger may be early configured * by hands without configuing the whole box. */ - if (say_logger_initialized()) + if (say_logger_initialized()) { + say_set_log_level(level); + say_set_log_format(say_format_by_name(format)); return; + } if (log_create(&log_std, init_str, nonblock) < 0) goto fail; @@ -749,33 +752,49 @@ say_logger_init(const char *init_str, int level, int nonblock, } _say = say_default; say_set_log_level(level); - log_background = background; log_pid = log_default->pid; say_set_log_format(say_format_by_name(format)); - if (background) { - fflush(stderr); - fflush(stdout); - if (log_default->fd == STDERR_FILENO) { - int fd = open("/dev/null", O_WRONLY); - if (fd < 0) { - diag_set(SystemError, "open /dev/null"); - goto fail; - } - dup2(fd, STDERR_FILENO); - dup2(fd, STDOUT_FILENO); - close(fd); - } else { - dup2(log_default->fd, STDERR_FILENO); - dup2(log_default->fd, STDOUT_FILENO); - } - } return; fail: diag_log(); panic("failed to initialize logging subsystem"); } +int +say_set_background(void) +{ + assert(say_logger_initialized()); + + if (log_background) + return 0; + + log_background = true; + + fflush(stderr); + fflush(stdout); + + int fd; + int fd_null = -1; + if (log_default->fd == STDERR_FILENO) { + fd_null = open("/dev/null", O_WRONLY); + if (fd_null < 0) { + diag_set(SystemError, "open(/dev/null)"); + return -1; + } + fd = fd_null; + } else { + fd = log_default->fd; + } + + dup2(fd, STDERR_FILENO); + dup2(fd, STDOUT_FILENO); + if (fd_null != -1) + close(fd_null); + + return 0; +} + void say_logger_free(void) { diff --git a/src/lib/core/say.h b/src/lib/core/say.h index 440e8462ee..b78e82f137 100644 --- a/src/lib/core/say.h +++ b/src/lib/core/say.h @@ -297,8 +297,19 @@ say_logrotate(struct ev_loop *, struct ev_signal *, int /* revents */); void say_logger_init(const char *init_str, int log_level, int nonblock, - const char *log_format, - int background); + const char *log_format); + +/** + * Turn on background mode for logger. Should be called after say_logger_init. + * + * If logger is NULL (writes to stderr) then stdout and stderr will be + * redirected to /dev/null. + * + * Otherwise (logger writes to file, pipe etc) stdout and stderr will be + * redirected to logger fd. + */ +int +say_set_background(void); /** Test if logger is initialized. */ bool diff --git a/src/lua/log.lua b/src/lua/log.lua index 7e73483885..2a5582d4a0 100644 --- a/src/lua/log.lua +++ b/src/lua/log.lua @@ -5,26 +5,21 @@ ffi.cdef[[ typedef void (*sayfunc_t)(int level, const char *filename, int line, const char *error, const char *format, ...); - enum say_logger_type { - SAY_LOGGER_BOOT, - SAY_LOGGER_STDERR, - SAY_LOGGER_FILE, - SAY_LOGGER_PIPE, - SAY_LOGGER_SYSLOG - }; - - enum say_logger_type - log_type(); - void say_set_log_level(int new_level); void say_set_log_format(enum say_format format); + int + say_check_cfg(const char *log, + int level, + int nonblock, + const char *format); + extern void say_logger_init(const char *init_str, int level, int nonblock, - const char *format, int background); + const char *format); extern bool say_logger_initialized(void); @@ -54,9 +49,6 @@ ffi.cdef[[ pid_t log_pid; extern int log_level; extern int log_format; - - int - say_parse_logger_type(const char **str, enum say_logger_type *type); ]] local S_CRIT = ffi.C.S_CRIT @@ -97,14 +89,6 @@ local fmt_str2num = { ["json"] = ffi.C.SF_JSON, } -local function fmt_list() - local keyset = {} - for k in pairs(fmt_str2num) do - keyset[#keyset + 1] = k - end - return table.concat(keyset, ',') -end - -- Logging levels symbolic representation. local log_level_keys = { ['fatal'] = ffi.C.S_FATAL, @@ -127,13 +111,15 @@ end -- Default options. The keys are part of -- user API , so change with caution. -local log_cfg = { +local default_cfg = { log = nil, nonblock = nil, level = S_INFO, format = fmt_num2str[ffi.C.SF_PLAIN], } +local log_cfg = table.copy(default_cfg) + -- Name mapping from box to log module and -- back. Make sure all required fields -- are covered! @@ -144,141 +130,6 @@ local log2box_keys = { ['format'] = 'log_format', } -local box2log_keys = { - ['log'] = 'log', - ['log_nonblock'] = 'nonblock', - ['log_level'] = 'level', - ['log_format'] = 'format', -} - --- Update cfg value(s) in box.cfg instance conditionally -local function box_cfg_update(log_key) - -- if it is not yet even exist just exit early - if type(box.cfg) ~= 'table' then - return - end - - local update = function(log_key, box_key) - -- the box entry may be under configuration - -- process thus equal to nil, skip it then - if log_cfg[log_key] ~= nil and - box.cfg[box_key] ~= nil and - box.cfg[box_key] ~= log_cfg[log_key] then - box.cfg[box_key] = log_cfg[log_key] - end - end - - if log_key == nil then - for k, v in pairs(log2box_keys) do - update(k, v) - end - else - assert(log2box_keys[log_key] ~= nil) - update(log_key, log2box_keys[log_key]) - end -end - --- Log options which can be set ony once. -local cfg_static_keys = { - log = true, - nonblock = true, -} - --- Test if static key is not changed. -local function verify_static(k, v) - assert(cfg_static_keys[k] ~= nil) - - if ffi.C.say_logger_initialized() == true then - if log_cfg[k] ~= v then - return false, "can't be set dynamically" - end - end - - return true -end - -local function parse_format(log) - -- There is no easy way to get pointer to ponter via FFI - local str_p = ffi.new('const char*[1]') - str_p[0] = ffi.cast('char*', log) - local logger_type = ffi.new('enum say_logger_type[1]') - local rc = ffi.C.say_parse_logger_type(str_p, logger_type) - - if rc ~= 0 then - box.error() - end - - return logger_type[0] -end - --- Test if format is valid. -local function verify_format(key, name, cfg) - assert(log_cfg[key] ~= nil) - - if not fmt_str2num[name] then - local m = "expected %s" - return false, m:format(fmt_list()) - end - - local log_type = ffi.C.log_type() - - -- When comes from log.cfg{} or box.cfg{} - -- initial call we might be asked to setup - -- syslog with json which is not allowed. - -- - -- Note the cfg table comes from two places: - -- box api interface and log module itself. - -- The good thing that we're only needed log - -- entry which is the same key for both. - if cfg ~= nil and cfg['log'] ~= nil then - log_type = parse_format(cfg['log']) - end - - if fmt_str2num[name] == ffi.C.SF_JSON then - if log_type == ffi.C.SAY_LOGGER_SYSLOG then - local m = "%s can't be used with syslog logger" - return false, m:format(fmt_num2str[ffi.C.SF_JSON]) - end - end - - return true -end - --- Test if level is a valid string. The --- number may be any for to backward compatibility. -local function verify_level(key, level) - assert(log_cfg[key] ~= nil) - - if type(level) == 'string' then - if not log_level_keys[level] then - local m = "expected %s" - return false, m:format(log_level_list()) - end - elseif type(level) ~= 'number' then - return false, "must be a number or a string" - end - - return true -end - -local verify_ops = { - ['log'] = verify_static, - ['nonblock'] = verify_static, - ['format'] = verify_format, - ['level'] = verify_level, -} - --- Verify a value for the particular key. -local function verify_option(k, v, ...) - assert(k ~= nil) - - if verify_ops[k] ~= nil then - return verify_ops[k](k, v, ...) - end - - return true -end - -- Main routine which pass data to C logging code. local function say(level, fmt, ...) if ffi.C.log_level < level then @@ -325,72 +176,18 @@ local function say_closure(lvl) end end +local log_error = say_closure(S_ERROR) +local log_warn = say_closure(S_WARN) +local log_info = say_closure(S_INFO) +local log_verbose = say_closure(S_VERBOSE) +local log_debug = say_closure(S_DEBUG) + -- Rotate log (basically reopen the log file and -- start writting into it). local function log_rotate() ffi.C.say_logrotate(nil, nil, 0) end --- Set new logging level, the level must be valid! -local function set_log_level(level, update_box_cfg) - assert(type(level) == 'number') - - ffi.C.say_set_log_level(level) - - rawset(log_cfg, 'level', level) - - if update_box_cfg then - box_cfg_update('level') - end - - local m = "log: level set to %s" - say(S_DEBUG, m:format(level)) -end - --- Tries to set a new level, or print an error. -local function log_level(level) - local ok, msg = verify_option('level', level) - if not ok then - error(msg) - end - - if type(level) == 'string' then - level = log_level_keys[level] - end - - set_log_level(level, true) -end - --- Set a new logging format, the name must be valid! -local function set_log_format(name, update_box_cfg) - assert(fmt_str2num[name] ~= nil) - - if fmt_str2num[name] == ffi.C.SF_JSON then - ffi.C.say_set_log_format(ffi.C.SF_JSON) - else - ffi.C.say_set_log_format(ffi.C.SF_PLAIN) - end - - rawset(log_cfg, 'format', name) - - if update_box_cfg then - box_cfg_update('format') - end - - local m = "log: format set to '%s'" - say(S_DEBUG, m:format(name)) -end - --- Tries to set a new format, or print an error. -local function log_format(name) - local ok, msg = verify_option('format', name) - if not ok then - error(msg) - end - - set_log_format(name, true) -end - -- Returns pid of a pipe process. local function log_pid() return tonumber(ffi.C.log_pid) @@ -470,170 +267,122 @@ end Ratelimit.log_crit = log_ratelimited_closure(S_CRIT) --- Fetch a value from log to box.cfg{}. -local function box_api_cfg_get(key) - return log_cfg[box2log_keys[key]] -end +local option_types = { + log = 'string', + nonblock = 'boolean', + level = 'number, string', + format = 'string', +} --- Set value to log from box.cfg{}. -local function box_api_cfg_set(cfg, key, value) - local log_key = box2log_keys[key] +local log_initialized = false - -- a special case where we need to restore - -- nil value from previous setup attempt. - if value == box.NULL then - log_cfg[log_key] = nil - return true +-- Convert cfg options to types suitable for ffi say_ functions. +local function log_C_cfg(cfg) + local cfg_C = table.copy(cfg) + if type(cfg.level) == 'string' then + cfg_C.level = log_level_keys[cfg.level] end - - local ok, msg = verify_option(log_key, value, cfg) - if not ok then - return false, msg + local nonblock + if cfg.nonblock ~= nil then + nonblock = cfg.nonblock and 1 or 0 + else + nonblock = -1 end - - log_cfg[log_key] = value - return true + cfg_C.nonblock = nonblock + return cfg_C end --- Set logging level from reloading box.cfg{} -local function box_api_cfg_set_log_level() - local log_key = box2log_keys['log_level'] - local v = box.cfg['log_level'] - - local ok, msg = verify_option(log_key, v) - if not ok then - box.error(box.error.CFG, 'log_level', msg) +-- Check cfg is valid and thus can be applied +local function log_check_cfg(cfg) + if type(cfg.level) == 'string' and + log_level_keys[cfg.level] == nil then + local err = ("expected %s"):format(log_level_list()) + box.error(box.error.CFG, "log_level", err) end - if type(v) == 'string' then - v = log_level_keys[v] + if log_initialized then + if log_cfg.log ~= cfg.log then + box.error(box.error.RELOAD_CFG, 'log'); + end + if log_cfg.nonblock ~= cfg.nonblock then + box.error(box.error.RELOAD_CFG, 'log_nonblock'); + end end - set_log_level(v, false) -end - --- Set logging format from reloading box.cfg{} -local function box_api_set_log_format() - local log_key = box2log_keys['log_format'] - local v = box.cfg['log_format'] - - local ok, msg = verify_option(log_key, v) - if not ok then - box.error(box.error.CFG, 'log_format', msg) + local cfg_C = log_C_cfg(cfg) + if ffi.C.say_check_cfg(cfg_C.log, cfg_C.level, + cfg_C.nonblock, cfg_C.format) ~= 0 then + box.error() end - - set_log_format(v, false) end --- Reload dynamic options. -local function reload_cfg(cfg) - for k in pairs(cfg_static_keys) do - if cfg[k] ~= nil then - local ok, msg = verify_static(k, cfg[k]) - if not ok then - local m = "log.cfg: \'%s\' %s" - error(m:format(k, msg)) - end +-- Update box.internal.cfg on log config changes +local function box_cfg_update(key) + if key == nil then + for km, kb in pairs(log2box_keys) do + box.internal.update_cfg(kb, log_cfg[km]) end - end - - if cfg.level ~= nil then - log_level(cfg.level) - end - - if cfg.format ~= nil then - log_format(cfg.format) + else + box.internal.update_cfg(log2box_keys[key], log_cfg[key]) end end --- Load or reload configuration via log.cfg({}) call. -local function load_cfg(self, cfg) - cfg = cfg or {} +local function set_log_level(level) + box.internal.check_cfg_option_type(option_types.level, 'level', level) + local cfg = table.copy(log_cfg) + cfg.level = level + log_check_cfg(cfg) - -- log option might be zero length string, which - -- is fine, we should treat it as nil. - if cfg.log ~= nil then - if type(cfg.log) ~= 'string' or cfg.log:len() == 0 then - cfg.log = nil - end - end + local cfg_C = log_C_cfg(cfg) + ffi.C.say_set_log_level(cfg_C.level) + log_cfg.level = level - if cfg.format ~= nil then - local ok, msg = verify_option('format', cfg.format, cfg) - if not ok then - local m = "log.cfg: \'%s\' %s" - error(m:format('format', msg)) - end - end + box_cfg_update('level') - if cfg.level ~= nil then - local ok, msg = verify_option('level', cfg.level) - if not ok then - local m = "log.cfg: \'%s\' %s" - error(m:format('level', msg)) - end - -- Convert level to a numeric value since - -- low level api operates with numbers only. - if type(cfg.level) == 'string' then - assert(log_level_keys[cfg.level] ~= nil) - cfg.level = log_level_keys[cfg.level] - end - end - - if cfg.nonblock ~= nil then - if type(cfg.nonblock) ~= 'boolean' then - error("log.cfg: 'nonblock' option must be 'true' or 'false'") - end - end + log_debug("log: level set to %s", level) +end - if ffi.C.say_logger_initialized() == true then - return reload_cfg(cfg) - end +local function set_log_format(format) + box.internal.check_cfg_option_type(option_types.format, 'format', format) + local cfg = table.copy(log_cfg) + cfg.format = format + log_check_cfg(cfg) - cfg.level = cfg.level or log_cfg.level - cfg.format = cfg.format or log_cfg.format - cfg.nonblock = cfg.nonblock or log_cfg.nonblock + ffi.C.say_set_log_format(fmt_str2num[format]) + log_cfg.format = format - -- nonblock is special: it has to become integer - -- for ffi call but in config we have to save - -- true value only for backward compatibility! - local nonblock = cfg.nonblock + box_cfg_update('format') - if nonblock == nil or nonblock == false then - nonblock = 0 - else - nonblock = 1 - end + log_debug("log: format set to '%s'", format) +end - -- Parsing for validation purposes - if cfg.log ~= nil then - parse_format(cfg.log) - end +local function log_configure(self, cfg) + cfg = box.internal.prepare_cfg(cfg, default_cfg, option_types) + box.internal.merge_cfg(cfg, log_cfg); - -- We never allow confgure the logger in background - -- mode since we don't know how the box will be configured - -- later. - ffi.C.say_logger_init(cfg.log, cfg.level, - nonblock, cfg.format, 0) + log_check_cfg(cfg) + local cfg_C = log_C_cfg(cfg) + ffi.C.say_logger_init(cfg_C.log, cfg_C.level, + cfg_C.nonblock, cfg_C.format) + log_initialized = true - if nonblock == 1 then - nonblock = true - else - nonblock = nil + for o in pairs(option_types) do + log_cfg[o] = cfg[o] end - -- Update log_cfg vars to show them in module - -- configuration output. - rawset(log_cfg, 'log', cfg.log) - rawset(log_cfg, 'level', cfg.level) - rawset(log_cfg, 'nonblock', nonblock) - rawset(log_cfg, 'format', cfg.format) - - -- and box.cfg output as well. box_cfg_update() - local m = "log.cfg({log=%s,level=%s,nonblock=%s,format=\'%s\'})" - say(S_DEBUG, m:format(cfg.log, cfg.level, cfg.nonblock, cfg.format)) + log_debug("log.cfg({log=%s, level=%s, nonblock=%s, format=%s})", + cfg.log, cfg.level, cfg.nonblock, cfg.format) +end + +local function box_to_log_cfg() + return { + log = box.cfg.log, + level = box.cfg.log_level, + format = box.cfg.log_format, + nonblock = box.cfg.log_nonblock, + } end local compat_warning_said = false @@ -648,23 +397,21 @@ local compat_v16 = { } local log = { - warn = say_closure(S_WARN), - info = say_closure(S_INFO), - verbose = say_closure(S_VERBOSE), - debug = say_closure(S_DEBUG), - error = say_closure(S_ERROR), + warn = log_warn, + info = log_info, + verbose = log_verbose, + debug = log_debug, + error = log_error, rotate = log_rotate, pid = log_pid, - level = log_level, - log_format = log_format, + level = set_log_level, + log_format = set_log_format, cfg = setmetatable(log_cfg, { - __call = load_cfg, + __call = log_configure, }), box_api = { - cfg_get = box_api_cfg_get, - cfg_set = box_api_cfg_set, - cfg_set_log_level = box_api_cfg_set_log_level, - cfg_set_log_format = box_api_set_log_format, + cfg = function() log_configure(log_cfg, box_to_log_cfg()) end, + cfg_check = function() log_check_cfg(box_to_log_cfg()) end, }, internal = { ratelimit = { diff --git a/src/main.cc b/src/main.cc index 1c45fa9d05..b0a2212685 100644 --- a/src/main.cc +++ b/src/main.cc @@ -87,6 +87,7 @@ #include "ssl_cert_paths_discover.h" #include "core/errinj.h" #include "core/clock_lowres.h" +#include "lua/utils.h" static pid_t master_pid = getpid(); static struct pidfh *pid_file_handle; @@ -428,7 +429,6 @@ load_cfg(void) int background = cfg_geti("background"); const char *log = cfg_gets("log"); - const char *log_format = cfg_gets("log_format"); pid_file = (char *)cfg_gets("pid_file"); if (pid_file != NULL) { pid_file = abspath(pid_file); @@ -476,11 +476,10 @@ load_cfg(void) * logger init must happen before daemonising in order for the error * to show and for the process to exit with a failure status */ - say_logger_init(log, - cfg_geti("log_level"), - cfg_getb("log_nonblock"), - log_format, - background); + if (box_init_say() != 0) { + diag_log(); + exit(EXIT_FAILURE); + } /* * Initialize flight recorder after say logger as we might use diff --git a/static-build/test/static-build/exports.test.lua b/static-build/test/static-build/exports.test.lua index 2b23062cf3..32f8035172 100755 --- a/static-build/test/static-build/exports.test.lua +++ b/static-build/test/static-build/exports.test.lua @@ -72,7 +72,6 @@ local check_symbols = { 'crc32_calc', 'decimal_unpack', - 'log_type', 'say_set_log_level', 'say_logrotate', 'say_set_log_format', diff --git a/test/app-tap/gh-5130-panic-on-invalid-log.test.lua b/test/app-tap/gh-5130-panic-on-invalid-log.test.lua index 7ebff14259..277b836bc0 100755 --- a/test/app-tap/gh-5130-panic-on-invalid-log.test.lua +++ b/test/app-tap/gh-5130-panic-on-invalid-log.test.lua @@ -18,6 +18,6 @@ test:ok(ok) -- Dynamic reconfiguration - error, no info about invalid logger type _, err = pcall(log.cfg, {log=' :invalid'}) -test:like(err, "can't be set dynamically") +test:like(err, "Can't set option 'log' dynamically") os.exit(test:check() and 0 or 1) diff --git a/test/app-tap/logger.test.lua b/test/app-tap/logger.test.lua index 25074fcb6d..dd3e9a14d7 100755 --- a/test/app-tap/logger.test.lua +++ b/test/app-tap/logger.test.lua @@ -1,22 +1,91 @@ #!/usr/bin/env tarantool +local log = require('log') + local test = require('tap').test('log') -test:plan(64) +test:plan(104) + +local function test_invalid_cfg(cfg_method, cfg, name, expected) + local _, err = pcall(cfg_method, cfg) + test:ok(tostring(err):find(expected) ~= nil, name) +end + +local function test_allowed_types(cfg_method, cfg, name, allowed_types) + local prefix + if string.find(allowed_types, ',') then + prefix = 'should be one of types ' + else + prefix = 'should be of type ' + end + test_invalid_cfg(cfg_method, cfg, name, prefix .. allowed_types) +end + +-- Test allowed types for options +test_allowed_types(log.cfg, {log = 1}, + 'log.cfg allowed log types', 'string') +test_allowed_types(log.cfg, {level = true}, + 'log.cfg allowed level types', 'number, string') +test_allowed_types(log.cfg, {format = true}, + 'log.cfg allowed format types', 'string') +test_allowed_types(log.cfg, {nonblock = 'hi'}, + 'log.cfg allowed nonblock types', 'boolean') +test_allowed_types(box.cfg, {log = 1}, + 'box.cfg allowed log types', 'string') +test_allowed_types(box.cfg,{log_level = true}, + 'box.cfg allowed log_level types', 'number, string') +test_allowed_types(box.cfg, {log_format = true}, + 'box.cfg allowed log_format types', 'string') +test_allowed_types(box.cfg, {log_nonblock = 'hi'}, + 'box.cfg allowed log_nonblock types', 'boolean') + +-- Test other invalid inputs + +test_invalid_cfg(log.cfg, {log = 'syslog:', format = 'json'}, + "log.cfg syslog and json", + "'json' can't be used with syslog logger") +test_invalid_cfg(box.cfg, {log = 'syslog:', log_format = 'json'}, + "box.cfg syslog and json", + "'json' can't be used with syslog logger") + +-- Don't check all invalid inputs for both box.cfg and log.cfg as +-- now they use same check function. + +-- gh-7447 +test_invalid_cfg(log.cfg, {log = 'syslog:xxx'}, + "log.cfg invalid syslog", + "bad option 'xxx'") + +test_invalid_cfg(log.cfg, {log = 'xxx:'}, + "log.cfg invalid logger prefix", + "expecting a file name or a prefix, such as " .. + "'|', 'pipe:', 'syslog:'") + +test_invalid_cfg(log.cfg, {format = 'xxx'}, + "log.cfg invalid format", + "expected 'plain' or 'json'") + +test_invalid_cfg(log.cfg, {nonblock = true}, + "log.cfg nonblock and stderr", + 'the option is incompatible with file/stderr logger') + +test_invalid_cfg(log.cfg, {log = '1.log', nonblock = true}, + "log.cfg nonblock and file", + 'the option is incompatible with file/stderr logger') + +test_invalid_cfg(log.cfg, {format = 'xxx'}, + "log.cfg invalid format", + "expected 'plain' or 'json'") + +test_invalid_cfg(log.cfg, {xxx = 1}, + "log.cfg unexpected option", + 'unexpected option') -- -- gh-5121: Allow to use 'json' output before box.cfg() -- -local log = require('log') local _, err = pcall(log.log_format, 'json') test:ok(err == nil) --- We're not allowed to use json with syslog though. -_, err = pcall(log.cfg, {log='syslog:', format='json'}) -test:ok(tostring(err):find("can\'t be used with syslog logger") ~= nil) - -_, err = pcall(box.cfg, {log='syslog:', log_format='json'}) -test:ok(tostring(err):find("can\'t be used with syslog logger") ~= nil) - -- switch back to plain to next tests log.log_format('plain') @@ -71,6 +140,14 @@ local line = file:read() local s = json.decode(line) test:ok(s['message'] == message, "message match") +-- Try to change options that can't be changed on first box.cfg() +test_invalid_cfg(box.cfg, {log = '2.log'}, + "reconfigure logger thru first box.cfg", + "Can't set option 'log' dynamically") +test_invalid_cfg(box.cfg, {log_nonblock = true}, + "reconfigure nonblock thru first box.cfg", + "Can't set option 'log_nonblock' dynamically") + -- Now switch to box.cfg interface box.cfg{ log = filename, @@ -83,21 +160,41 @@ verify_keys("box.cfg") -- Test symbolic names for loglevels log.cfg({level='fatal'}) -test:ok(log.cfg.level == 0 and box.cfg.log_level == 0, 'both got fatal') +test:ok(log.cfg.level == 'fatal' and box.cfg.log_level == 'fatal', + 'both got fatal') log.cfg({level='syserror'}) -test:ok(log.cfg.level == 1 and box.cfg.log_level == 1, 'both got syserror') +test:ok(log.cfg.level == 'syserror' and box.cfg.log_level == 'syserror', + 'both got syserror') log.cfg({level='error'}) -test:ok(log.cfg.level == 2 and box.cfg.log_level == 2, 'both got error') +test:ok(log.cfg.level == 'error' and box.cfg.log_level == 'error', + 'both got error') log.cfg({level='crit'}) -test:ok(log.cfg.level == 3 and box.cfg.log_level == 3, 'both got crit') +test:ok(log.cfg.level == 'crit' and box.cfg.log_level == 'crit', + 'both got crit') log.cfg({level='warn'}) -test:ok(log.cfg.level == 4 and box.cfg.log_level == 4, 'both got warn') +test:ok(log.cfg.level == 'warn' and box.cfg.log_level == 'warn', + 'both got warn') log.cfg({level='info'}) -test:ok(log.cfg.level == 5 and box.cfg.log_level == 5, 'both got info') +test:ok(log.cfg.level == 'info' and box.cfg.log_level == 'info', + 'both got info') log.cfg({level='verbose'}) -test:ok(log.cfg.level == 6 and box.cfg.log_level == 6, 'both got verbose') +test:ok(log.cfg.level == 'verbose' and box.cfg.log_level == 'verbose', + 'both got verbose') log.cfg({level='debug'}) -test:ok(log.cfg.level == 7 and box.cfg.log_level == 7, 'both got debug') +test:ok(log.cfg.level == 'debug' and box.cfg.log_level == 'debug', + 'both got debug') + +log.cfg{level = 4} +test:ok(log.cfg.level == 4, "log.cfg number level then read log.cfg") +test:ok(box.cfg.log_level == 4, "log.cfg number level then read box.cfg") + +box.cfg{log_level = 5} +test:ok(log.cfg.level == 5, "box.cfg number level then read log.cfg") +test:ok(box.cfg.log_level == 5, "box.cfg number level then read box.cfg") + +box.cfg{log_level = 'warn'} +test:ok(log.cfg.level == 'warn', "box.cfg string level then read log.cfg") +test:ok(box.cfg.log_level == 'warn', "box.cfg string level then read box.cfg") box.cfg{ log = filename, @@ -105,24 +202,51 @@ box.cfg{ memtx_memory = 107374182, } --- Now try to change a static field. -_, err = pcall(box.cfg, {log_level = 5, log = "2.txt"}) -test:ok(tostring(err):find("Can't set option 'log' dynamically") ~= nil, - "box.cfg.log cannot be set dynamically") +-- Try to change options that can't be changed on non-first box.cfg() +test_invalid_cfg(box.cfg, {log = '2.log'}, + "reconfigure logger thru non-first box.cfg", + "Can't set option 'log' dynamically") test:ok(box.cfg.log == filename, "filename match") test:ok(box.cfg.log_level == 6, "loglevel match") verify_keys("box.cfg static error") +test_invalid_cfg(box.cfg, {log_nonblock = true}, + "reconfigure nonblock thru non-first box.cfg", + "Can't set option 'log_nonblock' dynamically") + +-- Test invalid values for setters + +_, err = pcall(log.log_format, {}) +test:ok(tostring(err):find('should be of type string') ~= nil, + "invalid format setter value type") + +_, err = pcall(log.level, {}) +test:ok(tostring(err):find('should be one of types number, string') ~= nil, + "invalid format setter value type") + -- Change format and levels. + _, err = pcall(log.log_format, 'json') -test:ok(err == nil, "change to json") +test:ok(err == nil, "format setter result") +test:ok(log.cfg.format == 'json', "format setter cfg") +verify_keys("format setter verify keys") + _, err = pcall(log.level, 1) +test:ok(err == nil, "level setter number result") +test:ok(log.cfg.level == 1, "level setter number cfg") +verify_keys("level setter number verify keys") + +_, err = pcall(log.level, 'warn') test:ok(err == nil, "change log level") -verify_keys("log change json and level") +test:ok(log.cfg.level == 'warn', "level setter string") +verify_keys("level setter string verify keys") --- Restore defaults -log.log_format('plain') -log.level(5) +-- Check reset works thru log.cfg +log.cfg{level = box.NULL} +test:ok(log.cfg.level == 5, "reset of level thru log.cfg") + +log.cfg{format = ""} +test:ok(log.cfg.format == 'plain', "reset of plain thru log.cfg") -- -- Check that Tarantool creates ADMIN session for #! script diff --git a/test/app-tap/logmod.test.lua b/test/app-tap/logmod.test.lua index 6eab77f7e6..6fa06d4916 100755 --- a/test/app-tap/logmod.test.lua +++ b/test/app-tap/logmod.test.lua @@ -9,27 +9,27 @@ log.log_format('plain') -- Test symbolic names for loglevels local _, err = pcall(log.cfg, {level='fatal'}) -test:ok(err == nil and log.cfg.level == 0, 'both got fatal') +test:ok(err == nil and log.cfg.level == 'fatal', 'got fatal') _, err = pcall(log.cfg, {level='syserror'}) -test:ok(err == nil and log.cfg.level == 1, 'got syserror') +test:ok(err == nil and log.cfg.level == 'syserror', 'got syserror') _, err = pcall(log.cfg, {level='error'}) -test:ok(err == nil and log.cfg.level == 2, 'got error') +test:ok(err == nil and log.cfg.level == 'error', 'got error') _, err = pcall(log.cfg, {level='crit'}) -test:ok(err == nil and log.cfg.level == 3, 'got crit') +test:ok(err == nil and log.cfg.level == 'crit', 'got crit') _, err = pcall(log.cfg, {level='warn'}) -test:ok(err == nil and log.cfg.level == 4, 'got warn') +test:ok(err == nil and log.cfg.level == 'warn', 'got warn') _, err = pcall(log.cfg, {level='info'}) -test:ok(err == nil and log.cfg.level == 5, 'got info') +test:ok(err == nil and log.cfg.level == 'info', 'got info') _, err = pcall(log.cfg, {level='verbose'}) -test:ok(err == nil and log.cfg.level == 6, 'got verbose') +test:ok(err == nil and log.cfg.level == 'verbose', 'got verbose') _, err = pcall(log.cfg, {level='debug'}) -test:ok(err == nil and log.cfg.level == 7, 'got debug') +test:ok(err == nil and log.cfg.level == 'debug', 'got debug') os.exit(test:check() and 0 or 1) diff --git a/test/unit/memtx_allocator.cc b/test/unit/memtx_allocator.cc index 152fc385d2..d438529834 100644 --- a/test/unit/memtx_allocator.cc +++ b/test/unit/memtx_allocator.cc @@ -499,8 +499,7 @@ test_main() int main() { - say_logger_init("/dev/null", S_INFO, /*nonblock=*/true, "plain", - /*background=*/false); + say_logger_init("/dev/null", S_INFO, /*nonblock=*/true, "plain"); clock_lowres_signal_init(); memory_init(); fiber_init(fiber_c_invoke); diff --git a/test/unit/popen.c b/test/unit/popen.c index b4e1b9557d..25195ee63e 100644 --- a/test/unit/popen.c +++ b/test/unit/popen.c @@ -236,7 +236,7 @@ int main(int argc, char *argv[]) { #if 0 - say_logger_init(NULL, S_DEBUG, 0, "plain", 0); + say_logger_init(NULL, S_DEBUG, 0, "plain"); #endif memory_init(); diff --git a/test/unit/raft_test_utils.c b/test/unit/raft_test_utils.c index 186eeb8d4f..57fe8af646 100644 --- a/test/unit/raft_test_utils.c +++ b/test/unit/raft_test_utils.c @@ -627,7 +627,7 @@ raft_run_test(const char *log_file, fiber_func test) int fd = open(log_file, O_TRUNC); if (fd != -1) close(fd); - say_logger_init(log_file, 5, 1, "plain", 0); + say_logger_init(log_file, 5, 1, "plain"); /* Print the seed to be able to reproduce a bug with the same seed. */ say_info("Random seed = %llu", (unsigned long long) seed); diff --git a/test/unit/say.c b/test/unit/say.c index bfb72d5301..78cadce6c1 100644 --- a/test/unit/say.c +++ b/test/unit/say.c @@ -167,7 +167,7 @@ int main() { memory_init(); fiber_init(fiber_c_invoke); - say_logger_init("/dev/null", S_INFO, 0, "plain", 0); + say_logger_init("/dev/null", S_INFO, 0, "plain"); plan(33); diff --git a/test/unit/swim_proto.c b/test/unit/swim_proto.c index 50d27992e5..fb0fd30551 100644 --- a/test/unit/swim_proto.c +++ b/test/unit/swim_proto.c @@ -257,7 +257,7 @@ main() int fd = open("log.txt", O_TRUNC); if (fd != -1) close(fd); - say_logger_init("log.txt", 6, 1, "plain", 0); + say_logger_init("log.txt", 6, 1, "plain"); swim_test_member_def(); swim_test_meta(); diff --git a/test/unit/swim_test_utils.c b/test/unit/swim_test_utils.c index 6badabcbd8..189979bb91 100644 --- a/test/unit/swim_test_utils.c +++ b/test/unit/swim_test_utils.c @@ -866,7 +866,7 @@ swim_run_test(const char *log_file, fiber_func test) int fd = open(log_file, O_TRUNC); if (fd != -1) close(fd); - say_logger_init(log_file, 5, 1, "plain", 0); + say_logger_init(log_file, 5, 1, "plain"); /* * Print the seed to be able to reproduce a bug with the * same seed. -- GitLab