{
 "number": 35322,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/35322",
 "title": "logging: streamline Logger state and drop redundant methods",
 "author": "ryanofsky",
 "author_association": "MEMBER",
 "created_at": "2026-05-19T13:55:05Z",
 "updated_at": "2026-09-17T01:20:24Z",
 "age_days": 121,
 "draft": false,
 "labels": [],
 "milestone": null,
 "base": "master",
 "head_sha": "a73d23b7762fe99e4ef1a5055e74093787adf9fb",
 "head_ref": "pr/levels",
 "head_repo": "ryanofsky/bitcoin",
 "head_history": [
  {
   "t": "2026-05-26T20:18:28Z",
   "sha": "a76b13796a59e0c91548882c1a1970943a200153"
  },
  {
   "t": "2026-05-27T17:35:58Z",
   "sha": "30cb9ee48027f4b32ca1c98e2cafc1a6e4388e53"
  },
  {
   "t": "2026-05-29T14:36:32Z",
   "sha": "dd4b33e01ca2441fcb875e0ecb084643ecc50cd8"
  },
  {
   "t": "2026-06-01T23:14:19Z",
   "sha": "70b4bee3cc7392a8189a98d35f382e210ae7c2cc"
  },
  {
   "t": "2026-06-08T13:32:22Z",
   "sha": "be5e736f4976686c3fa0200f35e10ce0b656c351"
  },
  {
   "t": "2026-06-08T18:43:56Z",
   "sha": "395473b97ad229d4c80c45bd5dcfa4c885acf09d"
  },
  {
   "t": "2026-06-24T14:44:35Z",
   "sha": "faf44963b8974d0e08298c03c2c1b0ee804dbf7c"
  },
  {
   "t": "2026-06-24T17:01:21Z",
   "sha": "d03bc91c902acd52bbc9fa52efb26c18740a0995"
  },
  {
   "t": "2026-06-24T20:34:14Z",
   "sha": "42d98b7aad10c4905ecc8a7d0af3749c6ac0bb1f"
  },
  {
   "t": "2026-07-14T00:36:38Z",
   "sha": "addaf64128ed60f1bc147046c8c177aee2d49ed6"
  },
  {
   "t": "2026-08-15T01:17:40Z",
   "sha": "a73d23b7762fe99e4ef1a5055e74093787adf9fb"
  }
 ],
 "additions": 205,
 "deletions": 196,
 "changed_files": 14,
 "commit_count": 14,
 "size_bucket": "L",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {},
   "conflicts": [
    {
     "number": 36167,
     "title": "[RFC] Enable `-Wunused`",
     "author": "fanquake"
    },
    {
     "number": 35387,
     "title": "logging: make trace logging easily usable",
     "author": "ryanofsky"
    },
    {
     "number": 34778,
     "title": "logging: rewrite macros to enforce restrictions at compile-time, improve efficiency and usability",
     "author": "ryanofsky"
    },
    {
     "number": 34775,
     "title": "kernel: make logging callback global",
     "author": "stickies-v"
    },
    {
     "number": 34374,
     "title": "kernel: use struct-based logging and simplify logging interface",
     "author": "stickies-v"
    },
    {
     "number": 34038,
     "title": "logging: replace -loglevel with -trace, expose trace logging via RPC",
     "author": "ajtowns"
    },
    {
     "number": 29256,
     "title": "log, refactor: Allow log macros to accept context arguments",
     "author": "ryanofsky"
    },
    {
     "number": 17783,
     "title": "common: Disallow calling IsArgSet() on ALLOW_LIST options",
     "author": "ryanofsky"
    },
    {
     "number": 17581,
     "title": "refactor: Remove settings merge reverse precedence code",
     "author": "ryanofsky"
    },
    {
     "number": 17580,
     "title": "refactor: Add ALLOW_LIST flags and enforce usage in CheckArgFlags",
     "author": "ryanofsky"
    },
    {
     "number": 17493,
     "title": "util: Forbid ambiguous multiple assignments in config file",
     "author": "ryanofsky"
    }
   ]
  }
 },
 "acks_parsed": {},
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 0,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 2,
  "distinct_reviewers": [
   "l0rinc"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-08-15T01:17:40Z",
  "last_reviewer_activity": "2026-05-22T14:56:43Z",
  "last_reviewer": "l0rinc",
  "author_silent_days": 33,
  "waiting_on_author_days": 0,
  "days_since_update": 0
 },
 "refs": {
  "mentioned": [
   34051,
   34465,
   34806,
   35182,
   35205,
   35387,
   35597
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 34051,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2025-12-14",
    "title": "log: Remove brittle and confusing LogPrintLevel"
   },
   {
    "number": 34465,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-02-07",
    "title": "refactor: separate log generation from log handling"
   },
   {
    "number": 34806,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-05-22",
    "title": "refactor: logging: Various API improvements"
   },
   {
    "number": 35182,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-06-22",
    "title": "Replace libevent with our own HTTP and socket-handling implementation"
   },
   {
    "number": 35205,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-08-04",
    "title": "kernel,node: add `dbcache` setter and clarify defaults"
   },
   {
    "number": 35387,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "logging: make trace logging easily usable"
   },
   {
    "number": 35597,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-06-30",
    "title": "logging: More fully remove libevent log category"
   }
  ],
  "conflicts": [
   36167,
   35387,
   34778,
   34775,
   34374,
   34038,
   29256,
   17783,
   17581,
   17580,
   17493
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/kernel/bitcoinkernel.cpp",
  "src/logging.cpp",
  "src/logging.h",
  "src/rpc/node.cpp",
  "src/test/logging_tests.cpp"
 ],
 "body": "**Problem:** The `Logger` class currently tracks enabled log messages using three separate fields:\n\n- `m_categories` \u2014 bitmask of enabled log categories\n- `m_log_level` \u2014 global log level threshold\n- `m_category_log_levels` \u2014 hash map of per-category level overrides\n\nThis 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.\n\n**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.\n\nThis allows the following methods to be dropped: `EnableCategory`, `DisableCategory`, `WillLogCategory`, `LogLevel`, `SetLogLevel`, `GetCategoryMask`, `AddCategoryLogLevel`. The remaining `SetCategoryLogLevel` and `WillLogCategoryLevel` methods are now self-contained.\n\n**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.",
 "commits": [
  {
   "sha": "22ef791f7efb5b001a5c502aabeac877060677f5",
   "date": "2026-08-04T21:04:06Z",
   "message": "logging test: extend -loglevel test to check behavior with -debug\n\nCurrently there are no tests checking behavior of -loglevel and -debug options\ntogether, even though they need to be used together for -loglevel to have any\nvisible effect. This commit extends -loglevel unit tests to check its behavior\nwith and without -debug.\n\nBackground: Using the -loglevel option alone has no effect on log output,\nbecause it only assigns log levels (info/debug/trace) to categories, but\ndoesn't \"enable\" the categories, It needs to be combined with -debug which does\nenable them to take effect. (There are reasons to want to change this behavior,\nsee bitcoin/bitcoin#35387, but changing it is beyond the scope of this PR.) The\npoint of this commit to extend the test to provide meaningful coverage for\nsubsequent commits.\n\nIn addition to adding -debug arguments, make the following test improvements:\n\n- Add `init::SetLoggingCategories` calls needed to parse -debug options\n\n- Replace const char*[] variables with std::array to avoid needing to hardcode\n  parameter counts.\n\n- Drop unused result variables\n\n- Check WillLogCategoryLevel results to verify -loglevel settings actually\n  have expected impact on what is logged and not logged.\n\n- Replace -loglevel=debug options with -loglevel=trace options. Passing\n  -loglevel=debug is somewhat pointless because the default global log level is\n  already -debug.\n\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
  },
  {
   "sha": "c80e3de7ef087080ceed84eec3f3a4bf83578652",
   "date": "2026-08-04T21:04:06Z",
   "message": "logging test: add behavior assertions to kernel logging_tests\n\nDocuments two behaviors of the kernel logging API that change in the\nupcoming m_levels refactor:\n\n1. logging_set_level_category does not enable the category; a separate\n   logging_enable_category call is required. After the refactor,\n   set_level_category will also enable the category.\n\n2. When a category is enabled after its level has been set,\n   logging_enable_category uses the previously-assigned level (Trace here).\n   After the refactor, logging_enable_category always enables at Debug\n   regardless of any previously-assigned level.\n\nAdds #include <logging.h> to access LogInstance().WillLogCategoryLevel()\nfor the assertions.\n\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
  },
  {
   "sha": "8cd1ee83d13419046318c0da6e48c828925cff61",
   "date": "2026-08-04T21:04:06Z",
   "message": "logging: replace m_categories/m_log_level/m_category_log_levels with m_levels array\n\nReplaces three separate private fields with a single array of atomic\ncategory bitmasks indexed by log level:\n\n  std::array<std::atomic<CategoryMask>, 2> m_levels;\n\nEach category now has a single level threshold (Trace, Debug, or Info),\neliminating the separate enabled/disabled flag and stored global log level.\nThis reduces the number of possible states, removes mutex locking and hash\nlookups from log-level checks, and simplifies the Logger interface by\ndropping multiple enable, disable, and query methods that are no longer\nnecessary.\n\nThis is mostly a behavior-preserving refactor. The one exception is that\ncategories enabled at runtime via the `logging` RPC are now always enabled\nat debug level, regardless of whether `-loglevel=trace` is set. The kernel\nlogging API (`btck_logging_*`) has analogous behavior changes for the same\nreason. This is unlikely to matter in practice: very little LogTrace logging\nexists, and both APIs are being reworked (bitcoin/bitcoin#34038 and\nbitcoin/bitcoin#34374). The `-loglevel` setting otherwise continues to work\nexactly as before when categories are not changed at runtime.\n\nOld methods (EnableCategory, DisableCategory, WillLogCategory,\nGetCategoryMask, CategoryLevels, AddCategoryLogLevel, SetCategoryLogLevel\noverloads, LogLevel, SetLogLevel, DefaultShrinkDebugFile) are kept as\nbackward-compatible wrappers and will be removed in subsequent commits.\n\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
  },
  {
   "sha": "c98dddb1b5c0046584dbcf7a12c22b085086cc9c",
   "date": "2026-08-04T21:04:06Z",
   "message": "logging refactor: replace WillLogCategory with WillLogCategoryLevel\n\nThis commit does not change behavior in any way.\n\nCo-authored-by: l0rinc <pap.lorinc@gmail.com>\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
  },
  {
   "sha": "ab03208e26602694d934022eef068b92a7058e0c",
   "date": "2026-08-04T21:04:06Z",
   "message": "logging refactor: replace DefaultShrinkDebugFile with WillLogCategoryLevel\n\nThis commit does not change behavior in any way.\n\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
  },
  {
   "sha": "0bf449b79e307747c367da3d7f7cbfb2afbcd9d2",
   "date": "2026-08-04T21:04:06Z",
   "message": "logging refactor: rename node::getLogCategories() to isAnyDebugLoggingEnabled()\n\nThis commit does not change behavior in any way.\n\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
  },
  {
   "sha": "6eb56040903da2bb5f7ac352b3bd9d754db984a0",
   "date": "2026-08-04T21:04:06Z",
   "message": "logging refactor: add GetLogLevels/SetLogLevels; use them in tests\n\nThis commit does not change behavior in any way.\n\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
  },
  {
   "sha": "248c266441eaf3a3ba3150bee2c33e75d587e574",
   "date": "2026-08-04T21:04:06Z",
   "message": "logging refactor: replace EnableCategory with SetCategoryLogLevel\n\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
  },
  {
   "sha": "798cd192443eec965e50bbad8872526e0c38a135",
   "date": "2026-08-04T21:04:06Z",
   "message": "logging refactor: replace DisableCategory with SetCategoryLogLevel\n\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
  },
  {
   "sha": "dc605abd72a5debac811f3c58984f9bb13981582",
   "date": "2026-08-04T21:04:06Z",
   "message": "logging refactor: replace AddCategoryLogLevel with SetCategoryLogLevel\n\nThis commit does not change behavior in any way.\n\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
  },
  {
   "sha": "2831f3b9514574aa4839c44b29bdfce83e02a332",
   "date": "2026-08-04T21:04:06Z",
   "message": "logging refactor: drop EnableCategory/DisableCategory\n\nThis commit does not change behavior in any way.\n\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
  },
  {
   "sha": "651869fccf6d945e8311d5f4c7f17a3ff03a168c",
   "date": "2026-08-04T21:04:06Z",
   "message": "logging refactor: replace SetLogLevel with SetCategoryLogLevel\n\nThis commit does not change behavior in any way.\n\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
  },
  {
   "sha": "779b04f5063210d77ad19f507d011cbfa8ae9864",
   "date": "2026-08-04T21:04:06Z",
   "message": "logging refactor: drop SetCategoryLogLevel(string_view)\n\nThis commit does not change behavior in any way.\n\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
  },
  {
   "sha": "a73d23b7762fe99e4ef1a5055e74093787adf9fb",
   "date": "2026-08-04T21:04:06Z",
   "message": "logging refactor: drop LogLevel\n\nThis commit does not change behavior in any way.\n\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
  }
 ],
 "timeline": [
  {
   "t": "2026-05-20T08:58:56Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/test/logging_tests.cpp",
   "commit": "a76b13796a59e0c91548882c1a1970943a200153",
   "in_reply_to": null,
   "text": "There's a single commit here with a lot of changes that don't look closely related.\nI 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.\nIf 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)?"
  },
  {
   "t": "2026-05-20T08:59:42Z",
   "kind": "review",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "state": "CHANGES_REQUESTED",
   "commit": "ccd90a75125586f4f5a03fda5477738b10745d20",
   "text": ""
  },
  {
   "t": "2026-05-20T10:24:11Z",
   "kind": "review_comment",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "path": "src/test/logging_tests.cpp",
   "commit": "a76b13796a59e0c91548882c1a1970943a200153",
   "in_reply_to": 3272563675,
   "text": "re: https://github.com/bitcoin/bitcoin/pull/35322#discussion_r3272563675\n\n[quoted text omitted]\nThanks 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.\n\nI 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](https://github.com/ryanofsky/bitcoin/commits/pr/levels-explain). It looks ok but I want to verify.)\n\nIf I can give a review tip for the current PR, it would be to first look at the new [`SetCategoryLogLevel & WillLogCategoryLevel`](https://github.com/ryanofsky/bitcoin/blob/ccd90a75125586f4f5a03fda5477738b10745d20/src/logging.cpp#L128-L145) implementations and understand how they use the [m_levels](https://github.com/ryanofsky/bitcoin/blob/ccd90a75125586f4f5a03fda5477738b10745d20/src/logging.h#L147-L151) array to determine [what to log](https://github.com/ryanofsky/bitcoin/blob/ccd90a75125586f4f5a03fda5477738b10745d20/src/logging.h#L230-L236) and not log.\n\nAfter 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.\n\n[quoted text omitted]\nLet me know if I can help with this, I do hope the description is clear."
  },
  {
   "t": "2026-05-21T08:10:13Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/test/logging_tests.cpp",
   "commit": "a76b13796a59e0c91548882c1a1970943a200153",
   "in_reply_to": 3272563675,
   "text": "[quoted text omitted]\n\nI would prefer peeling off each method separately to have some progress during review.\nFor 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.\n\nReviewing whether the GUI should check for any enabled category mask or for actual Debug-level logging is already non-trivial and worthy of separation.\n\nisAnyDebugLoggingEnabled\n\n```patch\ndiff --git a/src/bitcoind.cpp b/src/bitcoind.cpp\nindex ac73158768..969ecb3910 100644\n--- a/src/bitcoind.cpp\n+++ b/src/bitcoind.cpp\n@@ -14,6 +14,7 @@\n #include <compat/compat.h>\n #include <init.h>\n #include <interfaces/chain.h>\n+#include <logging.h>\n #include <interfaces/init.h>\n #include <kernel/context.h>\n #include <node/context.h>\ndiff --git a/src/interfaces/node.h b/src/interfaces/node.h\nindex f6d8209f07..294b71e0eb 100644\n--- a/src/interfaces/node.h\n+++ b/src/interfaces/node.h\n@@ -7,7 +7,6 @@\n\n #include <common/settings.h>\n #include <consensus/amount.h>\n-#include <logging.h>\n #include <net.h>\n #include <net_types.h>\n #include <netaddress.h>\n@@ -84,8 +83,8 @@ public:\n     //! Get exit status.\n     virtual int getExitStatus() = 0;\n\n-    // Get log flags.\n-    virtual BCLog::CategoryMask getLogCategories() = 0;\n+    //! Returns true if any debug logging categories are enabled.\n+    virtual bool isAnyDebugLoggingEnabled() = 0;\n\n     //! Initialize app dependencies.\n     virtual bool baseInitialize() = 0;\ndiff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp\nindex 16db8692a1..347a998faf 100644\n--- a/src/node/interfaces.cpp\n+++ b/src/node/interfaces.cpp\n@@ -112,7 +112,7 @@ public:\n     void initParameterInteraction() override { InitParameterInteraction(args()); }\n     bilingual_str getWarnings() override { return Join(Assert(m_context->warnings)->GetMessages(), Untranslated(\"<hr />\")); }\n     int getExitStatus() override { return Assert(m_context)->exit_status.load(); }\n-    BCLog::CategoryMask getLogCategories() override { return LogInstance().GetCategoryMask(); }\n+    bool isAnyDebugLoggingEnabled() override { return LogInstance().WillLogCategoryLevel(BCLog::ALL, BCLog::Level::Debug); }\n     bool baseInitialize() override\n     {\n         if (!AppInitBasicSetup(args(), Assert(context())->exit_status)) return false;\ndiff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp\nindex d9c97d8529..3e59b01c49 100644\n--- a/src/qt/transactiondesc.cpp\n+++ b/src/qt/transactiondesc.cpp\n@@ -314,7 +314,7 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall\n     //\n     // Debug view\n     //\n-    if (node.getLogCategories() != BCLog::NONE)\n+    if (node.isAnyDebugLoggingEnabled())\n     {\n         strHTML += \"<hr>\" + tr(\"Debug information\") + \"\";\n         for (const CTxIn& txin : wtx.tx->vin)\ndiff --git a/src/test/node_init_tests.cpp b/src/test/node_init_tests.cpp\nindex 44635dd4aa..fb9bce6cff 100644\n--- a/src/test/node_init_tests.cpp\n+++ b/src/test/node_init_tests.cpp\n@@ -4,6 +4,7 @@\n\n #include <init.h>\n #include <interfaces/init.h>\n+#include <logging.h>\n #include <rpc/server.h>\n\n #include <boost/test/unit_test.hpp>\n```"
  },
  {
   "t": "2026-05-21T08:21:42Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/test/logging_tests.cpp",
   "commit": "a76b13796a59e0c91548882c1a1970943a200153",
   "in_reply_to": null,
   "text": "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?\n\n```patch\ndiff --git a/src/logging.cpp b/src/logging.cpp\nindex 7c1777adc1..a0fae9badf 100644\n--- a/src/logging.cpp\n+++ b/src/logging.cpp\n@@ -567,18 +567,6 @@ bool BCLog::LogRateLimiter::Stats::Consume(uint64_t bytes)\n     return true;\n }\n\n-bool BCLog::Logger::SetCategoryLogLevel(std::string_view category_str, std::string_view level_str)\n-{\n-    const auto flag{GetLogCategory(category_str)};\n-    if (!flag) return false;\n-\n-    const auto level = GetLogLevel(level_str);\n-    if (!level.has_value() || level.value() > BCLog::UNCONDITIONAL_LOG_LEVEL) return false;\n-\n-    SetCategoryLogLevel(*flag, level.value());\n-    return true;\n-}\n-\n bool util::log::ShouldLog(Category category, Level level)\n {\n     return LogInstance().WillLogCategoryLevel(static_cast<BCLog::LogFlags>(category), level);\ndiff --git a/src/logging.h b/src/logging.h\nindex b44349cc55..a543562a1e 100644\n--- a/src/logging.h\n+++ b/src/logging.h\n@@ -230,7 +230,6 @@ namespace BCLog {\n         //! Set the log level threshold for a specific category (or ALL categories if\n         //! flag == BCLog::ALL). Levels >= UNCONDITIONAL_LOG_LEVEL disable the category.\n         void SetCategoryLogLevel(CategoryMask category, Level level);\n-        bool SetCategoryLogLevel(std::string_view category_str, std::string_view level_str);\n\n         //! Returns true if a message at the given category and level would be logged.\n         bool WillLogCategoryLevel(LogFlags category, Level level) const;\ndiff --git a/src/test/logging_tests.cpp b/src/test/logging_tests.cpp\nindex 15e3b8ee4b..413df24af0 100644\n--- a/src/test/logging_tests.cpp\n+++ b/src/test/logging_tests.cpp\n@@ -175,7 +175,7 @@ BOOST_FIXTURE_TEST_CASE(logging_LogPrintMacros_CategoryName, LogSetup)\n BOOST_FIXTURE_TEST_CASE(logging_SeverityLevels, LogSetup)\n {\n     LogInstance().SetCategoryLogLevel(BCLog::LogFlags::ALL, BCLog::Level::Debug);\n-    LogInstance().SetCategoryLogLevel(/*category_str=*/\"net\", /*level_str=*/\"info\");\n+    LogInstance().SetCategoryLogLevel(BCLog::NET, BCLog::Level::Info);\n\n     // All categories at Debug level\n     LogInfo(\"info_%s\", 1);\n```"
  },
  {
   "t": "2026-05-21T08:41:56Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/logging.h",
   "commit": "a76b13796a59e0c91548882c1a1970943a200153",
   "in_reply_to": null,
   "text": "Could this naming cleanup be split before the atomic-mask change?\nThe 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`.\n\n```patch\ndiff --git a/src/logging.cpp b/src/logging.cpp\nindex 3373063c05..e0c2b2cef2 100644\n--- a/src/logging.cpp\n+++ b/src/logging.cpp\n@@ -21,7 +21,6 @@ using util::Join;\n using util::RemovePrefixView;\n\n const char * const DEFAULT_DEBUGLOGFILE = \"debug.log\";\n-constexpr auto MAX_USER_SETABLE_SEVERITY_LEVEL{BCLog::Level::Info};\n\n BCLog::Logger& LogInstance()\n {\n@@ -162,7 +161,7 @@ bool BCLog::Logger::WillLogCategoryLevel(BCLog::LogFlags category, BCLog::Level\n {\n     // Log messages at Info, Warning and Error level unconditionally, so that\n     // important troubleshooting information doesn't get lost.\n-    if (level >= BCLog::Level::Info) return true;\n+    if (level >= BCLog::UNCONDITIONAL_LOG_LEVEL) return true;\n\n     if (!WillLogCategory(category)) return false;\n\n@@ -263,7 +262,7 @@ static std::string LogCategoryToStr(BCLog::LogFlags category)\n     return it->second;\n }\n\n-static std::optional<BCLog::Level> GetLogLevel(std::string_view level_str)\n+std::optional<BCLog::Level> BCLog::Logger::GetLogLevel(std::string_view level_str)\n {\n     if (level_str == \"trace\") {\n         return BCLog::Level::Trace;\n@@ -586,7 +585,7 @@ bool BCLog::LogRateLimiter::Stats::Consume(uint64_t bytes)\n bool BCLog::Logger::SetLogLevel(std::string_view level_str)\n {\n     const auto level = GetLogLevel(level_str);\n-    if (!level.has_value() || level.value() > MAX_USER_SETABLE_SEVERITY_LEVEL) return false;\n+    if (!level.has_value() || level.value() > BCLog::UNCONDITIONAL_LOG_LEVEL) return false;\n     m_log_level = level.value();\n     return true;\n }\n@@ -597,7 +596,7 @@ bool BCLog::Logger::SetCategoryLogLevel(std::string_view category_str, std::stri\n     if (!flag) return false;\n\n     const auto level = GetLogLevel(level_str);\n-    if (!level.has_value() || level.value() > MAX_USER_SETABLE_SEVERITY_LEVEL) return false;\n+    if (!level.has_value() || level.value() > BCLog::UNCONDITIONAL_LOG_LEVEL) return false;\n\n     STDLOCK(m_cs);\n     m_category_log_levels[*flag] = level.value();\ndiff --git a/src/logging.h b/src/logging.h\nindex 67f5d6ef00..041e341d3a 100644\n--- a/src/logging.h\n+++ b/src/logging.h\n@@ -62,6 +62,8 @@ struct LogCategory {\n\n namespace BCLog {\n     constexpr auto DEFAULT_LOG_LEVEL{Level::Debug};\n+    //! Log messages at this severity level and above are always emitted regardless of category settings.\n+    constexpr auto UNCONDITIONAL_LOG_LEVEL{Level::Info};\n     constexpr size_t DEFAULT_MAX_LOG_BUFFER{1'000'000}; // buffer up to 1MB of log data prior to StartLogging\n     constexpr uint64_t RATELIMIT_MAX_BYTES{1_MiB}; // maximum number of bytes per source location that can be logged within the RATELIMIT_WINDOW\n     constexpr auto RATELIMIT_WINDOW{1h}; // time window after which log ratelimit stats are reset\n@@ -271,6 +273,8 @@ namespace BCLog {\n         //! Returns a string with all user-selectable log levels.\n         std::string LogLevelsString() const;\n\n+        //! Returns the log level corresponding to a string, or nullopt if unrecognized.\n+        static std::optional<BCLog::Level> GetLogLevel(std::string_view str);\n         //! Returns the string representation of a log level.\n         static std::string LogLevelToStr(BCLog::Level level);\n ```\nAnd we could probably use this new constant in a few more places:\n```patch\ndiff --git a/src/init/common.cpp b/src/init/common.cpp\n--- a/src/init/common.cpp\t(revision c9516e72f9c26f76aacc510e9d39448291fc2994)\n+++ b/src/init/common.cpp\t(revision f1467268d1159abb523fd65062d7533100a15217)\n@@ -67,7 +67,7 @@\n         if (level_str.find_first_of(':', 3) == std::string::npos) {\n             // user passed a global log level, i.e. -loglevel=<level>\n             const auto level{BCLog::Logger::GetLogLevel(level_str)};\n-            if (!level || *level > BCLog::Level::Info) {\n+            if (!level || *level > BCLog::UNCONDITIONAL_LOG_LEVEL) {\n                 return util::Error{strprintf(_(\"Unsupported global logging level %s=%s. Valid values: %s.\"), \"-loglevel\", level_str, LogInstance().LogLevelsString())};\n             }\n             global_level = *level;\n@@ -76,7 +76,7 @@\n             const auto& toks = SplitString(level_str, ':');\n             const auto category{toks.size() == 2 ? BCLog::Logger::GetLogCategory(toks[0]) : std::nullopt};\n             const auto level{toks.size() == 2 ? BCLog::Logger::GetLogLevel(toks[1]) : std::nullopt};\n-            if (!category || !level || *level > BCLog::Level::Info) {\n+            if (!category || !level || *level > BCLog::UNCONDITIONAL_LOG_LEVEL) {\n                 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())};\n             }\n             category_levels.emplace_back(*category, *level);\n```"
  },
  {
   "t": "2026-05-21T11:03:06Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/logging.cpp",
   "commit": "a73d23b7762fe99e4ef1a5055e74093787adf9fb",
   "in_reply_to": null,
   "text": "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?\n\n```patch\ndiff --git a/src/logging.cpp b/src/logging.cpp\nindex 6e24f2d8d9..3330ad9703 100644\n--- a/src/logging.cpp\n+++ b/src/logging.cpp\n@@ -144,6 +144,15 @@ bool BCLog::Logger::WillLogCategoryLevel(BCLog::LogFlags category, BCLog::Level\n     return (m_levels[static_cast<size_t>(level)].load(std::memory_order_relaxed) & category) != 0;\n }\n\n+BCLog::Logger::LogLevels BCLog::Logger::GetLogLevels() const\n+{\n+    LogLevels snapshot;\n+    for (size_t i = 0; i < m_levels.size(); ++i) {\n+        snapshot[i] = m_levels[i].load(std::memory_order_relaxed);\n+    }\n+    return snapshot;\n+}\n+\n bool BCLog::Logger::SetLogLevel(std::string_view level_str)\n {\n     const auto level{GetLogLevel(level_str)};\ndiff --git a/src/logging.h b/src/logging.h\nindex 94eaeeac95..07a20dd1b9 100644\n--- a/src/logging.h\n+++ b/src/logging.h\n@@ -244,10 +244,10 @@ namespace BCLog {\n         void AddCategoryLogLevel(LogFlags category, Level level) { SetCategoryLogLevel(category, level); }\n\n         Level LogLevel() const { return DEFAULT_LOG_LEVEL; }\n-        void SetLogLevel(Level level) { SetCategoryLogLevel(GetCategoryMask(), level); }\n+        void SetLogLevel(Level level) { SetCategoryLogLevel(GetLogLevels().back(), level); }\n         bool SetLogLevel(std::string_view level);\n\n-        CategoryMask GetCategoryMask() const { return m_levels[static_cast<size_t>(Level::Debug)].load(std::memory_order_relaxed); }\n+        CategoryMask GetCategoryMask() const { return GetLogLevels().back(); }\n\n         void EnableCategory(LogFlags flag) { SetCategoryLogLevel(flag, BCLog::Level::Debug); }\n         bool EnableCategory(std::string_view str);\n@@ -275,6 +275,11 @@ namespace BCLog {\n         //! Returns the string representation of a log level.\n         static std::string LogLevelToStr(BCLog::Level level);\n\n+        //! Snapshot type for the per-level category bitmask array (non-atomic, for save/restore).\n+        using LogLevels = std::array<CategoryMask, static_cast<size_t>(UNCONDITIONAL_LOG_LEVEL)>;\n+        //! Returns a snapshot of the current per-level category bitmasks.\n+        LogLevels GetLogLevels() const;\n+\n         bool DefaultShrinkDebugFile() const { return !WillLogCategoryLevel(BCLog::ALL, BCLog::Level::Debug); }\n     };\n```"
  },
  {
   "t": "2026-05-21T12:55:21Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/test/logging_tests.cpp",
   "commit": "a76b13796a59e0c91548882c1a1970943a200153",
   "in_reply_to": null,
   "text": "Isn\u2019t this a behavior change?\n\nPreviously, setting a category level at startup stored the override, but it had no visible effect unless the category was also enabled with -debug.\nHere, setting `-loglevel=net:trace` appears to enable net by itself."
  },
  {
   "t": "2026-05-22T11:26:59Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/logging.h",
   "commit": "a76b13796a59e0c91548882c1a1970943a200153",
   "in_reply_to": null,
   "text": "Could the comment say explicitly that `m_levels` only stores conditional levels (`Trace` and `Debug`), while `Info` and above bypass this array?\nI initially read the threshold wording as trying to represent all severities, but the array deliberately stops at `UNCONDITIONAL_LOG_LEVEL`.\n\nSeems 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?\n\nI find this design confusing for multiple reasons:\n* 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.\n* `debug`/`trace` can also be turned on by `BCLog::ALL`, and they would take a different route than unconditional `info`/`warn`/`error`."
  },
  {
   "t": "2026-05-22T11:51:14Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/logging.cpp",
   "commit": "a76b13796a59e0c91548882c1a1970943a200153",
   "in_reply_to": null,
   "text": "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?"
  },
  {
   "t": "2026-05-22T13:44:08Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/logging.cpp",
   "commit": "a73d23b7762fe99e4ef1a5055e74093787adf9fb",
   "in_reply_to": null,
   "text": "This loop has dual behavior that needed extra explanation in the header. Maybe we could make it more obvious via code instead:\n```C++\nvoid BCLog::Logger::SetCategoryLogLevel(CategoryMask category, BCLog::Level level)\n{\n    auto level_it{m_levels.begin()};\n    for (size_t i{0}; level_it != m_levels.end() && i < size_t(level); ++i, ++level_it) {\n        level_it->fetch_and(~category, std::memory_order_relaxed);\n    }\n    for (; level_it != m_levels.end(); ++level_it) {\n        level_it->fetch_or(category, std::memory_order_relaxed);\n    }\n}\n```\n\nBut even this seems excessive if indeed the only phase transitions are:\n```\n  Set Trace -> Trace mask on, Debug mask on\n  Set Debug -> Trace mask off, Debug mask on\n  Set Info  -> Trace mask off, Debug mask off\n```"
  },
  {
   "t": "2026-05-22T14:20:08Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/rpc/node.cpp",
   "commit": "a73d23b7762fe99e4ef1a5055e74093787adf9fb",
   "in_reply_to": null,
   "text": "Could we add a test that goes through this runtime-enable path after startup configured the category at Trace?\n\nThe 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."
  },
  {
   "t": "2026-05-22T14:30:13Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/rpc/node.cpp",
   "commit": "a76b13796a59e0c91548882c1a1970943a200153",
   "in_reply_to": null,
   "text": "We can probably deduplicate this:\n```suggestion\n    bool is_libevent_enabled{LogInstance().WillLogCategoryLevel(BCLog::LIBEVENT, BCLog::Level::Debug)};\n    if (original_libevent != is_libevent_enabled) UpdateHTTPServerLogging(is_libevent_enabled);\n```"
  },
  {
   "t": "2026-05-22T14:36:36Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/kernel/bitcoinkernel.cpp",
   "commit": "a73d23b7762fe99e4ef1a5055e74093787adf9fb",
   "in_reply_to": null,
   "text": "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?\nI see the tests don't always keep the enable-first-set-level after, but maybe we can do that:\n```patch\ndiff --git a/src/test/kernel/test_kernel.cpp b/src/test/kernel/test_kernel.cpp\n--- a/src/test/kernel/test_kernel.cpp\t(revision 06d9f340624c880883fba2b03aeab1b74d8812a8)\n+++ b/src/test/kernel/test_kernel.cpp\t(revision c9516e72f9c26f76aacc510e9d39448291fc2994)\n@@ -636,6 +636,7 @@\n     };\n\n     logging_set_options(logging_options);\n+    logging_enable_category(LogCategory::BENCH);\n     logging_set_level_category(LogCategory::BENCH, LogLevel::TRACE_LEVEL);\n     logging_disable_category(LogCategory::BENCH);\n     logging_enable_category(LogCategory::VALIDATION);\n@@ -643,8 +644,8 @@\n\n     // Check that connecting, connecting another, and then disconnecting and connecting a logger again works.\n     {\n-        logging_set_level_category(LogCategory::KERNEL, LogLevel::TRACE_LEVEL);\n         logging_enable_category(LogCategory::KERNEL);\n+        logging_set_level_category(LogCategory::KERNEL, LogLevel::TRACE_LEVEL);\n         Logger logger{std::make_unique<TestLog>()};\n         Logger logger_2{std::make_unique<TestLog>()};\n     }\n```"
  },
  {
   "t": "2026-05-22T14:56:43Z",
   "kind": "review",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "state": "CHANGES_REQUESTED",
   "commit": "ccd90a75125586f4f5a03fda5477738b10745d20",
   "text": "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.\n\nLeft comments on splitting the commit into smaller reviewable steps, clarifying or preserving behavior changes, and adding focused tests for intentional changes.\n\nMain concerns are startup/kernel logging semantics, runtime-enable behavior coverage, and making the new `m_levels` model easier to reason about."
  },
  {
   "t": "2026-05-26T20:18:28Z",
   "kind": "force_push",
   "who": "ryanofsky",
   "commit": "a76b13796a59e0c91548882c1a1970943a200153"
  },
  {
   "t": "2026-05-26T20:20:26Z",
   "kind": "comment",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "text": "Rebased ccd90a75125586f4f5a03fda5477738b10745d20 -> a76b13796a59e0c91548882c1a1970943a200153 ([`pr/levels.1`](https://github.com/ryanofsky/bitcoin/commits/pr/levels.1) -> [`pr/levels.2`](https://github.com/ryanofsky/bitcoin/commits/pr/levels.2), [compare](https://github.com/ryanofsky/bitcoin/compare/pr/levels.1-rebase..pr/levels.2)) due to conflict with #34806"
  },
  {
   "t": "2026-05-27T16:14:08Z",
   "kind": "review_comment",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "path": "src/test/logging_tests.cpp",
   "commit": "a76b13796a59e0c91548882c1a1970943a200153",
   "in_reply_to": 3272563675,
   "text": "re: https://github.com/bitcoin/bitcoin/pull/35322#discussion_r3279624744\n\n[quoted text omitted]\nMakes sense, and latest push removes each method in separate commits. Splitting this up does add some churn to the PR, because there is now temporary code added in the first commit that is removed in later commits. So some reviewers may prefer to just look at the overall diff, but probably splitting is an improvement."
  },
  {
   "t": "2026-05-27T16:15:45Z",
   "kind": "review_comment",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "path": "src/test/logging_tests.cpp",
   "commit": "a76b13796a59e0c91548882c1a1970943a200153",
   "in_reply_to": 3279693120,
   "text": "re: https://github.com/bitcoin/bitcoin/pull/35322#discussion_r3279693120\n\n[quoted text omitted]\nNice find! This lets another logger method to be dropped. Implemented this change."
  },
  {
   "t": "2026-05-27T16:23:08Z",
   "kind": "review_comment",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "path": "src/logging.h",
   "commit": "a76b13796a59e0c91548882c1a1970943a200153",
   "in_reply_to": 3279807731,
   "text": "re: https://github.com/bitcoin/bitcoin/pull/35322#discussion_r3279807731\n\n[quoted text omitted]\nFollowing your other suggestion https://github.com/bitcoin/bitcoin/pull/35322#discussion_r3279693120 this -loglevel parsing code is now dropped from the logger class.\n\n[quoted text omitted]\nThis is probably worth improving still. I'm happy to follow any preferences expressed in this PR or #35387 which improves the `-loglevel` implementation specifically. For now the code is using Info just to be explicit about the behavior it is trying to preserve."
  },
  {
   "t": "2026-05-27T16:23:55Z",
   "kind": "review_comment",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "path": "src/logging.cpp",
   "commit": "a73d23b7762fe99e4ef1a5055e74093787adf9fb",
   "in_reply_to": 3280651475,
   "text": "re: https://github.com/bitcoin/bitcoin/pull/35322#discussion_r3280651475\n\n[quoted text omitted]\nYes, this should be done now."
  },
  {
   "t": "2026-05-27T16:27:46Z",
   "kind": "review_comment",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "path": "src/test/logging_tests.cpp",
   "commit": "a76b13796a59e0c91548882c1a1970943a200153",
   "in_reply_to": 3281289661,
   "text": "re: https://github.com/bitcoin/bitcoin/pull/35322#discussion_r3281289661\n\n[quoted text omitted]\nYes, nice catch! I do think this behavior change is good, but it wasn't intended to be part of this PR. It's fixed by making `-loglevel` only update the levels of previously enabled categories.\n\nEdit: To follow up, I should add a test to make sure a standalone `-loglevel` option without `-debug` continues to be ignored."
  },
  {
   "t": "2026-05-27T16:48:14Z",
   "kind": "review_comment",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "path": "src/logging.h",
   "commit": "a76b13796a59e0c91548882c1a1970943a200153",
   "in_reply_to": 3288013785,
   "text": "re: https://github.com/bitcoin/bitcoin/pull/35322#discussion_r3288013785\n\n[quoted text omitted]\nYes, changed array declaration and added comment to make this clearer.\n\n[quoted text omitted]\nI don't think that's right. `Logger` accepts a category and severity level for each log message and it provides a `SetCategoryLogLevel(category, level)` method to set a minimum severity level for each category and control what is logged.\n\nThe fact that `Logger` always logs the highest severity messages seems like an internal implementation detail, not a reason to complicate its API. Or if the API should be more complicated, it would be good to know why, or what the goal would be.\n\n[quoted text omitted]\nNo, I think it makes sense to have some levels that can't be turned off or filtered by category. (IMO it would best if to introduce a new critical level for this purpose, so lower-severity errors, warnings, and status messages could be distinguished from each other. But this is just a weak preference and should be unrelated to this PR.)\n\n[quoted text omitted]\nI don't think I have an opinion about this. Having something like separate enums for conditional and unconditional levels could be reasonable, but I'd want to know what the goal would be, or what types of bugs this could prevent.\n\n[quoted text omitted]\nI'm not sure I understand the concern. Inside the `Logger` class, all messages are taking the same route. It is intentional to provide a way to turn on debug and tracing for all categories. The decision to make it difficult to turn off info/warning/error logs doesn't seem like it should affect this."
  },
  {
   "t": "2026-05-27T16:58:27Z",
   "kind": "review_comment",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "path": "src/logging.cpp",
   "commit": "a76b13796a59e0c91548882c1a1970943a200153",
   "in_reply_to": 3288138282,
   "text": "re: https://github.com/bitcoin/bitcoin/pull/35322#discussion_r3288138282\n\n[quoted text omitted]\nYeah agree this could be better, should be improved now but let me know."
  },
  {
   "t": "2026-05-27T17:05:25Z",
   "kind": "review_comment",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "path": "src/logging.cpp",
   "commit": "a73d23b7762fe99e4ef1a5055e74093787adf9fb",
   "in_reply_to": 3288806782,
   "text": "re: https://github.com/bitcoin/bitcoin/pull/35322#discussion_r3288806782\n\n[quoted text omitted]\nI added a comment here to be clearer, and I'm open to feedback on different ways to write this, but I feel like the current version is actually simpler than the suggestions (of using two `for` loops or hardcoding severity constants). The current code just loops through each level and setting category bits to 1 if the level is enabled, 0 if disabled."
  },
  {
   "t": "2026-05-27T17:07:39Z",
   "kind": "review_comment",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "path": "src/rpc/node.cpp",
   "commit": "a73d23b7762fe99e4ef1a5055e74093787adf9fb",
   "in_reply_to": 3289031399,
   "text": "re: https://github.com/bitcoin/bitcoin/pull/35322#discussion_r3289031399\n\n[quoted text omitted]\nYes this is a good idea. Current PR doesn't add new tests but it should and I will follow up with this."
  },
  {
   "t": "2026-05-27T17:07:59Z",
   "kind": "review_comment",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "path": "src/rpc/node.cpp",
   "commit": "a76b13796a59e0c91548882c1a1970943a200153",
   "in_reply_to": 3289092797,
   "text": "re: https://github.com/bitcoin/bitcoin/pull/35322#discussion_r3289092797\n\n[quoted text omitted]\nMake sense, appied suggestion"
  },
  {
   "t": "2026-05-27T17:16:55Z",
   "kind": "review_comment",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "path": "src/kernel/bitcoinkernel.cpp",
   "commit": "a73d23b7762fe99e4ef1a5055e74093787adf9fb",
   "in_reply_to": 3289129041,
   "text": "re: https://github.com/bitcoin/bitcoin/pull/35322#discussion_r3289129041\n\n[quoted text omitted]\nYes this analogous to the other change you pointed out https://github.com/bitcoin/bitcoin/pull/35322#discussion_r3281289661.\n\nIn this case I think the new behavior is probably better as there is no point to kernel code setting a level for a category that is ignored because the category is enabled. (For `bitcoind` it might have made sense previously because `logging` RPC is more limited than the kernel API.)\n\nBut this behavior change should be documented and tested, so I will try to add your test suggestion and follow up."
  },
  {
   "t": "2026-05-27T17:35:58Z",
   "kind": "force_push",
   "who": "ryanofsky",
   "commit": "30cb9ee48027f4b32ca1c98e2cafc1a6e4388e53"
  },
  {
   "t": "2026-05-27T17:39:33Z",
   "kind": "review",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "30cb9ee48027f4b32ca1c98e2cafc1a6e4388e53",
   "text": "Thanks for the review! PR is now split up instead of being a single commit and I implemented most suggestions, but need to follow up on the ideas for adding more tests. (EDIT: tests are added now)\n\nUpdated a76b13796a59e0c91548882c1a1970943a200153 -> 30cb9ee48027f4b32ca1c98e2cafc1a6e4388e53 ([`pr/levels.2`](https://github.com/ryanofsky/bitcoin/commits/pr/levels.2) -> [`pr/levels.3`](https://github.com/ryanofsky/bitcoin/commits/pr/levels.3), [compare](https://github.com/ryanofsky/bitcoin/compare/pr/levels.2..pr/levels.3)) with review suggestions\n\nUpdated 30cb9ee48027f4b32ca1c98e2cafc1a6e4388e53 -> dd4b33e01ca2441fcb875e0ecb084643ecc50cd8 ([`pr/levels.3`](https://github.com/ryanofsky/bitcoin/commits/pr/levels.3) -> [`pr/levels.4`](https://github.com/ryanofsky/bitcoin/commits/pr/levels.4), [compare](https://github.com/ryanofsky/bitcoin/compare/pr/levels.3..pr/levels.4)) adding new tests commits before changes to cover more behaviors, also making other small cleanups\n\nUpdated dd4b33e01ca2441fcb875e0ecb084643ecc50cd8 -> 70b4bee3cc7392a8189a98d35f382e210ae7c2cc ([`pr/levels.4`](https://github.com/ryanofsky/bitcoin/commits/pr/levels.4) -> [`pr/levels.5`](https://github.com/ryanofsky/bitcoin/commits/pr/levels.5), [compare](https://github.com/ryanofsky/bitcoin/compare/pr/levels.4..pr/levels.5)) to fix CI link errors: LogInstance() hidden by REDUCE_EXPORTS=ON; override visibility for logging.cpp in kernel build https://github.com/bitcoin/bitcoin/actions/runs/26643504769/job/78546254795\n\nUpdated 70b4bee3cc7392a8189a98d35f382e210ae7c2cc -> be5e736f4976686c3fa0200f35e10ce0b656c351 ([`pr/levels.5`](https://github.com/ryanofsky/bitcoin/commits/pr/levels.5) -> [`pr/levels.6`](https://github.com/ryanofsky/bitcoin/commits/pr/levels.6), [compare](https://github.com/ryanofsky/bitcoin/compare/pr/levels.5..pr/levels.6)) to fix Windows DLL link errors in test_kernel https://github.com/bitcoin/bitcoin/actions/runs/26787798064/job/78967518054\n\nUpdated be5e736f4976686c3fa0200f35e10ce0b656c351 -> 395473b97ad229d4c80c45bd5dcfa4c885acf09d ([`pr/levels.6`](https://github.com/ryanofsky/bitcoin/commits/pr/levels.6) -> [`pr/levels.7`](https://github.com/ryanofsky/bitcoin/commits/pr/levels.7), [compare](https://github.com/ryanofsky/bitcoin/compare/pr/levels.6..pr/levels.7)) to fix Windows cross CI: --export-dynamic-symbol and -fvisibility=default are ELF-only; use .def file to export logging symbols from DLL https://github.com/bitcoin/bitcoin/actions/runs/27141219495/job/80118211985\n\nRebased 395473b97ad229d4c80c45bd5dcfa4c885acf09d -> faf44963b8974d0e08298c03c2c1b0ee804dbf7c ([`pr/levels.7`](https://github.com/ryanofsky/bitcoin/commits/pr/levels.7) -> [`pr/levels.8`](https://github.com/ryanofsky/bitcoin/commits/pr/levels.8), [compare](https://github.com/ryanofsky/bitcoin/compare/pr/levels.7-rebase..pr/levels.8)) due to conflicts with #35182 and #34051\n\nSquashed faf44963b8974d0e08298c03c2c1b0ee804dbf7c -> d03bc91c902acd52bbc9fa52efb26c18740a0995 ([`pr/levels.8`](https://github.com/ryanofsky/bitcoin/commits/pr/levels.8) -> [`pr/levels.9`](https://github.com/ryanofsky/bitcoin/commits/pr/levels.9), [compare](https://github.com/ryanofsky/bitcoin/compare/pr/levels.8..pr/levels.9)) to fix CI: hardcoded category count in logging_Conf test (29 != 30) in intermediate commit failing in ancestor commits job https://github.com/bitcoin/bitcoin/actions/runs/28106965454/job/83223705517 due to LIBEVENT removal\n\nSquashed d03bc91c902acd52bbc9fa52efb26c18740a0995 -> 42d98b7aad10c4905ecc8a7d0af3749c6ac0bb1f ([`pr/levels.9`](https://github.com/ryanofsky/bitcoin/commits/pr/levels.9) -> [`pr/levels.10`](https://github.com/ryanofsky/bitcoin/commits/pr/levels.10), [compare](https://github.com/ryanofsky/bitcoin/compare/pr/levels.9..pr/levels.10)) to fix ancestor commits job https://github.com/bitcoin/bitcoin/actions/runs/28115551994/job/83254010233 failing due to intermediate commits accidentally squashed and moved in previous push\n\nRebased 42d98b7aad10c4905ecc8a7d0af3749c6ac0bb1f -> addaf64128ed60f1bc147046c8c177aee2d49ed6 ([`pr/levels.10`](https://github.com/ryanofsky/bitcoin/commits/pr/levels.10) -> [`pr/levels.11`](https://github.com/ryanofsky/bitcoin/commits/pr/levels.11), [compare](https://github.com/ryanofsky/bitcoin/compare/pr/levels.10-rebase..pr/levels.11)) due to conflicts with #34465 and #35597\n\nRebased addaf64128ed60f1bc147046c8c177aee2d49ed6 -> a73d23b7762fe99e4ef1a5055e74093787adf9fb ([`pr/levels.11`](https://github.com/ryanofsky/bitcoin/commits/pr/levels.11) -> [`pr/levels.12`](https://github.com/ryanofsky/bitcoin/commits/pr/levels.12), [compare](https://github.com/ryanofsky/bitcoin/compare/pr/levels.11-rebase..pr/levels.12)) due to conflict with #35205"
  },
  {
   "t": "2026-05-29T14:36:32Z",
   "kind": "force_push",
   "who": "ryanofsky",
   "commit": "dd4b33e01ca2441fcb875e0ecb084643ecc50cd8"
  },
  {
   "t": "2026-06-01T23:14:19Z",
   "kind": "force_push",
   "who": "ryanofsky",
   "commit": "70b4bee3cc7392a8189a98d35f382e210ae7c2cc"
  },
  {
   "t": "2026-06-08T13:32:22Z",
   "kind": "force_push",
   "who": "ryanofsky",
   "commit": "be5e736f4976686c3fa0200f35e10ce0b656c351"
  },
  {
   "t": "2026-06-08T18:43:56Z",
   "kind": "force_push",
   "who": "ryanofsky",
   "commit": "395473b97ad229d4c80c45bd5dcfa4c885acf09d"
  },
  {
   "t": "2026-06-24T14:44:35Z",
   "kind": "force_push",
   "who": "ryanofsky",
   "commit": "faf44963b8974d0e08298c03c2c1b0ee804dbf7c"
  },
  {
   "t": "2026-06-24T17:01:21Z",
   "kind": "force_push",
   "who": "ryanofsky",
   "commit": "d03bc91c902acd52bbc9fa52efb26c18740a0995"
  },
  {
   "t": "2026-06-24T20:34:14Z",
   "kind": "force_push",
   "who": "ryanofsky",
   "commit": "42d98b7aad10c4905ecc8a7d0af3749c6ac0bb1f"
  },
  {
   "t": "2026-07-14T00:36:38Z",
   "kind": "force_push",
   "who": "ryanofsky",
   "commit": "addaf64128ed60f1bc147046c8c177aee2d49ed6"
  },
  {
   "t": "2026-08-15T01:17:40Z",
   "kind": "force_push",
   "who": "ryanofsky",
   "commit": "a73d23b7762fe99e4ef1a5055e74093787adf9fb"
  }
 ],
 "labels_log": [
  {
   "t": "2026-05-22T03:59:36Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-26T21:46:55Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-26T21:49:47Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-27T19:01:45Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-29T18:10:49Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-08T20:07:32Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-22T14:11:43Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-24T16:00:50Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-24T17:01:48Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-24T21:27:47Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-30T17:25:03Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-14T00:45:20Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-04T20:34:55Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-15T02:48:23Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 36976,
 "text_tokens_estimate": 9244,
 "changed_paths": [
  "doc/release-notes-35322.md",
  "src/bench/logging.cpp",
  "src/init/common.cpp",
  "src/interfaces/node.h",
  "src/kernel/CMakeLists.txt",
  "src/kernel/bitcoinkernel.cpp",
  "src/logging.cpp",
  "src/logging.h",
  "src/node/interfaces.cpp",
  "src/qt/transactiondesc.cpp",
  "src/rpc/node.cpp",
  "src/test/i2p_tests.cpp",
  "src/test/kernel/test_kernel.cpp",
  "src/test/logging_tests.cpp"
 ],
 "files": [
  {
   "path": "doc/release-notes-35322.md",
   "add": 8,
   "del": 0
  },
  {
   "path": "src/bench/logging.cpp",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/init/common.cpp",
   "add": 23,
   "del": 6
  },
  {
   "path": "src/interfaces/node.h",
   "add": 2,
   "del": 2
  },
  {
   "path": "src/kernel/CMakeLists.txt",
   "add": 17,
   "del": 0
  },
  {
   "path": "src/kernel/bitcoinkernel.cpp",
   "add": 3,
   "del": 8
  },
  {
   "path": "src/logging.cpp",
   "add": 26,
   "del": 62
  },
  {
   "path": "src/logging.h",
   "add": 21,
   "del": 41
  },
  {
   "path": "src/node/interfaces.cpp",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/qt/transactiondesc.cpp",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/rpc/node.cpp",
   "add": 3,
   "del": 9
  },
  {
   "path": "src/test/i2p_tests.cpp",
   "add": 5,
   "del": 5
  },
  {
   "path": "src/test/kernel/test_kernel.cpp",
   "add": 8,
   "del": 0
  },
  {
   "path": "src/test/logging_tests.cpp",
   "add": 86,
   "del": 60
  }
 ],
 "test_lines": 166,
 "git": {
  "head": "a73d23b7762fe99e4ef1a5055e74093787adf9fb",
  "head_matches_backup": true,
  "base": "d3cfd02bd7f8033a528dae163314f75dcae49f3d",
  "commits": [
   {
    "sha": "22ef791f7e",
    "subject": "logging test: extend -loglevel test to check behavior with -debug",
    "files": 1,
    "add": 80,
    "del": 16
   },
   {
    "sha": "c80e3de7ef",
    "subject": "logging test: add behavior assertions to kernel logging_tests",
    "files": 2,
    "add": 25,
    "del": 0
   },
   {
    "sha": "8cd1ee83d1",
    "subject": "logging: replace m_categories/m_log_level/m_category_log_levels with m_levels array",
    "files": 6,
    "add": 138,
    "del": 78
   },
   {
    "sha": "c98dddb1b5",
    "subject": "logging refactor: replace WillLogCategory with WillLogCategoryLevel",
    "files": 2,
    "add": 1,
    "del": 8
   },
   {
    "sha": "ab03208e26",
    "subject": "logging refactor: replace DefaultShrinkDebugFile with WillLogCategoryLevel",
    "files": 3,
    "add": 4,
    "del": 8
   },
   {
    "sha": "0bf449b79e",
    "subject": "logging refactor: rename node::getLogCategories() to isAnyDebugLoggingEnabled()",
    "files": 3,
    "add": 4,
    "del": 4
   },
   {
    "sha": "6eb5604090",
    "subject": "logging refactor: add GetLogLevels/SetLogLevels; use them in tests",
    "files": 4,
    "add": 35,
    "del": 70
   },
   {
    "sha": "248c266441",
    "subject": "logging refactor: replace EnableCategory with SetCategoryLogLevel",
    "files": 2,
    "add": 10,
    "del": 11
   },
   {
    "sha": "798cd19244",
    "subject": "logging refactor: replace DisableCategory with SetCategoryLogLevel",
    "files": 2,
    "add": 2,
    "del": 2
   },
   {
    "sha": "dc605abd72",
    "subject": "logging refactor: replace AddCategoryLogLevel with SetCategoryLogLevel",
    "files": 3,
    "add": 1,
    "del": 13
   },
   {
    "sha": "2831f3b951",
    "subject": "logging refactor: drop EnableCategory/DisableCategory",
    "files": 4,
    "add": 9,
    "del": 47
   },
   {
    "sha": "651869fccf",
    "subject": "logging refactor: replace SetLogLevel with SetCategoryLogLevel",
    "files": 3,
    "add": 7,
    "del": 19
   },
   {
    "sha": "779b04f506",
    "subject": "logging refactor: drop SetCategoryLogLevel(string_view)",
    "files": 4,
    "add": 9,
    "del": 26
   },
   {
    "sha": "a73d23b776",
    "subject": "logging refactor: drop LogLevel",
    "files": 3,
    "add": 0,
    "del": 14
   }
  ],
  "patch_truncated": true
 },
 "input_hash": "c71a3367713be25e",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}