tl;dr: kernel logging is cumbersome. I propose we layer logging so we can enable structured logging and a much simplified kernel logging interface without overcomplicating node logging.
Motivation
The bitcoinkernel library (#27587) exposes functionality to interface with the kernel logging. This includes registering callbacks for log statements, level/category filtering, string formatting options, and more.
Kernel logging has a few problems:
- P1: callbacks operate on formatted strings, so users need to parse the string to get the timestamp, category, level, … based on which options are set. This is cumbersome, brittle, and inefficient.
- P2: the filtering interface is not really intuitive, requiring users to call combinations of
btck_logging_set_level_categoryandbtck_logging_enable_categorywhen they want to producedebugortracelogs. The level/category system makes sense for node, because it directly controls what gets written to disk and stdout, and there are quite a lot more categories producing logs. Kernel doesn’t really need this - users control what happens to the logs, and can do any filtering/manipulation in the callback they provide. - P3: the node logging infrastructure has quite a bit more functionality than is necessary for a library, including ratelimiting, log formatting, outputting, buffering, … This introduces unnecessary code and interface complexity.
- P4: it uses the global (node) logger, which goes against the otherwise context local (or stateless) design of the
bitcoinkernelinterface.
Approach
I propose we address problems P1, P2 and P3 by layering the logging infrastructure. Roughly, this would mean:
- Implement a minimal
util::log::Loggerclass (see Appendix) that dispatches structured log entries to registered callbacks. This is a low-level logger that does not perform formatting, rate limiting, or category filtering - it simply dispatches to callbacks. - Refactor
BCLog::LoggerintoBCLog::Sink, which registers a callback onutil::log::GetLogger()and handles all node-specific functionality (category filtering, rate limiting, file output, formatting, …). - Update kernel code to include
util/log.hinstead oflogging.h. The existing logging macros (LogInfo,LogDebug, etc.) continue to work unchanged. - Simplify
bitcoinkernelC API to reflect the simplerutil::log::Loggerinterface (structured logging, level-based, …), we can now completely removebtck_LoggingOptions. - Remove
logging.handlogging.cppfrom the kernel library completely.
Note: I’m leaving problem P4 out of scope for this proposal. I think it already has sufficient merit with a global logger, and I want to keep scope limited. However, I do believe layered logging infrastructure will make implementing P4 easier.
Balance
The main benefits and downsides of this approach:
Benefits:
- B1: a kernel-native structured logging interface that minimizes complexity and makes for a much more ergonomic interface, enabling structured logging instead of string parsing.
- B2: doesn’t complicate node logging purely for the purpose of accommodating diverging requirements for kernel. For example, I suspect projects like #30342 should be much easier with layered kernel logging.
- B3: reduces friction if/when node migrates to use the public
bitcoinkernelinterface by having already completely decoupled logging. - … (what have I missed?)
Downsides:
- D1: levels-based filtering has some performance overhead by formatting all (kernel) debug log strings instead of just the subset of categories specified. Note that this only applies when debug is enabled, and that kernel has a lot less logging than node.
- … (what have I missed?)
Scope
I have implemented this approach in https://github.com/bitcoin/bitcoin/compare/master...stickies-v:bitcoin:2025-12/kernel-logging-layering. Most of the changes are quite straightforward, including refactoring BCLog::Logger to BCLog::Sink, adding tests, and adding a util::log::Logger interface.
Appendix
Interfaces
util::log::Logger
0class Logger
1{
2public:
3 using Callback = std::function<void(const Entry&)>;
4 using CallbackHandle = std::list<Callback>::iterator;
5
6 //! Register a callback to receive log entries. Returns a handle for unregistration.
7 [[nodiscard]] CallbackHandle RegisterCallback(Callback callback);
8
9 //! Unregister a previously registered callback.
10 void UnregisterCallback(CallbackHandle handle);
11
12 //! Set the minimum log level. Messages below this level are discarded.
13 void SetMinLevel(Level level);
14
15 //! Get the current minimum log level.
16 Level GetMinLevel() const;
17
18 //! Returns true if a message at the given level would be logged.
19 bool WillLog(Level level) const;
20
21 //! Returns true if any callbacks are registered.
22 bool Enabled() const;
23
24 //! Format message and dispatch to all registered callbacks. No-op if minimum log level not met.
25 template <typename... Args>
26 void Log(Level level, uint64_t category, std::source_location loc, bool should_ratelimit,
27 util::ConstevalFormatString<sizeof...(Args)> fmt, const Args&... args);
28};
bitcoinkernel C logging interface
0typedef void (*btck_LogCallback)(void* user_data, const btck_LogEntry* entry);
1
2struct btck_LogEntry {
3 const char* message; //!< Log message
4 size_t message_len; //!< Log message length
5 const char* file_name; //!< Source file name
6 size_t file_name_len; //!< Source file name length
7 const char* function_name; //!< Source function name
8 size_t function_name_len; //!< Source function name length
9 const char* thread_name; //!< Thread name
10 size_t thread_name_len; //!< Thread name length
11 int64_t timestamp_ns; //!< Timestamp in nanoseconds since epoch
12 uint32_t line; //!< Source line number
13 btck_LogLevel level; //!< Log level
14 btck_LogCategory category; //!< Log category
15};
16
17BITCOINKERNEL_API void btck_logging_set_min_level(btck_LogLevel level);
18
19BITCOINKERNEL_API btck_LoggingConnection* BITCOINKERNEL_WARN_UNUSED_RESULT btck_logging_connection_create(
20 btck_LogCallback log_callback,
21 void* user_data,
22 btck_DestroyCallback user_data_destroy_callback) BITCOINKERNEL_ARG_NONNULL(1);
23
24BITCOINKERNEL_API void btck_logging_connection_destroy(btck_LoggingConnection* logging_connection);