From 912741e665cb6b3353174668c409a3c95338abbe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20=C5=A0pa=C4=8Dek?= <petr.spacek@nic.cz>
Date: Thu, 12 Mar 2020 13:41:49 +0100
Subject: [PATCH] unify verbose logging and request tracing

Formerly both logs used slightly different formats and duplicated code.
From now on verbose log and request tracing are generated using the same
code.

This required a small change to request trace_log_f definition so it
might affect external modules.
---
 daemon/lua/kres-gen.lua         |  2 +-
 daemon/lua/kres-gen.sh          |  4 +--
 doc/upgrading.rst               |  9 ++++++
 lib/layer.h                     |  6 ++--
 lib/utils.c                     | 44 ++++++++---------------------
 lib/utils.h                     | 24 ++++++----------
 modules/http/http_trace.lua     | 40 +++-----------------------
 modules/policy/policy.lua       | 50 +++++++++++++++++++++++++++++++++
 modules/rebinding/rebinding.lua |  2 +-
 modules/watchdog/watchdog.lua   | 10 ++-----
 10 files changed, 91 insertions(+), 100 deletions(-)

diff --git a/daemon/lua/kres-gen.lua b/daemon/lua/kres-gen.lua
index 973a7cfa0..40588282f 100644
--- a/daemon/lua/kres-gen.lua
+++ b/daemon/lua/kres-gen.lua
@@ -15,7 +15,7 @@ typedef struct knot_mm {
 
 typedef void *(*map_alloc_f)(void *, size_t);
 typedef void (*map_free_f)(void *baton, void *ptr);
-typedef void (*trace_log_f) (const struct kr_query *, const char *, const char *);
+typedef void (*trace_log_f) (const char *);
 typedef void (*trace_callback_f)(struct kr_request *);
 typedef enum {KNOT_ANSWER, KNOT_AUTHORITY, KNOT_ADDITIONAL} knot_section_t;
 typedef struct {
diff --git a/daemon/lua/kres-gen.sh b/daemon/lua/kres-gen.sh
index c213eead9..785110e66 100755
--- a/daemon/lua/kres-gen.sh
+++ b/daemon/lua/kres-gen.sh
@@ -69,7 +69,7 @@ typedef struct knot_mm {
 
 typedef void *(*map_alloc_f)(void *, size_t);
 typedef void (*map_free_f)(void *baton, void *ptr);
-typedef void (*trace_log_f) (const struct kr_query *, const char *, const char *);
+typedef void (*trace_log_f) (const char *);
 typedef void (*trace_callback_f)(struct kr_request *);
 "
 
@@ -190,7 +190,7 @@ ${CDEFS} ${LIBKRES} functions <<-EOF
 # Nameservers
 	kr_nsrep_set
 # Utils
-	kr_log_qverbose_impl
+	kr_qlog
 	kr_make_query
 	kr_pkt_make_auth_header
 	kr_pkt_put
diff --git a/doc/upgrading.rst b/doc/upgrading.rst
index 8f349c526..541b0044c 100644
--- a/doc/upgrading.rst
+++ b/doc/upgrading.rst
@@ -9,6 +9,15 @@ Upgrading
 This section summarizes steps required for upgrade to newer Knot Resolver versions.
 We advise users to also read :ref:`release_notes` for respective versions.
 
+5.0 to 5.1
+==========
+
+Module changes
+--------------
+
+* Custom modules which use :c:type:`trace_log_f` handler in request structure :c:type:`kr_request` need update to simplified handler API.
+
+
 4.x to 5.x
 ==========
 
diff --git a/lib/layer.h b/lib/layer.h
index 128d7a128..35d0064eb 100644
--- a/lib/layer.h
+++ b/lib/layer.h
@@ -18,10 +18,8 @@
 	 */
 	#define QRVERBOSE(_query, _cls, ...) do { \
 		const struct kr_query *_qry = (_query); \
-		if (kr_log_trace_enabled(_qry)) { \
-			kr_log_trace(_qry, (_cls), __VA_ARGS__); \
-		} else if (VERBOSE_STATUS) { \
-			kr_log_qverbose_impl(_qry, (_cls), __VA_ARGS__); \
+		if (VERBOSE_STATUS || kr_log_trace_enabled(_qry)) { \
+			kr_qlog(_qry, (_cls), __VA_ARGS__); \
 		}  \
 	} while (false)
 #endif
diff --git a/lib/utils.c b/lib/utils.c
index 34f80cc5f..c48b2cfd0 100644
--- a/lib/utils.c
+++ b/lib/utils.c
@@ -35,7 +35,6 @@
 /* Always compile-in log symbols, even if disabled. */
 #undef kr_verbose_status
 #undef kr_verbose_set
-#undef kr_log_verbose
 
 /* Logging & debugging */
 bool kr_verbose_status = false;
@@ -121,52 +120,31 @@ bool kr_verbose_set(bool status)
 	return kr_verbose_status;
 }
 
-void kr_log_verbose(const char *fmt, ...)
+void kr_qlog(const struct kr_query *qry, const char *source, const char *fmt, ...)
 {
-	if (unlikely(kr_verbose_status)) {
-		va_list args;
-		va_start(args, fmt);
-		vprintf(fmt, args);
-		va_end(args);
-	}
-}
+	struct mempool *mp = mp_new(512);
 
-void kr_log_qverbose_impl(const struct kr_query *qry, const char *cls, const char *fmt, ...)
-{
 	unsigned ind = 0;
 	for (const struct kr_query *q = qry; q; q = q->parent)
 		ind += 2;
 	uint32_t qry_uid = qry ? qry->uid : 0;
 	uint32_t req_uid = qry && qry->request ? qry->request->uid : 0;
-	/* Simplified kr_log_verbose() calls, first prefix then passed fmt...
-	 * Calling it would take about the same amount of code. */
-	printf("[%05u.%02u][%s] %*s", req_uid, qry_uid, cls, ind, "");
-	va_list args;
-	va_start(args, fmt);
-	vprintf(fmt, args);
-	va_end(args);
-}
-
-bool kr_log_trace(const struct kr_query *query, const char *source, const char *fmt, ...)
-{
-	if (!kr_log_trace_enabled(query)) {
-		return false;
-	}
 
-	auto_free char *msg = NULL;
+	char *msg = mp_printf(mp, "[%05u.%02u][%s] %*s", req_uid, qry_uid, source, ind, "");
 
 	va_list args;
 	va_start(args, fmt);
-	int len = vasprintf(&msg, fmt, args);
+	msg = mp_vprintf_append(mp, msg, fmt, args);
 	va_end(args);
 
-	/* Check formatting result before logging */
-	if (len < 0) {
-		return false;
-	}
+	if (kr_log_trace_enabled(qry))
+		qry->request->trace_log(msg);
+	else
+		/* caller is responsible for detecting verbose mode, use QRVERBOSE() macro */
+		printf("%s", msg);
 
-	query->request->trace_log(query, source, msg);
-	return true;
+	mp_delete(mp);
+	return;
 }
 
 char* kr_strcatdup(unsigned n, ...)
diff --git a/lib/utils.h b/lib/utils.h
index 1a5e3aafd..09b12197c 100644
--- a/lib/utils.h
+++ b/lib/utils.h
@@ -34,8 +34,10 @@ struct kr_request;
 
 /** @brief Callback for request events. */
 typedef void (*trace_callback_f)(struct kr_request *request);
-/** @brief Callback for request logging handler. */
-typedef void (*trace_log_f)(const struct kr_query *query, const char *source, const char *msg);
+/**
+ * @brief Callback for request logging handler.
+ * @param[in] msg Log message. Pointer is not valid after handler returns. */
+typedef void (*trace_log_f)(const char *msg);
 
 #define kr_log_info printf
 #define kr_log_error(...) fprintf(stderr, ## __VA_ARGS__)
@@ -48,14 +50,6 @@ KR_EXPORT extern bool kr_verbose_status;
 /** Set --verbose mode.  Not available if compiled with -DNOVERBOSELOG. */
 KR_EXPORT bool kr_verbose_set(bool status);
 
-/** Log a message if in --verbose mode. */
-KR_EXPORT KR_PRINTF(1)
-void kr_log_verbose(const char *fmt, ...);
-
-/** Utility for QRVERBOSE - use that instead. */
-KR_EXPORT KR_PRINTF(3)
-void kr_log_qverbose_impl(const struct kr_query *qry, const char *cls, const char *fmt, ...);
-
 /**
  * @brief Return true if the query has request log handler installed.
  */
@@ -64,14 +58,14 @@ void kr_log_qverbose_impl(const struct kr_query *qry, const char *cls, const cha
 	false))
 
 /**
- * Log a message through the request log handler.
+ * Log a message through the request log handler or stdout.
+ * Caller is responsible for detecting verbose mode, use QRVERBOSE() macro.
  * @param  query current query
  * @param  source message source
  * @param  fmt message format
- * @return true if the message was logged
  */
-KR_EXPORT KR_PRINTF(3)
-bool kr_log_trace(const struct kr_query *query, const char *source, const char *fmt, ...);
+void kr_qlog(const struct kr_query *query, const char *source, const char *fmt, ...)
+KR_EXPORT KR_PRINTF(3);
 
 #ifdef NOVERBOSELOG
 /* Efficient compile-time disabling of verbose messages. */
@@ -82,7 +76,7 @@ bool kr_log_trace(const struct kr_query *query, const char *source, const char *
 /** Block run in --verbose mode; optimized when not run. */
 #define VERBOSE_STATUS __builtin_expect(kr_verbose_status, false)
 #define WITH_VERBOSE(query) if(__builtin_expect(kr_verbose_status || kr_log_trace_enabled(query), false))
-#define kr_log_verbose if(VERBOSE_STATUS) kr_log_verbose
+#define kr_log_verbose if(VERBOSE_STATUS) printf
 
 #define KR_DNAME_GET_STR(dname_str, dname) \
 	char dname_str[KR_DNAME_STR_MAXLEN]; \
diff --git a/modules/http/http_trace.lua b/modules/http/http_trace.lua
index 2e89aa2d6..7aec8453d 100644
--- a/modules/http/http_trace.lua
+++ b/modules/http/http_trace.lua
@@ -1,33 +1,7 @@
 -- SPDX-License-Identifier: GPL-3.0-or-later
 local ffi = require('ffi')
-local bit = require('bit')
 local condition = require('cqueues.condition')
 
--- Buffer selected record information to a table
-local function add_selected_records(dst, records)
-	for _, rec in ipairs(records) do
-		local rank = rec.rank
-		-- Separate the referral chain verified flag
-		local verified = bit.band(rec.rank, kres.rank.AUTH)
-		if verified then
-			rank = bit.band(rank, bit.bnot(kres.rank.AUTH))
-		end
-		local rank_name = kres.tostring.rank[rank] or tostring(rank)
-		-- Write out each individual RR
-		for rr in tostring(rec.rr):gmatch('[^\n]+\n?') do
-			local row = string.format('cached: %s, rank: %s, record: %s',
-				rec.cached, rank_name:lower(), rr)
-			table.insert(dst, row)
-		end
-	end
-end
-
-local function format_selected_records(header, records)
-	if #records == 0 then return '' end
-	return string.format('%s\n%s\n', header, string.rep('-', #header))
-	       .. table.concat(records, '') .. '\n'
-end
-
 -- Trace execution of DNS queries
 local function serve_trace(h, _)
 	local path = h:get(':path')
@@ -48,21 +22,17 @@ local function serve_trace(h, _)
 
 	-- Create logging handler callback
 	local buffer = {}
-	local buffer_log_cb = ffi.cast('trace_log_f', function (query, source, msg)
-		local message = string.format('[%5s] [%s] %s',
-			query.id, ffi.string(source), ffi.string(msg))
-		table.insert(buffer, message)
+	local buffer_log_cb = ffi.cast('trace_log_f', function (msg)
+		table.insert(buffer, ffi.string(msg))
 	end)
 
 	-- Wait for the result of the query
 	-- Note: We can't do non-blocking write to stream directly from resolve callbacks
 	-- because they don't run inside cqueue.
-	local answers, authority = {}, {}
 	local cond = condition.new()
 	local waiting, done = false, false
 	local finish_cb = ffi.cast('trace_callback_f', function (req)
-		add_selected_records(answers, req.answ_selected)
-		add_selected_records(authority, req.auth_selected)
+		table.insert(buffer, req:selected_tostring())
 		if waiting then
 			cond:signal()
 		end
@@ -90,9 +60,7 @@ local function serve_trace(h, _)
 	finish_cb:free()
 
 	-- Build the result
-	local result = table.concat(buffer, '') .. '\n'
-	               .. format_selected_records('Used records from answer:', answers)
-	               .. format_selected_records('Used records from authority:', authority)
+	local result = table.concat(buffer, '')
 	-- Return buffered data
 	if not done then
 		return 504, result
diff --git a/modules/policy/policy.lua b/modules/policy/policy.lua
index 2cc8f5ed6..6d6ea1c95 100644
--- a/modules/policy/policy.lua
+++ b/modules/policy/policy.lua
@@ -527,6 +527,56 @@ function policy.DENY_MSG(msg)
 		return kres.DONE
 	end
 end
+
+local debug_logline_cb = ffi.cast('trace_log_f', function (msg)
+	-- msg typically ends with newline
+	io.write(ffi.string(msg))
+end)
+
+local debug_logselected_cb = ffi.cast('trace_callback_f', function (req)
+	print(req:selected_tostring())
+end)
+
+function policy.DEBUG(_, req)
+	policy.QTRACE(_, req)
+	req.trace_log = debug_logline_cb
+	req.trace_finish = debug_logselected_cb
+	return
+end
+
+local function request_answered_from_cache(req)
+	local rplan = ffi.C.kr_resolve_plan(req)
+	if tonumber(rplan.pending.len) > 0 then
+		-- an unresolved query, i.e. something is missing from the cache
+		return false
+	end
+	for idx=0, tonumber(rplan.resolved.len) - 1 do
+		if not rplan.resolved.at[idx].flags.CACHED then
+			return false
+		end
+	end
+	return true
+end
+
+function policy.DEBUG_CACHE_MISS(_, req)
+	policy.QTRACE(_, req)
+	local debug_log_buf = {}
+	req.trace_log = ffi.cast('trace_log_f', function (msg)
+		-- stash messages for conditional logging in trace_finish
+		table.insert(debug_log_buf, ffi.string(msg))
+	end)
+
+	req.trace_finish = ffi.cast('trace_callback_f', function (cbreq)
+		if not request_answered_from_cache(cbreq) then
+			log(table.concat(debug_log_buf, ''))
+			log(cbreq:selected_tostring())
+		end
+		req.trace_log:free()
+		req.trace_finish:free()
+	end)
+	return
+end
+
 policy.DENY = policy.DENY_MSG() -- compatibility with < 2.0
 
 function policy.DROP(_, _)
diff --git a/modules/rebinding/rebinding.lua b/modules/rebinding/rebinding.lua
index 3ba9db6d0..9a4cfca49 100644
--- a/modules/rebinding/rebinding.lua
+++ b/modules/rebinding/rebinding.lua
@@ -114,7 +114,7 @@ function M.layer.consume(state, req, pkt)
 	--]]
 	if qry.parent == nil then refuse(req) end
 	if verbose() then
-		ffi.C.kr_log_qverbose_impl(qry, 'rebinding',
+		ffi.C.kr_qlog(qry, 'rebinding',
 		    'blocking blacklisted IP in RR \'%s\' received from IP %s\n',
 		    kres.rr2str(bad_rr),
 		    tostring(kres.sockaddr_t(req.upstream.addr)))
diff --git a/modules/watchdog/watchdog.lua b/modules/watchdog/watchdog.lua
index fc22dfda3..f51da18b4 100644
--- a/modules/watchdog/watchdog.lua
+++ b/modules/watchdog/watchdog.lua
@@ -22,14 +22,8 @@ end
 -- logging
 local function add_tracer(logbuf)
 	return function (req)
-		local function qrylogger(qry, src, msg)
-			local req_uid = (qry and qry.request and qry.request.uid) or 0
-			local qry_uid = (qry and qry.uid) or 0
-			local logline = string.format("[%05u.%02u][%s] %s", req_uid, qry_uid, ffi.string(src), ffi.string(msg))
-			table.insert(logbuf, logline)
-			if verbose() then  -- without this message would be missing in verbose log
-				ffi.C.kr_log_qverbose_impl(qry, src, msg)
-			end
+		local function qrylogger(msg)
+			table.insert(logbuf, ffi.string(msg))
 		end
 		req.trace_log = ffi.cast('trace_log_f', qrylogger)
 	end
-- 
GitLab