{
 "number": 34374,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/34374",
 "title": "kernel: use struct-based logging and simplify logging interface",
 "author": "stickies-v",
 "author_association": "MEMBER",
 "created_at": "2026-01-21T19:56:44Z",
 "updated_at": "2026-09-17T09:22:22Z",
 "age_days": 238,
 "draft": false,
 "labels": [
  "Validation"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "cfd2312f0fdedcba6ba827ffa9d85d6c1d5c70f1",
 "head_ref": "2026-01/kernel-logging-layering-predicate",
 "head_repo": "stickies-v/bitcoin",
 "head_history": [
  {
   "t": "2026-01-21T20:15:16Z",
   "sha": "446f5238fd257a2f5c5d167a4284419da8f832e4"
  },
  {
   "t": "2026-01-21T20:26:14Z",
   "sha": "c20ff4c521832ddfcc47e2f404d701bb04161676"
  },
  {
   "t": "2026-01-22T17:52:46Z",
   "sha": "0d075e3ff6b486ac2156b4580424c84d3190f982"
  },
  {
   "t": "2026-01-26T18:02:28Z",
   "sha": "68681ee267dc70719ac12cc5fa358c6df041b3d3"
  },
  {
   "t": "2026-01-26T18:50:36Z",
   "sha": "5b98d4be50ed5923f2f0de4cd7688b99c972601b"
  },
  {
   "t": "2026-01-26T19:56:28Z",
   "sha": "a4e8f3c8d6763a95c1804fc40781b379e6127b66"
  },
  {
   "t": "2026-01-28T16:09:13Z",
   "sha": "dca56e0237abb485edd1cfe870069e2d75f08e42"
  },
  {
   "t": "2026-01-30T16:00:36Z",
   "sha": "a7a87281ee3e3a7f356a3977688d1ba5fbaea027"
  },
  {
   "t": "2026-01-30T17:25:47Z",
   "sha": "ebe4409b722facd7d019aa800e226e26db404f81"
  },
  {
   "t": "2026-09-08T22:03:47Z",
   "sha": "f4ee36024cd5ffb732d01afefe4d3fd77c18fcd1"
  },
  {
   "t": "2026-09-09T13:58:26Z",
   "sha": "58cd2aa5c5553712f25503959849f46571da5797"
  },
  {
   "t": "2026-09-09T15:00:50Z",
   "sha": "8677c26229641618a500ac353ed5bad25a130b09"
  },
  {
   "t": "2026-09-14T15:10:39Z",
   "sha": "b0d3a47af2477915df3e41d4ac58e9604fd0a35a"
  },
  {
   "t": "2026-09-14T15:26:13Z",
   "sha": "bd728416fb9a23b7326d0cad54c3bf8f96440940"
  },
  {
   "t": "2026-09-15T14:14:41Z",
   "sha": "398b2e2423f77a2f168fefbc1d5190d70261acbc"
  },
  {
   "t": "2026-09-17T07:22:12Z",
   "sha": "cfd2312f0fdedcba6ba827ffa9d85d6c1d5c70f1"
  }
 ],
 "additions": 464,
 "deletions": 269,
 "changed_files": 8,
 "commit_count": 6,
 "size_bucket": "L",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "approach_ack": [
     {
      "login": "ryanofsky",
      "url": "https://github.com/bitcoin/bitcoin/pull/34374#issuecomment-3813719678"
     }
    ]
   },
   "conflicts": [
    {
     "number": 36244,
     "title": "validation, net: Process blocks asynchronously and reduce cs_main contention",
     "author": "w0xlt"
    },
    {
     "number": 36000,
     "title": "validation: prefetch blocks while connecting",
     "author": "l0rinc"
    },
    {
     "number": 35641,
     "title": "kernel: Add script evaluation tracer",
     "author": "sedited"
    },
    {
     "number": 35322,
     "title": "logging: streamline Logger state and drop redundant methods",
     "author": "ryanofsky"
    },
    {
     "number": 34775,
     "title": "kernel: make logging callback global",
     "author": "stickies-v"
    },
    {
     "number": 29700,
     "title": "kernel, refactor: return error status on all fatal errors",
     "author": "ryanofsky"
    },
    {
     "number": 28690,
     "title": "build: Introduce internal kernel library",
     "author": "sedited"
    },
    {
     "number": 26022,
     "title": "Add util::ResultPtr class",
     "author": "ryanofsky"
    },
    {
     "number": 25665,
     "title": "refactor: Add util::Result failure types and ability to merge result values",
     "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": 0,
  "distinct_reviewers": [
   "ajtowns",
   "purpleKarrot",
   "ryanofsky",
   "w0xlt"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-09-17T09:22:22Z",
  "last_reviewer_activity": "2026-09-16T20:38:37Z",
  "last_reviewer": "w0xlt",
  "author_silent_days": 0,
  "waiting_on_author_days": 0,
  "days_since_update": 0
 },
 "refs": {
  "mentioned": [
   27587,
   30342,
   33847,
   34062,
   34465,
   34865,
   36197,
   36207
  ],
  "depends_on": [],
  "fixes": [
   34062
  ],
  "linked_issues": [
   {
    "number": 34062,
    "type": "issue",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "RFC: separate kernel logging infrastructure"
   }
  ],
  "references": [
   {
    "number": 34062,
    "type": "issue",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "RFC: separate kernel logging infrastructure"
   },
   {
    "number": 27587,
    "type": "issue",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "Bitcoin Kernel Library Project Tracking"
   },
   {
    "number": 34465,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-02-07",
    "title": "refactor: separate log generation from log handling"
   },
   {
    "number": 30342,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "kernel, logging: Pass Logger instances to kernel objects"
   },
   {
    "number": 33847,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "kernel: Improve logging API"
   },
   {
    "number": 34865,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-04-23",
    "title": "logging: better use of log::Entry internally"
   },
   {
    "number": 36197,
    "type": "pull",
    "state": "closed",
    "merged": false,
    "merged_at": null,
    "title": "kernel: Replace application log callbacks with file logging"
   },
   {
    "number": 36207,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-09-15",
    "title": "kernel: add wtxid accessor"
   }
  ],
  "conflicts": [
   36244,
   36000,
   35641,
   35322,
   34775,
   29700,
   28690,
   26022,
   25665
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/kernel/bitcoinkernel.cpp",
  "src/logging.h",
  "src/util/log.h"
 ],
 "body": "tl;dr: kernel logging is cumbersome. This PR delivers log entries as a struct instead of a formatted string, and simplifies the kernel logging interface. Closes #34062.\n\n## Motivation\n\nThe `bitcoinkernel` library (#27587) [exposes](https://github.com/bitcoin/bitcoin/blob/b26762bdcb941096ccc6f53c3fb5a7786ad735e7/src/kernel/bitcoinkernel.h#L698-L778) functionality to interface with the kernel logging. This includes registering callbacks for log statements, level/category filtering, string formatting options, and more.\n\nKernel logging has a few problems:\n- [callbacks](https://github.com/bitcoin/bitcoin/blob/b26762bdcb941096ccc6f53c3fb5a7786ad735e7/src/kernel/bitcoinkernel.h#L304) 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.\n- the [filtering interface](https://github.com/bitcoin/bitcoin/blob/b26762bdcb941096ccc6f53c3fb5a7786ad735e7/src/kernel/bitcoinkernel.h#L737) 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.\n- 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.\n\n## Approach\n\nLog generation (`util/log.h`: macros, `util::log::Entry`, and the `ShouldDebugLog`/`ShouldTraceLog`/`Log` hooks an application must provide) was already separated from log handling (`logging.h`) in #34465 and subsequent PRs. This PR gives `bitcoinkernel` its own implementation of those hooks, so it no longer depends on `logging.cpp`, and upgrades the C API to deliver struct-based entries. Node logging is not changed.\n\n1. **Preparatory work**: expose the missing levels and categories (`WARNING`, `ERROR`, `TXPACKAGES`, `LOCK`) and the `btck_LogEntry` struct, add `Name()` helpers to the C++ wrapper, and remove `btck_logging_set_options` and `btck_logging_disable` (with struct-based entries there is no kernel-side format to configure, and without buffering there is nothing to disable).\n2. **Add `KernelLogger`**: a kernel-owned backend that holds the registered callbacks and the minimum level, and converts each `util::log::Entry` into a `btck_LogEntry`. Introduced in a separate commit to keep the scope of the behaviour-changing commit smaller.\n3. **Update the bitcoinkernel C API**: `btck_LogCallback` receives a `btck_LogEntry` instead of a string, the `util::log` hooks are implemented on `KernelLogger`, `logging.cpp` is dropped from the kernel build, and the logging configuration interface is reduced to `btck_logging_set_min_level()`.\n\nBehaviour changes for consumers (also described in each commit message):\n- Logging callbacks deliver structs instead of formatted strings, and the entire logging interface is simplified.\n- Filtering is levels-based only and can also filter `Info` and above. `Debug`/`Trace` apply to all categories, consumers can filter on `btck_LogEntry::category` (but string formatting is done for the entire level, instead of per enabled category).\n- Entries logged before the first connection are dropped instead of buffered.\n- Messages are delivered unescaped and without a trailing newline.\n\n## Appendix\n\n### `bitcoinkernel` C logging interface\n\n```c\ntypedef struct {\n    const char* message;       //!< Log message text (not null-terminated).\n    size_t message_len;\n    const char* thread_name;   //!< Name of the thread that produced the log message.\n    size_t thread_name_len;\n    int64_t timestamp_ns;      //!< Timestamp in nanoseconds since the Unix epoch.\n    int64_t mocktime;          //!< Mock time in seconds since the Unix epoch, or 0 if not set.\n    const char* file_name;     //!< Source file name.\n    size_t file_name_len;\n    const char* function_name; //!< Source function name.\n    size_t function_name_len;\n    uint32_t line;             //!< Source line number.\n    btck_LogLevel level;       //!< Log severity level.\n    btck_LogCategory category; //!< Log category.\n} btck_LogEntry;\n\ntypedef void (*btck_LogCallback)(void* user_data, const btck_LogEntry* entry);\n\nBITCOINKERNEL_API void btck_logging_set_min_level(btck_LogLevel level);\n\nBITCOINKERNEL_API btck_LoggingConnection* BITCOINKERNEL_WARN_UNUSED_RESULT btck_logging_connection_create(\n    btck_LogCallback log_callback,\n    void* user_data,\n    btck_DestroyCallback user_data_destroy_callback) BITCOINKERNEL_ARG_NONNULL(1);\n\nBITCOINKERNEL_API void btck_logging_connection_destroy(btck_LoggingConnection* logging_connection);\n```",
 "commits": [
  {
   "sha": "3a72f3040c295db54df534bd193f118b3d028749",
   "date": "2026-09-15T13:53:54Z",
   "message": "kernel: add missing log levels and categories\n\nThere are log levels and categories emitted by kernel code that\nare not yet part of the C API. Once entries are delivered as\nstructs in a future commit, every emitted value must map to a\nconstant, so add the missing ones first in this preparatory commit.\n\nThe LOCK constant is unconditional so the C API is the same in every\nbuild.\n\nNo behaviour change. Part of a series of commits to make kernel logging\nstruct-based instead of string-based."
  },
  {
   "sha": "94cf10c4bf8417e4543097c1e8673ad35485d1fd",
   "date": "2026-09-15T13:53:55Z",
   "message": "kernel: expose btck_LogEntry\n\nIntroduce the btck_LogEntry struct the logging callback will use in\na future commit (instead of the current string argument). The struct\nis not yet used.\n\nThe level and category definitions move up so the callback typedef can\nreference btck_LogEntry; review with --color-moved.\n\nNo behaviour change. Part of a series of commits to make kernel logging\nstruct-based instead of string-based."
  },
  {
   "sha": "4469d1f57e96804e5e7547f8d9b5fb1b118c45d4",
   "date": "2026-09-15T13:53:55Z",
   "message": "kernel: add log level and category name helpers to wrapper\n\nConvenience functions for consumers that want to format levels and\ncategories.\n\nNo behaviour change. Part of a series of commits to make kernel logging\nstruct-based instead of string-based."
  },
  {
   "sha": "b4edc987456e2029f421cdbc53bc923aa090409c",
   "date": "2026-09-15T14:03:35Z",
   "message": "kernel: remove API to format and disable logging\n\nBoth functions lose their purpose once the kernel logger is no longer\ncoupled to the node logger: formatting becomes the consumer's job, and\nnothing is buffered before a connection exists, so there is nothing to\ndisable. Remove them early to simplify the next commits.\n\nBehaviour change until the backend switch later in this series:\n- strings are delivered in BCLog::Logger's default format\n- the 1MB pre-connection buffer cannot be turned off.\n\nPart of a series of commits to make kernel logging struct-based instead\nof string-based."
  },
  {
   "sha": "95e63e954af32b2c4c7fa11a291e642ae961839d",
   "date": "2026-09-17T07:01:25Z",
   "message": "kernel: add KernelLogger\n\nAdd the kernel's own logging backend, unused until the next commit\nswitches the C API over to it.\n\nCallbacks run while the logger's mutex is held: invocations are\nserialized, and unregistering waits for an in-flight callback.\n\nCallbacks are typed with a local LogCallback alias carrying the\nstruct-based signature, because btck_LogCallback still delivers a\nstring at this point. The next commit changes the typedef and replaces\nthe alias.\n\nNo behaviour change. Part of a series of commits to make kernel logging\nstruct-based instead of string-based."
  },
  {
   "sha": "cfd2312f0fdedcba6ba827ffa9d85d6c1d5c70f1",
   "date": "2026-09-17T07:01:26Z",
   "message": "kernel: make logging struct-based and simplify configuration\n\nUpdate btck_LogCallback to use struct-based logging and drop logging.cpp\nfrom the kernel build.\n\nSince consumers can now easily filter by category, simplify the logging\nconfiguration to just btck_logging_set_min_level.\n\nBehaviour changes for consumers:\n- Filtering is now purely levels-based, and consumers can choose any\n  level they like. Debug and Trace logging applies to all categories,\n  further filtering should be done client-side.\n  Note: evaluation remains unchanged: Debug and Trace statements are\n  only evaluated when they are requested, Info and above is always\n  evaluated.\n- Entries logged before the first connection are dropped, not buffered.\n- Trailing newlines are stripped, callback exceptions are swallowed,\n  and connection create and destroy no longer take cs_main.\n- Log messages are no longer escaped, this can be done client-side.\n\nKernelLogger moves into the anonymous namespace now that every member\nis used. It stayed outside in the previous commit to avoid unused\nmember warnings.\n\nNode logging is unchanged. Final commit of the series to make kernel\nlogging struct-based instead of string-based."
  }
 ],
 "timeline": [
  {
   "t": null,
   "kind": "review",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "state": "PENDING",
   "commit": "dca56e0237abb485edd1cfe870069e2d75f08e42",
   "text": ""
  },
  {
   "t": "2026-01-21T20:15:16Z",
   "kind": "force_push",
   "who": "stickies-v",
   "commit": "446f5238fd257a2f5c5d167a4284419da8f832e4"
  },
  {
   "t": "2026-01-21T20:26:14Z",
   "kind": "force_push",
   "who": "stickies-v",
   "commit": "c20ff4c521832ddfcc47e2f404d701bb04161676"
  },
  {
   "t": "2026-01-22T17:52:46Z",
   "kind": "force_push",
   "who": "stickies-v",
   "commit": "0d075e3ff6b486ac2156b4580424c84d3190f982"
  },
  {
   "t": "2026-01-23T10:48:40Z",
   "kind": "review_comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "path": "src/logging.h",
   "commit": "0d075e3ff6b486ac2156b4580424c84d3190f982",
   "in_reply_to": null,
   "text": "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()`.\n\nAs far as I can see, `g_dispatcher()` should just have independent implementations for bitcoin-kernel vs bitcoind, bitcoin-qt, bitcoin-cli etc."
  },
  {
   "t": "2026-01-23T10:50:03Z",
   "kind": "review_comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "path": "src/util/log.h",
   "commit": "dca56e0237abb485edd1cfe870069e2d75f08e42",
   "in_reply_to": null,
   "text": "Jumping through a `std::function` when we could just be checking an atomic bitfield doesn't seem like a good approach."
  },
  {
   "t": "2026-01-23T14:03:28Z",
   "kind": "review_comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "path": "src/util/log.h",
   "commit": "dca56e0237abb485edd1cfe870069e2d75f08e42",
   "in_reply_to": 2720709919,
   "text": "[quoted text omitted]\n\nI 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?\n\nI 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."
  },
  {
   "t": "2026-01-23T16:37:22Z",
   "kind": "review_comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "path": "src/logging.h",
   "commit": "0d075e3ff6b486ac2156b4580424c84d3190f982",
   "in_reply_to": 2720705368,
   "text": "Yes, I think that's a much better approach, thanks for the suggestion. I'll incorpocate that and update shortly.\n\n[quoted text omitted]\nI 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."
  },
  {
   "t": "2026-01-26T18:02:28Z",
   "kind": "force_push",
   "who": "stickies-v",
   "commit": "68681ee267dc70719ac12cc5fa358c6df041b3d3"
  },
  {
   "t": "2026-01-26T18:50:36Z",
   "kind": "force_push",
   "who": "stickies-v",
   "commit": "5b98d4be50ed5923f2f0de4cd7688b99c972601b"
  },
  {
   "t": "2026-01-26T19:56:28Z",
   "kind": "force_push",
   "who": "stickies-v",
   "commit": "a4e8f3c8d6763a95c1804fc40781b379e6127b66"
  },
  {
   "t": "2026-01-26T20:36:45Z",
   "kind": "comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "text": "Force-pushed to:\n- address merge conflict from https://github.com/bitcoin/bitcoin/pull/33822\n- address @ajtowns [comment](https://github.com/bitcoin/bitcoin/pull/34374#discussion_r2720705368): 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.\n- improved logging callback handling: checking for nullptr and catching exceptions.\n- use `BCLog::Logger::Enabled()` in the `filter` function\n- address drahtbot linter suggestions, incl typo and named args, and a couple of other small touch-ups here and there."
  },
  {
   "t": "2026-01-26T20:37:11Z",
   "kind": "review_comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "path": "src/logging.h",
   "commit": "0d075e3ff6b486ac2156b4580424c84d3190f982",
   "in_reply_to": 2720705368,
   "text": "I've changed the approach to your suggestion, so marking this as resolved."
  },
  {
   "t": "2026-01-27T05:32:52Z",
   "kind": "comment",
   "who": "purpleKarrot",
   "assoc": "MEMBER",
   "text": "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](https://en.cppreference.com/w/cpp/error/set_terminate.html) in the C++ standard library:\n\n```c\ntypedef void (*btck_LogHandler)(btck_LogEntry const* entry);\n\nBITCOINKERNEL_API btck_LogHandler btck_set_log(btck_LogHandler handler);\n```\n\nI 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."
  },
  {
   "t": "2026-01-27T05:47:59Z",
   "kind": "review_comment",
   "who": "purpleKarrot",
   "assoc": "MEMBER",
   "path": "src/kernel/bitcoinkernel.cpp",
   "commit": "ebe4409b722facd7d019aa800e226e26db404f81",
   "in_reply_to": null,
   "text": "I interpret that as \"We have to use a singleton here because of other singletons\".\nNow everybody should understand why singletons are *viral* and therefore should be forbidden. I have zero tolerance for such code."
  },
  {
   "t": "2026-01-27T11:24:59Z",
   "kind": "review_comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "path": "src/kernel/bitcoinkernel.cpp",
   "commit": "ebe4409b722facd7d019aa800e226e26db404f81",
   "in_reply_to": 2730355874,
   "text": "[quoted text omitted]\n\nI'm sorry to hear. What do you suggest as an alternative?"
  },
  {
   "t": "2026-01-27T11:28:56Z",
   "kind": "comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nI 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 ?\n\n[quoted text omitted]\nThey 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."
  },
  {
   "t": "2026-01-28T04:22:15Z",
   "kind": "review_comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "path": "src/util/log.h",
   "commit": "dca56e0237abb485edd1cfe870069e2d75f08e42",
   "in_reply_to": 2720709919,
   "text": "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.\n\nI 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()`."
  },
  {
   "t": "2026-01-28T12:36:21Z",
   "kind": "review_comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "path": "src/util/log.h",
   "commit": "dca56e0237abb485edd1cfe870069e2d75f08e42",
   "in_reply_to": 2720709919,
   "text": "[quoted text omitted]\n\nI 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.\n\n[quoted text omitted]\nI'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?\n\nOn master (34a5ecadd7203121b03ac692d22a752d2a364111):\n\n```\n|               ns/op |                op/s |    err% |     total | benchmark\n|--------------------:|--------------------:|--------:|----------:|:----------\n|            1,837.04 |          544,353.35 |    2.6% |      0.01 | `LogWithDebug`\n|            1,926.63 |          519,041.49 |    0.2% |      0.01 | `LogWithThreadNames`\n|                2.81 |      355,468,404.18 |    3.1% |      0.01 | `LogWithoutDebug`\n|            1,828.42 |          546,919.86 |    1.1% |      0.01 | `LogWithoutThreadNames`\n|               18.31 |       54,600,726.18 |    1.1% |      0.01 | `LogWithoutWriteToFile`\n```\n\nOn a4e8f3c8d6763a95c1804fc40781b379e6127b66 (with small unpushed modification):\n```\n|               ns/op |                op/s |    err% |     total | benchmark\n|--------------------:|--------------------:|--------:|----------:|:----------\n|            1,826.40 |          547,524.98 |    0.7% |      0.01 | `LogWithDebug`\n|            1,863.31 |          536,680.71 |    1.0% |      0.01 | `LogWithThreadNames`\n|                4.23 |      236,235,773.60 |    0.4% |      0.01 | `LogWithoutDebug`\n|            1,800.52 |          555,395.22 |    1.3% |      0.01 | `LogWithoutThreadNames`\n|               19.92 |       50,188,600.76 |    1.0% |      0.01 | `LogWithoutWriteToFile`\n```"
  },
  {
   "t": "2026-01-28T16:09:13Z",
   "kind": "force_push",
   "who": "stickies-v",
   "commit": "dca56e0237abb485edd1cfe870069e2d75f08e42"
  },
  {
   "t": "2026-01-28T16:09:21Z",
   "kind": "review_comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "path": "src/util/log.h",
   "commit": "dca56e0237abb485edd1cfe870069e2d75f08e42",
   "in_reply_to": 2720709919,
   "text": "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)."
  },
  {
   "t": "2026-01-28T16:12:46Z",
   "kind": "comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "text": "Latest force-push:\n- 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](https://github.com/bitcoin/bitcoin/pull/34374#discussion_r2734793117).\n- `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."
  },
  {
   "t": "2026-01-28T20:18:05Z",
   "kind": "comment",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "text": "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:\n\n- Providing stuctured log entries instead of strings that need to be parsed to get information like timestamps and source locations.\n- Dropping `set_options` function with obscure options like `log_time_micros`.\n- 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.\n- Dropping `logging_disable` function that needs to be called by applications that don't use logging. (Note: [#33847](https://github.com/bitcoin/bitcoin/pull/33847) also eliminates this).\n- Eliminating global logging functions not tied to logging connections (also addressed by [#33847](https://github.com/bitcoin/bitcoin/pull/33847)).\n\n#### Suggestion to remove extra overhead\n\nI do agree with AJ that requiring a call through a function pointer https://github.com/bitcoin/bitcoin/pull/34374#discussion_r2720709919 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:\n\ndiff\n\n```diff\n--- a/src/kernel/bitcoinkernel.cpp\n+++ b/src/kernel/bitcoinkernel.cpp\n@@ -57,9 +57,7 @@\n\n struct KernelLogger {\n     std::atomic<util::log::Level> min_level{util::log::Level::Info};\n-    util::log::Dispatcher dispatcher{[this](util::log::Level level, uint64_t) {\n-        return level >= min_level.load(std::memory_order_relaxed);\n-    }};\n+    util::log::Dispatcher dispatcher{this};\n };\n\n // Kernel logging state. Intentionally leaked to avoid use-after-destroy if logging occurs during\n@@ -75,6 +73,12 @@ util::log::Dispatcher& util::log::g_dispatcher()\n     return g_kernel_logger().dispatcher;\n }\n\n+bool util::log::g_dispatcher_should_log(void* context, util::log::Level level, uint64_t category)\n+{\n+    KernelLogger& logger{*static_cast<KernelLogger*>(context)};\n+    return level >= logger.min_level.load(std::memory_order_relaxed);\n+}\n+\n using kernel::ChainstateRole;\n using util::ImmediateTaskRunner;\n\n--- a/src/logging.cpp\n+++ b/src/logging.cpp\n@@ -49,6 +49,12 @@ util::log::Dispatcher& util::log::g_dispatcher()\n     return *LogInstance().GetDispatcher();\n }\n\n+bool util::log::g_dispatcher_should_log(void* context, util::log::Level level, uint64_t category)\n+{\n+    BCLog::Logger& logger{*static_cast<BCLog::Logger*>(context)};\n+    return logger.Enabled() && logger.WillLogCategoryLevel(static_cast<BCLog::LogFlags>(category), level);\n+}\n+\n bool fLogIPs = DEFAULT_LOGIPS;\n\n static int FileWriteStr(std::string_view str, FILE *fp)\n@@ -617,10 +623,7 @@ bool BCLog::Logger::SetCategoryLogLevel(std::string_view category_str, std::stri\n\n BCLog::Logger::Logger()\n {\n-    auto filter_func = [this](util::log::Level level, uint64_t cat) {\n-        return this->Enabled() && this->WillLogCategoryLevel(static_cast<BCLog::LogFlags>(cat), level);\n-    };\n-    m_dispatcher = std::make_unique<util::log::Dispatcher>(filter_func);\n+    m_dispatcher = std::make_unique<util::log::Dispatcher>(this);\n\n     m_callback_handle = m_dispatcher->RegisterCallback([this](const util::log::Entry& entry) {\n         LogPrintStr(entry);\n--- a/src/test/util_log_tests.cpp\n+++ b/src/test/util_log_tests.cpp\n@@ -50,7 +50,7 @@ BOOST_AUTO_TEST_CASE(dispatcher_filter)\n     // Use a simple level filter predicate to only log above a specified min_level\n     Level min_level;\n     auto level_filter = [&min_level](Level level, uint64_t) { return level >= min_level; };\n-    Dispatcher dispatcher{level_filter};\n+    Dispatcher dispatcher{nullptr, level_filter};\n     int callback_count{0};\n     auto handle = dispatcher.RegisterCallback([&](const Entry&) { ++callback_count; });\n\n--- a/src/util/log.h\n+++ b/src/util/log.h\n@@ -61,6 +61,9 @@ struct Entry {\n     bool should_ratelimit{false}; //!< Hint for consumers if this entry should be ratelimited\n };\n\n+//! Early custom callback to determine whether log message should be produced.\n+bool g_dispatcher_should_log(void* context, Level level, uint64_t category);\n+\n /**\n  * Dispatcher is responsible for producing logs. It forwards structured log entries to one or\n  * multiple logging sinks (e.g. BCLog::Logger) through its registered callbacks.\n@@ -79,12 +82,13 @@ public:\n\n     /**\n      * Construct a Dispatcher.\n+     * @param[in] context Optional context passed to g_dispatcher_should_log()\n      * @param[in] filter  Optional predicate to filter log entries before dispatch to minimize\n      *                    overhead (e.g. string formatting) for entries that all of the sinks would\n      *                    be discarding anyway (e.g. due to a low logging level).\n      *                    If null, all entries are dispatched (when callbacks are registered).\n      */\n-    Dispatcher(FilterFunc filter = nullptr) : m_filter{std::move(filter)} {}\n+    Dispatcher(void* context = nullptr, FilterFunc filter = nullptr) : m_context{context}, m_filter{std::move(filter)} {}\n\n     /**\n      * Register a callback to receive log entries.\n@@ -103,7 +107,7 @@ public:\n     bool Enabled() const { return m_callback_count.load(std::memory_order_acquire) > 0; }\n\n     /** @return true if Enabled() and the filter (if set) passes for the provided level and category. */\n-    bool WillLog(Level level, uint64_t category) const { return Enabled() && (!m_filter || m_filter(level, category)); }\n+    bool WillLog(Level level, uint64_t category) const { return Enabled() && g_dispatcher_should_log(m_context, level, category) && (!m_filter || m_filter(level, category)); }\n\n     /**\n      * Format message and dispatch to all registered callbacks. No-op if WillLog() doesn't pass.\n@@ -148,6 +152,8 @@ private:\n     std::list<Callback> m_callbacks GUARDED_BY(m_mutex);\n     //! Lock-free size of m_callbacks for fast checks.\n     std::atomic<size_t> m_callback_count{0};\n+    //! Optional context argument set in constructor.\n+    void* m_context;\n     //! Optional filter set in constructor; @see WillLog().\n     const FilterFunc m_filter{nullptr};\n };\n```\n\n(Note: this could be rearranged and simplified more, it is just meant to provide a minimal change to consider.)\n\n#### Suggestion to split up PR\n\nMy 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:\n\n- Portion of first commit moving `SourceLocation` from `logging.h` to `util/log.h` (base PR could introduce `util/log.h`)\n- Commit \"test: verify log argument evaluation semantics\"\n- Portion of third commit moving `BCLog::Level` from `logging.h` to `util/log.h`\n- Commit \"kernel: add log level/category name getters\"\n- Portion of commit \"kernel: implement structured logging C API\" adding missing category and level mappings.\n- Commit \"move-only: move logging categories to separate header\"\n- Commit \"logging: move LogAcceptCategory to util\" (modulo g_dispatcher call)\n- Commit \"logging: move macros to util\"\n\nThese are all worthwhile changes on their own, and moving them out of this PR should make it substantially smaller and easier to navigate.\n\nI plan to review this PR more and also rebase #30342 on top of this. I think the PR's should complement each other well."
  },
  {
   "t": "2026-01-30T12:34:15Z",
   "kind": "comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "text": "Thanks for the in-depth review and suggestions, @ryanofsky !\n\n[quoted text omitted]\nI 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.\n\n[quoted text omitted]\nI'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`."
  },
  {
   "t": "2026-01-30T14:22:15Z",
   "kind": "comment",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nYour 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.\n\nThis also makes sense conceptually: log-emitting and log-handling are separate concerns, and there\u2019s no real reason code that only emits log messages should need to include a header defining a full log management API.\n\nAnother 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."
  },
  {
   "t": "2026-01-30T16:00:36Z",
   "kind": "force_push",
   "who": "stickies-v",
   "commit": "a7a87281ee3e3a7f356a3977688d1ba5fbaea027"
  },
  {
   "t": "2026-01-30T17:25:47Z",
   "kind": "force_push",
   "who": "stickies-v",
   "commit": "ebe4409b722facd7d019aa800e226e26db404f81"
  },
  {
   "t": "2026-01-30T18:04:46Z",
   "kind": "comment",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "text": "re: https://github.com/bitcoin/bitcoin/pull/34374#issuecomment-3824030533\n\n[quoted text omitted]\nI 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."
  },
  {
   "t": "2026-01-31T07:12:06Z",
   "kind": "review_comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "path": "src/util/log.h",
   "commit": "dca56e0237abb485edd1cfe870069e2d75f08e42",
   "in_reply_to": 2720709919,
   "text": "[quoted text omitted]\n\n[quoted text omitted]\nThe 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."
  },
  {
   "t": "2026-03-06T15:14:33Z",
   "kind": "comment",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "text": "Hi stickies, it'd be really nice to have this rebased & start review. Here is a rebase branch I made: https://github.com/ryanofsky/bitcoin/commits/review.34374.2-edit with minimal changes.\n\nThere are more simplifications that can be made, but it's already much smaller and simpler after #34465"
  },
  {
   "t": "2026-03-16T05:47:49Z",
   "kind": "comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nApologies for the slow follow-up here @ryanofsky. My priorities were elsewhere for a while, but I've now picked this up again.\n\nSince we now have the `{Should}Log` interface in `util/log.h`, I think also having `util::log::Dispatcher` doesn't really make sense anymore, so I've been reworking that into a kernel-specific type that implements the `{Should}Log` interface. I've been looking at a couple of different approaches and commit orderings to make reviewing as smooth as possible and think I'm close to having a good solution."
  },
  {
   "t": "2026-03-16T13:26:13Z",
   "kind": "comment",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nFWIW I did this in the linked [branch](https://github.com/ryanofsky/bitcoin/commits/review.34374.2-edit):\n\nhttps://github.com/bitcoin/bitcoin/blob/8611ff533c5c384a2a8fd32000fc9d6619d46b48/src/kernel/bitcoinkernel.cpp#L85-L93\n\nThe Dispatcher type is kernel-specific there and not used outside the kernel. The only thing I didn't do was move the dispatcher class definition and strip out some unused code because I was avoiding extraneous changes in the rebase. Seems fine to reimplement, though. Should be equivalent to using the branch and the stripping bits of code that are no longer needed."
  },
  {
   "t": "2026-04-10T11:59:59Z",
   "kind": "comment",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "text": "Here is a rebased version of this PR with the logger cleanups from #34865 dropped: ec9e622d2da96ac482cf1bf599e20292d2f78759 ([branch](https://github.com/ryanofsky/bitcoin/commits/pr/kslog))\n\nNow there's basically no overlap between this PR and #34865 so both could be reviewed in parallel.\n\nAside from the logger change I also dropped some dead code in the dispatcher class from the previous [rebase](https://github.com/bitcoin/bitcoin/pull/34374#issuecomment-4012333941) and rewrote one commit message. Otherwise the branch is unchanged and the commits are straightforward:\n\n- 6c148e8c405532338a41f8787ddf76ac299b2b73 util: add log::Dispatcher for struct-based log dispatch (1/10)\n- 1f861b7f46ae1227208c35b880a6c8ebdcafee35 kernel: add log level/category name getters (2/10)\n- ddd655b6dd31b26a414f948a25e48c3366c50fba kernel: add missing log levels and categories (3/10)\n- d73cc47d9804a63b6ff9b3c88ab772032e4188a0 kernel: expose btck_LogEntry (4/10)\n- 8efdf8504959404326970610389dcd3130a31ec3 kernel: make logging struct-based (5/10)\n- 18a9a3c0cad3fb6c3e3c3d091879e75c629e519d kernel: remove unused logging functions (6/10)\n- d4e3282a593b646cb0a6a4d167394bf18e9904c4 logging: declare LogAcceptCategory in util (7/10)\n- ef451c810031168acc37ae52403f558abf72715d doc: advise on log rate limiting in developer notes (8/10)\n- c0cda82e443f2730ef423041d43551c4847ac590 logging: use util/log.h where possible (9/10)\n- ec9e622d2da96ac482cf1bf599e20292d2f78759 kernel: implement levels-based logging C API (10/10)"
  },
  {
   "t": "2026-06-08T13:57:33Z",
   "kind": "comment",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "text": "Here is a rebased version of this pr: 683056775c58f05f9603f3f7023467ab0e7f723e ([branch](https://github.com/ryanofsky/bitcoin/commits/pr/kslog)) again with minimal changes from the original.\n\nI'd really like to review and be able to merge this change because it makes the kernel logging API simpler and more usable.\n\nWhen I rebased this, I made as few changes as possible so comparison to the current version would be simple. But there are a few additional cleanups that could be made on top of this:\n\n- `Dispatcher` class should probably be moved out of `util/log` since dispatching is a log-handling function not a log-generating function. As mentioned https://github.com/bitcoin/bitcoin/pull/34865#issuecomment-4236420269,  the `Dispatcher` class could also be dropped and replaced with a btcsignals::signal. But it is a very simple class so it seems fine to keep.\n\n- A few simplifications / renames in `bitcoinkernel.cpp` would probably be appropriate like inlining `g_dispatcher`. I just kept current names for things so diffs would be minimal."
  },
  {
   "t": "2026-09-08T22:03:47Z",
   "kind": "force_push",
   "who": "stickies-v",
   "commit": "f4ee36024cd5ffb732d01afefe4d3fd77c18fcd1"
  },
  {
   "t": "2026-09-09T13:58:26Z",
   "kind": "force_push",
   "who": "stickies-v",
   "commit": "58cd2aa5c5553712f25503959849f46571da5797"
  },
  {
   "t": "2026-09-09T15:00:50Z",
   "kind": "force_push",
   "who": "stickies-v",
   "commit": "8677c26229641618a500ac353ed5bad25a130b09"
  },
  {
   "t": "2026-09-09T17:14:58Z",
   "kind": "comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "text": "Apologies for the months of radio silence here. I've had many cycles of exploring various architectures and ways to minimize review burden, to then kind of get stuck on something I really didn't like, give up, and repeat a while later. While I've found pretty much every approach to have its own downsides, I believe the current version has the best set of trade-offs. `kernel` and `node` logging having to co-exist complicates many things, but is a reality we have to work around.\n\nThe main changes in this latest version are:\n- rebased onto latest `master`\n- `util::log::Dispatcher` is gone. Instead, this PR implements the `Log` and `Should{Debug,Trace}Log` interface, and removes the `std::function` indirection callbacks this PR used to have. The new `KernelLogger` that keeps state (minimum level and callbacks) is small and kernel-specific, and an implementation detail.\n- `mocktime` is added to `btck_LogEntry`\n- removed the `btck_log_level_get_name`/`btck_log_category_get_name` C functions from the earlier version; the C++ wrapper has `Name()` helpers instead. Consumers know the constants, so I don't think this belongs in the C API. It removes a lot of the churn in the previous approach wrt using arrays for the btck/util/name mapping.\n- when kernel code emits an unmapped category, this will now only crash in debug builds (with `Assume`). We can't currently enforce a complete mapping at compile time, and it's fairly easy for a new category to accidentally slip into kernel code. If that's in a rare code path, it'd be dangerous for that to crash in non-debug builds.\n\nThoughts on things I've explored:\n- the top commit is fairly large. In previous approaches, I had separated this into smaller commits, but I've decided to revert it to two big ones to minimize churn. Because the `util::log` interface requires exactly one implementation, certain gradual changes are not really possible without workarounds that then add review complexity.\n- there's fairly minimal test coverage. Since `util::log` needs exactly one implementation per binary, `test_bitcoin` can't link the kernel library, and `test_kernel` only sees the exported C API, so `KernelLogger` isn't reachable from either test binary without workarounds. I've tried moving `KernelLogger` into its own header so `test_bitcoin` can compile it directly. That runs into the circular dependency linter and, when header-only, into clang's `-Wunique-object-duplication` for the singleton. Working around that means moving the logging C functions out of `bitcoinkernel.cpp` and sharing the `Handle` template through yet another header, which is even more churn. I think we should improve this through https://github.com/bitcoin/bitcoin/pull/28690 and then a new organization of test binaries (e.g. test_node, test_kernel, and test_kernel_interface), but I'm very hesitant to add that to the scope of this PR, which I feel is already fairly large.\n\nDespite my hesitations, I think these changes would massively improve the kernel logging interface, and help with better separation between `kernel` and `node`."
  },
  {
   "t": "2026-09-10T16:11:50Z",
   "kind": "review_comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "path": "src/util/log.h",
   "commit": "dca56e0237abb485edd1cfe870069e2d75f08e42",
   "in_reply_to": 2720709919,
   "text": "Marking this as resolved, as the PR has changed a lot since then and the conversation no longer applies I think. The indirection is gone, and this PR now doesn't touch util/node logging (including logging statement evaluation) at all (except for a documentation change). Happy to re-open if I've missed anything."
  },
  {
   "t": "2026-09-10T23:15:39Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "src/kernel/bitcoinkernel.cpp",
   "commit": "8677c26229641618a500ac353ed5bad25a130b09",
   "in_reply_to": null,
   "text": "`RegisterCallback()` and `LogDebug()` can also throw `std::bad_alloc`.\n\n`btck_LoggingConnection` can be a smart pointer.  Holding the connection in a `std::unique_ptr` prevents its allocation from leaking if logging throws.\n\nShouldn't `user_data` be destroyed if the callback registration fails ?\n\n```diff\ndiff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp\nindex 72c1587450..635f1bacaf 100644\n--- a/src/kernel/bitcoinkernel.cpp\n+++ b/src/kernel/bitcoinkernel.cpp\n@@ -949,17 +949,20 @@ void btck_logging_set_min_level(btck_LogLevel level)\n btck_LoggingConnection* btck_logging_connection_create(btck_LogCallback callback, void* user_data, btck_DestroyCallback user_data_destroy_callback)\n {\n     assert(callback);\n-    auto handle{GetKernelLogger().RegisterCallback(\n-        callback, user_data, user_data_destroy_callback)};\n-    btck_LoggingConnection* connection;\n+    std::optional<KernelLogger::CallbackHandle> handle;\n     try {\n-        connection = btck_LoggingConnection::create(handle);\n+        handle = GetKernelLogger().RegisterCallback(callback, user_data, user_data_destroy_callback);\n+        std::unique_ptr<btck_LoggingConnection> connection{btck_LoggingConnection::create(*handle)};\n+        LogDebug(BCLog::KERNEL, \"Logger connected.\");\n+        return connection.release();\n     } catch (...) {\n-        GetKernelLogger().UnregisterCallback(handle);\n+        if (handle) {\n+            GetKernelLogger().UnregisterCallback(*handle);\n+        } else if (user_data && user_data_destroy_callback) {\n+            user_data_destroy_callback(user_data);\n+        }\n         return nullptr;\n     }\n-    LogDebug(BCLog::KERNEL, \"Logger connected.\");\n-    return connection;\n }\n\n void btck_logging_connection_destroy(btck_LoggingConnection* connection)\n```"
  },
  {
   "t": "2026-09-11T10:18:21Z",
   "kind": "review_comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "path": "src/kernel/bitcoinkernel.cpp",
   "commit": "8677c26229641618a500ac353ed5bad25a130b09",
   "in_reply_to": 3984322784,
   "text": "Good point, thanks. I think `btck_logging_connection_destroy` also needs similar improvements."
  },
  {
   "t": "2026-09-14T15:10:39Z",
   "kind": "force_push",
   "who": "stickies-v",
   "commit": "b0d3a47af2477915df3e41d4ac58e9604fd0a35a"
  },
  {
   "t": "2026-09-14T15:16:11Z",
   "kind": "review_comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "path": "src/kernel/bitcoinkernel.cpp",
   "commit": "8677c26229641618a500ac353ed5bad25a130b09",
   "in_reply_to": 3984322784,
   "text": "Fixed by wrapping user data in a RAII `UserData` wrapper, and the callback in a RAII `CallbackHandle` wrapper. It's a larger diff, but I think it makes for cleaner code. The `UserData` can be reused in other places too, but I'd prefer doing that in a follow-up as it's unrelated to this change. Does this address your concern? You're right that `LogDebug` also could potentially throw, but this is true in the bitcoinkernel code in general and I feel might be out of scope here?"
  },
  {
   "t": "2026-09-14T15:26:13Z",
   "kind": "force_push",
   "who": "stickies-v",
   "commit": "bd728416fb9a23b7326d0cad54c3bf8f96440940"
  },
  {
   "t": "2026-09-15T11:19:38Z",
   "kind": "comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "text": "Force-pushed to wrap user data and the callbacks in RAII wrappers, addressing @w0xlt's [comment](https://github.com/bitcoin/bitcoin/pull/34374#discussion_r3984322784) and making it harder to introduce similar issues in the future. Also moved `KernelLogger` into the anonymous namespace in the final commit."
  },
  {
   "t": "2026-09-15T14:14:41Z",
   "kind": "force_push",
   "who": "stickies-v",
   "commit": "398b2e2423f77a2f168fefbc1d5190d70261acbc"
  },
  {
   "t": "2026-09-15T14:15:09Z",
   "kind": "comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "text": "Force-pushed to address merge conflict with #36207, no other changes."
  },
  {
   "t": "2026-09-16T20:38:37Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "src/kernel/bitcoinkernel.cpp",
   "commit": "cfd2312f0fdedcba6ba827ffa9d85d6c1d5c70f1",
   "in_reply_to": null,
   "text": "There is a narrow failure case here, related to the concerns about callbacks running under locks discussed in #36197.\n\nRegistration creates a temporary `Callback` while holding the logger mutex. That temporary owns `user_data`. If allocating the list node throws `std::bad_alloc`, the temporary is destroyed before the mutex is released, invoking `user_data_destroy_callback`.\n\nIf that callback destroys another logging connection, it tries to acquire the same mutex and deadlocks, preventing `btck_logging_connection_create` from returning `nullptr`.\n\nSuggestion:  the callback can be constructed before  the lock guard, so exception cleanup releases the mutex before destroying the user data, consistent with `UnregisterCallback`.\n\n```diff\ndiff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp\nindex 0a4b5e30cb..4f512df146 100644\n--- a/src/kernel/bitcoinkernel.cpp\n+++ b/src/kernel/bitcoinkernel.cpp\n@@ -564,8 +564,10 @@ public:\n\n KernelLogger::CallbackHandle KernelLogger::RegisterCallback(btck_LogCallback fn, UserData user_data)\n {\n+    // Destroy user_data after releasing m_mutex if insertion throws.\n+    Callback callback{fn, std::move(user_data)};\n     STDLOCK(m_mutex);\n-    m_callbacks.push_back({fn, std::move(user_data)});\n+    m_callbacks.push_back(std::move(callback));\n     return {*this, std::prev(m_callbacks.end())};\n }\n ```"
  },
  {
   "t": "2026-09-17T07:22:12Z",
   "kind": "force_push",
   "who": "stickies-v",
   "commit": "cfd2312f0fdedcba6ba827ffa9d85d6c1d5c70f1"
  },
  {
   "t": "2026-09-17T07:26:59Z",
   "kind": "review_comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "path": "src/kernel/bitcoinkernel.cpp",
   "commit": "cfd2312f0fdedcba6ba827ffa9d85d6c1d5c70f1",
   "in_reply_to": 4030551866,
   "text": "Good catch, fixed. You're right to point out we need to be careful with executing callbacks when under a lock. I think we should work to making `Log` execute the logging callbacks without lock too, but I think that's best kept for a follow-up.\n\n[quoted text omitted]\nEven worse, it can be triggered if the callback interacts in any way with the logger, including producing a logging statement. (_edit: of course, this is still a very narrow failure case, requiring allocation failure and an I think unusual path where cleaning up user data interacts with the kernel)_."
  },
  {
   "t": "2026-09-17T09:22:22Z",
   "kind": "comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "text": "Force-pushed to address a (hard to reach) deadlock issue [flagged](https://github.com/bitcoin/bitcoin/pull/34374#discussion_r4030551866) by @w0xlt"
  }
 ],
 "labels_log": [
  {
   "t": "2026-01-21T19:56:49Z",
   "action": "labeled",
   "label": "Validation",
   "who": "DrahtBot"
  },
  {
   "t": "2026-01-21T20:15:59Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-01-21T23:17:42Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-01-23T13:34:28Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-01-26T18:51:26Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-01-26T20:04:11Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-01-26T22:48:28Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-01-28T19:03:19Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-01-30T00:03:32Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-01-30T18:00:42Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-01-30T20:42:38Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-01-31T12:26:13Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-08T22:22:53Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-08T23:05:23Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-09T16:38:06Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-14T15:27:04Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-14T16:35:53Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-15T13:41:11Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-15T15:49:50Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-01-30T12:34:25Z",
   "kind": "convert_to_draft",
   "who": "stickies-v"
  },
  {
   "t": "2026-09-09T15:40:12Z",
   "kind": "renamed",
   "who": "stickies-v",
   "from": "kernel: use structured logging and simplify logging interface",
   "to": "kernel: use struct-based logging and simplify logging interface"
  },
  {
   "t": "2026-09-09T17:15:34Z",
   "kind": "ready_for_review",
   "who": "stickies-v"
  }
 ],
 "text_chars": 39226,
 "text_tokens_estimate": 9806,
 "changed_paths": [],
 "files": [],
 "test_lines": null,
 "git": null,
 "input_hash": "15505d3c78020a06",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}