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 0000000000000000000000000000000000000000..519adf4bb47e075f8043d20053846de6065fb8d7 --- /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 f30e7bbfe96552e547ba8802e6023444730807c7..e88a087672a33abdbf09f5edd25164ef8a075899 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 a722c8ed7f77dc58ff790beda315454f3401f06a..aa43a9afe61e93089fe46316d26ffb6846c73b18 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 71d72c55e520980c771953cac53f62525a9d1c72..acb76f425253bf8fc7264beef40e7e88866ca13d 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 6956cdce3eebc4491c1e8102dfdbd09c860aabec..beaf6d5dd286572f4e2967bf2c37b481f84766f5 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 15785d69152029a1bf5fd319b0a493024e890a12..f45c0c642ae9da16723799ce0567d6de3d8560a2 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 440e8462eed57023e60a55930dcad996fea27c6b..b78e82f1372591f0b67a6d3c8fd6a8adb1eafd45 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 7e734838850e2209e1ce3618909b8aa9a9506f97..2a5582d4a09834f224aeb516833659f34a80aafb 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 1c45fa9d051c51995c7d78fbc5c59d298624ef04..b0a2212685de5f2983d7abdc58e84abe0ed0feb2 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 2b23062cf360877a50416cfdaca481ab6b3f8733..32f8035172e3f41d7350f3b02f392860c3dc786d 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 7ebff14259a30b99135e37d89edfab30aeb642ed..277b836bc04d014cda7af68cca53ff522b2f6215 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 25074fcb6d87c00fbc829beabfc2419b9f0003d1..dd3e9a14d75877076f52330097decec9e686263c 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 6eab77f7e65bea417c0f5178045349826783182f..6fa06d4916763cea8611f970af6cd16babe07cd7 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 152fc385d2a186f6b82d7e516ce411b3db7d5825..d43852983433151a5b5d3c3a45edd264a0d68b56 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 b4e1b9557d074bbbb02abaef4d6d3ec211632ad6..25195ee63e5a5456c2d1dde38bc26d4a7971d424 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 186eeb8d4fd1d57d445f1fbfd9448695cfd479a9..57fe8af6467a54690e88569c42f746da6b3b81b5 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 bfb72d530157e6fca07b3bd1e02f4f9e082ec432..78cadce6c1ccde1908be700c530ec45d30713122 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 50d27992e59824ffced466cddb757cf1d0379da6..fb0fd30551a3b544f2a4dd24f6a53b2d20559a31 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 6badabcbd87ba62ccce180d2cf9508b4fbac16e4..189979bb918c78d4368ce9401b7f0ffb06a96fce 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.