Bitcoin Core 32.99.0
P2P Digital Currency
Loading...
Searching...
No Matches
logging_tests.cpp
Go to the documentation of this file.
1// Copyright (c) 2019-present The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5#include <init/common.h>
6#include <logging.h>
7#include <logging/timer.h>
8#include <scheduler.h>
9#include <test/util/common.h>
10#include <test/util/logging.h>
12#include <tinyformat.h>
13#include <util/fs.h>
14#include <util/fs_helpers.h>
15#include <util/string.h>
16
17#include <boost/test/unit_test.hpp>
18
19#include <chrono>
20#include <fstream>
21#include <future>
22#include <ios>
23#include <iostream>
24#include <source_location>
25#include <string>
26#include <type_traits>
27#include <unordered_map>
28#include <utility>
29#include <vector>
30
33
34/* defined in logging.cpp */
35namespace BCLog {
36 std::string LogEscapeMessage(std::string_view str);
37}
38
39template <auto str>
40concept IsLogFormatString = requires { typename std::bool_constant<(util::log::FormatString<0>{str()}, true)>; };
41
42static_assert(IsLogFormatString<[] { return "format string without a trailing newline"; }>);
43static_assert(!IsLogFormatString<[] { return "format string ending in a newline\n"; }>);
44
46
52
53static std::vector<std::string> ReadDebugLogLines()
54{
55 std::vector<std::string> lines;
56 std::ifstream ifs{LogInstance().m_file_path.std_path()};
57 for (std::string line; std::getline(ifs, line);) {
58 lines.push_back(std::move(line));
59 }
60 return lines;
61}
62
63struct LogSetup : public BasicTestingSetup {
64 fs::path prev_log_path;
65 fs::path tmp_log_path;
71 std::unordered_map<BCLog::LogFlags, BCLog::Level> prev_category_levels;
74
76 tmp_log_path{m_args.GetDataDirBase() / "tmp_debug.log"},
77 prev_reopen_file{LogInstance().m_reopen_file},
78 prev_print_to_file{LogInstance().m_print_to_file},
79 prev_log_timestamps{LogInstance().m_log_timestamps},
80 prev_log_threadnames{LogInstance().m_log_threadnames},
81 prev_log_sourcelocations{LogInstance().m_log_sourcelocations},
82 prev_category_levels{LogInstance().CategoryLevels()},
83 prev_log_level{LogInstance().LogLevel()},
84 prev_category_mask{LogInstance().GetCategoryMask()}
85 {
91
92 // Prevent tests from failing when the line number of the logs changes.
94
95 LogInstance().SetLogLevel(BCLog::Level::Debug);
99 }
100
116};
117
119{
120 auto micro_timer = BCLog::Timer<std::chrono::microseconds>("tests", "end_msg");
121 const std::string_view result_prefix{"tests: msg ("};
122 BOOST_CHECK_EQUAL(micro_timer.LogMsg("msg").substr(0, result_prefix.size()), result_prefix);
123}
124
126{
127 // ASCII and UTF-8 must pass through unaltered.
128 BOOST_CHECK_EQUAL(BCLog::LogEscapeMessage("Valid log message貓"), "Valid log message貓");
129 // Newlines are escaped too, so a message can't forge log lines.
130 BOOST_CHECK_EQUAL(BCLog::LogEscapeMessage("Message\n with newlines\n"), R"(Message\x0a with newlines\x0a)");
131 // Other control characters are escaped in C syntax.
132 BOOST_CHECK_EQUAL(BCLog::LogEscapeMessage("\x01\x7f Corrupted log message\x0d"), R"(\x01\x7f Corrupted log message\x0d)");
133 // Embedded NULL characters are escaped too.
134 const std::string NUL("O\x00O", 3);
136}
137
139{
141
142 struct Case {
143 std::string msg;
144 BCLog::LogFlags category;
145 BCLog::Level level;
146 std::string prefix;
148 };
149
150 std::vector<Case> cases = {
151 {"foo1: bar1", BCLog::NET, BCLog::Level::Debug, "[net] ", SourceLocation{__func__}},
152 {"foo2: bar2", BCLog::NET, BCLog::Level::Info, "[net:info] ", SourceLocation{__func__}},
153 {"foo3: bar3", BCLog::ALL, BCLog::Level::Debug, "[debug] ", SourceLocation{__func__}},
154 {"foo4: bar4", BCLog::ALL, BCLog::Level::Info, "", SourceLocation{__func__}},
155 {"foo5: bar5", BCLog::NONE, BCLog::Level::Debug, "[debug] ", SourceLocation{__func__}},
156 {"foo6: bar6", BCLog::NONE, BCLog::Level::Info, "", SourceLocation{__func__}},
157 };
158
159 std::vector<std::string> expected;
160 for (auto& [msg, category, level, prefix, loc] : cases) {
161 expected.push_back(tfm::format("[%s:%s] [%s] %s%s", util::RemovePrefix(loc.file_name(), "./"), loc.line(), loc.function_name_short(), prefix, msg));
162 LogInstance().LogPrint({.category = category, .level = level, .should_ratelimit = false, .source_loc = std::move(loc), .message = msg});
163 }
164 std::vector<std::string> log_lines{ReadDebugLogLines()};
165 BOOST_CHECK_EQUAL_COLLECTIONS(log_lines.begin(), log_lines.end(), expected.begin(), expected.end());
166}
167
169{
171 LogTrace(BCLog::NET, "foo6: %s", "bar6"); // not logged
172 LogDebug(BCLog::NET, "foo7: %s", "bar7");
173 LogInfo("foo8: %s", "bar8");
174 LogWarning("foo9: %s", "bar9");
175 LogError("foo10: %s", "bar10");
176 std::vector<std::string> log_lines{ReadDebugLogLines()};
177 std::vector<std::string> expected = {
178 "[net] foo7: bar7",
179 "foo8: bar8",
180 "[warning] foo9: bar9",
181 "[error] foo10: bar10",
182 };
183 BOOST_CHECK_EQUAL_COLLECTIONS(log_lines.begin(), log_lines.end(), expected.begin(), expected.end());
184}
185
187{
190 std::vector<std::pair<BCLog::LogFlags, std::string>> expected_category_names;
191 const auto category_names = SplitString(concatenated_category_names, ',');
192 for (const auto& category_name : category_names) {
193 const auto trimmed_category_name = TrimString(category_name);
195 expected_category_names.emplace_back(category, trimmed_category_name);
196 }
197
198 std::vector<std::string> expected;
199 for (const auto& [category, name] : expected_category_names) {
200 LogDebug(category, "foo: %s\n", "bar");
201 std::string expected_log = "[";
203 expected_log += "] foo: bar";
204 expected.push_back(expected_log);
205 }
206
207 std::vector<std::string> log_lines{ReadDebugLogLines()};
208 BOOST_CHECK_EQUAL_COLLECTIONS(log_lines.begin(), log_lines.end(), expected.begin(), expected.end());
209}
210
212{
213 LogInstance().SetLogLevel(BCLog::Level::Debug);
215 LogInstance().SetCategoryLogLevel(/*category_str=*/"net", /*level_str=*/"info");
216
217 // Global log level
218 LogInfo("info_%s", 1);
219 LogTrace(BCLog::HTTP, "trace_%s. This log level is lower than the global one.", 2);
220 LogDebug(BCLog::HTTP, "debug_%s", 3);
221 LogWarning("warn_%s", 4);
222 LogError("err_%s", 5);
223
224 // Category-specific log level
225 LogDebug(BCLog::NET, "debug_%s. This log level is the same as the global one but lower than the category-specific one, which takes precedence.", 6);
226
227 std::vector<std::string> expected = {
228 "info_1",
229 "[http] debug_3",
230 "[warning] warn_4",
231 "[error] err_5",
232 };
233 std::vector<std::string> log_lines{ReadDebugLogLines()};
234 BOOST_CHECK_EQUAL_COLLECTIONS(log_lines.begin(), log_lines.end(), expected.begin(), expected.end());
235}
236
237namespace {
238void CheckLogWarnThenDebug(std::string_view message)
239{
240 LogWarnThenDebug(BCLog::NET, "warn_then_debug: %s", message);
241}
242} // namespace
243
245{
246 ResetLogger();
247 CheckLogWarnThenDebug("first");
248 CheckLogWarnThenDebug("suppressed");
250 CheckLogWarnThenDebug("debug");
251
252 std::vector log_lines{ReadDebugLogLines()};
253 std::vector<std::string> expected = {
254 "[warning] warn_then_debug: first",
255 "[net] warn_then_debug: debug",
256 };
257 BOOST_CHECK_EQUAL_COLLECTIONS(log_lines.begin(), log_lines.end(), expected.begin(), expected.end());
258}
259
261{
262 // Set global log level
263 {
264 ResetLogger();
267 const char* argv_test[] = {"bitcoind", "-loglevel=debug"};
268 std::string err;
270
271 auto result = init::SetLoggingLevel(args);
272 BOOST_REQUIRE(result);
273 BOOST_CHECK_EQUAL(LogInstance().LogLevel(), BCLog::Level::Debug);
274 }
275
276 // Set category-specific log level
277 {
278 ResetLogger();
281 const char* argv_test[] = {"bitcoind", "-loglevel=net:trace"};
282 std::string err;
284
285 auto result = init::SetLoggingLevel(args);
286 BOOST_REQUIRE(result);
288
292 BOOST_CHECK_EQUAL(net_it->second, BCLog::Level::Trace);
293 }
294
295 // Set both global log level and category-specific log level
296 {
297 ResetLogger();
300 const char* argv_test[] = {"bitcoind", "-loglevel=debug", "-loglevel=net:trace", "-loglevel=http:info"};
301 std::string err;
303
304 auto result = init::SetLoggingLevel(args);
305 BOOST_REQUIRE(result);
306 BOOST_CHECK_EQUAL(LogInstance().LogLevel(), BCLog::Level::Debug);
307
310
313 BOOST_CHECK_EQUAL(net_it->second, BCLog::Level::Trace);
314
317 BOOST_CHECK_EQUAL(http_it->second, BCLog::Level::Info);
318 }
319
320 // Removed categories (like "libevent") should not store a category-specific level
321 {
322 ResetLogger();
323 BOOST_CHECK(LogInstance().SetCategoryLogLevel(/*category_str=*/"libevent", /*level_str=*/"trace"));
324 BOOST_CHECK(LogInstance().CategoryLevels().empty());
325 }
326}
327
330
332 {
333 scheduler.m_service_thread = std::thread([this] { scheduler.serviceQueue(); });
334 }
336 {
337 scheduler.stop();
338 }
339 void MockForwardAndSync(std::chrono::seconds duration)
340 {
341 scheduler.MockForward(duration);
342 std::promise<void> promise;
343 scheduler.scheduleFromNow([&promise] { promise.set_value(); }, 0ms);
344 promise.get_future().wait();
345 }
346 std::shared_ptr<BCLog::LogRateLimiter> GetLimiter(size_t max_bytes, std::chrono::seconds window)
347 {
348 auto sched_func = [this](auto func, auto w) {
349 scheduler.scheduleEvery(std::move(func), w);
350 };
352 }
353};
354
356{
357 uint64_t max_bytes{1024};
358 auto reset_window{1min};
359 ScopedScheduler scheduler{};
360 auto limiter_{scheduler.GetLimiter(max_bytes, reset_window)};
361 auto& limiter{*Assert(limiter_)};
362
363 using Status = BCLog::LogRateLimiter::Status;
366
367 // A fresh limiter should not have any suppressions
368 BOOST_CHECK(!limiter.SuppressionsActive());
369
370 // Resetting an unused limiter is fine
371 limiter.Reset();
372 BOOST_CHECK(!limiter.SuppressionsActive());
373
374 // No suppression should happen until more than max_bytes have been consumed
375 BOOST_CHECK_EQUAL(limiter.Consume(source_loc_1, std::string(max_bytes - 1, 'a')), Status::UNSUPPRESSED);
376 BOOST_CHECK_EQUAL(limiter.Consume(source_loc_1, "a"), Status::UNSUPPRESSED);
377 BOOST_CHECK(!limiter.SuppressionsActive());
378 BOOST_CHECK_EQUAL(limiter.Consume(source_loc_1, "a"), Status::NEWLY_SUPPRESSED);
379 BOOST_CHECK(limiter.SuppressionsActive());
380 BOOST_CHECK_EQUAL(limiter.Consume(source_loc_1, "a"), Status::STILL_SUPPRESSED);
381 BOOST_CHECK(limiter.SuppressionsActive());
382
383 // Location 2 should not be affected by location 1's suppression
384 BOOST_CHECK_EQUAL(limiter.Consume(source_loc_2, std::string(max_bytes, 'a')), Status::UNSUPPRESSED);
385 BOOST_CHECK_EQUAL(limiter.Consume(source_loc_2, "a"), Status::NEWLY_SUPPRESSED);
386 BOOST_CHECK(limiter.SuppressionsActive());
387
388 // After reset_window time has passed, all suppressions should be cleared.
389 scheduler.MockForwardAndSync(reset_window);
390
391 BOOST_CHECK(!limiter.SuppressionsActive());
392 BOOST_CHECK_EQUAL(limiter.Consume(source_loc_1, std::string(max_bytes, 'a')), Status::UNSUPPRESSED);
393 BOOST_CHECK_EQUAL(limiter.Consume(source_loc_2, std::string(max_bytes, 'a')), Status::UNSUPPRESSED);
394}
395
418
419namespace {
420
421enum class Location {
422 INFO_1,
423 INFO_2,
424 DEBUG_LOG,
425 INFO_NOLIMIT,
426};
427
428void LogFromLocation(Location location, const std::string& message) {
429 switch (location) {
430 case Location::INFO_1:
431 LogInfo("%s\n", message);
432 return;
433 case Location::INFO_2:
434 LogInfo("%s\n", message);
435 return;
436 case Location::DEBUG_LOG:
437 LogDebug(BCLog::LogFlags::HTTP, "%s\n", message);
438 return;
439 case Location::INFO_NOLIMIT:
440 LogInfo(util::log::NO_RATE_LIMIT, "%s\n", message);
441 return;
442 } // no default case, so the compiler can warn about missing cases
443 assert(false);
444}
445
450void TestLogFromLocation(Location location, const std::string& message,
452 std::source_location source = std::source_location::current())
453{
454 BOOST_TEST_INFO_SCOPE("TestLogFromLocation called from " << source.file_name() << ":" << source.line());
455 using Status = BCLog::LogRateLimiter::Status;
456 if (!suppressions_active) assert(status == Status::UNSUPPRESSED); // developer error
457
458 std::ofstream ofs(LogInstance().m_file_path.std_path(), std::ios::out | std::ios::trunc); // clear debug log
459 LogFromLocation(location, message);
461 BOOST_TEST_INFO_SCOPE(log_lines.size() << " log_lines read: \n" << util::Join(log_lines, "\n"));
462
463 if (status == Status::STILL_SUPPRESSED) {
464 BOOST_CHECK_EQUAL(log_lines.size(), 0);
465 return;
466 }
467
468 if (status == Status::NEWLY_SUPPRESSED) {
470 BOOST_CHECK(log_lines[0].starts_with("[*] [warning] Excessive logging detected"));
471 log_lines.erase(log_lines.begin());
472 }
474 auto& payload{log_lines.back()};
475 BOOST_CHECK_EQUAL(suppressions_active, payload.starts_with("[*]"));
476 BOOST_CHECK(payload.ends_with(message));
477}
478
479} // namespace
480
482{
483 using Status = BCLog::LogRateLimiter::Status;
488
489 constexpr int64_t line_length{1024};
490 constexpr int64_t num_lines{10};
492 constexpr auto time_window{1h};
493
494 ScopedScheduler scheduler{};
495 auto limiter{scheduler.GetLimiter(bytes_quota, time_window)};
497
498 const std::string log_message(line_length - 1, 'a'); // subtract one for newline
499
500 for (int i = 0; i < num_lines; ++i) {
501 TestLogFromLocation(Location::INFO_1, log_message, Status::UNSUPPRESSED, /*suppressions_active=*/false);
502 }
503 TestLogFromLocation(Location::INFO_1, "a", Status::NEWLY_SUPPRESSED, /*suppressions_active=*/true);
504 TestLogFromLocation(Location::INFO_1, "b", Status::STILL_SUPPRESSED, /*suppressions_active=*/true);
505 TestLogFromLocation(Location::INFO_2, "c", Status::UNSUPPRESSED, /*suppressions_active=*/true);
506 {
507 scheduler.MockForwardAndSync(time_window);
508 BOOST_CHECK(ReadDebugLogLines().back().starts_with("[warning] Restarting logging"));
509 }
510 // Check that logging from previously suppressed location is unsuppressed again.
511 TestLogFromLocation(Location::INFO_1, log_message, Status::UNSUPPRESSED, /*suppressions_active=*/false);
512 // Check that conditional logging, and unconditional logging with should_ratelimit=false is
513 // not being ratelimited.
514 for (Location location : {Location::DEBUG_LOG, Location::INFO_NOLIMIT}) {
515 for (int i = 0; i < num_lines + 2; ++i) {
516 TestLogFromLocation(location, log_message, Status::UNSUPPRESSED, /*suppressions_active=*/false);
517 }
518 }
519}
520
ArgsManager & args
Definition bitcoind.cpp:280
#define Assert(val)
Identity function.
Definition check.h:119
bool ParseParameters(int argc, const char *const argv[], std::string &error) EXCLUSIVE_LOCKS_REQUIRED(!cs_args)
Definition args.cpp:176
@ ALLOW_ANY
disable validation
Definition args.h:109
void AddArg(const std::string &name, const std::string &help, unsigned int flags, const OptionsCategory &cat) EXCLUSIVE_LOCKS_REQUIRED(!cs_args)
Add argument.
Definition args.cpp:641
static std::shared_ptr< LogRateLimiter > Create(SchedulerFunction &&scheduler_func, uint64_t max_bytes, std::chrono::seconds reset_window)
Definition logging.cpp:390
Status
Suppression status of a source log location.
Definition logging.h:114
bool m_log_sourcelocations
Definition logging.h:174
void SetCategoryLogLevel(const std::unordered_map< LogFlags, Level > &levels) EXCLUSIVE_LOCKS_REQUIRED(!m_cs)
Definition logging.h:230
void SetLogLevel(Level level)
Definition logging.h:238
fs::path m_file_path
Definition logging.h:177
bool m_log_threadnames
Definition logging.h:173
void SetRateLimiting(std::shared_ptr< LogRateLimiter > limiter) EXCLUSIVE_LOCKS_REQUIRED(!m_cs)
Definition logging.h:210
static std::optional< BCLog::LogFlags > GetLogCategory(std::string_view str)
Return log flag if str parses as a log category.
Definition logging.cpp:228
void EnableCategory(LogFlags flag)
Definition logging.cpp:129
bool m_log_timestamps
Definition logging.h:171
std::atomic< bool > m_reopen_file
Definition logging.h:178
void LogPrint(util::log::Entry log_entry) EXCLUSIVE_LOCKS_REQUIRED(!m_cs)
Send an entry to the log output.
Definition logging.cpp:437
bool m_print_to_file
Definition logging.h:169
std::unordered_map< LogFlags, Level > CategoryLevels() const EXCLUSIVE_LOCKS_REQUIRED(!m_cs)
Definition logging.h:225
std::string LogCategoriesString() const
Returns a string with the log categories in alphabetical order.
Definition logging.h:254
void DisableCategory(LogFlags flag)
Definition logging.cpp:143
RAII-style object that outputs timing information to logs.
Definition timer.h:24
Simple class for background tasks that should be run periodically or once "after a while".
Definition scheduler.h:39
void MockForward(std::chrono::seconds delta_seconds) EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex)
Mock the scheduler to fast forward in time.
Definition scheduler.cpp:80
void serviceQueue() EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex)
Services the queue 'forever'.
Definition scheduler.cpp:23
void scheduleEvery(Function f, std::chrono::milliseconds delta) EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex)
Repeat f until the scheduler is stopped.
std::thread m_service_thread
Definition scheduler.h:44
void stop() EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex)
Tell any threads running serviceQueue to stop as soon as the current task is done.
Definition scheduler.h:79
void scheduleFromNow(Function f, std::chrono::milliseconds delta) EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex)
Call f once after the delta has passed.
Definition scheduler.h:52
Like std::source_location, but allowing to override the function name.
Definition log.h:24
BOOST_FIXTURE_TEST_SUITE(cuckoocache_tests, BasicTestingSetup)
Test Suite for CuckooCache.
BOOST_AUTO_TEST_SUITE_END()
Common init functions shared by bitcoin-node, bitcoin-wallet, etc.
#define LogWarning(...)
Definition log.h:137
#define LogInfo(...)
Definition log.h:136
#define LogError(...)
Definition log.h:138
#define LogWarnThenDebug(category,...)
Definition log.h:158
#define LogTrace(category,...)
Definition log.h:155
#define LogDebug(category,...)
Definition log.h:154
BCLog::Logger & LogInstance()
Definition logging.cpp:27
static std::vector< std::string > ReadDebugLogLines()
BOOST_AUTO_TEST_CASE(logging_timer)
BOOST_FIXTURE_TEST_CASE(logging_LogPrint, LogSetup)
static void ResetLogger()
std::string LogEscapeMessage(std::string_view str)
Belts and suspenders: make sure outgoing log messages don't contain potentially suspicious characters...
Definition logging.cpp:340
constexpr uint64_t RATELIMIT_MAX_BYTES
Definition logging.h:66
uint64_t CategoryMask
Definition categories.h:12
constexpr auto DEFAULT_LOG_LEVEL
Definition logging.h:64
util::Result< void > SetLoggingLevel(const ArgsManager &args)
Definition common.cpp:62
void format(std::ostream &out, FormatStringCheck< sizeof...(Args)> fmt, const Args &... args)
Format list of arguments to the stream according to given format string.
Level
Definition log.h:53
constexpr NoRateLimitTag NO_RATE_LIMIT
Definition log.h:51
std::vector< std::string > SplitString(std::string_view str, char sep)
Definition string.h:153
std::string TrimString(std::string_view str, std::string_view pattern=" \f\n\r\t\v")
Definition string.h:173
std::string RemovePrefix(std::string_view str, std::string_view prefix)
Definition string.h:194
auto Join(const C &container, const S &separator, UnaryOp unary_op)
Join all container items.
Definition string.h:209
#define BOOST_CHECK_EQUAL(v1, v2)
Definition object.cpp:17
#define BOOST_CHECK(expr)
Definition object.cpp:16
const char * prefix
Definition rest.cpp:1197
const char * name
Definition rest.cpp:71
const char * source
Keeps track of an individual source location and how many available bytes are left for logging from i...
Definition logging.h:75
uint64_t m_available_bytes
Remaining bytes.
Definition logging.h:77
bool Consume(uint64_t bytes)
Updates internal accounting and returns true if enough available_bytes were remaining.
Definition logging.cpp:588
uint64_t m_dropped_bytes
Number of bytes that were consumed but didn't fit in the available bytes.
Definition logging.h:79
Basic testing setup.
ArgsManager m_args
Test-specific arguments and settings.
fs::path tmp_log_path
bool prev_reopen_file
fs::path prev_log_path
std::unordered_map< BCLog::LogFlags, BCLog::Level > prev_category_levels
bool prev_print_to_file
BCLog::Level prev_log_level
bool prev_log_threadnames
bool prev_log_sourcelocations
bool prev_log_timestamps
BCLog::CategoryMask prev_category_mask
std::shared_ptr< BCLog::LogRateLimiter > GetLimiter(size_t max_bytes, std::chrono::seconds window)
void MockForwardAndSync(std::chrono::seconds duration)
A format string for a log entry.
Definition log.h:86
BOOST_CHECK_EQUAL_COLLECTIONS(R1L.begin(), R1L.end(), R1Array, R1Array+uint256::size())
constexpr auto Ticks(Dur2 d)
Helper to count the seconds of a duration/time_point.
Definition time.h:82
assert(!tx.IsCoinBase())