kernel: use structured logging and simplify logging interface #34374

pull stickies-v wants to merge 15 commits into bitcoin:master from stickies-v:2026-01/kernel-logging-layering-predicate changing 51 files +809 −459
  1. stickies-v commented at 7:56 pm on January 21, 2026: contributor

    tl;dr: kernel logging is cumbersome. This PR enables structured logging and a much simplified kernel logging interface. Closes #34062.

    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:

    • 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.
    • 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.
    • 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

    Most of the logic in logging.h is not necessary for bitcoinkernel, so we first separate it into util/log.h and logging.h (common), then upgrade the bitcoinkernel logging interface, and finally remove the logging.cpp dependency from kernel entirely.

    1. Separate log generation: Add a minimal util::log::Dispatcher that is used to generate logs, and forwards structured log entries to registered callbacks. No formatting, rate-limiting, or output handling - that’s the consumer’s responsibility. Dispatcher can be configured with a filter predicate (e.g. WillLogCategoryLevel() for node, or a simpler levels-based filter for bitcoinkernel) to avoid unnecessary string formatting and other overhead.
    2. Register BCLog::Logger on Dispatcher: Register a callback on the global Dispatcher to receive entries and handle node-specific concerns (formatting, rate-limiting, file output) without changing most of its logic.
    3. Move logging macros to util/log.h: Allows the entire codebase (including kernel code) to use the same macros without depending on logging.h.
    4. Update bitcoinkernel C API: Replace the string-based callback with btck_LogEntry containing full metadata. Remove btck_LoggingOptions and category enable/disable functions. Add btck_logging_set_min_level() for early filtering.
    5. Move logging.cpp to common: remove it as a kernel dependency entirely.

    Appendix

    bitcoinkernel C logging interface

     0struct btck_LogEntry {
     1    const char* message;       //!< Log message
     2    size_t message_len;        //!< Log message length
     3    const char* file_name;     //!< Source file name
     4    size_t file_name_len;      //!< Source file name length
     5    const char* function_name; //!< Source function name
     6    size_t function_name_len;  //!< Source function name length
     7    const char* thread_name;   //!< Thread name
     8    size_t thread_name_len;    //!< Thread name length
     9    int64_t timestamp_ns;      //!< Timestamp in nanoseconds since epoch
    10    uint32_t line;             //!< Source line number
    11    btck_LogLevel level;       //!< Log level
    12    btck_LogCategory category; //!< Log category
    13};
    14
    15typedef void (*btck_LogCallback)(void* user_data, const btck_LogEntry* entry);
    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);
    
  2. DrahtBot added the label Validation on Jan 21, 2026
  3. DrahtBot commented at 7:56 pm on January 21, 2026: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/34374.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    Approach ACK ryanofsky

    If your review is incorrectly listed, please copy-paste <!–meta-tag:bot-skip–> into the comment that the bot should ignore.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #34448 (ci, iwyu: Fix warnings in src/util and treat them as errors by hebasto)
    • #34436 (refactor + log: use new CeilDiv helper in UTXO Flushing warning by l0rinc)
    • #34435 (refactor: use _MiB/_GiB consistently for byte conversions by l0rinc)
    • #34176 (wallet: crash fix, handle non-writable db directories by furszy)
    • #34164 (validation: add reusable coins view for ConnectBlock by andrewtoth)
    • #34038 (logging: API improvements by ajtowns)
    • #33878 (refactor, docs: Embedded ASMap [2/3]: Refactor asmap internals and add documentation by fjahr)
    • #33847 (kernel: Improve logging API by ryanofsky)
    • #33512 (coins: use number of dirty cache entries in flush warnings/logs by l0rinc)
    • #33324 (blocks: add -reobfuscate-blocks argument to enable (de)obfuscating existing blocks by l0rinc)
    • #31644 (leveldb: show non-default options during init by l0rinc)
    • #30343 (wallet, logging: Replace WalletLogPrintf() with LogInfo() by ryanofsky)
    • #29491 ([EXPERIMENTAL] Schnorr batch verification for blocks by fjahr)
    • #29256 (log, refactor: Allow log macros to accept context arguments by ryanofsky)
    • #28792 (build: Embedded ASMap [3/3]: Build binary dump header file by fjahr)
    • #28690 (build: Introduce internal kernel library by sedited)
    • #26022 (Add util::ResultPtr class by ryanofsky)
    • #25722 (refactor: Use util::Result class for wallet loading by ryanofsky)
    • #25665 (refactor: Add util::Result failure types and ability to merge result values by ryanofsky)
    • #24230 (indexes: Stop using node internal types and locking cs_main, improve sync logic by ryanofsky)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  4. stickies-v force-pushed on Jan 21, 2026
  5. DrahtBot added the label CI failed on Jan 21, 2026
  6. DrahtBot commented at 8:16 pm on January 21, 2026: contributor

    🚧 At least one of the CI tasks failed. Task tidy: https://github.com/bitcoin/bitcoin/actions/runs/21223722631/job/61065673932 LLM reason (✨ experimental): Linker errors due to unresolved detail::InitLogger() from logging.cpp.o, causing the build/test script to fail.

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  7. stickies-v force-pushed on Jan 21, 2026
  8. DrahtBot removed the label CI failed on Jan 21, 2026
  9. ?
    added_to_project_v2 sedited
  10. ?
    project_v2_item_status_changed github-project-automation[bot]
  11. ?
    project_v2_item_status_changed sedited
  12. stickies-v force-pushed on Jan 22, 2026
  13. in src/logging.h:317 in 0d075e3ff6
    319+ * from a raw pointer to a std::unique_ptr.
    320+ *
    321+ * This method of initialization was originally introduced in
    322+ * ee3374234c60aba2cc4c5cd5cac1c0aefc2d817c.
    323+ */
    324+inline BCLog::Logger* g_logger{InitLogger()};
    


    ajtowns commented at 10:48 am on January 23, 2026:

    This seems like a regression. With this approach you can’t assume LogInfo called during global initialization won’t be called prior to InitLogger(), as it now only needs to pass through GetDispatcher() and g_dispatcher().

    As far as I can see, g_dispatcher() should just have independent implementations for bitcoin-kernel vs bitcoind, bitcoin-qt, bitcoin-cli etc.


    stickies-v commented at 4:37 pm on January 23, 2026:

    Yes, I think that’s a much better approach, thanks for the suggestion. I’ll incorpocate that and update shortly.

    As far as I can see, g_dispatcher() should just have independent implementations for bitcoin-kernel vs bitcoind, bitcoin-qt, bitcoin-cli etc.

    I think it should suffice to declare g_dispatcher() in util/log.h, and have one implementation in bitcoinkernel.cpp, and one in logging.cpp (which just calls LogInstance().GetDispatcher()). All non-kernel binaries can (at least for now) just use the same implementation, I think? Lmk if I’m misunderstanding something.


    stickies-v commented at 8:37 pm on January 26, 2026:
    I’ve changed the approach to your suggestion, so marking this as resolved.
  14. in src/util/log.h:79 in 0d075e3ff6 outdated
    73+    //! Type for callbacks invoked for each log entry that passes filtering.
    74+    using Callback = std::function<void(const Entry&)>;
    75+    //! Type for opaque handles returned by RegisterCallback(), used to unregister.
    76+    using CallbackHandle = std::list<Callback>::iterator;
    77+    //! Type for predicates called before logging; returns true if entry should be dispatched.
    78+    using FilterFunc = std::function<bool(Level level, uint64_t category)>;
    


    ajtowns commented at 10:50 am on January 23, 2026:
    Jumping through a std::function when we could just be checking an atomic bitfield doesn’t seem like a good approach.

    stickies-v commented at 2:03 pm on January 23, 2026:

    when we could just be checking an atomic bitfield

    I think that’s only possible if we either 1) have util::log::Dispatcher expose all of BCLog::Logger’s category/level setting logic, or 2) make Dispatcher::WillLog virtual and let each logging sink create its own Dispatcher subtype. Were you thinking of another approach?

    I don’t think either approach is warranted to justify the minimal overhead that std::function incurs. 1) would just make Dispatcher’s scope way too big in my view, and 2) would require inheritance and still have some (although probably less) overhead from the virtual table lookup.


    ajtowns commented at 4:22 am on January 28, 2026:

    I don’t know that Dispatcher needs to make the category/levels modifiable – that seems like something the g_dispatcher implementation could handle via marking them as protected in Dispatcher. They should be directly testable via Dispatcher, so that disabled debug/trace categories are trivial to test, without needing indirection.

    I think your latest implementation (a4e8f3c8d6763a95c1804fc40781b379e6127b66) is dangerously wrong, btw – LogInfo() etc currently always evaluate their arguments, whereas you’re making that conditional on LogAcceptCategory which calls WillLog which is conditional on Enabled().


    stickies-v commented at 12:36 pm on January 28, 2026:

    I think your latest implementation (a4e8f3c) is dangerously wrong, btw – LogInfo() etc currently always evaluate their arguments, whereas you’re making that conditional on LogAcceptCategory which calls WillLog which is conditional on Enabled().

    I didn’t realize developer-notes.md says to only avoid relying on side-effects for LogDebug and LogTrace, I thought this was true for logging in general. I think it’s very brittle that program execution potentially relies on side-effects in logging statements, and I think we should move away from that completely. A cursory search didn’t show me any {Info,Warning,Error} statements that have such side-effects, but I agree that it’s possible and that makes reviewing this PR much more difficult, so I’ll revert that behaviour to minimize scope and leave room for a follow-up. Thanks for pointing this out.

    They should be directly testable via Dispatcher, so that disabled debug/trace categories are trivial to test, without needing indirection.

    I’m not sure I understand your concern here. They are testable via Dispatcher::WillLog(). Is it purely the indirection performance overhead you’re worried about? When running the benches on main and on a4e8f3c8d6763a95c1804fc40781b379e6127b66 (with small change to run this->Enabled() after this->WillLogCategoryLevel(static_cast<BCLog::LogFlags>(cat), level) which I’ll include in next force-push, it doens’t look like we have anything to worry about?

    On master (34a5ecadd7203121b03ac692d22a752d2a364111):

    0|               ns/op |                op/s |    err% |     total | benchmark
    1|--------------------:|--------------------:|--------:|----------:|:----------
    2|            1,837.04 |          544,353.35 |    2.6% |      0.01 | `LogWithDebug`
    3|            1,926.63 |          519,041.49 |    0.2% |      0.01 | `LogWithThreadNames`
    4|                2.81 |      355,468,404.18 |    3.1% |      0.01 | `LogWithoutDebug`
    5|            1,828.42 |          546,919.86 |    1.1% |      0.01 | `LogWithoutThreadNames`
    6|               18.31 |       54,600,726.18 |    1.1% |      0.01 | `LogWithoutWriteToFile`
    

    On a4e8f3c8d6763a95c1804fc40781b379e6127b66 (with small unpushed modification):

    0|               ns/op |                op/s |    err% |     total | benchmark
    1|--------------------:|--------------------:|--------:|----------:|:----------
    2|            1,826.40 |          547,524.98 |    0.7% |      0.01 | `LogWithDebug`
    3|            1,863.31 |          536,680.71 |    1.0% |      0.01 | `LogWithThreadNames`
    4|                4.23 |      236,235,773.60 |    0.4% |      0.01 | `LogWithoutDebug`
    5|            1,800.52 |          555,395.22 |    1.3% |      0.01 | `LogWithoutThreadNames`
    6|               19.92 |       50,188,600.76 |    1.0% |      0.01 | `LogWithoutWriteToFile`
    

    stickies-v commented at 4:09 pm on January 28, 2026:
    Latest force-push (dca56e0237abb485edd1cfe870069e2d75f08e42) reverts the unconditional argument evaluation for Info and higher levels, and adds a unit test to ensure behaviour before and after this PR remains the same. Thanks for raising this. Would be nice to remove logging side-effects entirely in a follow-up (probably just a documentation change and maybe some fix-ups).

    ajtowns commented at 7:12 am on January 31, 2026:

    I didn’t realize developer-notes.md says to only avoid relying on side-effects for LogDebug and LogTrace, I thought this was true for logging in general. I think it’s very brittle that program execution potentially relies on side-effects in logging statements,

    Would be nice to remove logging side-effects entirely in a follow-up

    The easiest way to ensure any potential/accidental side-effects don’t make the behaviour brittle is to always evaluate the logging statements, which is what our code currently does – it’s similar logic as always evaluating assertions. Changing that is not a nice follow-up.

  15. DrahtBot added the label Needs rebase on Jan 23, 2026
  16. stickies-v force-pushed on Jan 26, 2026
  17. stickies-v force-pushed on Jan 26, 2026
  18. DrahtBot added the label CI failed on Jan 26, 2026
  19. DrahtBot commented at 6:51 pm on January 26, 2026: contributor

    🚧 At least one of the CI tasks failed. Task iwyu: https://github.com/bitcoin/bitcoin/actions/runs/21368395725/job/61506234805 LLM reason (✨ experimental): IWYU reported include-what-you-use changes and caused the CI step to fail.

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  20. stickies-v force-pushed on Jan 26, 2026
  21. DrahtBot removed the label Needs rebase on Jan 26, 2026
  22. stickies-v commented at 8:36 pm on January 26, 2026: contributor

    Force-pushed to:

    • address merge conflict from #33822
    • address @ajtowns comment: removed SetDispatcher and GetDispatcher and instead implemented different g_dispatcher functions for logging.cpp and bitcoinkernel.cpp. This required quite a bit of code re-organization, including splitting out the kernel API changes into multiple commits (one for structured logging, one for levels-based filtering) - which I think improve the overall commit organization, but will make range-diff reviews pretty hard for this force-push.
    • improved logging callback handling: checking for nullptr and catching exceptions.
    • use BCLog::Logger::Enabled() in the filter function
    • address drahtbot linter suggestions, incl typo and named args, and a couple of other small touch-ups here and there.
  23. DrahtBot removed the label CI failed on Jan 26, 2026
  24. purpleKarrot commented at 5:32 am on January 27, 2026: contributor

    I doubt that there is an actual use case for multiple logging connections. The logger is global internally and can be handled as global by clients as well (no need for userdata). Consider modelling the logging API after set_terminate in the C++ standard library:

    0typedef void (*btck_LogHandler)(btck_LogEntry const* entry);
    1
    2BITCOINKERNEL_API btck_LogHandler btck_set_log(btck_LogHandler handler);
    

    I also think that clients are able to apply their own filtering, so the btck_logging_set_min_level function can be removed. It is worth noting that this function was never designed to filter per-connection, which was leaking the implementation detail that the logger is global.

  25. in src/kernel/bitcoinkernel.cpp:69 in a4e8f3c8d6 outdated
    68+// static destruction.
    69+static KernelLogger& g_kernel_logger()
    70+{
    71+    static KernelLogger* p{new KernelLogger{}};
    72+    return *p;
    73+}
    


    purpleKarrot commented at 5:47 am on January 27, 2026:
    I interpret that as “We have to use a singleton here because of other singletons”. Now everybody should understand why singletons are viral and therefore should be forbidden. I have zero tolerance for such code.

    stickies-v commented at 11:24 am on January 27, 2026:

    I have zero tolerance for such code.

    I’m sorry to hear. What do you suggest as an alternative?

  26. stickies-v commented at 11:28 am on January 27, 2026: contributor

    I doubt that there is an actual use case for multiple logging connections.

    I think this concern is orthogonal to the changes proposed in this PR, so I think that conversation might be better had e.g. in #30342 ?

    I also think that clients are able to apply their own filtering, so the btck_logging_set_min_level function can be removed.

    They are, but I think it’s reasonable to offer a (minimal) way for clients to avoid paying for overhead (mostly string formatting, lambda evaluation, and expensive operations like memory usage calculations) for logs from levels that will be dropped anyway. Keeping this as-is for now, happy to reconsider if this is a general point of contention.

  27. stickies-v force-pushed on Jan 28, 2026
  28. stickies-v commented at 4:12 pm on January 28, 2026: contributor

    Latest force-push:

    • reverts the unconditional argument evaluation for Info and higher levels, and adds a unit test to ensure behaviour before and after this PR remains the same. Addresses @ajtown’s comment.
    • BCLog::Logger’s callback now first evaluates WillLogCategoryLevel() before verifying the logger is Enabled(), so we can keep the operation lock-free when the category isn’t disabled. No behaviour change, just a small optimization.
  29. DrahtBot added the label CI failed on Jan 28, 2026
  30. DrahtBot commented at 7:03 pm on January 28, 2026: contributor

    🚧 At least one of the CI tasks failed. Task iwyu: https://github.com/bitcoin/bitcoin/actions/runs/21445810981/job/61765316670 LLM reason (✨ experimental): IWYU failure: include-what-you-use detected issues and exited non-zero.

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  31. ryanofsky commented at 8:18 pm on January 28, 2026: contributor

    Code review a4e8f3c8d6763a95c1804fc40781b379e6127b66. Major concept and approach ACK. This seems thoughtfully implemented to be minimally disruptive to existing code while rationalizing the kernel logging API:

    • Providing stuctured log entries instead of strings that need to be parsed to get information like timestamps and source locations.
    • Dropping set_options function with obscure options like log_time_micros.
    • Dropping filtering functions like set_level_category enable_category disable_category that can interact in unexpected ways. Just providing basic level filtering, while leaving space to add other filtering options later.
    • Dropping logging_disable function that needs to be called by applications that don’t use logging. (Note: #33847 also eliminates this).
    • Eliminating global logging functions not tied to logging connections (also addressed by #33847).

    Suggestion to remove extra overhead

    I do agree with AJ that requiring a call through a function pointer #34374 (review) just to detect whether to log something does not seem good. Fortunately, there should be no need to do this for bitcoin core logging, though we may want to preserve it as an option for kernel logging. a4e8f3c8d6763a95c1804fc40781b379e6127b66 already has a statically linked g_dispatcher() function with different implementations in kernel/bitcoinkernel.cpp and logging.cpp, so it would only need to add a second g_dispatcher_should_log hook, defined differently for kernel and non-kernel binaries to avoid std::function overhead:

      0--- a/src/kernel/bitcoinkernel.cpp
      1+++ b/src/kernel/bitcoinkernel.cpp
      2@@ -57,9 +57,7 @@
      3 
      4 struct KernelLogger {
      5     std::atomic<util::log::Level> min_level{util::log::Level::Info};
      6-    util::log::Dispatcher dispatcher{[this](util::log::Level level, uint64_t) {
      7-        return level >= min_level.load(std::memory_order_relaxed);
      8-    }};
      9+    util::log::Dispatcher dispatcher{this};
     10 };
     11 
     12 // Kernel logging state. Intentionally leaked to avoid use-after-destroy if logging occurs during
     13@@ -75,6 +73,12 @@ util::log::Dispatcher& util::log::g_dispatcher()
     14     return g_kernel_logger().dispatcher;
     15 }
     16 
     17+bool util::log::g_dispatcher_should_log(void* context, util::log::Level level, uint64_t category)
     18+{
     19+    KernelLogger& logger{*static_cast<KernelLogger*>(context)};
     20+    return level >= logger.min_level.load(std::memory_order_relaxed);
     21+}
     22+
     23 using kernel::ChainstateRole;
     24 using util::ImmediateTaskRunner;
     25 
     26--- a/src/logging.cpp
     27+++ b/src/logging.cpp
     28@@ -49,6 +49,12 @@ util::log::Dispatcher& util::log::g_dispatcher()
     29     return *LogInstance().GetDispatcher();
     30 }
     31 
     32+bool util::log::g_dispatcher_should_log(void* context, util::log::Level level, uint64_t category)
     33+{
     34+    BCLog::Logger& logger{*static_cast<BCLog::Logger*>(context)};
     35+    return logger.Enabled() && logger.WillLogCategoryLevel(static_cast<BCLog::LogFlags>(category), level);
     36+}
     37+
     38 bool fLogIPs = DEFAULT_LOGIPS;
     39 
     40 static int FileWriteStr(std::string_view str, FILE *fp)
     41@@ -617,10 +623,7 @@ bool BCLog::Logger::SetCategoryLogLevel(std::string_view category_str, std::stri
     42 
     43 BCLog::Logger::Logger()
     44 {
     45-    auto filter_func = [this](util::log::Level level, uint64_t cat) {
     46-        return this->Enabled() && this->WillLogCategoryLevel(static_cast<BCLog::LogFlags>(cat), level);
     47-    };
     48-    m_dispatcher = std::make_unique<util::log::Dispatcher>(filter_func);
     49+    m_dispatcher = std::make_unique<util::log::Dispatcher>(this);
     50 
     51     m_callback_handle = m_dispatcher->RegisterCallback([this](const util::log::Entry& entry) {
     52         LogPrintStr(entry);
     53--- a/src/test/util_log_tests.cpp
     54+++ b/src/test/util_log_tests.cpp
     55@@ -50,7 +50,7 @@ BOOST_AUTO_TEST_CASE(dispatcher_filter)
     56     // Use a simple level filter predicate to only log above a specified min_level
     57     Level min_level;
     58     auto level_filter = [&min_level](Level level, uint64_t) { return level >= min_level; };
     59-    Dispatcher dispatcher{level_filter};
     60+    Dispatcher dispatcher{nullptr, level_filter};
     61     int callback_count{0};
     62     auto handle = dispatcher.RegisterCallback([&](const Entry&) { ++callback_count; });
     63 
     64--- a/src/util/log.h
     65+++ b/src/util/log.h
     66@@ -61,6 +61,9 @@ struct Entry {
     67     bool should_ratelimit{false}; //!< Hint for consumers if this entry should be ratelimited
     68 };
     69 
     70+//! Early custom callback to determine whether log message should be produced.
     71+bool g_dispatcher_should_log(void* context, Level level, uint64_t category);
     72+
     73 /**
     74  * Dispatcher is responsible for producing logs. It forwards structured log entries to one or
     75  * multiple logging sinks (e.g. BCLog::Logger) through its registered callbacks.
     76@@ -79,12 +82,13 @@ public:
     77 
     78     /**
     79      * Construct a Dispatcher.
     80+     * [@param](/bitcoin-bitcoin/contributor/param/)[in] context Optional context passed to g_dispatcher_should_log()
     81      * [@param](/bitcoin-bitcoin/contributor/param/)[in] filter  Optional predicate to filter log entries before dispatch to minimize
     82      *                    overhead (e.g. string formatting) for entries that all of the sinks would
     83      *                    be discarding anyway (e.g. due to a low logging level).
     84      *                    If null, all entries are dispatched (when callbacks are registered).
     85      */
     86-    Dispatcher(FilterFunc filter = nullptr) : m_filter{std::move(filter)} {}
     87+    Dispatcher(void* context = nullptr, FilterFunc filter = nullptr) : m_context{context}, m_filter{std::move(filter)} {}
     88 
     89     /**
     90      * Register a callback to receive log entries.
     91@@ -103,7 +107,7 @@ public:
     92     bool Enabled() const { return m_callback_count.load(std::memory_order_acquire) > 0; }
     93 
     94     /** [@return](/bitcoin-bitcoin/contributor/return/) true if Enabled() and the filter (if set) passes for the provided level and category. */
     95-    bool WillLog(Level level, uint64_t category) const { return Enabled() && (!m_filter || m_filter(level, category)); }
     96+    bool WillLog(Level level, uint64_t category) const { return Enabled() && g_dispatcher_should_log(m_context, level, category) && (!m_filter || m_filter(level, category)); }
     97 
     98     /**
     99      * Format message and dispatch to all registered callbacks. No-op if WillLog() doesn't pass.
    100@@ -148,6 +152,8 @@ private:
    101     std::list<Callback> m_callbacks GUARDED_BY(m_mutex);
    102     //! Lock-free size of m_callbacks for fast checks.
    103     std::atomic<size_t> m_callback_count{0};
    104+    //! Optional context argument set in constructor.
    105+    void* m_context;
    106     //! Optional filter set in constructor; [@see](/bitcoin-bitcoin/contributor/see/) WillLog().
    107     const FilterFunc m_filter{nullptr};
    108 };
    

    (Note: this could be rearranged and simplified more, it is just meant to provide a minimal change to consider.)

    Suggestion to split up PR

    My main feedback on the PR is that it is making a lot of trivial / move-only changes that I think would be better to move to a base PR:

    • Portion of first commit moving SourceLocation from logging.h to util/log.h (base PR could introduce util/log.h)
    • Commit “test: verify log argument evaluation semantics”
    • Portion of third commit moving BCLog::Level from logging.h to util/log.h
    • Commit “kernel: add log level/category name getters”
    • Portion of commit “kernel: implement structured logging C API” adding missing category and level mappings.
    • Commit “move-only: move logging categories to separate header”
    • Commit “logging: move LogAcceptCategory to util” (modulo g_dispatcher call)
    • Commit “logging: move macros to util”

    These are all worthwhile changes on their own, and moving them out of this PR should make it substantially smaller and easier to navigate.

    I plan to review this PR more and also rebase #30342 on top of this. I think the PR’s should complement each other well.

  32. DrahtBot added the label Needs rebase on Jan 30, 2026
  33. stickies-v commented at 12:34 pm on January 30, 2026: contributor

    Thanks for the in-depth review and suggestions, @ryanofsky !

    so it would only need to add a second g_dispatcher_should_log hook, defined differently for kernel and non-kernel binaries to avoid std::function overhead:

    I hadn’t considered this approach yet, and I think it’s great. If we already have a g_should_log() global hook, I think it makes more sense to just scrap the filtering from Dispatcher entirely and let the user manage it completely. This simplifies the code, improves performance (e.g. if filtering is already done before arg evaluation, it doesn’t have to be done again before dispatching) and avoids the pattern of having Dispatcher instances opaquely call a global function, when users would probably expect it to be local.

    My main feedback on the PR is that it is making a lot of trivial / move-only changes that I think would be better to move to a base PR:

    I’ve adopted some of your carve-out suggestions, and identified a few other places to make commits more straightforward. I prefer bundling (manageable) amount of clean-up with an actual functional improvement, so I’ll open up a new PR that introduces the struct-based logging (i.e. the first commits), and convert this (now draft) follow-up PR to be about levels-based logging and removing logging.cpp from kernel.

  34. stickies-v marked this as a draft on Jan 30, 2026
  35. ryanofsky commented at 2:22 pm on January 30, 2026: contributor

    I prefer bundling (manageable) amount of clean-up with an actual functional improvement

    Your approach sounds reasonable. That said, I think it would be nice to first disentangle the move-only changes and make an initial PR that simply moves the log-emitting code from logging.h to util/log.h (logging macros, level and category constants, SourceLocation, LogAcceptCategory). This could likely be merged quickly, making the more substantive changes that follow easier to review.

    This also makes sense conceptually: log-emitting and log-handling are separate concerns, and there’s no real reason code that only emits log messages should need to include a header defining a full log management API.

    Another selfish reason I have for preferring the move-only changes first is that it should significantly reduce conflicts between your changes and my existing PRs, and make it easier to combine my logging work with yours.

  36. stickies-v force-pushed on Jan 30, 2026
  37. move-only: move SourceLocation to separate header
    This is preparatory work for a future commit so SourceLocation can
    be used without requiring logging.h.
    e88433b1db
  38. move-only: move BCLog::Level to util::log::Level
    This is preparatory work for a future commit so Level can
    be used without requiring logging.h.
    856761b8dc
  39. util: add log::Dispatcher for struct-based log dispatch
    Add a minimal, callback-based dispatcher that forwards struct-based log
    entries to registered callbacks. This provides the foundation for
    separating log generation from log consumption, enabling different
    consumers (node sink, kernel C API) to receive the same log entries.
    5b01eaef7a
  40. logging: add Dispatcher to Logger and introduce global dispatcher
    Preparatory work for a future commit where the logging macros are moved
    to the util library. The macros require a single global entry point to
    dispatch log entries.
    
    Make BCLog::Logger aware of util::log::Dispatcher by giving it ownership
    of a Dispatcher instance which it can register callbacks on.
    
    Currently, only a single sink (i.e. BCLog::Logger) exists, but in future
    commits this will change as kernel removes its dependency on logging.cpp
    and introduces its own global logging sink.
    146ad2492a
  41. logging: route log macros through Dispatcher
    Log macros now call util::log::g_dispatcher().Log() directly instead of
    going through LogInstance(). BCLog::Logger registers a callback on its
    Dispatcher to receive entries and handle formatting, rate limiting, and
    output.
    b54139ece1
  42. kernel: add log level/category name getters
    Expose btck_log_level_get_name and btck_log_category_get_name to allow
    library users to get string representations of log levels and categories.
    
    Use constexpr lookup tables as a single source of truth for mapping
    btck types to both BCLog equivalents and string names.
    797cb4a912
  43. kernel: add missing log levels and categories
    Ensure all levels and categories that may be logged by kernel code
    are well defined. Necessary preparation for future work to move
    from string-based to struct-based logging.
    fb6e9eccff
  44. kernel: expose btck_LogEntry
    Preparatory work for a future commit where the btck_LogCallback
    exposes btck_LogEntry instead of a string.
    
    Contains a lot of move-only changes, consider reviewing with
    --color-moved=dimmed-zebra
    38e92d0665
  45. kernel: make logging struct-based
    Replace the string-based logging callback with btck_LogEntry containing
    full metadata (message, source location, timestamp, level, category).
    This lets consumers format and filter logs as needed.
    
    Use the global log dispatcher instead of BCLog::Logger's buffered
    callback system. Messages are discarded when no callbacks are registered.
    3f05248952
  46. kernel: remove unused logging functions
    As logging is struct-based, there is no more need for the user to
    configure how logs are printed, so simplify the interface and remove
    dead code.
    94699eb1da
  47. move-only: move logging categories to separate header
    Logging categories are currently shared between node and kernel. This
    separation allows future commits to completely remove kernel's
    dependency on logging.h.
    
    Also applies clang-format suggestions to the moved code.
    
    Review with --color-moved=dimmed-zebra --color-moved-ws=ignore-all-space
    25ecc28e30
  48. logging: declare LogAcceptCategory in util
    For now, only logging.cpp implements LogAcceptCategory. In future
    commits, bitcoinkernel.cpp will implement its own levels-based
    functionality.
    
    Also add some test coverage on LogAcceptCategory.
    af413a50a9
  49. logging: move macros to util
    Move logging macros to util/log.h so the entire codebase can use the same
    macros. Since (un)conditional logging is a node concept, it is no longer
    mentioned in the util macros. BCLog::Logger still maintains its logic to
    ensure the log level never drops below Info.
    4a7a3233a5
  50. logging: use util/log.h where possible
    Preparation for a future commit where kernel's dependency
    on logging.cpp is removed completely.
    
    Replace usage of logging\.h with util/log\.h where it
    suffices, and fix wrong includes according to iwyu.
    d425d8dd03
  51. kernel: implement levels-based logging C API
    Because logging is now structured, bitcoinkernel users can implement
    their own filtering logic using the btck_LogEntry fields.
    
    Simplify the interface by replacing the category/levels setters with
    a single global btck_logging_set_min_level function.
    
    Implements a kernel-specific g_dispatcher with the levels-based
    filtering, so kernel's dependency on logging.cpp is now removed
    entirely.
    ebe4409b72
  52. stickies-v force-pushed on Jan 30, 2026
  53. DrahtBot removed the label Needs rebase on Jan 30, 2026
  54. ryanofsky commented at 6:04 pm on January 30, 2026: contributor

    re: #34374 (comment)

    I think it would be nice to first disentangle the move-only changes and make an initial PR that simply moves the log-emitting code from logging.h to util/log.h (logging macros, level and category constants, SourceLocation, LogAcceptCategory). This could likely be merged quickly, making the more substantive changes that follow easier to review.

    I implemented this in #34465 (after chatting with stickies in IRC). The changes in the first 4 commits were taken directly from this PR. Only the last commit adds a bit of new code.

  55. DrahtBot removed the label CI failed on Jan 30, 2026
  56. DrahtBot added the label Needs rebase on Jan 31, 2026
  57. DrahtBot commented at 12:26 pm on January 31, 2026: contributor
    🐙 This pull request conflicts with the target branch and needs rebase.

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-02-18 06:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me