diff --git a/daemon/main.c b/daemon/main.c
index fc7715d5cfc47cf7c4ec370bfb9d6f7e8b2dd926..3c300da522973c3c6f81f54ac3a2ff5eae32aafa 100644
--- a/daemon/main.c
+++ b/daemon/main.c
@@ -17,6 +17,7 @@
 #include "lib/dnssec.h"
 #include "lib/dnssec/ta.h"
 #include "lib/resolve.h"
+#include "lib/log.h"
 
 #include <arpa/inet.h>
 #include <getopt.h>
@@ -424,6 +425,7 @@ int main(int argc, char **argv)
 
 	the_args = &the_args_value;
 	args_init(the_args);
+	kr_log_init(LOG_WARNING);
 	int ret = parse_args(argc, argv, the_args);
 	if (ret >= 0) goto cleanup_args;
 
diff --git a/lib/log.c b/lib/log.c
new file mode 100644
index 0000000000000000000000000000000000000000..ad140c1f778efc39ebcf2eca7518520b145919e4
--- /dev/null
+++ b/lib/log.c
@@ -0,0 +1,23 @@
+/*  Copyright (C) 2014-2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+ *  SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <stdio.h>
+#include "lib/log.h"
+
+log_level_t kr_log_level = LOG_CRIT;
+
+void kr_log_fmt(log_level_t level, const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+	if (level <= kr_log_level)
+		vfprintf(stdout, fmt, args);
+	va_end(args);
+}
+
+void kr_log_init(log_level_t level)
+{
+	kr_log_level = level;
+}
+
diff --git a/lib/log.h b/lib/log.h
new file mode 100644
index 0000000000000000000000000000000000000000..1dd1d8c1ae750ee34b7d8d5801561548292d8e4e
--- /dev/null
+++ b/lib/log.h
@@ -0,0 +1,30 @@
+/*  Copyright (C) 2014-2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+ *  SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <stdarg.h>
+#include "lib/defines.h"
+
+typedef enum {
+	LOG_CRIT = 0,
+	LOG_ERR,
+	LOG_WARNING,
+	LOG_NOTICE,
+	LOG_INFO,
+	LOG_DEBUG
+} log_level_t;
+
+KR_EXPORT KR_PRINTF(2) void kr_log_fmt(log_level_t level, const char *fmt, ...);
+KR_EXPORT extern log_level_t kr_log_level;
+void kr_log_init(log_level_t level);
+
+#define kr_log_debug(fmt, ...) kr_log_fmt(LOG_DEBUG, fmt, ## __VA_ARGS__)
+#define kr_log_info(fmt, ...) kr_log_fmt(LOG_INFO, fmt, ## __VA_ARGS__)
+#define kr_log_notice(fmt, ...) kr_log_fmt(LOG_NOTICE, fmt, ## __VA_ARGS__)
+#define kr_log_warning(fmt, ...) kr_log_fmt(LOG_WARNING, fmt, ## __VA_ARGS__)
+#define kr_log_error(fmt, ...) kr_log_fmt(LOG_ERR, fmt, ## __VA_ARGS__)
+#define kr_log_fatal(fmt, ...) kr_log_fmt(LOG_CRIT, fmt, ## __VA_ARGS__)
+
+#define kr_log_deprecate(fmt, ...) kr_log_fmt(LOG_WARNING, "deprecation WARNING: " fmt, ## __VA_ARGS__)
diff --git a/lib/meson.build b/lib/meson.build
index d24d997c79fc90c3d7959138433c1d03408cba16..2098b343b1af39ce99f9062ba9295867ab29371e 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -23,6 +23,7 @@ libkres_src = files([
   'layer/cache.c',
   'layer/iterate.c',
   'layer/validate.c',
+  'log.c',
   'module.c',
   'resolve.c',
   'rplan.c',
@@ -53,6 +54,7 @@ libkres_headers = files([
   'generic/trie.h',
   'layer.h',
   'layer/iterate.h',
+  'log.h',
   'module.h',
   'resolve.h',
   'rplan.h',
diff --git a/lib/utils.c b/lib/utils.c
index 098c53432aba9e974e2eca6b149d87c3addb8bfc..625fdb2e41637869436ae41ecb976f6d49ed434e 100644
--- a/lib/utils.c
+++ b/lib/utils.c
@@ -94,7 +94,6 @@ static inline int u16tostr(uint8_t *dst, uint16_t num)
 /*
  * Cleanup callbacks.
  */
-
 static void kres_gnutls_log(int level, const char *message)
 {
 	kr_log_verbose("[gnutls] (%d) %s", level, message);
diff --git a/lib/utils.h b/lib/utils.h
index b8bfe17224f8f6e12cdc602680c9f63ad092b4cc..bcea6a01ca07df2712611c19d0d381ad09e80fa5 100644
--- a/lib/utils.h
+++ b/lib/utils.h
@@ -26,6 +26,7 @@
 #include "contrib/mempattern.h"
 #include "lib/defines.h"
 #include "lib/generic/array.h"
+#include "lib/log.h"
 
 struct kr_query;
 struct kr_request;
@@ -41,11 +42,6 @@ typedef void (*trace_callback_f)(struct kr_request *request);
  * @param[in] msg Log message. Pointer is not valid after handler returns. */
 typedef void (*trace_log_f)(const struct kr_request *request, const char *msg);
 
-#define kr_log_info printf
-#define kr_log_error(...) fprintf(stderr, ## __VA_ARGS__)
-#define kr_log_critical(...) kr_log_error(__VA_ARGS__)
-#define kr_log_deprecate(...) fprintf(stderr, "deprecation WARNING: " __VA_ARGS__)
-
 /** Assert() but always, regardless of -DNDEBUG.  See also kr_assert(). */
 #define kr_require(expression) do { if (!(expression)) { \
 		kr_fail(true, #expression, __func__, __FILE__, __LINE__); \