Skip to content

Commit 7281fac

Browse files
committed
Merge bitcoin/bitcoin#25614: Severity-based logging, step 2
9580480 Update debug logging section in the developer notes (Jon Atack) 1abaa31 Update -debug and -debugexclude help docs for severity level logging (Jon Atack) 45f9282 Create BCLog::Level::Trace log severity level (Jon Atack) 2a8712d Unit test coverage for -loglevel configuration option (klementtan) eb7bee5 Create -loglevel configuration option (klementtan) 98a1f9c Unit test coverage for log severity levels (klementtan) 9c7507b Create BCLog::Logger::LogLevelsString() helper function (klementtan) 8fe3457 Update LogAcceptCategory() and unit tests with log severity levels (klementtan) c2797cf Add BCLog::Logger::SetLogLevel()/SetCategoryLogLevel() for string inputs (klementtan) f6c0cc0 Add BCLog::Logger::m_category_log_levels data member and getter/setter (Jon Atack) 2978b38 Add BCLog::Logger::m_log_level data member and getter/setter (Jon Atack) f1379ae Simplify BCLog::Level enum class and LogLevelToStr() function (Jon Atack) Pull request description: This is an updated version of bitcoin/bitcoin#25287 and the next steps in parent PR #25203 implementing, with Klement Tan, user-configurable, per-category severity log levels based on an idea by John Newbery and refined in GitHub discussions by Wladimir Van der Laan and Marco Falke. - simplify the `BCLog::Level` enum class and the `LogLevelToStr()` function and add documentation - update the logging logic to filter logs by log level both globally and per-category - add a hidden `-loglevel` help-debug config option to allow testing setting the global or per-category severity level on startup for logging categories enabled with the `-debug` configuration option or the logging RPC (Klement Tan) - add a `trace` log severity level selectable by the user; the plan is for the current debug messages to become trace, LogPrint ones to become debug, and LogPrintf ones to become info, warning, or error ``` $ ./src/bitcoind -help-debug | grep -A10 loglevel -loglevel=<level>|<category>:<level> Set the global or per-category severity level for logging categories enabled with the -debug configuration option or the logging RPC: info, debug, trace (default=info); warning and error levels are always logged. If <category>:<level> is supplied, the setting will override the global one and may be specified multiple times to set multiple category-specific levels. <category> can be: addrman, bench, blockstorage, cmpctblock, coindb, estimatefee, http, i2p, ipc, leveldb, libevent, lock, mempool, mempoolrej, net, proxy, prune, qt, rand, reindex, rpc, selectcoins, tor, util, validation, walletdb, zmq. ``` See the individual commit messages for details. ACKs for top commit: jonatack: One final push per `git range-diff a5d5569 ce3c4c9 9580480` (should be trivial to re-ACK) to ensure this pull changes no default behavior in any way for users or the tests/CI in order to be completely v24 compatible, to update the unit test setup in general, and to update the debug logging section in the developer notes. klementtan: reACK bitcoin/bitcoin@9580480 1440000bytes: reACK bitcoin/bitcoin@9580480 vasild: ACK 9580480 dunxen: reACK 9580480 brunoerg: reACK 9580480 Tree-SHA512: 476a638e0581f40b5d058a9992691722e8b546471ec85e07cbc990798d1197fbffbd02e1b3d081b4978404e07a428378cdc8e159c0004b81f58be7fb01b7cba0
2 parents 7921026 + 9580480 commit 7281fac

File tree

10 files changed

+270
-36
lines changed

10 files changed

+270
-36
lines changed

doc/developer-notes.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -386,8 +386,10 @@ Run configure with the `--enable-gprof` option, then make.
386386
If the code is behaving strangely, take a look in the `debug.log` file in the data directory;
387387
error and debugging messages are written there.
388388

389-
The `-debug=...` command-line option controls debugging; running with just `-debug` or `-debug=1` will turn
390-
on all categories (and give you a very large `debug.log` file).
389+
Debug logging can be enabled on startup with the `-debug` and `-loglevel`
390+
configuration options and toggled while bitcoind is running with the `logging`
391+
RPC. For instance, launching bitcoind with `-debug` or `-debug=1` will turn on
392+
all log categories and `-loglevel=trace` will turn on all log severity levels.
391393

392394
The Qt code routes `qDebug()` output to `debug.log` under category "qt": run with `-debug=qt`
393395
to see it.

src/init.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,7 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb
915915

916916
// ********************************************************* Step 3: parameter-to-internal-flags
917917
init::SetLoggingCategories(args);
918+
init::SetLoggingLevel(args);
918919

919920
fCheckBlockIndex = args.GetBoolArg("-checkblockindex", chainparams.DefaultConsistencyChecks());
920921
fCheckpointsEnabled = args.GetBoolArg("-checkpoints", DEFAULT_CHECKPOINTS_ENABLED);

src/init/common.cpp

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <logging.h>
1212
#include <node/interface_ui.h>
1313
#include <tinyformat.h>
14+
#include <util/string.h>
1415
#include <util/system.h>
1516
#include <util/time.h>
1617
#include <util/translation.h>
@@ -23,11 +24,12 @@ namespace init {
2324
void AddLoggingArgs(ArgsManager& argsman)
2425
{
2526
argsman.AddArg("-debuglogfile=<file>", strprintf("Specify location of debug log file. Relative paths will be prefixed by a net-specific datadir location. (-nodebuglogfile to disable; default: %s)", DEFAULT_DEBUGLOGFILE), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
26-
argsman.AddArg("-debug=<category>", "Output debugging information (default: -nodebug, supplying <category> is optional). "
27-
"If <category> is not supplied or if <category> = 1, output all debugging information. <category> can be: " + LogInstance().LogCategoriesString() + ". This option can be specified multiple times to output multiple categories.",
27+
argsman.AddArg("-debug=<category>", "Output debug and trace logging (default: -nodebug, supplying <category> is optional). "
28+
"If <category> is not supplied or if <category> = 1, output all debug and trace logging. <category> can be: " + LogInstance().LogCategoriesString() + ". This option can be specified multiple times to output multiple categories.",
2829
ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
29-
argsman.AddArg("-debugexclude=<category>", "Exclude debugging information for a category. Can be used in conjunction with -debug=1 to output debug logs for all categories except the specified category. This option can be specified multiple times to exclude multiple categories.", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
30+
argsman.AddArg("-debugexclude=<category>", "Exclude debug and trace logging for a category. Can be used in conjunction with -debug=1 to output debug and trace logging for all categories except the specified category. This option can be specified multiple times to exclude multiple categories.", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
3031
argsman.AddArg("-logips", strprintf("Include IP addresses in debug output (default: %u)", DEFAULT_LOGIPS), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
32+
argsman.AddArg("-loglevel=<level>|<category>:<level>", strprintf("Set the global or per-category severity level for logging categories enabled with the -debug configuration option or the logging RPC: %s (default=%s); warning and error levels are always logged. If <category>:<level> is supplied, the setting will override the global one and may be specified multiple times to set multiple category-specific levels. <category> can be: %s.", LogInstance().LogLevelsString(), LogInstance().LogLevelToStr(BCLog::DEFAULT_LOG_LEVEL), LogInstance().LogCategoriesString()), ArgsManager::DISALLOW_NEGATION | ArgsManager::DISALLOW_ELISION | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
3133
argsman.AddArg("-logtimestamps", strprintf("Prepend debug output with timestamp (default: %u)", DEFAULT_LOGTIMESTAMPS), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
3234
#ifdef HAVE_THREAD_LOCAL
3335
argsman.AddArg("-logthreadnames", strprintf("Prepend debug output with name of the originating thread (only available on platforms supporting thread_local) (default: %u)", DEFAULT_LOGTHREADNAMES), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
@@ -55,6 +57,26 @@ void SetLoggingOptions(const ArgsManager& args)
5557
fLogIPs = args.GetBoolArg("-logips", DEFAULT_LOGIPS);
5658
}
5759

60+
void SetLoggingLevel(const ArgsManager& args)
61+
{
62+
if (args.IsArgSet("-loglevel")) {
63+
for (const std::string& level_str : args.GetArgs("-loglevel")) {
64+
if (level_str.find_first_of(':', 3) == std::string::npos) {
65+
// user passed a global log level, i.e. -loglevel=<level>
66+
if (!LogInstance().SetLogLevel(level_str)) {
67+
InitWarning(strprintf(_("Unsupported global logging level -loglevel=%s. Valid values: %s."), level_str, LogInstance().LogLevelsString()));
68+
}
69+
} else {
70+
// user passed a category-specific log level, i.e. -loglevel=<category>:<level>
71+
const auto& toks = SplitString(level_str, ':');
72+
if (!(toks.size() == 2 && LogInstance().SetCategoryLogLevel(toks[0], toks[1]))) {
73+
InitWarning(strprintf(_("Unsupported category-specific logging level -loglevel=%s. Expected -loglevel=<category>:<loglevel>. Valid categories: %s. Valid loglevels: %s."), level_str, LogInstance().LogCategoriesString(), LogInstance().LogLevelsString()));
74+
}
75+
}
76+
}
77+
}
78+
}
79+
5880
void SetLoggingCategories(const ArgsManager& args)
5981
{
6082
if (args.IsArgSet("-debug")) {

src/init/common.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ namespace init {
1414
void AddLoggingArgs(ArgsManager& args);
1515
void SetLoggingOptions(const ArgsManager& args);
1616
void SetLoggingCategories(const ArgsManager& args);
17+
void SetLoggingLevel(const ArgsManager& args);
1718
bool StartLogging(const ArgsManager& args);
1819
void LogPackageVersion();
1920
} // namespace init

src/logging.cpp

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@
55

66
#include <fs.h>
77
#include <logging.h>
8-
#include <util/threadnames.h>
98
#include <util/string.h>
9+
#include <util/threadnames.h>
1010
#include <util/time.h>
1111

1212
#include <algorithm>
1313
#include <array>
1414
#include <mutex>
15+
#include <optional>
1516

1617
const char * const DEFAULT_DEBUGLOGFILE = "debug.log";
18+
constexpr auto MAX_USER_SETABLE_SEVERITY_LEVEL{BCLog::Level::Info};
1719

1820
BCLog::Logger& LogInstance()
1921
{
@@ -122,6 +124,19 @@ bool BCLog::Logger::WillLogCategory(BCLog::LogFlags category) const
122124
return (m_categories.load(std::memory_order_relaxed) & category) != 0;
123125
}
124126

127+
bool BCLog::Logger::WillLogCategoryLevel(BCLog::LogFlags category, BCLog::Level level) const
128+
{
129+
// Log messages at Warning and Error level unconditionally, so that
130+
// important troubleshooting information doesn't get lost.
131+
if (level >= BCLog::Level::Warning) return true;
132+
133+
if (!WillLogCategory(category)) return false;
134+
135+
StdLockGuard scoped_lock(m_cs);
136+
const auto it{m_category_log_levels.find(category)};
137+
return level >= (it == m_category_log_levels.end() ? LogLevel() : it->second);
138+
}
139+
125140
bool BCLog::Logger::DefaultShrinkDebugFile() const
126141
{
127142
return m_categories == BCLog::NONE;
@@ -135,7 +150,7 @@ struct CLogCategoryDesc {
135150
const CLogCategoryDesc LogCategories[] =
136151
{
137152
{BCLog::NONE, "0"},
138-
{BCLog::NONE, "none"},
153+
{BCLog::NONE, ""},
139154
{BCLog::NET, "net"},
140155
{BCLog::TOR, "tor"},
141156
{BCLog::MEMPOOL, "mempool"},
@@ -184,11 +199,11 @@ bool GetLogCategory(BCLog::LogFlags& flag, const std::string& str)
184199
return false;
185200
}
186201

187-
std::string LogLevelToStr(BCLog::Level level)
202+
std::string BCLog::Logger::LogLevelToStr(BCLog::Level level) const
188203
{
189204
switch (level) {
190-
case BCLog::Level::None:
191-
return "none";
205+
case BCLog::Level::Trace:
206+
return "trace";
192207
case BCLog::Level::Debug:
193208
return "debug";
194209
case BCLog::Level::Info:
@@ -197,6 +212,8 @@ std::string LogLevelToStr(BCLog::Level level)
197212
return "warning";
198213
case BCLog::Level::Error:
199214
return "error";
215+
case BCLog::Level::None:
216+
return "";
200217
}
201218
assert(false);
202219
}
@@ -206,7 +223,7 @@ std::string LogCategoryToStr(BCLog::LogFlags category)
206223
// Each log category string representation should sync with LogCategories
207224
switch (category) {
208225
case BCLog::LogFlags::NONE:
209-
return "none";
226+
return "";
210227
case BCLog::LogFlags::NET:
211228
return "net";
212229
case BCLog::LogFlags::TOR:
@@ -269,6 +286,25 @@ std::string LogCategoryToStr(BCLog::LogFlags category)
269286
assert(false);
270287
}
271288

289+
static std::optional<BCLog::Level> GetLogLevel(const std::string& level_str)
290+
{
291+
if (level_str == "trace") {
292+
return BCLog::Level::Trace;
293+
} else if (level_str == "debug") {
294+
return BCLog::Level::Debug;
295+
} else if (level_str == "info") {
296+
return BCLog::Level::Info;
297+
} else if (level_str == "warning") {
298+
return BCLog::Level::Warning;
299+
} else if (level_str == "error") {
300+
return BCLog::Level::Error;
301+
} else if (level_str == "none") {
302+
return BCLog::Level::None;
303+
} else {
304+
return std::nullopt;
305+
}
306+
}
307+
272308
std::vector<LogCategory> BCLog::Logger::LogCategoriesList() const
273309
{
274310
// Sort log categories by alphabetical order.
@@ -287,6 +323,18 @@ std::vector<LogCategory> BCLog::Logger::LogCategoriesList() const
287323
return ret;
288324
}
289325

326+
/** Log severity levels that can be selected by the user. */
327+
static constexpr std::array<BCLog::Level, 3> LogLevelsList()
328+
{
329+
return {BCLog::Level::Info, BCLog::Level::Debug, BCLog::Level::Trace};
330+
}
331+
332+
std::string BCLog::Logger::LogLevelsString() const
333+
{
334+
const auto& levels = LogLevelsList();
335+
return Join(std::vector<BCLog::Level>{levels.begin(), levels.end()}, ", ", [this](BCLog::Level level) { return LogLevelToStr(level); });
336+
}
337+
290338
std::string BCLog::Logger::LogTimestampStr(const std::string& str)
291339
{
292340
std::string strStamped;
@@ -334,7 +382,7 @@ namespace BCLog {
334382
}
335383
} // namespace BCLog
336384

337-
void BCLog::Logger::LogPrintStr(const std::string& str, const std::string& logging_function, const std::string& source_file, const int source_line, const BCLog::LogFlags category, const BCLog::Level level)
385+
void BCLog::Logger::LogPrintStr(const std::string& str, const std::string& logging_function, const std::string& source_file, int source_line, BCLog::LogFlags category, BCLog::Level level)
338386
{
339387
StdLockGuard scoped_lock(m_cs);
340388
std::string str_prefixed = LogEscapeMessage(str);
@@ -443,3 +491,24 @@ void BCLog::Logger::ShrinkDebugFile()
443491
else if (file != nullptr)
444492
fclose(file);
445493
}
494+
495+
bool BCLog::Logger::SetLogLevel(const std::string& level_str)
496+
{
497+
const auto level = GetLogLevel(level_str);
498+
if (!level.has_value() || level.value() > MAX_USER_SETABLE_SEVERITY_LEVEL) return false;
499+
m_log_level = level.value();
500+
return true;
501+
}
502+
503+
bool BCLog::Logger::SetCategoryLogLevel(const std::string& category_str, const std::string& level_str)
504+
{
505+
BCLog::LogFlags flag;
506+
if (!GetLogCategory(flag, category_str)) return false;
507+
508+
const auto level = GetLogLevel(level_str);
509+
if (!level.has_value() || level.value() > MAX_USER_SETABLE_SEVERITY_LEVEL) return false;
510+
511+
StdLockGuard scoped_lock(m_cs);
512+
m_category_log_levels[flag] = level.value();
513+
return true;
514+
}

src/logging.h

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
#define BITCOIN_LOGGING_H
88

99
#include <fs.h>
10-
#include <tinyformat.h>
1110
#include <threadsafety.h>
11+
#include <tinyformat.h>
1212
#include <util/string.h>
1313

1414
#include <atomic>
@@ -17,6 +17,7 @@
1717
#include <list>
1818
#include <mutex>
1919
#include <string>
20+
#include <unordered_map>
2021
#include <vector>
2122

2223
static const bool DEFAULT_LOGTIMEMICROS = false;
@@ -68,12 +69,14 @@ namespace BCLog {
6869
ALL = ~(uint32_t)0,
6970
};
7071
enum class Level {
71-
Debug = 0,
72-
None = 1,
73-
Info = 2,
74-
Warning = 3,
75-
Error = 4,
72+
Trace = 0, // High-volume or detailed logging for development/debugging
73+
Debug, // Reasonably noisy logging, but still usable in production
74+
Info, // Default
75+
Warning,
76+
Error,
77+
None, // Internal use only
7678
};
79+
constexpr auto DEFAULT_LOG_LEVEL{Level::Debug};
7780

7881
class Logger
7982
{
@@ -91,6 +94,13 @@ namespace BCLog {
9194
*/
9295
std::atomic_bool m_started_new_line{true};
9396

97+
//! Category-specific log level. Overrides `m_log_level`.
98+
std::unordered_map<LogFlags, Level> m_category_log_levels GUARDED_BY(m_cs);
99+
100+
//! If there is no category-specific log level, all logs with a severity
101+
//! level lower than `m_log_level` will be ignored.
102+
std::atomic<Level> m_log_level{DEFAULT_LOG_LEVEL};
103+
94104
/** Log categories bitfield. */
95105
std::atomic<uint32_t> m_categories{0};
96106

@@ -112,7 +122,7 @@ namespace BCLog {
112122
std::atomic<bool> m_reopen_file{false};
113123

114124
/** Send a string to the log output */
115-
void LogPrintStr(const std::string& str, const std::string& logging_function, const std::string& source_file, const int source_line, const BCLog::LogFlags category, const BCLog::Level level);
125+
void LogPrintStr(const std::string& str, const std::string& logging_function, const std::string& source_file, int source_line, BCLog::LogFlags category, BCLog::Level level);
116126

117127
/** Returns whether logs will be written to any output */
118128
bool Enabled() const
@@ -143,6 +153,22 @@ namespace BCLog {
143153

144154
void ShrinkDebugFile();
145155

156+
std::unordered_map<LogFlags, Level> CategoryLevels() const
157+
{
158+
StdLockGuard scoped_lock(m_cs);
159+
return m_category_log_levels;
160+
}
161+
void SetCategoryLogLevel(const std::unordered_map<LogFlags, Level>& levels)
162+
{
163+
StdLockGuard scoped_lock(m_cs);
164+
m_category_log_levels = levels;
165+
}
166+
bool SetCategoryLogLevel(const std::string& category_str, const std::string& level_str);
167+
168+
Level LogLevel() const { return m_log_level.load(); }
169+
void SetLogLevel(Level level) { m_log_level = level; }
170+
bool SetLogLevel(const std::string& level);
171+
146172
uint32_t GetCategoryMask() const { return m_categories.load(); }
147173

148174
void EnableCategory(LogFlags flag);
@@ -151,6 +177,8 @@ namespace BCLog {
151177
bool DisableCategory(const std::string& str);
152178

153179
bool WillLogCategory(LogFlags category) const;
180+
bool WillLogCategoryLevel(LogFlags category, Level level) const;
181+
154182
/** Returns a vector of the log categories in alphabetical order. */
155183
std::vector<LogCategory> LogCategoriesList() const;
156184
/** Returns a string with the log categories in alphabetical order. */
@@ -159,6 +187,12 @@ namespace BCLog {
159187
return Join(LogCategoriesList(), ", ", [&](const LogCategory& i) { return i.category; });
160188
};
161189

190+
//! Returns a string with all user-selectable log levels.
191+
std::string LogLevelsString() const;
192+
193+
//! Returns the string representation of a log level.
194+
std::string LogLevelToStr(BCLog::Level level) const;
195+
162196
bool DefaultShrinkDebugFile() const;
163197
};
164198

@@ -169,12 +203,7 @@ BCLog::Logger& LogInstance();
169203
/** Return true if log accepts specified category, at the specified level. */
170204
static inline bool LogAcceptCategory(BCLog::LogFlags category, BCLog::Level level)
171205
{
172-
// Log messages at Warning and Error level unconditionally, so that
173-
// important troubleshooting information doesn't get lost.
174-
if (level >= BCLog::Level::Warning) {
175-
return true;
176-
}
177-
return LogInstance().WillLogCategory(category);
206+
return LogInstance().WillLogCategoryLevel(category, level);
178207
}
179208

180209
/** Return true if str parses as a log category and set the flag */

src/test/i2p_tests.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

55
#include <i2p.h>
6+
#include <logging.h>
67
#include <netaddress.h>
78
#include <test/util/logging.h>
89
#include <test/util/net.h>
@@ -19,6 +20,8 @@ BOOST_FIXTURE_TEST_SUITE(i2p_tests, BasicTestingSetup)
1920

2021
BOOST_AUTO_TEST_CASE(unlimited_recv)
2122
{
23+
const auto prev_log_level{LogInstance().LogLevel()};
24+
LogInstance().SetLogLevel(BCLog::Level::Trace);
2225
auto CreateSockOrig = CreateSock;
2326

2427
// Mock CreateSock() to create MockSock.
@@ -39,6 +42,7 @@ BOOST_AUTO_TEST_CASE(unlimited_recv)
3942
}
4043

4144
CreateSock = CreateSockOrig;
45+
LogInstance().SetLogLevel(prev_log_level);
4246
}
4347

4448
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)