logging: make trace logging easily usable #35387

pull ryanofsky wants to merge 2 commits into bitcoin:master from ryanofsky:pr/loglevel changing 11 files +297 −49
  1. ryanofsky commented at 8:34 PM on May 26, 2026: contributor

    Problem: Currently it is difficult to enable trace logging.

    The -loglevel=trace setting works, but does nothing by default and needs to be combined with -debug settings to have an effect.

    Additionally, it is not possible to switch between debug and trace logging at runtime without restarting the node. These problems make trace logging less useful than it could be, effectively make existing trace logs undiscoverable, and making it not worthwhile to add new tracing.

    Solution: Fix these issues by allowing the -loglevel option to work without an accompanying -debug option, and by adding a matching loglevel RPC that allows log levels to be configured at runtime.

    Usage examples:

    bitcoin rpc loglevel                               # See current log levels
    bitcoin rpc loglevel trace                         # Set global level to -loglevel=trace
    bitcoin rpc loglevel debug                         # Set global level to -loglevel=debug
    bitcoin rpc loglevel info                          # Set global level to -loglevel=info
    bitcoin rpc loglevel libevent=info                 # Set category level to -loglevel=libevent:info
    bitcoin rpc loglevel trace net=debug libevent=info # Set global & category levels to -loglevel=trace,net:debug,libevent:info
    

    Compatibility: This PR is backwards compatible and doesn't change the -debug and -debugexclude options (which become synonyms for -loglevel=debug and -loglevel=info, respectively). The logging RPC is also mostly unchanged, except now it validates category names before updating log levels, instead of failing with half-applied changes. Also the logging RPC now only toggles between info and debug levels instead of trying to remember previously assigned levels, which was needlessly confusing.

  2. logging: make -loglevel work standalone without -debug
    Previously, -loglevel only set level thresholds but required -debug to
    be separately specified to actually enable log categories. This made
    -loglevel harder to use than necessary.
    
    After this change, -loglevel enables categories automatically:
    - A global level (-loglevel=debug) enables all categories at that level,
      replacing the need for -debug=1.
    - A per-category level (-loglevel=net:trace) enables that category,
      replacing the need for -debug=net.
    - Comma-separated entries in one argument are now supported
      (-loglevel=debug,net:trace).
    
    The three logging options now have a fixed processing order and clear
    precedence: -debug is processed first (lowest precedence), then
    -loglevel, then -debugexclude (highest precedence). This ordering holds
    regardless of the order arguments appear on the command line or in
    config files. Equivalences:
      -debug=1        ==  -loglevel=debug
      -debug=<cat>    ==  -loglevel=<cat>:debug
      -debugexclude=<cat>  ==  -loglevel=<cat>:info
    
    Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
    730e2a724c
  3. rpc: add loglevel RPC
    Add loglevel RPC to control log levels.
    
    Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
    828a0287fa
  4. DrahtBot commented at 8:34 PM on May 26, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

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

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

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

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #35355 (Use atomics for determining whether trace logging is enabled by ajtowns)
    • #35322 (logging: streamline Logger state and drop redundant methods by ryanofsky)
    • #35182 (Replace libevent with our own HTTP and socket-handling implementation by pinheadmz)
    • #34411 ([POC] Full Libevent removal by fanquake)
    • #34038 (logging: replace -loglevel with -trace, expose trace logging via RPC by ajtowns)
    • #31260 (scripted-diff: Type-safe settings retrieval by ryanofsky)
    • #17783 (common: Disallow calling IsArgSet() on ALLOW_LIST options by ryanofsky)
    • #17581 (refactor: Remove settings merge reverse precedence code by ryanofsky)
    • #17580 (refactor: Add ALLOW_LIST flags and enforce usage in CheckArgFlags by ryanofsky)
    • #17493 (util: Forbid ambiguous multiple assignments in config file by ryanofsky)

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

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

    LLM Linter (✨ experimental)

    Possible places where comparison-specific test macros should replace generic comparisons:

    • test/functional/feature_logging.py assert result['net'] == 'trace' / assert result['http'] == 'info' -> use assert_equal(result['net'], 'trace') and assert_equal(result['http'], 'info')
    • test/functional/feature_logging.py assert result['net'] == 'debug' / assert result['net'] == 'info' -> use assert_equal(...)
    • test/functional/feature_logging.py assert result['net'] == 'trace' / assert result['http'] == 'info' (later RPC checks) -> use assert_equal(...)
    • test/functional/feature_logging.py assert result['net'] == 'info' / assert result['http'] == 'trace' -> use assert_equal(...)
    • test/functional/feature_logging.py assert active == expected_active, f"Inconsistency for {cat}: ..." -> use assert_equal(active, expected_active, ...)

    <sup>2026-05-27 00:50:54</sup>

  5. DrahtBot added the label CI failed on May 26, 2026
  6. DrahtBot commented at 10:07 PM on May 26, 2026: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task macOS native, fuzz: https://github.com/bitcoin/bitcoin/actions/runs/26473571406/job/77953158807</sub> <sub>LLM reason (✨ experimental): CI failed because the fuzz target rpc crashed on an unknown RPC command loglevel (not listed in RPC_COMMANDS_SAFE_FOR_FUZZING/RPC_COMMANDS_NOT_SAFE_FOR_FUZZING), causing exit code -6.</sub>

    <details><summary>Hints</summary>

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

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

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

    • An intermittent issue.

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

    </details>

  7. ryanofsky force-pushed on May 27, 2026
  8. ryanofsky commented at 12:50 AM on May 27, 2026: contributor

    Updated 2e4ac6d568af2ae0bf3e81748ba1a63d0cdb71b8 -> 828a0287fa194b7b94e600a10e06e21f74434366 (pr/loglevel.2 -> pr/loglevel.3, compare)<!-- end --> to fix clang-tidy and other ci errors https://github.com/bitcoin/bitcoin/actions/runs/26473571406/job/77954549771?pr=35387

  9. DrahtBot removed the label CI failed on May 27, 2026
  10. in src/rpc/node.cpp:258 in 828a0287fa
     264 | +        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
     265 | +{
     266 | +    std::vector<std::pair<BCLog::LogFlags, BCLog::Level>> changes;
     267 | +
     268 | +    // Optional positional "level" param.
     269 | +    if (!request.params[0].isNull()) {
    


    stickies-v commented at 10:46 AM on May 27, 2026:

    nit: the MaybeArg helper is better suited here:

    <details> <summary>git diff on 828a0287fa</summary>

    diff --git a/src/rpc/node.cpp b/src/rpc/node.cpp
    index 6de4562495..88bf0404f9 100644
    --- a/src/rpc/node.cpp
    +++ b/src/rpc/node.cpp
    @@ -235,7 +235,7 @@ static RPCMethod loglevel()
                 "The valid log levels are: " + LogInstance().LogLevelsString() + "\n"
                 ,
                     {
    -                    {"all", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Log level to set for all categories."},
    +                    {"level", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Log level to set for all categories."},
                         {"categories", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "Per-category log levels.", std::move(category_args)},
                     },
                     RPCResult{
    @@ -255,21 +255,19 @@ static RPCMethod loglevel()
         std::vector<std::pair<BCLog::LogFlags, BCLog::Level>> changes;
     
         // Optional positional "level" param.
    -    if (!request.params[0].isNull()) {
    -        const std::string level_str = request.params[0].get_str();
    -        const auto level = BCLog::Logger::GetLogLevel(level_str);
    +    if (auto level_str{self.MaybeArg<std::string_view>("level")}) {
    +        const auto level = BCLog::Logger::GetLogLevel(*level_str);
             if (!level || *level > BCLog::Level::Info) {
    -            throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown log level \"" + level_str + "\". Valid values: " + LogInstance().LogLevelsString());
    +            throw JSONRPCError(RPC_INVALID_PARAMETER, tfm::format("unknown log level \"%s\". Valid values: %s", *level_str, LogInstance().LogLevelsString()));
             }
             changes.emplace_back(BCLog::ALL, *level);
         }
     
         // Named "categories" params: applied in the order they appear in the request.
         // Category names are validated by OBJ_NAMED_PARAMS, so GetLogCategory always succeeds here.
    -    if (!request.params[1].isNull()) {
    -        const UniValue& cats = request.params[1].get_obj();
    -        for (const std::string& cat : cats.getKeys()) {
    -            const std::string level_str = cats[cat].get_str();
    +    if (auto cats{self.MaybeArg<UniValue>("categories")}) {
    +        for (const std::string& cat : cats->getKeys()) {
    +            const std::string level_str = (*cats)[cat].get_str();
                 const auto level = BCLog::Logger::GetLogLevel(level_str);
                 if (!level || *level > BCLog::Level::Info) {
                     throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown log level \"" + level_str + "\". Valid values: " + LogInstance().LogLevelsString());
    
    

    </details>

  11. stickies-v commented at 10:46 AM on May 27, 2026: contributor

    This looks like an elegant approach, I think it's an intuitive RPC and -loglevel interface. The PR description should probably reference #34038 as an alternativate?

    I don't think we should have 2 parallel active logging RPC methods, so imo this PR should mark logging deprecated, and we should keep it around for a long time as it's probably heavily used.

  12. ryanofsky referenced this in commit af3e850146 on May 29, 2026

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-05-31 17:50 UTC

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