Repository: bitcoin/bitcoin
File: src/test/logging_tests.cpp (C++)

Code before:
        break;
    case 2:
        LogPrintLevel(BCLog::LogFlags::NONE, BCLog::Level::Info, "%s\n", message);
        break;
    case 3:
        LogPrintLevel(BCLog::LogFlags::ALL, BCLog::Level::Info, "%s\n", message);
        break;
    }
}

void LogFromLocationAndExpect(int location, std::string message, std::string expect)
{
    ASSERT_DEBUG_LOG(expect);
    LogFromLocation(location, message);
}

BOOST_FIXTURE_TEST_CASE(logging_filesize_rate_limit, LogSetup)
{
    bool prev_log_timestamps = LogInstance().m_log_timestamps;
    LogInstance().m_log_timestamps = false;
    bool prev_log_sourcelocations = LogInstance().m_log_sourcelocations;
    LogInstance().m_log_sourcelocations = false;
    bool prev_log_threadnames = LogInstance().m_log_threadnames;
    LogInstance().m_log_threadnames = false;

    CScheduler scheduler{};
    scheduler.m_service_thread = std::thread([&] { scheduler.serviceQueue(); });
    auto sched_func = [&scheduler](auto func, auto window) { scheduler.scheduleEvery(std::move(func), window); };
    auto limiter = std::make_unique<BCLog::LogRateLimiter>(sched_func, 1024 * 1024, 20s);
    LogInstance().SetRateLimiting(std::move(limiter));

    // Log 1024-character lines (1023 plus newline) to make the math simple.
    std::string log_message(1023, 'a');

    std::string utf8_path{LogInstance().m_file_path.utf8string()};
    const char* log_path{utf8_path.c_str()};

    // Use GetFileSize because fs::file_size may require a flush to be accurate.
    std::streamsize log_file_size{static_cast<std::streamsize>(GetFileSize(log_path))};

    // Logging 1 MiB should be allowed.
    for (int i = 0; i < 1024; ++i) {
        LogFromLocation(0, log_message);
    }
    BOOST_CHECK_MESSAGE(log_file_size < GetFileSize(log_path), "should be able to log 1 MiB from location 0");

    log_file_size = GetFileSize(log_path);

    BOOST_CHECK_NO_THROW(LogFromLocationAndExpect(0, log_message, "Excessive logging detected"));
    BOOST_CHECK_MESSAGE(log_file_size < GetFileSize(log_path), "the start of the suppression period should be logged");


Review comment (bug):
(source location is unrelated, just wanted to get into thread mode)
I wanted to add something to this [this thread](https://github.com/bitcoin/bitcoin/pull/32604#discussion_r2115526293).

I didn't review the original PR, but I would appreciate an `-ratelimitlogging` option for the sake of testing, undocumented / `DEBUG_ONLY` would be fine for me:
- already now, multiple functional tests hit the limit (`feature_taproot.py`, `p2p_headers_sync_with_minchainwork.py`, `wallet_avoidreuse.py`, `wallet_taproot.py`). Having suppressed logs could make it harder to debug these in case of Errors.
Rate logging could also be unset by default by the functional tests.
- In tests, you can do things more often that are assumed to be rare on mainnet and therefore are logged unconditionally: e.g. create long chains on regtest with [this log](https://github.com/bitcoin/bitcoin/blob/aef2dbb402763e49fadc37dc75dff87ff3a5c6b0/src/node/miner.cpp#L174). Sometimes you want to keep logs for these.
- This can also make printf-debugging bothersome for devs testing something (e.g. IBD with feature X and extra logging for it), who would need to use some workaround such as long, annoying `LogPrintLevel_ / should_ratelimit=false` statements or a category, which will also log other additional stuff.

Code after:
    BOOST_CHECK(!stats.Consume(500));
    BOOST_CHECK_EQUAL(stats.m_available_bytes, uint64_t{0});
    BOOST_CHECK_EQUAL(stats.m_dropped_bytes, uint64_t{500});
}

namespace {

enum class Location {
    INFO_1,
    INFO_2,
    DEBUG_LOG,
    INFO_NOLIMIT,
};

void LogFromLocation(Location location, const std::string& message) {
    switch (location) {
    case Location::INFO_1:
        LogInfo("%s\n", message);
        return;
    case Location::INFO_2:
        LogInfo("%s\n", message);
        return;
    case Location::DEBUG_LOG:
        LogDebug(BCLog::LogFlags::HTTP, "%s\n", message);
        return;
    case Location::INFO_NOLIMIT:
        LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Info, /*should_ratelimit=*/false, "%s\n", message);
        return;
    } // no default case, so the compiler can warn about missing cases
    assert(false);
}

/**
 * For a given `location` and `message`, ensure that the on-disk debug log behaviour resembles what
 * we'd expect it to be for `status` and `suppressions_active`.
 */
void TestLogFromLocation(Location location, const std::string& message,
                         BCLog::LogRateLimiter::Status status, bool suppressions_active,
                         std::source_location source = std::source_location::current())
{
    using Status = BCLog::LogRateLimiter::Status;
    if (!suppressions_active) assert(status == Status::UNSUPPRESSED); // developer error

    std::ofstream ofs(LogInstance().m_file_path, std::ios::out | std::ios::trunc); // clear debug log
    LogFromLocation(location, message);
    auto log_lines{ReadDebugLogLines()};

    BOOST_TEST_CONTEXT("TestLogFromLocation failed from " << source.file_name() << ":" << source.line())
    {
        if (status == Status::STILL_SUPPRESSED) {
            BOOST_CHECK_EQUAL(log_lines.size(), 0);