diff --git a/daemon/engine.c b/daemon/engine.c
index 1022691e3e0c88c17d7956279dbaf80b2a5c3123..baeeb7e8482a77811aecc8e81cfb6a2acbc59b79 100644
--- a/daemon/engine.c
+++ b/daemon/engine.c
@@ -74,8 +74,9 @@ static int l_help(lua_State *L)
 		"hostname()\n    hostname\n"
 		"package_version()\n    return package version\n"
 		"user(name[, group])\n    change process user (and group)\n"
-		"log_level(level)\n	logging level (crit, err, warning, notice, info or debug)\n"
-		"log_target(target)\n	logging target (syslog, stderr, stdout)\n"
+		"log_level(level)\n    logging level (crit, err, warning, notice, info or debug)\n"
+		"log_target(target)\n    logging target (syslog, stderr, stdout)\n"
+		"log_groups(groups)\n    turn on debug log for selected groups\n"
 		"option(opt[, new_val])\n    get/set server option\n"
 		"mode(strict|normal|permissive)\n    set resolver strictness level\n"
 		"reorder_RR([true|false])\n    set/get reordering of RRs within RRsets\n"
@@ -212,61 +213,48 @@ static int l_log_target(lua_State *L)
 	lua_error_p(L, "takes one string parameter or nothing");
 }
 
-static int handle_log_groups(lua_State *L, void (*action)(enum kr_log_group grp))
+static int l_log_groups(lua_State *L)
 {
-	if (lua_gettop(L) != 1 || (!lua_isstring(L, 1) && !lua_istable(L, 1)))
-		lua_error_p(L, "takes string or table of strings");
-
-	if (lua_isstring(L, 1)) {
-		enum kr_log_group grp = kr_log_name2grp(lua_tostring(L, 1));
-		if (grp == 0)
-			lua_error_p(L, "unknown group \"%s\"", lua_tostring(L, -1));
-		action(grp);
-	}
+	const int params = lua_gettop(L);
+	if (params > 1)
+		goto bad_call;
+	if (params == 1) {  // set
+		if (!lua_istable(L, 1))
+			goto bad_call;
+		kr_log_group_reset();
 
-	if (lua_istable(L, 1)) {
 		int idx = 1;
 		lua_pushnil(L);
 		while (lua_next(L, 1) != 0) {
-			if (!lua_isstring(L, -1))
-				lua_error_p(L, "wrong value at index %d, must be string", idx);
-			enum kr_log_group grp = kr_log_name2grp(lua_tostring(L, -1));
+			const char *grp_str = lua_tostring(L, -1);
+			if (!grp_str)
+				goto bad_call;
+			enum kr_log_group grp = kr_log_name2grp(grp_str);
 			if (grp == 0)
-				lua_error_p(L, "unknown group \"%s\"", lua_tostring(L, -1));
+				lua_error_p(L, "unknown log group '%s'", lua_tostring(L, -1));
 
-			action(grp);
+			kr_log_group_add(grp);
 			++idx;
 			lua_pop(L, 1);
 		}
 	}
-
-	return 0;
-}
-
-static int l_add_log_groups(lua_State *L)
-{
-	return handle_log_groups(L, kr_log_add_group);
-}
-
-static int l_del_log_groups(lua_State *L)
-{
-	return handle_log_groups(L, kr_log_del_group);
-}
-
-static int l_get_log_groups(lua_State *L)
-{
+	// get
 	lua_newtable(L);
+	int i = 1;
 	for (int grp = LOG_GRP_SYSTEM; grp <= LOG_GRP_DEVEL; grp++) {
 		const char *name = kr_log_grp2name(grp);
 		if (kr_fails_assert(name))
 			continue;
 		if (kr_log_group_is_set(grp)) {
-			lua_pushboolean(L, true);
-			lua_setfield(L, -2, name);
+			lua_pushinteger(L, i);
+			lua_pushstring(L, name);
+			lua_settable(L, -3);
+			i++;
 		}
 	}
-
 	return 1;
+bad_call:
+	lua_error_p(L, "takes a table of string groups as parameter or nothing");
 }
 
 char *engine_get_hostname(struct engine *engine) {
@@ -553,12 +541,8 @@ static int init_state(struct engine *engine)
 	lua_setglobal(engine->L, "log_level");
 	lua_pushcfunction(engine->L, l_log_target);
 	lua_setglobal(engine->L, "log_target");
-	lua_pushcfunction(engine->L, l_add_log_groups);
-	lua_setglobal(engine->L, "add_log_groups");
-	lua_pushcfunction(engine->L, l_del_log_groups);
-	lua_setglobal(engine->L, "del_log_groups");
-	lua_pushcfunction(engine->L, l_get_log_groups);
-	lua_setglobal(engine->L, "get_log_groups");
+	lua_pushcfunction(engine->L, l_log_groups);
+	lua_setglobal(engine->L, "log_groups");
 	lua_pushcfunction(engine->L, l_setuser);
 	lua_setglobal(engine->L, "user");
 	lua_pushcfunction(engine->L, l_hint_root_file);
diff --git a/daemon/lua/log.test.lua b/daemon/lua/log.test.lua
index e3926297b2227228cad84e586eb05643b82b7f4c..8809ef8e58366d8731047f3a95b0fac2003328b8 100644
--- a/daemon/lua/log.test.lua
+++ b/daemon/lua/log.test.lua
@@ -1,23 +1,12 @@
 local function test_log_groups()
-	same(get_log_groups()['system'], nil, '"system" log group not logged by default')
-	add_log_groups('system')
-	same(get_log_groups()['system'], true, 'add "system" log group as string')
-	add_log_groups('devel')
-	same(get_log_groups()['devel'], true, 'add another ("devel") log group as string')
-	add_log_groups({ 'cache' })
-	same(get_log_groups()['cache'], true, 'add "cache" log group as table')
-	add_log_groups({ 'io', 'tests' })
-	same(get_log_groups()['io'], true, 'add "io" log group as table with multiple entires')
-	same(get_log_groups()['tests'], true, 'add "tests" log group as table with multiple entries')
-	del_log_groups('system')
-	same(get_log_groups()['system'], nil, 'remove "system" log group as string')
-	del_log_groups({ 'cache' })
-	same(get_log_groups()['cache'], nil, 'remove "cache" log group as table')
-	del_log_groups({ 'io', 'tests' })
-	same(get_log_groups()['io'], nil, 'remove "io" log group as table with multiple entries')
-	same(get_log_groups()['tests'], nil, 'remove "tests" log group as table with multiple entries')
-	boom(add_log_groups, { 'nonexistent' }, "nonexistent group cant't be added")
-	boom(del_log_groups, { 'nonexistent2' }, "nonexistent2 group can't be removed")
+	same(log_groups(), {}, 'no groups are logged by default')
+	same(log_groups({'system'}), {'system'}, 'configure "system" group')
+	same(log_groups({'devel'}), {'devel'}, 'another call overrides previously set groups')
+	same(log_groups({'devel', 'system'}), {'system', 'devel'}, 'configure multiple groups')
+	same(log_groups({}), {}, 'clear groups with empty table')
+	boom(log_groups, { 'string' }, "group argument can't be string")
+	boom(log_groups, { {'nonexistent'} }, "nonexistent group can't be added")
+	boom(log_groups, { 1, 2 }, "group doesn't take multiple arguments")
 end
 
 return {
diff --git a/doc/config-logging-monitoring.rst b/doc/config-logging-monitoring.rst
index 111e1b922d4971fd66b0a9781ef671f086b0f11e..6e6035296343443259d53b1ab8e320e8c28d6db8 100644
--- a/doc/config-logging-monitoring.rst
+++ b/doc/config-logging-monitoring.rst
@@ -18,8 +18,8 @@ For debugging purposes it is possible to use the very verbose ``debug`` level.
 
 In addition to levels, logging is also divided into the
 :ref:`groups <config_log_groups>`. All groups
-are logged by default, but you can enable ``debug`` level for some groups using
-:func:`add_log_groups` function. Other groups are logged to the log level
+are logged by default, but you can enable ``debug`` level for selected groups using
+:func:`log_groups` function. Other groups are logged to the log level
 set by :func:`log_level`.
 
 .. py:function:: log_level([level])
@@ -47,29 +47,24 @@ set by :func:`log_level`.
   :param: string ``'syslog'``, ``'stderr'``, ``'stdout'``
   :return: string Current logging target.
 
-     Knot Resolver logs to standard error stream by default,
-     but typical systemd units change that to ``'syslog'``.
-     That setting logs directly through systemd's facilities
-     (if available) to preserve more meta-data.
+   Knot Resolver logs to standard error stream by default,
+   but typical systemd units change that to ``'syslog'``.
+   That setting logs directly through systemd's facilities
+   (if available) to preserve more meta-data.
 
-.. py:function:: get_log_groups()
+.. py:function:: log_groups([table])
 
-  :return: table :ref:`Groups <config_log_groups>` switched to ``debug`` level.
+  :param: table of string(s) representing ref:`log groups <config_log_groups>`
+  :return: table of string with currently set log groups
 
-  Get :ref:`groups <config_log_groups>` switched to ``debug`` level.
+  Use to turn-on debug logging for the selected groups regardless of the global
+  log level. Calling with no argument lists the currently active log groups. To
+  remove all log groups, call the function with an empty table.
 
-.. py:function:: add_log_groups([string | table])
-
-  :param: :ref:`Groups <config_log_groups>` to switch to ``debug`` level.
-
-  Set debug level for selected :ref:`groups <config_log_groups>`.
-
-.. py:function:: del_log_groups([string | table])
-
-  :param: :ref:`Groups <config_log_groups>` switched to global logging level.
-
-  Switch selected :ref:`groups <config_log_groups>` to logging level set
-  by :func:`log_level`.
+  .. code-block:: lua
+     log_groups({'io', 'tls'}  -- turn on debug logging for io and tls groups
+     log_groups()              -- list active log groups
+     log_groups({})            -- remove all log groups
 
 It is also possible to enable ``debug`` logging level for *a single request*, see chapter :ref:`mod-http-trace`.
 
diff --git a/lib/log.c b/lib/log.c
index 4fd6318046dbd677565823eedbc07a06a69bfefc..ede5b83ec8b78d24856832f5d4ba88517f0a3899 100644
--- a/lib/log.c
+++ b/lib/log.c
@@ -224,17 +224,18 @@ void kr_log_level_set(kr_log_level_t level)
 	return;
 }
 
-void kr_log_add_group(enum kr_log_group group)
+void kr_log_group_add(enum kr_log_group group)
 {
 	kr_log_groups |= (1ULL << group);
 	if (group == LOG_GRP_GNUTLS)
 		kr_gnutls_log_level_set();
 }
 
-void kr_log_del_group(enum kr_log_group group)
+void kr_log_group_reset()
 {
-	kr_log_groups &= (~(1ULL << group));
-	if (group == LOG_GRP_GNUTLS)
+	bool had_gnutls = kr_log_group_is_set(LOG_GRP_GNUTLS);
+	kr_log_groups = 0;
+	if (had_gnutls)
 		kr_gnutls_log_level_set();
 }
 
diff --git a/lib/log.h b/lib/log.h
index d81262f7930875cbf603341c7478f7e924dbd210..3a0e3c7a54cbf9928a75108de0b6f617f0a29727 100644
--- a/lib/log.h
+++ b/lib/log.h
@@ -126,9 +126,9 @@ enum kr_log_group {
 KR_EXPORT
 bool kr_log_group_is_set(enum kr_log_group group);
 KR_EXPORT
-void kr_log_add_group(enum kr_log_group group);
+void kr_log_group_add(enum kr_log_group group);
 KR_EXPORT
-void kr_log_del_group(enum kr_log_group group);
+void kr_log_group_reset();
 KR_EXPORT
 const char *kr_log_grp2name(enum kr_log_group group);
 KR_EXPORT