diff --git a/extra/exports b/extra/exports
index d724211235f5b5e31afc61151f81f79d5aac0770..21fa9bf006245cfea2f389dbaf9dcd8d22e6f9ec 100644
--- a/extra/exports
+++ b/extra/exports
@@ -65,6 +65,7 @@ mp_encode_float
 mp_decode_double
 mp_decode_float
 
+log_type
 say_set_log_level
 say_logrotate
 say_set_log_format
diff --git a/src/lib/core/say.c b/src/lib/core/say.c
index 50ee556929708613b7ec454907d361a540e2b087..68aa92f61146faf5dbe0b79adf60b36da6986a5f 100644
--- a/src/lib/core/say.c
+++ b/src/lib/core/say.c
@@ -161,6 +161,12 @@ level_to_syslog_priority(int level)
 	}
 }
 
+enum say_logger_type
+log_type()
+{
+	return log_default->type;
+}
+
 void
 log_set_level(struct log *log, enum say_level level)
 {
@@ -171,9 +177,7 @@ void
 log_set_format(struct log *log, log_format_func_t format_func)
 {
 	assert(format_func == say_format_plain ||
-	       log->type == SAY_LOGGER_STDERR ||
-	       log->type == SAY_LOGGER_PIPE || log->type == SAY_LOGGER_FILE);
-
+	       log->type != SAY_LOGGER_SYSLOG);
 	log->format_func = format_func;
 }
 
diff --git a/src/lib/core/say.h b/src/lib/core/say.h
index 70050362c3ea3d7dbc276749db29fbb8013870f3..d26c3ddefa7d75a1dd2be079f7590915b1397696 100644
--- a/src/lib/core/say.h
+++ b/src/lib/core/say.h
@@ -195,6 +195,13 @@ int
 log_say(struct log *log, int level, const char *filename,
 	int line, const char *error, const char *format, ...);
 
+/**
+ * Default logger type info.
+ * @retval say_logger_type.
+ */
+enum say_logger_type
+log_type();
+
 /**
  * Set log level. Can be used dynamically.
  *
diff --git a/src/lua/log.lua b/src/lua/log.lua
index 0ac0e8f261b47ba7c1ff969ec9e150d9e458e622..312c14d5eb07ecbe132b95d6aa58556d5afa3f7d 100644
--- a/src/lua/log.lua
+++ b/src/lua/log.lua
@@ -4,6 +4,18 @@ local ffi = require('ffi')
 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);
 
@@ -117,6 +129,9 @@ end
 
 local function log_format(format_name)
     if format_name == "json" then
+        if ffi.C.log_type() == ffi.C.SAY_LOGGER_SYSLOG then
+            error("log_format: 'json' can't be used with syslog logger")
+        end
         ffi.C.say_set_log_format(ffi.C.SF_JSON)
     elseif format_name == "plain" then
         ffi.C.say_set_log_format(ffi.C.SF_PLAIN)
diff --git a/test/app-tap/logger.test.lua b/test/app-tap/logger.test.lua
index 0c11702c8bca2a889054f39b25fd0af10e7806cc..492d5ea0b49eec4d99898e13bed9a376a2681971 100755
--- a/test/app-tap/logger.test.lua
+++ b/test/app-tap/logger.test.lua
@@ -3,6 +3,11 @@
 local test = require('tap').test('log')
 test:plan(24)
 
+-- gh-3946: Assertion failure when using log_format() before box.cfg()
+local log = require('log')
+log.log_format('json')
+log.log_format('plain')
+
 --
 -- Check that Tarantool creates ADMIN session for #! script
 --
@@ -12,7 +17,6 @@ box.cfg{
     log=filename,
     memtx_memory=107374182,
 }
-local log = require('log')
 local fio = require('fio')
 local json = require('json')
 local fiber = require('fiber')