Skip to content

RFC: separate kernel logging infrastructure #34062

Description

@stickies-v

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_category and btck_logging_enable_category when they want to produce debug or trace logs. 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.

Approach

I propose we address problems P1, P2 and P3 by layering the logging infrastructure. Roughly, this would mean:

  1. Implement a minimal util::log::Logger class (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.
  2. Refactor BCLog::Logger into BCLog::Sink, which registers a callback on util::log::GetLogger() and handles all node-specific functionality (category filtering, rate limiting, file output, formatting, ...).
  3. Update kernel code to include util/log.h instead of logging.h. The existing logging macros (LogInfo, LogDebug, etc.) continue to work unchanged.
  4. Simplify bitcoinkernel C API to reflect the simpler util::log::Logger interface (structured logging, level-based, ...), we can now completely remove btck_LoggingOptions.
  5. Remove logging.h and logging.cpp from the kernel library completely.

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 kernel, logging: Pass Logger instances to kernel objects #30342 should be much easier with layered kernel logging.
  • B3: reduces friction if/when node migrates to use the public bitcoinkernel interface 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 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

class Logger
{
public:
    using Callback = std::function<void(const Entry&)>;
    using CallbackHandle = std::list<Callback>::iterator;

    //! Register a callback to receive log entries. Returns a handle for unregistration.
    [[nodiscard]] CallbackHandle RegisterCallback(Callback callback);

    //! Unregister a previously registered callback.
    void UnregisterCallback(CallbackHandle handle);

    //! Set the minimum log level. Messages below this level are discarded.
    void SetMinLevel(Level level);

    //! Get the current minimum log level.
    Level GetMinLevel() const;

    //! Returns true if a message at the given level would be logged.
    bool WillLog(Level level) const;

    //! Returns true if any callbacks are registered.
    bool Enabled() const;

    //! Format message and dispatch to all registered callbacks. No-op if minimum log level not met.
    template <typename... Args>
    void Log(Level level, uint64_t category, std::source_location loc, bool should_ratelimit,
             util::ConstevalFormatString<sizeof...(Args)> fmt, const Args&... args);
};

bitcoinkernel C logging interface

typedef void (*btck_LogCallback)(void* user_data, const btck_LogEntry* entry);

struct btck_LogEntry {
    const char* message;       //!< Log message
    size_t message_len;        //!< Log message length
    const char* file_name;     //!< Source file name
    size_t file_name_len;      //!< Source file name length
    const char* function_name; //!< Source function name
    size_t function_name_len;  //!< Source function name length
    const char* thread_name;   //!< Thread name
    size_t thread_name_len;    //!< Thread name length
    int64_t timestamp_ns;      //!< Timestamp in nanoseconds since epoch
    uint32_t line;             //!< Source line number
    btck_LogLevel level;       //!< Log level
    btck_LogCategory category; //!< Log category
};

BITCOINKERNEL_API void btck_logging_set_min_level(btck_LogLevel level);

BITCOINKERNEL_API btck_LoggingConnection* BITCOINKERNEL_WARN_UNUSED_RESULT btck_logging_connection_create(
    btck_LogCallback log_callback,
    void* user_data,
    btck_DestroyCallback user_data_destroy_callback) BITCOINKERNEL_ARG_NONNULL(1);

BITCOINKERNEL_API void btck_logging_connection_destroy(btck_LoggingConnection* logging_connection);

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    Outstanding Issues

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions