logging: streamline Logger state and drop redundant methods #35322

pull ryanofsky wants to merge 1 commits into bitcoin:master from ryanofsky:pr/levels changing 14 files +110 −178
  1. ryanofsky commented at 1:55 PM on May 19, 2026: contributor

    Problem: The Logger class currently tracks enabled log messages using three separate fields:

    • m_categories β€” bitmask of enabled log categories
    • m_log_level β€” global log level threshold
    • m_category_log_levels β€” hash map of per-category level overrides

    This representation is confusing and redundant. Bitcoin Core has only ~30 log categories and 2 conditional log levels, so a mutex-protected hash map, a bitset, and a separate global level are unnecessary. The added complexity is also harmful: it makes the code harder to follow, and basic operations like setting a category's level require coordinating multiple fields and method calls.

    Solution: This PR streamlines the Logger class without changing behavior, replacing the three fields with two atomics: one bitmask for each conditional log level (Debug and Trace). Each bitmask records which categories are enabled at that level.

    This allows the following methods to be dropped: EnableCategory, DisableCategory, WillLogCategory, LogLevel, SetLogLevel, GetCategoryMask, AddCategoryLogLevel. The remaining SetCategoryLogLevel and WillLogCategoryLevel methods are now self-contained.

    Behavior note: This is a behavior-preserving refactor with one exception: categories dynamically enabled at runtime through the logging RPC or the kernel btck_logging_* API are now always enabled at Debug level, even if -loglevel=trace or its kernel equivalent was set at startup. This is unlikely to matter in practice. Very little LogTrace logging currently exists, and both APIs are being reworked in bitcoin/bitcoin#34038 and bitcoin/bitcoin#34374. The -loglevel option otherwise continues to work as before when categories are not changed at runtime.

  2. logging: replace m_categories/m_log_level/m_category_log_levels with m_levels array
    Replaces three separate private fields with a single array of atomic
    category bitmasks indexed by log level:
    
      std::array<std::atomic<CategoryMask>, 2> m_levels;
    
    Each category now has a single level threshold (Trace, Debug, or Info),
    eliminating the separate enabled/disabled flag and stored global log level.
    This reduces the number of possible states, removes mutex locking and hash
    lookups from log-level checks, and simplifies the Logger interface by
    dropping multiple enable, disable, and query methods that are no longer
    necessary.
    
    This is mostly a behavior-preserving refactor. The one exception is that
    categories enabled at runtime via the `logging` RPC are now always enabled
    at debug level, regardless of whether `-loglevel=trace` is set. The kernel
    logging API (`btck_logging_*`) has analogous behavior changes for the same
    reason. This is unlikely to matter in practice: very little LogTrace logging
    exists, and both APIs are being reworked (bitcoin/bitcoin#34038 and
    bitcoin/bitcoin#34374). The `-loglevel` setting otherwise continues to work
    exactly as before when categories are not changed at runtime.
    
    Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
    ccd90a7512
  3. DrahtBot commented at 1:55 PM on May 19, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

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

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline for information on the review process. A summary of reviews will appear here.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #35182 (Replace libevent with our own HTTP and socket-handling implementation by pinheadmz)
    • #34806 (refactor: logging: Various API improvements by ajtowns)
    • #34778 (logging: rewrite macros to add ratelimit option, avoid unused strprintf, clarify confusing errors by ryanofsky)
    • #34411 ([POC] Full Libevent removal by fanquake)
    • #34038 (logging: replace -loglevel with -trace, expose trace logging via RPC by ajtowns)
    • #33847 (kernel: Improve logging API by ryanofsky)
    • #30343 (wallet, logging: Replace WalletLogPrintf() with LogInfo() by ryanofsky)
    • #29256 (log, refactor: Allow log macros to accept context arguments by ryanofsky)
    • #17783 (common: Disallow calling IsArgSet() on ALLOW_LIST options by ryanofsky)
    • #17581 (refactor: Remove settings merge reverse precedence code by ryanofsky)
    • #17580 (refactor: Add ALLOW_LIST flags and enforce usage in CheckArgFlags by ryanofsky)
    • #17493 (util: Forbid ambiguous multiple assignments in config file 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.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  4. in src/test/logging_tests.cpp:209 in ccd90a7512
     206 |          ArgsManager args;
     207 | +        args.AddArg("-debug", "...", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
     208 | +        args.AddArg("-debugexclude", "...", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
     209 |          args.AddArg("-loglevel", "...", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
     210 | -        const char* argv_test[] = {"bitcoind", "-loglevel=debug"};
     211 | +        std::array argv_test{"bitcoind", "-debug", "-debugexclude=net", "-loglevel=trace"};
    


    l0rinc commented at 8:58 AM on May 20, 2026:

    There's a single commit here with a lot of changes that don't look closely related. I don't have a strong opinion about logging, but it's obviously important to others so I don't mind reviewing it - though in this current state I can't do that meaningfully. If you think it's worth it, could you please split this into smaller, more focused commits (maybe multiple PRs, since the title already suggests it's hard to properly describe the goal of the changes)?


    ryanofsky commented at 10:24 AM on May 20, 2026:

    re: #35322 (review)

    I don't mind reviewing it - though in this current state I can't do that meaningfully

    Thanks for looking at this! This PR is a fairly small change: only +77/-124 lines excluding tests. But I appreciate it could be pretty difficult to understand if you are coming in cold and don't know where to begin.

    I think it should be possible to split the PR up and I'll experiment with this. (Here is a branch claude made which splits the PR into two commits. pr/levels-explain. It looks ok but I want to verify.)

    If I can give a review tip for the current PR, it would be to first look at the new SetCategoryLogLevel & WillLogCategoryLevel implementations and understand how they use the m_levels array to determine what to log and not log.

    After you understand those two methods, you basically understand the whole PR because it is just updating the rest of the code to call those methods.

    it's hard to properly describe the goal of the changes

    Let me know if I can help with this, I do hope the description is clear.


    l0rinc commented at 8:10 AM on May 21, 2026:

    I think it should be possible to split the PR up and I'll experiment with this.

    I would prefer peeling off each method separately to have some progress during review. For example, getLogCategories() is only used in a single place to decide whether the GUI should show debug information, and that can be split out before the category-mask refactor.

    Reviewing whether the GUI should check for any enabled category mask or for actual Debug-level logging is already non-trivial and worthy of separation.

    <details><summary>isAnyDebugLoggingEnabled</summary>

    diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp
    index ac73158768..969ecb3910 100644
    --- a/src/bitcoind.cpp
    +++ b/src/bitcoind.cpp
    @@ -14,6 +14,7 @@
     #include <compat/compat.h>
     #include <init.h>
     #include <interfaces/chain.h>
    +#include <logging.h>
     #include <interfaces/init.h>
     #include <kernel/context.h>
     #include <node/context.h>
    diff --git a/src/interfaces/node.h b/src/interfaces/node.h
    index f6d8209f07..294b71e0eb 100644
    --- a/src/interfaces/node.h
    +++ b/src/interfaces/node.h
    @@ -7,7 +7,6 @@
     
     #include <common/settings.h>
     #include <consensus/amount.h>
    -#include <logging.h>
     #include <net.h>
     #include <net_types.h>
     #include <netaddress.h>
    @@ -84,8 +83,8 @@ public:
         //! Get exit status.
         virtual int getExitStatus() = 0;
     
    -    // Get log flags.
    -    virtual BCLog::CategoryMask getLogCategories() = 0;
    +    //! Returns true if any debug logging categories are enabled.
    +    virtual bool isAnyDebugLoggingEnabled() = 0;
     
         //! Initialize app dependencies.
         virtual bool baseInitialize() = 0;
    diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp
    index 16db8692a1..347a998faf 100644
    --- a/src/node/interfaces.cpp
    +++ b/src/node/interfaces.cpp
    @@ -112,7 +112,7 @@ public:
         void initParameterInteraction() override { InitParameterInteraction(args()); }
         bilingual_str getWarnings() override { return Join(Assert(m_context->warnings)->GetMessages(), Untranslated("<hr />")); }
         int getExitStatus() override { return Assert(m_context)->exit_status.load(); }
    -    BCLog::CategoryMask getLogCategories() override { return LogInstance().GetCategoryMask(); }
    +    bool isAnyDebugLoggingEnabled() override { return LogInstance().WillLogCategoryLevel(BCLog::ALL, BCLog::Level::Debug); }
         bool baseInitialize() override
         {
             if (!AppInitBasicSetup(args(), Assert(context())->exit_status)) return false;
    diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp
    index d9c97d8529..3e59b01c49 100644
    --- a/src/qt/transactiondesc.cpp
    +++ b/src/qt/transactiondesc.cpp
    @@ -314,7 +314,7 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall
         //
         // Debug view
         //
    -    if (node.getLogCategories() != BCLog::NONE)
    +    if (node.isAnyDebugLoggingEnabled())
         {
             strHTML += "<hr><br>" + tr("Debug information") + "<br><br>";
             for (const CTxIn& txin : wtx.tx->vin)
    diff --git a/src/test/node_init_tests.cpp b/src/test/node_init_tests.cpp
    index 44635dd4aa..fb9bce6cff 100644
    --- a/src/test/node_init_tests.cpp
    +++ b/src/test/node_init_tests.cpp
    @@ -4,6 +4,7 @@
     
     #include <init.h>
     #include <interfaces/init.h>
    +#include <logging.h>
     #include <rpc/server.h>
     
     #include <boost/test/unit_test.hpp>
    

    </details>

  5. l0rinc changes_requested
  6. in src/test/logging_tests.cpp:178 in ccd90a7512
     173 | @@ -185,13 +174,12 @@ BOOST_FIXTURE_TEST_CASE(logging_LogPrintMacros_CategoryName, LogSetup)
     174 |  
     175 |  BOOST_FIXTURE_TEST_CASE(logging_SeverityLevels, LogSetup)
     176 |  {
     177 | -    LogInstance().SetLogLevel(BCLog::Level::Debug);
     178 | -    LogInstance().EnableCategory(BCLog::LogFlags::ALL);
     179 | +    LogInstance().SetCategoryLogLevel(BCLog::LogFlags::ALL, BCLog::Level::Debug);
     180 |      LogInstance().SetCategoryLogLevel(/*category_str=*/"net", /*level_str=*/"info");
    


    l0rinc commented at 8:21 AM on May 21, 2026:

    This string-parsing overload no longer has production callers after startup parsing moves out of Logger. Could we drop it and have the test use the typed setter directly?

    diff --git a/src/logging.cpp b/src/logging.cpp
    index 7c1777adc1..a0fae9badf 100644
    --- a/src/logging.cpp
    +++ b/src/logging.cpp
    @@ -567,18 +567,6 @@ bool BCLog::LogRateLimiter::Stats::Consume(uint64_t bytes)
         return true;
     }
     
    -bool BCLog::Logger::SetCategoryLogLevel(std::string_view category_str, std::string_view level_str)
    -{
    -    const auto flag{GetLogCategory(category_str)};
    -    if (!flag) return false;
    -
    -    const auto level = GetLogLevel(level_str);
    -    if (!level.has_value() || level.value() > BCLog::UNCONDITIONAL_LOG_LEVEL) return false;
    -
    -    SetCategoryLogLevel(*flag, level.value());
    -    return true;
    -}
    -
     bool util::log::ShouldLog(Category category, Level level)
     {
         return LogInstance().WillLogCategoryLevel(static_cast<BCLog::LogFlags>(category), level);
    diff --git a/src/logging.h b/src/logging.h
    index b44349cc55..a543562a1e 100644
    --- a/src/logging.h
    +++ b/src/logging.h
    @@ -230,7 +230,6 @@ namespace BCLog {
             //! Set the log level threshold for a specific category (or ALL categories if
             //! flag == BCLog::ALL). Levels >= UNCONDITIONAL_LOG_LEVEL disable the category.
             void SetCategoryLogLevel(CategoryMask category, Level level);
    -        bool SetCategoryLogLevel(std::string_view category_str, std::string_view level_str);
     
             //! Returns true if a message at the given category and level would be logged.
             bool WillLogCategoryLevel(LogFlags category, Level level) const;
    diff --git a/src/test/logging_tests.cpp b/src/test/logging_tests.cpp
    index 15e3b8ee4b..413df24af0 100644
    --- a/src/test/logging_tests.cpp
    +++ b/src/test/logging_tests.cpp
    @@ -175,7 +175,7 @@ BOOST_FIXTURE_TEST_CASE(logging_LogPrintMacros_CategoryName, LogSetup)
     BOOST_FIXTURE_TEST_CASE(logging_SeverityLevels, LogSetup)
     {
         LogInstance().SetCategoryLogLevel(BCLog::LogFlags::ALL, BCLog::Level::Debug);
    -    LogInstance().SetCategoryLogLevel(/*category_str=*/"net", /*level_str=*/"info");
    +    LogInstance().SetCategoryLogLevel(BCLog::NET, BCLog::Level::Info);
     
         // All categories at Debug level
         LogInfo("info_%s", 1);
    
  7. in src/logging.h:66 in ccd90a7512
      61 | @@ -62,6 +62,8 @@ struct LogCategory {
      62 |  
      63 |  namespace BCLog {
      64 |      constexpr auto DEFAULT_LOG_LEVEL{Level::Debug};
      65 | +    //! Log messages at this severity level and above are always emitted regardless of category settings.
      66 | +    constexpr auto UNCONDITIONAL_LOG_LEVEL{Level::Info};
    


    l0rinc commented at 8:41 AM on May 21, 2026:

    Could this naming cleanup be split before the atomic-mask change? The current code already has one threshold that means "levels at or above this always log". Giving that threshold a shared name first would make the later mask change smaller, and the startup parsing checks could use the same name instead of hardcoding Level::Info.

    diff --git a/src/logging.cpp b/src/logging.cpp
    index 3373063c05..e0c2b2cef2 100644
    --- a/src/logging.cpp
    +++ b/src/logging.cpp
    @@ -21,7 +21,6 @@ using util::Join;
     using util::RemovePrefixView;
     
     const char * const DEFAULT_DEBUGLOGFILE = "debug.log";
    -constexpr auto MAX_USER_SETABLE_SEVERITY_LEVEL{BCLog::Level::Info};
     
     BCLog::Logger& LogInstance()
     {
    @@ -162,7 +161,7 @@ bool BCLog::Logger::WillLogCategoryLevel(BCLog::LogFlags category, BCLog::Level
     {
         // Log messages at Info, Warning and Error level unconditionally, so that
         // important troubleshooting information doesn't get lost.
    -    if (level >= BCLog::Level::Info) return true;
    +    if (level >= BCLog::UNCONDITIONAL_LOG_LEVEL) return true;
     
         if (!WillLogCategory(category)) return false;
     
    @@ -263,7 +262,7 @@ static std::string LogCategoryToStr(BCLog::LogFlags category)
         return it->second;
     }
     
    -static std::optional<BCLog::Level> GetLogLevel(std::string_view level_str)
    +std::optional<BCLog::Level> BCLog::Logger::GetLogLevel(std::string_view level_str)
     {
         if (level_str == "trace") {
             return BCLog::Level::Trace;
    @@ -586,7 +585,7 @@ bool BCLog::LogRateLimiter::Stats::Consume(uint64_t bytes)
     bool BCLog::Logger::SetLogLevel(std::string_view level_str)
     {
         const auto level = GetLogLevel(level_str);
    -    if (!level.has_value() || level.value() > MAX_USER_SETABLE_SEVERITY_LEVEL) return false;
    +    if (!level.has_value() || level.value() > BCLog::UNCONDITIONAL_LOG_LEVEL) return false;
         m_log_level = level.value();
         return true;
     }
    @@ -597,7 +596,7 @@ bool BCLog::Logger::SetCategoryLogLevel(std::string_view category_str, std::stri
         if (!flag) return false;
     
         const auto level = GetLogLevel(level_str);
    -    if (!level.has_value() || level.value() > MAX_USER_SETABLE_SEVERITY_LEVEL) return false;
    +    if (!level.has_value() || level.value() > BCLog::UNCONDITIONAL_LOG_LEVEL) return false;
     
         STDLOCK(m_cs);
         m_category_log_levels[*flag] = level.value();
    diff --git a/src/logging.h b/src/logging.h
    index 67f5d6ef00..041e341d3a 100644
    --- a/src/logging.h
    +++ b/src/logging.h
    @@ -62,6 +62,8 @@ struct LogCategory {
     
     namespace BCLog {
         constexpr auto DEFAULT_LOG_LEVEL{Level::Debug};
    +    //! Log messages at this severity level and above are always emitted regardless of category settings.
    +    constexpr auto UNCONDITIONAL_LOG_LEVEL{Level::Info};
         constexpr size_t DEFAULT_MAX_LOG_BUFFER{1'000'000}; // buffer up to 1MB of log data prior to StartLogging
         constexpr uint64_t RATELIMIT_MAX_BYTES{1_MiB}; // maximum number of bytes per source location that can be logged within the RATELIMIT_WINDOW
         constexpr auto RATELIMIT_WINDOW{1h}; // time window after which log ratelimit stats are reset
    @@ -271,6 +273,8 @@ namespace BCLog {
             //! Returns a string with all user-selectable log levels.
             std::string LogLevelsString() const;
     
    +        //! Returns the log level corresponding to a string, or nullopt if unrecognized.
    +        static std::optional<BCLog::Level> GetLogLevel(std::string_view str);
             //! Returns the string representation of a log level.
             static std::string LogLevelToStr(BCLog::Level level);
    

    And we could probably use this new constant in a few more places:

    diff --git a/src/init/common.cpp b/src/init/common.cpp
    --- a/src/init/common.cpp	(revision c9516e72f9c26f76aacc510e9d39448291fc2994)
    +++ b/src/init/common.cpp	(revision f1467268d1159abb523fd65062d7533100a15217)
    @@ -67,7 +67,7 @@
             if (level_str.find_first_of(':', 3) == std::string::npos) {
                 // user passed a global log level, i.e. -loglevel=<level>
                 const auto level{BCLog::Logger::GetLogLevel(level_str)};
    -            if (!level || *level > BCLog::Level::Info) {
    +            if (!level || *level > BCLog::UNCONDITIONAL_LOG_LEVEL) {
                     return util::Error{strprintf(_("Unsupported global logging level %s=%s. Valid values: %s."), "-loglevel", level_str, LogInstance().LogLevelsString())};
                 }
                 global_level = *level;
    @@ -76,7 +76,7 @@
                 const auto& toks = SplitString(level_str, ':');
                 const auto category{toks.size() == 2 ? BCLog::Logger::GetLogCategory(toks[0]) : std::nullopt};
                 const auto level{toks.size() == 2 ? BCLog::Logger::GetLogLevel(toks[1]) : std::nullopt};
    -            if (!category || !level || *level > BCLog::Level::Info) {
    +            if (!category || !level || *level > BCLog::UNCONDITIONAL_LOG_LEVEL) {
                     return util::Error{strprintf(_("Unsupported category-specific logging level %1$s=%2$s. Expected %1$s=<category>:<loglevel>. Valid categories: %3$s. Valid loglevels: %4$s."), "-loglevel", level_str, LogInstance().LogCategoriesString(), LogInstance().LogLevelsString())};
                 }
                 category_levels.emplace_back(*category, *level);
    
  8. in src/logging.cpp:147 in ccd90a7512
     177 | +}
     178 |  
     179 | -    STDLOCK(m_cs);
     180 | -    const auto it{m_category_log_levels.find(category)};
     181 | -    return level >= (it == m_category_log_levels.end() ? LogLevel() : it->second);
     182 | +BCLog::Logger::LogLevels BCLog::Logger::GetLogLevels() const
    


    l0rinc commented at 11:03 AM on May 21, 2026:

    This looks like save/restore plumbing for tests, not part of the core representation swap. Could it be split into the commit where the test fixtures first need to snapshot the level masks?

    diff --git a/src/logging.cpp b/src/logging.cpp
    index 6e24f2d8d9..3330ad9703 100644
    --- a/src/logging.cpp
    +++ b/src/logging.cpp
    @@ -144,6 +144,15 @@ bool BCLog::Logger::WillLogCategoryLevel(BCLog::LogFlags category, BCLog::Level
         return (m_levels[static_cast<size_t>(level)].load(std::memory_order_relaxed) & category) != 0;
     }
     
    +BCLog::Logger::LogLevels BCLog::Logger::GetLogLevels() const
    +{
    +    LogLevels snapshot;
    +    for (size_t i = 0; i < m_levels.size(); ++i) {
    +        snapshot[i] = m_levels[i].load(std::memory_order_relaxed);
    +    }
    +    return snapshot;
    +}
    +
     bool BCLog::Logger::SetLogLevel(std::string_view level_str)
     {
         const auto level{GetLogLevel(level_str)};
    diff --git a/src/logging.h b/src/logging.h
    index 94eaeeac95..07a20dd1b9 100644
    --- a/src/logging.h
    +++ b/src/logging.h
    @@ -244,10 +244,10 @@ namespace BCLog {
             void AddCategoryLogLevel(LogFlags category, Level level) { SetCategoryLogLevel(category, level); }
     
             Level LogLevel() const { return DEFAULT_LOG_LEVEL; }
    -        void SetLogLevel(Level level) { SetCategoryLogLevel(GetCategoryMask(), level); }
    +        void SetLogLevel(Level level) { SetCategoryLogLevel(GetLogLevels().back(), level); }
             bool SetLogLevel(std::string_view level);
     
    -        CategoryMask GetCategoryMask() const { return m_levels[static_cast<size_t>(Level::Debug)].load(std::memory_order_relaxed); }
    +        CategoryMask GetCategoryMask() const { return GetLogLevels().back(); }
     
             void EnableCategory(LogFlags flag) { SetCategoryLogLevel(flag, BCLog::Level::Debug); }
             bool EnableCategory(std::string_view str);
    @@ -275,6 +275,11 @@ namespace BCLog {
             //! Returns the string representation of a log level.
             static std::string LogLevelToStr(BCLog::Level level);
     
    +        //! Snapshot type for the per-level category bitmask array (non-atomic, for save/restore).
    +        using LogLevels = std::array<CategoryMask, static_cast<size_t>(UNCONDITIONAL_LOG_LEVEL)>;
    +        //! Returns a snapshot of the current per-level category bitmasks.
    +        LogLevels GetLogLevels() const;
    +
             bool DefaultShrinkDebugFile() const { return !WillLogCategoryLevel(BCLog::ALL, BCLog::Level::Debug); }
         };
    
  9. in src/test/logging_tests.cpp:230 in ccd90a7512
     230 |  
     231 | -        const auto& category_levels{LogInstance().CategoryLevels()};
     232 | -        const auto net_it{category_levels.find(BCLog::LogFlags::NET)};
     233 | -        BOOST_REQUIRE(net_it != category_levels.end());
     234 | -        BOOST_CHECK_EQUAL(net_it->second, BCLog::Level::Trace);
     235 | +        BOOST_CHECK(LogInstance().WillLogCategoryLevel(BCLog::NET, BCLog::Level::Trace));
    


    l0rinc commented at 12:55 PM on May 21, 2026:

    Isn’t this a behavior change?

    Previously, setting a category level at startup stored the override, but it had no visible effect unless the category was also enabled with -debug. Here, setting -loglevel=net:trace appears to enable net by itself.

  10. DrahtBot added the label Needs rebase on May 22, 2026
  11. DrahtBot commented at 3:59 AM on May 22, 2026: contributor

    <!--cf906140f33d8803c4a75a2196329ecb-->

    πŸ™ This pull request conflicts with the target branch and needs rebase.

  12. in src/logging.h:151 in ccd90a7512
     155 | -        std::atomic<CategoryMask> m_categories{BCLog::NONE};
     156 | +        //! Per-level log category bitmasks. m_levels[i] holds a mask of log categories
     157 | +        //! whose verbosity threshold is <= Level(i), so that ShouldLog(cat, Level(i))
     158 | +        //! is true iff (m_levels[i] & cat) != 0. Invariant: if m_levels[i] has a
     159 | +        //! category bit set, all m_levels[j] with j > i also have that bit set.
     160 | +        std::array<std::atomic<CategoryMask>, static_cast<size_t>(UNCONDITIONAL_LOG_LEVEL)> m_levels{BCLog::NONE, BCLog::NONE};
    


    l0rinc commented at 11:26 AM on May 22, 2026:

    Could the comment say explicitly that m_levels only stores conditional levels (Trace and Debug), while Info and above bypass this array? I initially read the threshold wording as trying to represent all severities, but the array deliberately stops at UNCONDITIONAL_LOG_LEVEL.

    Seems we have two fundamentally different ways of logging that we're forcing together here. Do you anticipate category-based filtering for all levels in the future?

    I find this design confusing for multiple reasons:

    • Should we guard against setting log levels for unconditional logs, either through the API or internal checks? My understanding is that we're setting dummy values there and just ignoring them later.
    • debug/trace can also be turned on by BCLog::ALL, and they would take a different route than unconditional info/warn/error.
  13. in src/logging.cpp:143 in ccd90a7512
     170 |      // Log messages at Info, Warning and Error level unconditionally, so that
     171 |      // important troubleshooting information doesn't get lost.
     172 | -    if (level >= BCLog::Level::Info) return true;
     173 | -
     174 | -    if (!WillLogCategory(category)) return false;
     175 | +    if (level >= BCLog::UNCONDITIONAL_LOG_LEVEL) return true;
    


    l0rinc commented at 11:51 AM on May 22, 2026:

    This seems like a code smell (even though it was similar before). Wouldn't the categories already return the same value here - Is this an optimization? So if info has BCLog::LogFlags::ALL it will take this branch, but if debug has the same it will take the next line?

  14. in src/logging.cpp:128 in ccd90a7512
     124 | @@ -125,55 +125,39 @@ void BCLog::Logger::DisableLogging()
     125 |      StartLogging();
     126 |  }
     127 |  
     128 | -void BCLog::Logger::EnableCategory(BCLog::LogFlags flag)
     129 | +void BCLog::Logger::SetCategoryLogLevel(CategoryMask category, BCLog::Level level)
    


    l0rinc commented at 1:44 PM on May 22, 2026:

    This loop has dual behavior that needed extra explanation in the header. Maybe we could make it more obvious via code instead:

    void BCLog::Logger::SetCategoryLogLevel(CategoryMask category, BCLog::Level level)
    {
        auto level_it{m_levels.begin()};
        for (size_t i{0}; level_it != m_levels.end() && i < size_t(level); ++i, ++level_it) {
            level_it->fetch_and(~category, std::memory_order_relaxed);
        }
        for (; level_it != m_levels.end(); ++level_it) {
            level_it->fetch_or(category, std::memory_order_relaxed);
        }
    }
    

    But even this seems excessive if indeed the only phase transitions are:

      Set Trace -> Trace mask on, Debug mask on
      Set Debug -> Trace mask off, Debug mask on
      Set Info  -> Trace mask off, Debug mask off
    
  15. in src/rpc/node.cpp:208 in ccd90a7512
     212 | -        if (!success) {
     213 | +        const auto flag{GetLogCategory(cat)};
     214 | +        if (!flag) {
     215 |              throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown logging category " + cat);
     216 |          }
     217 | +        LogInstance().SetCategoryLogLevel(*flag, enable ? BCLog::Level::Debug : BCLog::Level::Info);
    


    l0rinc commented at 2:20 PM on May 22, 2026:

    Could we add a test that goes through this runtime-enable path after startup configured the category at Trace?

    The PR description notes that runtime enables now use Debug even if startup used Trace. A test through the RPC/kernel entry point would make that intentional behavior change easy to verify.

  16. in src/rpc/node.cpp:256 in ccd90a7512
     256 |  
     257 |      // Update libevent logging if BCLog::LIBEVENT has changed.
     258 | -    if (changed_log_categories & BCLog::LIBEVENT) {
     259 | -        UpdateHTTPServerLogging(LogInstance().WillLogCategory(BCLog::LIBEVENT));
     260 | +    if (original_libevent != LogInstance().WillLogCategoryLevel(BCLog::LIBEVENT, BCLog::Level::Debug)) {
     261 | +        UpdateHTTPServerLogging(LogInstance().WillLogCategoryLevel(BCLog::LIBEVENT, BCLog::Level::Debug));
    


    l0rinc commented at 2:30 PM on May 22, 2026:

    We can probably deduplicate this:

        bool is_libevent_enabled{LogInstance().WillLogCategoryLevel(BCLog::LIBEVENT, BCLog::Level::Debug)};
        if (original_libevent != is_libevent_enabled) UpdateHTTPServerLogging(is_libevent_enabled);
    
  17. in src/kernel/bitcoinkernel.cpp:769 in ccd90a7512
     770 | -    if (category == btck_LogCategory_ALL) {
     771 | -        LogInstance().SetLogLevel(get_bclog_level(level));
     772 | -    }
     773 | -
     774 | -    LogInstance().AddCategoryLogLevel(get_bclog_flag(category), get_bclog_level(level));
     775 | +    LogInstance().SetCategoryLogLevel(get_bclog_flag(category), get_bclog_level(level));
    


    l0rinc commented at 2:36 PM on May 22, 2026:

    This looks similar to the earlier concern. Isn't this a behavior change as well? Previously, setting a category level did not enable the category. Now it looks like setting the level also enables it. Should we adjust this to match the old behavior? I see the tests don't always keep the enable-first-set-level after, but maybe we can do that:

    diff --git a/src/test/kernel/test_kernel.cpp b/src/test/kernel/test_kernel.cpp
    --- a/src/test/kernel/test_kernel.cpp	(revision 06d9f340624c880883fba2b03aeab1b74d8812a8)
    +++ b/src/test/kernel/test_kernel.cpp	(revision c9516e72f9c26f76aacc510e9d39448291fc2994)
    @@ -636,6 +636,7 @@
         };
     
         logging_set_options(logging_options);
    +    logging_enable_category(LogCategory::BENCH);
         logging_set_level_category(LogCategory::BENCH, LogLevel::TRACE_LEVEL);
         logging_disable_category(LogCategory::BENCH);
         logging_enable_category(LogCategory::VALIDATION);
    @@ -643,8 +644,8 @@
     
         // Check that connecting, connecting another, and then disconnecting and connecting a logger again works.
         {
    -        logging_set_level_category(LogCategory::KERNEL, LogLevel::TRACE_LEVEL);
             logging_enable_category(LogCategory::KERNEL);
    +        logging_set_level_category(LogCategory::KERNEL, LogLevel::TRACE_LEVEL);
             Logger logger{std::make_unique<TestLog>()};
             Logger logger_2{std::make_unique<TestLog>()};
         }
    
  18. l0rinc changes_requested
  19. l0rinc commented at 2:56 PM on May 22, 2026: contributor

    I tried splitting this locally to make review easier. I still think the current version mixes several separate concerns, and I have questions about whether grouping levels and categories this way is the right model, since it seems to apply a general rule to behavior that is currently quite specific and ad hoc.

    Left comments on splitting the commit into smaller reviewable steps, clarifying or preserving behavior changes, and adding focused tests for intentional changes.

    Main concerns are startup/kernel logging semantics, runtime-enable behavior coverage, and making the new m_levels model easier to reason about.


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-05-22 23:51 UTC

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