2022-12-24 23:49:50 +00:00
// Copyright (c) 2019-2022 The Bitcoin Core developers
2019-09-19 13:59:49 -04:00
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
2022-08-18 11:03:28 +02:00
# include <init/common.h>
2019-09-19 13:59:49 -04:00
# include <logging.h>
# include <logging/timer.h>
2025-06-05 12:19:28 -04:00
# include <scheduler.h>
2025-06-05 13:42:03 -04:00
# include <test/util/logging.h>
2019-11-05 15:18:59 -05:00
# include <test/util/setup_common.h>
2025-06-05 13:37:27 -04:00
# include <tinyformat.h>
# include <util/fs.h>
# include <util/fs_helpers.h>
2022-03-02 16:34:53 +08:00
# include <util/string.h>
2019-09-19 13:59:49 -04:00
# include <chrono>
2022-03-02 16:34:53 +08:00
# include <fstream>
2025-06-05 12:19:28 -04:00
# include <future>
2025-06-05 13:42:03 -04:00
# include <ios>
2022-03-02 16:34:53 +08:00
# include <iostream>
2025-06-05 13:37:27 -04:00
# include <source_location>
2025-06-05 13:42:03 -04:00
# include <string>
2022-08-18 13:37:25 +02:00
# include <unordered_map>
2022-03-02 16:34:53 +08:00
# include <utility>
# include <vector>
2019-09-19 13:59:49 -04:00
# include <boost/test/unit_test.hpp>
2023-12-06 15:13:39 -05:00
using util : : SplitString ;
using util : : TrimString ;
2019-09-19 13:59:49 -04:00
BOOST_FIXTURE_TEST_SUITE ( logging_tests , BasicTestingSetup )
2022-08-18 11:03:28 +02:00
static void ResetLogger ( )
{
LogInstance ( ) . SetLogLevel ( BCLog : : DEFAULT_LOG_LEVEL ) ;
LogInstance ( ) . SetCategoryLogLevel ( { } ) ;
}
2022-03-02 16:34:53 +08:00
struct LogSetup : public BasicTestingSetup {
fs : : path prev_log_path ;
fs : : path tmp_log_path ;
bool prev_reopen_file ;
bool prev_print_to_file ;
bool prev_log_timestamps ;
bool prev_log_threadnames ;
bool prev_log_sourcelocations ;
2022-08-18 13:37:25 +02:00
std : : unordered_map < BCLog : : LogFlags , BCLog : : Level > prev_category_levels ;
BCLog : : Level prev_log_level ;
2022-03-02 16:34:53 +08:00
LogSetup ( ) : prev_log_path { LogInstance ( ) . m_file_path } ,
tmp_log_path { m_args . GetDataDirBase ( ) / " tmp_debug.log " } ,
prev_reopen_file { LogInstance ( ) . m_reopen_file } ,
prev_print_to_file { LogInstance ( ) . m_print_to_file } ,
prev_log_timestamps { LogInstance ( ) . m_log_timestamps } ,
prev_log_threadnames { LogInstance ( ) . m_log_threadnames } ,
2022-08-18 13:37:25 +02:00
prev_log_sourcelocations { LogInstance ( ) . m_log_sourcelocations } ,
prev_category_levels { LogInstance ( ) . CategoryLevels ( ) } ,
prev_log_level { LogInstance ( ) . LogLevel ( ) }
2022-03-02 16:34:53 +08:00
{
LogInstance ( ) . m_file_path = tmp_log_path ;
LogInstance ( ) . m_reopen_file = true ;
LogInstance ( ) . m_print_to_file = true ;
LogInstance ( ) . m_log_timestamps = false ;
LogInstance ( ) . m_log_threadnames = false ;
2022-08-18 13:37:25 +02:00
// Prevent tests from failing when the line number of the logs changes.
LogInstance ( ) . m_log_sourcelocations = false ;
LogInstance ( ) . SetLogLevel ( BCLog : : Level : : Debug ) ;
LogInstance ( ) . SetCategoryLogLevel ( { } ) ;
2025-07-23 22:06:37 +01:00
LogInstance ( ) . SetRateLimiting ( nullptr ) ;
2022-03-02 16:34:53 +08:00
}
~ LogSetup ( )
{
LogInstance ( ) . m_file_path = prev_log_path ;
LogPrintf ( " Sentinel log to reopen log file \n " ) ;
LogInstance ( ) . m_print_to_file = prev_print_to_file ;
LogInstance ( ) . m_reopen_file = prev_reopen_file ;
LogInstance ( ) . m_log_timestamps = prev_log_timestamps ;
LogInstance ( ) . m_log_threadnames = prev_log_threadnames ;
LogInstance ( ) . m_log_sourcelocations = prev_log_sourcelocations ;
2022-08-18 13:37:25 +02:00
LogInstance ( ) . SetLogLevel ( prev_log_level ) ;
LogInstance ( ) . SetCategoryLogLevel ( prev_category_levels ) ;
2025-07-23 22:06:37 +01:00
LogInstance ( ) . SetRateLimiting ( nullptr ) ;
2022-03-02 16:34:53 +08:00
}
} ;
2019-09-19 13:59:49 -04:00
BOOST_AUTO_TEST_CASE ( logging_timer )
{
2021-09-06 21:01:57 +02:00
auto micro_timer = BCLog : : Timer < std : : chrono : : microseconds > ( " tests " , " end_msg " ) ;
2023-01-03 12:28:52 +01:00
const std : : string_view result_prefix { " tests: msg ( " } ;
BOOST_CHECK_EQUAL ( micro_timer . LogMsg ( " msg " ) . substr ( 0 , result_prefix . size ( ) ) , result_prefix ) ;
2019-09-19 13:59:49 -04:00
}
2024-07-30 10:27:18 +02:00
BOOST_FIXTURE_TEST_CASE ( logging_LogPrintStr , LogSetup )
2022-03-02 16:34:53 +08:00
{
2022-08-18 13:37:25 +02:00
LogInstance ( ) . m_log_sourcelocations = true ;
2025-06-05 13:37:27 -04:00
struct Case {
std : : string msg ;
BCLog : : LogFlags category ;
BCLog : : Level level ;
std : : string prefix ;
std : : source_location loc ;
} ;
std : : vector < Case > cases = {
{ " foo1: bar1 " , BCLog : : NET , BCLog : : Level : : Debug , " [net] " , std : : source_location : : current ( ) } ,
{ " foo2: bar2 " , BCLog : : NET , BCLog : : Level : : Info , " [net:info] " , std : : source_location : : current ( ) } ,
{ " foo3: bar3 " , BCLog : : ALL , BCLog : : Level : : Debug , " [debug] " , std : : source_location : : current ( ) } ,
{ " foo4: bar4 " , BCLog : : ALL , BCLog : : Level : : Info , " " , std : : source_location : : current ( ) } ,
{ " foo5: bar5 " , BCLog : : NONE , BCLog : : Level : : Debug , " [debug] " , std : : source_location : : current ( ) } ,
{ " foo6: bar6 " , BCLog : : NONE , BCLog : : Level : : Info , " " , std : : source_location : : current ( ) } ,
} ;
std : : vector < std : : string > expected ;
for ( auto & [ msg , category , level , prefix , loc ] : cases ) {
expected . push_back ( tfm : : format ( " [%s:%s] [%s] %s%s " , util : : RemovePrefix ( loc . file_name ( ) , " ./ " ) , loc . line ( ) , loc . function_name ( ) , prefix , msg ) ) ;
2025-06-05 13:42:03 -04:00
LogInstance ( ) . LogPrintStr ( msg , std : : move ( loc ) , category , level , /*should_ratelimit=*/ false ) ;
2025-06-05 13:37:27 -04:00
}
2022-03-02 16:34:53 +08:00
std : : ifstream file { tmp_log_path } ;
std : : vector < std : : string > log_lines ;
for ( std : : string log ; std : : getline ( file , log ) ; ) {
log_lines . push_back ( log ) ;
}
BOOST_CHECK_EQUAL_COLLECTIONS ( log_lines . begin ( ) , log_lines . end ( ) , expected . begin ( ) , expected . end ( ) ) ;
}
2023-08-22 13:23:38 +10:00
BOOST_FIXTURE_TEST_CASE ( logging_LogPrintMacrosDeprecated , LogSetup )
2022-03-02 16:34:53 +08:00
{
LogPrintf ( " foo5: %s \n " , " bar5 " ) ;
2023-08-22 13:23:38 +10:00
LogPrintLevel ( BCLog : : NET , BCLog : : Level : : Trace , " foo4: %s \n " , " bar4 " ) ; // not logged
2022-05-25 11:31:58 +02:00
LogPrintLevel ( BCLog : : NET , BCLog : : Level : : Debug , " foo7: %s \n " , " bar7 " ) ;
LogPrintLevel ( BCLog : : NET , BCLog : : Level : : Info , " foo8: %s \n " , " bar8 " ) ;
LogPrintLevel ( BCLog : : NET , BCLog : : Level : : Warning , " foo9: %s \n " , " bar9 " ) ;
LogPrintLevel ( BCLog : : NET , BCLog : : Level : : Error , " foo10: %s \n " , " bar10 " ) ;
2022-03-02 16:34:53 +08:00
std : : ifstream file { tmp_log_path } ;
std : : vector < std : : string > log_lines ;
for ( std : : string log ; std : : getline ( file , log ) ; ) {
log_lines . push_back ( log ) ;
}
std : : vector < std : : string > expected = {
" foo5: bar5 " ,
2023-08-22 14:17:53 +10:00
" [net] foo7: bar7 " ,
2022-03-02 16:34:53 +08:00
" [net:info] foo8: bar8 " ,
" [net:warning] foo9: bar9 " ,
2022-05-25 18:26:54 +02:00
" [net:error] foo10: bar10 " ,
} ;
2022-03-02 16:34:53 +08:00
BOOST_CHECK_EQUAL_COLLECTIONS ( log_lines . begin ( ) , log_lines . end ( ) , expected . begin ( ) , expected . end ( ) ) ;
}
2023-08-22 13:23:38 +10:00
BOOST_FIXTURE_TEST_CASE ( logging_LogPrintMacros , LogSetup )
{
2024-09-19 17:09:10 +02:00
LogTrace ( BCLog : : NET , " foo6: %s " , " bar6 " ) ; // not logged
LogDebug ( BCLog : : NET , " foo7: %s " , " bar7 " ) ;
LogInfo ( " foo8: %s " , " bar8 " ) ;
LogWarning ( " foo9: %s " , " bar9 " ) ;
LogError ( " foo10: %s " , " bar10 " ) ;
2023-08-22 13:23:38 +10:00
std : : ifstream file { tmp_log_path } ;
std : : vector < std : : string > log_lines ;
for ( std : : string log ; std : : getline ( file , log ) ; ) {
log_lines . push_back ( log ) ;
}
std : : vector < std : : string > expected = {
" [net] foo7: bar7 " ,
" foo8: bar8 " ,
" [warning] foo9: bar9 " ,
" [error] foo10: bar10 " ,
} ;
BOOST_CHECK_EQUAL_COLLECTIONS ( log_lines . begin ( ) , log_lines . end ( ) , expected . begin ( ) , expected . end ( ) ) ;
}
2022-03-02 16:34:53 +08:00
BOOST_FIXTURE_TEST_CASE ( logging_LogPrintMacros_CategoryName , LogSetup )
{
LogInstance ( ) . EnableCategory ( BCLog : : LogFlags : : ALL ) ;
2022-08-18 13:37:25 +02:00
const auto concatenated_category_names = LogInstance ( ) . LogCategoriesString ( ) ;
2022-03-02 16:34:53 +08:00
std : : vector < std : : pair < BCLog : : LogFlags , std : : string > > expected_category_names ;
2022-08-18 13:37:25 +02:00
const auto category_names = SplitString ( concatenated_category_names , ' , ' ) ;
2022-03-02 16:34:53 +08:00
for ( const auto & category_name : category_names ) {
2022-08-18 13:37:25 +02:00
BCLog : : LogFlags category ;
2022-03-02 16:34:53 +08:00
const auto trimmed_category_name = TrimString ( category_name ) ;
2022-08-18 13:37:25 +02:00
BOOST_REQUIRE ( GetLogCategory ( category , trimmed_category_name ) ) ;
2022-03-02 16:34:53 +08:00
expected_category_names . emplace_back ( category , trimmed_category_name ) ;
}
std : : vector < std : : string > expected ;
for ( const auto & [ category , name ] : expected_category_names ) {
2024-08-08 10:49:13 +02:00
LogDebug ( category , " foo: %s \n " , " bar " ) ;
2022-03-02 16:34:53 +08:00
std : : string expected_log = " [ " ;
expected_log + = name ;
expected_log + = " ] foo: bar " ;
expected . push_back ( expected_log ) ;
}
std : : ifstream file { tmp_log_path } ;
std : : vector < std : : string > log_lines ;
for ( std : : string log ; std : : getline ( file , log ) ; ) {
log_lines . push_back ( log ) ;
}
BOOST_CHECK_EQUAL_COLLECTIONS ( log_lines . begin ( ) , log_lines . end ( ) , expected . begin ( ) , expected . end ( ) ) ;
}
2022-08-18 11:02:54 +02:00
BOOST_FIXTURE_TEST_CASE ( logging_SeverityLevels , LogSetup )
{
LogInstance ( ) . EnableCategory ( BCLog : : LogFlags : : ALL ) ;
Create BCLog::Level::Trace log severity level
for verbose log messages for development or debugging only, as bitcoind may run
more slowly, that are more granular/frequent than the Debug log level, i.e. for
very high-frequency, low-level messages to be logged distinctly from
higher-level, less-frequent debug logging that could still be usable in production.
An example would be to log higher-level peer events (connection, disconnection,
misbehavior, eviction) as Debug, versus Trace for low-level, high-volume p2p
messages in the BCLog::NET category. This will enable the user to log only the
former without the latter, in order to focus on high-level peer management events.
With respect to the name, "trace" is suggested as the most granular level
in resources like the following:
- https://sematext.com/blog/logging-levels
- https://howtodoinjava.com/log4j2/logging-levels
Update the test framework and add test coverage.
2022-06-01 13:44:59 +02:00
LogInstance ( ) . SetLogLevel ( BCLog : : Level : : Debug ) ;
2022-08-18 11:02:54 +02:00
LogInstance ( ) . SetCategoryLogLevel ( /*category_str=*/ " net " , /*level_str=*/ " info " ) ;
// Global log level
LogPrintLevel ( BCLog : : HTTP , BCLog : : Level : : Info , " foo1: %s \n " , " bar1 " ) ;
Create BCLog::Level::Trace log severity level
for verbose log messages for development or debugging only, as bitcoind may run
more slowly, that are more granular/frequent than the Debug log level, i.e. for
very high-frequency, low-level messages to be logged distinctly from
higher-level, less-frequent debug logging that could still be usable in production.
An example would be to log higher-level peer events (connection, disconnection,
misbehavior, eviction) as Debug, versus Trace for low-level, high-volume p2p
messages in the BCLog::NET category. This will enable the user to log only the
former without the latter, in order to focus on high-level peer management events.
With respect to the name, "trace" is suggested as the most granular level
in resources like the following:
- https://sematext.com/blog/logging-levels
- https://howtodoinjava.com/log4j2/logging-levels
Update the test framework and add test coverage.
2022-06-01 13:44:59 +02:00
LogPrintLevel ( BCLog : : MEMPOOL , BCLog : : Level : : Trace , " foo2: %s. This log level is lower than the global one. \n " , " bar2 " ) ;
2022-08-18 11:02:54 +02:00
LogPrintLevel ( BCLog : : VALIDATION , BCLog : : Level : : Warning , " foo3: %s \n " , " bar3 " ) ;
LogPrintLevel ( BCLog : : RPC , BCLog : : Level : : Error , " foo4: %s \n " , " bar4 " ) ;
// Category-specific log level
LogPrintLevel ( BCLog : : NET , BCLog : : Level : : Warning , " foo5: %s \n " , " bar5 " ) ;
Create BCLog::Level::Trace log severity level
for verbose log messages for development or debugging only, as bitcoind may run
more slowly, that are more granular/frequent than the Debug log level, i.e. for
very high-frequency, low-level messages to be logged distinctly from
higher-level, less-frequent debug logging that could still be usable in production.
An example would be to log higher-level peer events (connection, disconnection,
misbehavior, eviction) as Debug, versus Trace for low-level, high-volume p2p
messages in the BCLog::NET category. This will enable the user to log only the
former without the latter, in order to focus on high-level peer management events.
With respect to the name, "trace" is suggested as the most granular level
in resources like the following:
- https://sematext.com/blog/logging-levels
- https://howtodoinjava.com/log4j2/logging-levels
Update the test framework and add test coverage.
2022-06-01 13:44:59 +02:00
LogPrintLevel ( BCLog : : NET , BCLog : : Level : : Debug , " foo6: %s. This log level is the same as the global one but lower than the category-specific one, which takes precedence. \n " , " bar6 " ) ;
2022-08-18 11:02:54 +02:00
LogPrintLevel ( BCLog : : NET , BCLog : : Level : : Error , " foo7: %s \n " , " bar7 " ) ;
std : : vector < std : : string > expected = {
" [http:info] foo1: bar1 " ,
" [validation:warning] foo3: bar3 " ,
" [rpc:error] foo4: bar4 " ,
" [net:warning] foo5: bar5 " ,
" [net:error] foo7: bar7 " ,
} ;
std : : ifstream file { tmp_log_path } ;
std : : vector < std : : string > log_lines ;
for ( std : : string log ; std : : getline ( file , log ) ; ) {
log_lines . push_back ( log ) ;
}
BOOST_CHECK_EQUAL_COLLECTIONS ( log_lines . begin ( ) , log_lines . end ( ) , expected . begin ( ) , expected . end ( ) ) ;
}
2022-08-18 11:03:28 +02:00
BOOST_FIXTURE_TEST_CASE ( logging_Conf , LogSetup )
{
// Set global log level
{
ResetLogger ( ) ;
ArgsManager args ;
args . AddArg ( " -loglevel " , " ... " , ArgsManager : : ALLOW_ANY , OptionsCategory : : DEBUG_TEST ) ;
const char * argv_test [ ] = { " bitcoind " , " -loglevel=debug " } ;
std : : string err ;
BOOST_REQUIRE ( args . ParseParameters ( 2 , argv_test , err ) ) ;
2023-05-12 00:58:35 +02:00
auto result = init : : SetLoggingLevel ( args ) ;
BOOST_REQUIRE ( result ) ;
2022-08-18 11:03:28 +02:00
BOOST_CHECK_EQUAL ( LogInstance ( ) . LogLevel ( ) , BCLog : : Level : : Debug ) ;
}
// Set category-specific log level
{
ResetLogger ( ) ;
ArgsManager args ;
args . AddArg ( " -loglevel " , " ... " , ArgsManager : : ALLOW_ANY , OptionsCategory : : DEBUG_TEST ) ;
Create BCLog::Level::Trace log severity level
for verbose log messages for development or debugging only, as bitcoind may run
more slowly, that are more granular/frequent than the Debug log level, i.e. for
very high-frequency, low-level messages to be logged distinctly from
higher-level, less-frequent debug logging that could still be usable in production.
An example would be to log higher-level peer events (connection, disconnection,
misbehavior, eviction) as Debug, versus Trace for low-level, high-volume p2p
messages in the BCLog::NET category. This will enable the user to log only the
former without the latter, in order to focus on high-level peer management events.
With respect to the name, "trace" is suggested as the most granular level
in resources like the following:
- https://sematext.com/blog/logging-levels
- https://howtodoinjava.com/log4j2/logging-levels
Update the test framework and add test coverage.
2022-06-01 13:44:59 +02:00
const char * argv_test [ ] = { " bitcoind " , " -loglevel=net:trace " } ;
2022-08-18 11:03:28 +02:00
std : : string err ;
BOOST_REQUIRE ( args . ParseParameters ( 2 , argv_test , err ) ) ;
2023-05-12 00:58:35 +02:00
auto result = init : : SetLoggingLevel ( args ) ;
BOOST_REQUIRE ( result ) ;
2022-08-18 11:03:28 +02:00
BOOST_CHECK_EQUAL ( LogInstance ( ) . LogLevel ( ) , BCLog : : DEFAULT_LOG_LEVEL ) ;
const auto & category_levels { LogInstance ( ) . CategoryLevels ( ) } ;
const auto net_it { category_levels . find ( BCLog : : LogFlags : : NET ) } ;
BOOST_REQUIRE ( net_it ! = category_levels . end ( ) ) ;
Create BCLog::Level::Trace log severity level
for verbose log messages for development or debugging only, as bitcoind may run
more slowly, that are more granular/frequent than the Debug log level, i.e. for
very high-frequency, low-level messages to be logged distinctly from
higher-level, less-frequent debug logging that could still be usable in production.
An example would be to log higher-level peer events (connection, disconnection,
misbehavior, eviction) as Debug, versus Trace for low-level, high-volume p2p
messages in the BCLog::NET category. This will enable the user to log only the
former without the latter, in order to focus on high-level peer management events.
With respect to the name, "trace" is suggested as the most granular level
in resources like the following:
- https://sematext.com/blog/logging-levels
- https://howtodoinjava.com/log4j2/logging-levels
Update the test framework and add test coverage.
2022-06-01 13:44:59 +02:00
BOOST_CHECK_EQUAL ( net_it - > second , BCLog : : Level : : Trace ) ;
2022-08-18 11:03:28 +02:00
}
// Set both global log level and category-specific log level
{
ResetLogger ( ) ;
ArgsManager args ;
args . AddArg ( " -loglevel " , " ... " , ArgsManager : : ALLOW_ANY , OptionsCategory : : DEBUG_TEST ) ;
Create BCLog::Level::Trace log severity level
for verbose log messages for development or debugging only, as bitcoind may run
more slowly, that are more granular/frequent than the Debug log level, i.e. for
very high-frequency, low-level messages to be logged distinctly from
higher-level, less-frequent debug logging that could still be usable in production.
An example would be to log higher-level peer events (connection, disconnection,
misbehavior, eviction) as Debug, versus Trace for low-level, high-volume p2p
messages in the BCLog::NET category. This will enable the user to log only the
former without the latter, in order to focus on high-level peer management events.
With respect to the name, "trace" is suggested as the most granular level
in resources like the following:
- https://sematext.com/blog/logging-levels
- https://howtodoinjava.com/log4j2/logging-levels
Update the test framework and add test coverage.
2022-06-01 13:44:59 +02:00
const char * argv_test [ ] = { " bitcoind " , " -loglevel=debug " , " -loglevel=net:trace " , " -loglevel=http:info " } ;
2022-08-18 11:03:28 +02:00
std : : string err ;
BOOST_REQUIRE ( args . ParseParameters ( 4 , argv_test , err ) ) ;
2023-05-12 00:58:35 +02:00
auto result = init : : SetLoggingLevel ( args ) ;
BOOST_REQUIRE ( result ) ;
2022-08-18 11:03:28 +02:00
BOOST_CHECK_EQUAL ( LogInstance ( ) . LogLevel ( ) , BCLog : : Level : : Debug ) ;
const auto & category_levels { LogInstance ( ) . CategoryLevels ( ) } ;
BOOST_CHECK_EQUAL ( category_levels . size ( ) , 2 ) ;
const auto net_it { category_levels . find ( BCLog : : LogFlags : : NET ) } ;
BOOST_CHECK ( net_it ! = category_levels . end ( ) ) ;
Create BCLog::Level::Trace log severity level
for verbose log messages for development or debugging only, as bitcoind may run
more slowly, that are more granular/frequent than the Debug log level, i.e. for
very high-frequency, low-level messages to be logged distinctly from
higher-level, less-frequent debug logging that could still be usable in production.
An example would be to log higher-level peer events (connection, disconnection,
misbehavior, eviction) as Debug, versus Trace for low-level, high-volume p2p
messages in the BCLog::NET category. This will enable the user to log only the
former without the latter, in order to focus on high-level peer management events.
With respect to the name, "trace" is suggested as the most granular level
in resources like the following:
- https://sematext.com/blog/logging-levels
- https://howtodoinjava.com/log4j2/logging-levels
Update the test framework and add test coverage.
2022-06-01 13:44:59 +02:00
BOOST_CHECK_EQUAL ( net_it - > second , BCLog : : Level : : Trace ) ;
2022-08-18 11:03:28 +02:00
const auto http_it { category_levels . find ( BCLog : : LogFlags : : HTTP ) } ;
BOOST_CHECK ( http_it ! = category_levels . end ( ) ) ;
BOOST_CHECK_EQUAL ( http_it - > second , BCLog : : Level : : Info ) ;
}
}
2025-06-05 12:19:28 -04:00
void MockForwardAndSync ( CScheduler & scheduler , std : : chrono : : seconds duration )
{
scheduler . MockForward ( duration ) ;
std : : promise < void > promise ;
scheduler . scheduleFromNow ( [ & promise ] { promise . set_value ( ) ; } , 0 ms ) ;
promise . get_future ( ) . wait ( ) ;
}
BOOST_AUTO_TEST_CASE ( logging_log_rate_limiter )
{
CScheduler scheduler { } ;
scheduler . m_service_thread = std : : thread ( [ & scheduler ] { scheduler . serviceQueue ( ) ; } ) ;
uint64_t max_bytes { 1024 } ;
auto reset_window { 1 min } ;
auto sched_func = [ & scheduler ] ( auto func , auto window ) { scheduler . scheduleEvery ( std : : move ( func ) , window ) ; } ;
2025-07-23 22:06:37 +01:00
auto limiter_ { BCLog : : LogRateLimiter : : Create ( sched_func , max_bytes , reset_window ) } ;
auto & limiter { * limiter_ } ;
2025-06-05 12:19:28 -04:00
using Status = BCLog : : LogRateLimiter : : Status ;
auto source_loc_1 { std : : source_location : : current ( ) } ;
auto source_loc_2 { std : : source_location : : current ( ) } ;
// A fresh limiter should not have any suppressions
BOOST_CHECK ( ! limiter . SuppressionsActive ( ) ) ;
// Resetting an unused limiter is fine
limiter . Reset ( ) ;
BOOST_CHECK ( ! limiter . SuppressionsActive ( ) ) ;
// No suppression should happen until more than max_bytes have been consumed
BOOST_CHECK_EQUAL ( limiter . Consume ( source_loc_1 , std : : string ( max_bytes - 1 , ' a ' ) ) , Status : : UNSUPPRESSED ) ;
BOOST_CHECK_EQUAL ( limiter . Consume ( source_loc_1 , " a " ) , Status : : UNSUPPRESSED ) ;
BOOST_CHECK ( ! limiter . SuppressionsActive ( ) ) ;
BOOST_CHECK_EQUAL ( limiter . Consume ( source_loc_1 , " a " ) , Status : : NEWLY_SUPPRESSED ) ;
BOOST_CHECK ( limiter . SuppressionsActive ( ) ) ;
BOOST_CHECK_EQUAL ( limiter . Consume ( source_loc_1 , " a " ) , Status : : STILL_SUPPRESSED ) ;
BOOST_CHECK ( limiter . SuppressionsActive ( ) ) ;
// Location 2 should not be affected by location 1's suppression
BOOST_CHECK_EQUAL ( limiter . Consume ( source_loc_2 , std : : string ( max_bytes , ' a ' ) ) , Status : : UNSUPPRESSED ) ;
BOOST_CHECK_EQUAL ( limiter . Consume ( source_loc_2 , " a " ) , Status : : NEWLY_SUPPRESSED ) ;
BOOST_CHECK ( limiter . SuppressionsActive ( ) ) ;
// After reset_window time has passed, all suppressions should be cleared.
MockForwardAndSync ( scheduler , reset_window ) ;
BOOST_CHECK ( ! limiter . SuppressionsActive ( ) ) ;
BOOST_CHECK_EQUAL ( limiter . Consume ( source_loc_1 , std : : string ( max_bytes , ' a ' ) ) , Status : : UNSUPPRESSED ) ;
BOOST_CHECK_EQUAL ( limiter . Consume ( source_loc_2 , std : : string ( max_bytes , ' a ' ) ) , Status : : UNSUPPRESSED ) ;
scheduler . stop ( ) ;
}
BOOST_AUTO_TEST_CASE ( logging_log_limit_stats )
{
2025-07-18 10:18:59 -04:00
BCLog : : LogRateLimiter : : Stats stats ( BCLog : : RATELIMIT_MAX_BYTES ) ;
2025-06-05 12:19:28 -04:00
2025-07-18 10:18:59 -04:00
// Check that stats gets initialized correctly.
BOOST_CHECK_EQUAL ( stats . m_available_bytes , BCLog : : RATELIMIT_MAX_BYTES ) ;
BOOST_CHECK_EQUAL ( stats . m_dropped_bytes , uint64_t { 0 } ) ;
2025-06-05 12:19:28 -04:00
2025-07-18 10:18:59 -04:00
const uint64_t MESSAGE_SIZE { BCLog : : RATELIMIT_MAX_BYTES / 2 } ;
BOOST_CHECK ( stats . Consume ( MESSAGE_SIZE ) ) ;
BOOST_CHECK_EQUAL ( stats . m_available_bytes , BCLog : : RATELIMIT_MAX_BYTES - MESSAGE_SIZE ) ;
BOOST_CHECK_EQUAL ( stats . m_dropped_bytes , uint64_t { 0 } ) ;
2025-06-05 12:19:28 -04:00
2025-07-18 10:18:59 -04:00
BOOST_CHECK ( stats . Consume ( MESSAGE_SIZE ) ) ;
BOOST_CHECK_EQUAL ( stats . m_available_bytes , BCLog : : RATELIMIT_MAX_BYTES - MESSAGE_SIZE * 2 ) ;
BOOST_CHECK_EQUAL ( stats . m_dropped_bytes , uint64_t { 0 } ) ;
2025-06-05 12:19:28 -04:00
2025-07-18 10:18:59 -04:00
// Consuming more bytes after already having consumed RATELIMIT_MAX_BYTES should fail.
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 } ) ;
2025-06-05 12:19:28 -04:00
}
2025-06-05 13:42:03 -04:00
void LogFromLocation ( int location , std : : string message )
{
switch ( location ) {
case 0 :
LogInfo ( " %s \n " , message ) ;
break ;
case 1 :
LogInfo ( " %s \n " , message ) ;
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 ) ; } ;
2025-07-23 22:06:37 +01:00
LogInstance ( ) . SetRateLimiting ( BCLog : : LogRateLimiter : : Create ( sched_func , 1024 * 1024 , 20 s ) ) ;
2025-06-05 13:42:03 -04:00
// 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 " ) ;
log_file_size = GetFileSize ( log_path ) ;
for ( int i = 0 ; i < 1024 ; + + i ) {
LogFromLocation ( 0 , log_message ) ;
}
BOOST_CHECK_MESSAGE ( log_file_size = = GetFileSize ( log_path ) , " all further logs from location 0 should be dropped " ) ;
BOOST_CHECK_THROW ( LogFromLocationAndExpect ( 1 , log_message , " Excessive logging detected " ) , std : : runtime_error ) ;
BOOST_CHECK_MESSAGE ( log_file_size < GetFileSize ( log_path ) , " location 1 should be unaffected by other locations " ) ;
log_file_size = GetFileSize ( log_path ) ;
{
ASSERT_DEBUG_LOG ( " Restarting logging " ) ;
MockForwardAndSync ( scheduler , 1 min ) ;
}
BOOST_CHECK_MESSAGE ( log_file_size < GetFileSize ( log_path ) , " the end of the suppression period should be logged " ) ;
BOOST_CHECK_THROW ( LogFromLocationAndExpect ( 1 , log_message , " Restarting logging " ) , std : : runtime_error ) ;
// Attempt to log 1MiB from location 2 and 1MiB from location 3. These exempt locations should be allowed to log
// without limit.
log_file_size = GetFileSize ( log_path ) ;
for ( int i = 0 ; i < 1024 ; + + i ) {
BOOST_CHECK_THROW ( LogFromLocationAndExpect ( 2 , log_message , " Excessive logging detected " ) , std : : runtime_error ) ;
}
BOOST_CHECK_MESSAGE ( log_file_size < GetFileSize ( log_path ) , " location 2 should be exempt from rate limiting " ) ;
log_file_size = GetFileSize ( log_path ) ;
for ( int i = 0 ; i < 1024 ; + + i ) {
BOOST_CHECK_THROW ( LogFromLocationAndExpect ( 3 , log_message , " Excessive logging detected " ) , std : : runtime_error ) ;
}
BOOST_CHECK_MESSAGE ( log_file_size < GetFileSize ( log_path ) , " location 3 should be exempt from rate limiting " ) ;
LogInstance ( ) . m_log_timestamps = prev_log_timestamps ;
LogInstance ( ) . m_log_sourcelocations = prev_log_sourcelocations ;
LogInstance ( ) . m_log_threadnames = prev_log_threadnames ;
scheduler . stop ( ) ;
LogInstance ( ) . SetRateLimiting ( nullptr ) ;
}
2019-09-19 13:59:49 -04:00
BOOST_AUTO_TEST_SUITE_END ( )