At the recent coredev it was suggested that it would be helpful to have logging output be more structured. This would go further than #34374 in that not only the internal metadata (time, category, level, source location, thread name) would be provided in a structured way, but so would per-message data, so that eg:
2026-05-24T01:56:29.650279Z [net] got inv: wtx 0629cda06a87a145a229417ba49b564779c7ddafd3cd4c1b388e99d0a059b845 have peer=58556
might instead be reported (perhaps via a -logjson=1 option) as
{"t": 1779587789650279, "c": "net", "l": "debug", "m": "got inv",
"p": {
"wtx": "0629cda06a87a145a229417ba49b564779c7ddafd3cd4c1b388e99d0a059b845",
"have": 1,
"peer": 58556
}
}
Internally, that might be represented by adding std::vector<KV> kvs; to util::log::Entry, instead of putting the data in the Entry::message field, eg.
Streaming structured log messages like these over zmq, or storing them directly to a database might also be plausible and enable interesting features.
On the consumer side, I think this is relatively straightforward: include a vector<KV> in util::log::Entry where KV has key/value strings. The only complication is if you'd like to be able to distinguish values that are meant to be numbers, and could be represented in json as "foo": 1.23, in which case adding an "intended type" enum could be worthwhile.
On the producer side, I think there are a few options for how we could make this work. For example:
// current code for comparison:
LogDebug(BCLog::NET, "got inv: %s %s peer=%d", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom.GetId());
// parse printf-style format strings ourselves:
LogDebug(BCLog::NET, "got inv wtx=%s have=%d peer=%d", inv.ToString(), fAlreadyHave, pfrom.GetId());
// switch to std::format syntax but parse ourselves:
LogDebug(BCLog::NET, "got inv wtx={} have={:d} peer={:d}", inv.ToString(), fAlreadyHave, pfrom.GetId());
// custom syntax for providing parameters based on user-defined literals:
LogDebug(BCLog::NET, "got inv", "wtx"_LP(inv.ToString()), "have"_LP(fAlreadyHave), "peer"_LP(pfrom.GetId()));
// custom syntax for providing parameters based on macros:
LogDebug(BCLog::NET, "got inv", LP("wtx", inv.ToString()), LP("have", fAlreadyHave), LP("peer", pfrom.GetId()));