-
Notifications
You must be signed in to change notification settings - Fork 37.7k
logging: Simplify API for level based logging #28318
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c5c76dc
dfe98b6
ab34dc6
667ce3e
782bb6a
fbd7642
f7ce5ac
e60fc7d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -723,6 +723,47 @@ General Bitcoin Core | |||||||||
- *Explanation*: If the test suite is to be updated for a change, this has to | ||||||||||
be done first. | ||||||||||
|
||||||||||
Logging | ||||||||||
------- | ||||||||||
|
||||||||||
The macros `LogInfo`, `LogDebug`, `LogTrace`, `LogWarning` and `LogError` are available for | ||||||||||
logging messages. They should be used as follows: | ||||||||||
|
||||||||||
- `LogDebug(BCLog::CATEGORY, fmt, params...)` is what you want | ||||||||||
most of the time, and it should be used for log messages that are | ||||||||||
useful for debugging and can reasonably be enabled on a production | ||||||||||
system (that has sufficient free storage space). They will be logged | ||||||||||
if the program is started with `-debug=category` or `-debug=1`. | ||||||||||
Note that `LogPrint(BCLog::CATEGORY, fmt, params...)` is a deprecated | ||||||||||
alias for `LogDebug`. | ||||||||||
Comment on lines
+737
to
+738
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: for consistency to avoid confusion
Suggested change
|
||||||||||
|
||||||||||
- `LogInfo(fmt, params...)` should only be used rarely, eg for startup | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's somewhat wonky that e.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I had that intuition at first too, but then changed my mind after reading ajtowns's comment stating:
Given that LogInfo is unconditional, what would be your use case for adding a log category? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Simply that "info = unconditional" might not always be the case (and is an unusual convention to have hardcoded), and a user could rightly want to see only WARNING+ logs as well as, say, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think ajtowns addressed that quite well at the bottom of this comment:
Seeing warning/error-logs only is easy enough to do with grep, and info logging should be infrequent enough to not take up significant system resources. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @stickies-v it's not about system resources; it's about allowing the end user to articulate what they see. Not a big deal either way, but I think our current scheme is a little wonky if you compare it to more widely deployed systems e.g. the python logging module or log4j. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To clarify, I don't think that anyone is debating that this changeset is an improvement over the status quo. The ask (at least from my end) would be to unify the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO, being able to filter log messages and only see messages from a particular component is a useful thing to support. Current bitcoin behavior of defining categories and not actually using them is weird, and wanting to bake this behavior into the logging API seems even weirder. The python logger is very flexible and of course it allows you to log per-component or globally. But if you did both these things in a single python project, it would be confusing and weird there too. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No opinion on this thread, but I wanted to mention that it is not always possible to easily define a logging category right now. For example, log statements in the flatfile module may fit in the "blockstorage" or "indexes" category, depending on context. Thus, either the log category would have to be passed down the call stack before logging, or the log-string would have to be passed up the call stack before logging. Also, some modules, such as Again, no opinion on this. I think either solution is fine. PRs welcome, I guess? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Thanks, I started playing around with a change that should make it easier to set the category without being verbose, and also start relying less on a global logging instance, which should be useful for kernel applications and tests: ae450f1. Will see how this looks and probably open a PR There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Opened #29256 with these changes. Code changes should be complete but there are some test failures so it is a draft. |
||||||||||
messages or for infrequent and important events such as a new block tip | ||||||||||
being found or a new outbound connection being made. These log messages | ||||||||||
are unconditional so care must be taken that they can't be used by an | ||||||||||
attacker to fill up storage. Note that `LogPrintf(fmt, params...)` is | ||||||||||
a deprecated alias for `LogInfo`. | ||||||||||
|
||||||||||
- `LogError(fmt, params...)` should be used in place of `LogInfo` for | ||||||||||
severe problems that require the node (or a subsystem) to shut down | ||||||||||
entirely (eg, insufficient storage space). | ||||||||||
|
||||||||||
- `LogWarning(fmt, params...)` should be used in place of `LogInfo` for | ||||||||||
severe problems that the node admin should address, but are not | ||||||||||
severe enough to warrant shutting down the node (eg, system time | ||||||||||
appears to be wrong, unknown soft fork appears to have activated). | ||||||||||
|
||||||||||
- `LogTrace(BCLog::CATEGORY, fmt, params...) should be used in place of | ||||||||||
`LogDebug` for log messages that would be unusable on a production | ||||||||||
system, eg due to being too noisy in normal use, or too resource | ||||||||||
intensive to process. These will be logged if the startup | ||||||||||
options `-debug=category -loglevel=category:trace` or `-debug=1 | ||||||||||
-loglevel=trace` are selected. | ||||||||||
|
||||||||||
Note that the format strings and parameters of `LogDebug` and `LogTrace` | ||||||||||
are only evaluated if the logging category is enabled, so you must be | ||||||||||
careful to avoid side-effects in those expressions. | ||||||||||
|
||||||||||
Wallet | ||||||||||
------- | ||||||||||
|
||||||||||
|
@@ -891,7 +932,7 @@ Strings and formatting | |||||||||
`wcstoll`, `wcstombs`, `wcstoul`, `wcstoull`, `wcstoumax`, `wcswidth`, | ||||||||||
`wcsxfrm`, `wctob`, `wctomb`, `wctrans`, `wctype`, `wcwidth`, `wprintf` | ||||||||||
|
||||||||||
- For `strprintf`, `LogPrint`, `LogPrintf` formatting characters don't need size specifiers. | ||||||||||
- For `strprintf`, `LogInfo`, `LogDebug`, etc formatting characters don't need size specifiers. | ||||||||||
|
||||||||||
- *Rationale*: Bitcoin Core uses tinyformat, which is type safe. Leave them out to avoid confusion. | ||||||||||
|
||||||||||
|
@@ -903,7 +944,7 @@ Strings and formatting | |||||||||
|
||||||||||
- *Rationale*: Although this is guaranteed to be safe starting with C++11, `.data()` communicates the intent better. | ||||||||||
|
||||||||||
- Do not use it when passing strings to `tfm::format`, `strprintf`, `LogPrint[f]`. | ||||||||||
- Do not use it when passing strings to `tfm::format`, `strprintf`, `LogInfo`, `LogDebug`, etc. | ||||||||||
|
||||||||||
- *Rationale*: This is redundant. Tinyformat handles strings. | ||||||||||
|
||||||||||
|
ajtowns marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -126,9 +126,9 @@ bool BCLog::Logger::WillLogCategory(BCLog::LogFlags category) const | |
|
||
bool BCLog::Logger::WillLogCategoryLevel(BCLog::LogFlags category, BCLog::Level level) const | ||
{ | ||
// Log messages at Warning and Error level unconditionally, so that | ||
// Log messages at Info, Warning and Error level unconditionally, so that | ||
// important troubleshooting information doesn't get lost. | ||
if (level >= BCLog::Level::Warning) return true; | ||
if (level >= BCLog::Level::Info) return true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just noting that this doesn't change the the one existing use of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ab34dc6 The loglevel help wasn't updated with this change (note that this commit is an incomplete copy of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Possibly relevant comment: |
||
|
||
if (!WillLogCategory(category)) return false; | ||
|
||
|
@@ -202,7 +202,7 @@ bool GetLogCategory(BCLog::LogFlags& flag, const std::string& str) | |
return false; | ||
} | ||
|
||
std::string BCLog::Logger::LogLevelToStr(BCLog::Level level) const | ||
std::string BCLog::Logger::LogLevelToStr(BCLog::Level level) | ||
{ | ||
switch (level) { | ||
case BCLog::Level::Trace: | ||
|
@@ -215,8 +215,6 @@ std::string BCLog::Logger::LogLevelToStr(BCLog::Level level) const | |
return "warning"; | ||
case BCLog::Level::Error: | ||
return "error"; | ||
case BCLog::Level::None: | ||
return ""; | ||
} | ||
assert(false); | ||
} | ||
|
@@ -307,8 +305,6 @@ static std::optional<BCLog::Level> GetLogLevel(const std::string& level_str) | |
return BCLog::Level::Warning; | ||
} else if (level_str == "error") { | ||
return BCLog::Level::Error; | ||
} else if (level_str == "none") { | ||
return BCLog::Level::None; | ||
} else { | ||
return std::nullopt; | ||
} | ||
|
@@ -341,7 +337,7 @@ static constexpr std::array<BCLog::Level, 3> LogLevelsList() | |
std::string BCLog::Logger::LogLevelsString() const | ||
{ | ||
const auto& levels = LogLevelsList(); | ||
return Join(std::vector<BCLog::Level>{levels.begin(), levels.end()}, ", ", [this](BCLog::Level level) { return LogLevelToStr(level); }); | ||
return Join(std::vector<BCLog::Level>{levels.begin(), levels.end()}, ", ", [](BCLog::Level level) { return LogLevelToStr(level); }); | ||
} | ||
|
||
std::string BCLog::Logger::LogTimestampStr(const std::string& str) | ||
|
@@ -392,29 +388,39 @@ namespace BCLog { | |
} | ||
} // namespace BCLog | ||
|
||
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) | ||
std::string BCLog::Logger::GetLogPrefix(BCLog::LogFlags category, BCLog::Level level) const | ||
{ | ||
StdLockGuard scoped_lock(m_cs); | ||
std::string str_prefixed = LogEscapeMessage(str); | ||
if (category == LogFlags::NONE) category = LogFlags::ALL; | ||
|
||
if ((category != LogFlags::NONE || level != Level::None) && m_started_new_line) { | ||
std::string s{"["}; | ||
const bool has_category{m_always_print_category_level || category != LogFlags::ALL}; | ||
|
||
if (category != LogFlags::NONE) { | ||
s += LogCategoryToStr(category); | ||
} | ||
// If there is no category, Info is implied | ||
if (!has_category && level == Level::Info) return {}; | ||
|
||
if (category != LogFlags::NONE && level != Level::None) { | ||
// Only add separator if both flag and level are not NONE | ||
s += ":"; | ||
} | ||
std::string s{"["}; | ||
if (has_category) { | ||
s += LogCategoryToStr(category); | ||
} | ||
|
||
if (level != Level::None) { | ||
s += LogLevelToStr(level); | ||
} | ||
if (m_always_print_category_level || !has_category || level != Level::Debug) { | ||
// If there is a category, Debug is implied, so don't add the level | ||
|
||
// Only add separator if we have a category | ||
if (has_category) s += ":"; | ||
s += Logger::LogLevelToStr(level); | ||
} | ||
|
||
s += "] "; | ||
return s; | ||
} | ||
|
||
s += "] "; | ||
str_prefixed.insert(0, s); | ||
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) | ||
{ | ||
StdLockGuard scoped_lock(m_cs); | ||
std::string str_prefixed = LogEscapeMessage(str); | ||
|
||
if (m_started_new_line) { | ||
str_prefixed.insert(0, GetLogPrefix(category, level)); | ||
} | ||
|
||
if (m_log_sourcelocations && m_started_new_line) { | ||
|
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -25,6 +25,7 @@ static const bool DEFAULT_LOGIPS = false; | |||||||
static const bool DEFAULT_LOGTIMESTAMPS = true; | ||||||||
static const bool DEFAULT_LOGTHREADNAMES = false; | ||||||||
static const bool DEFAULT_LOGSOURCELOCATIONS = false; | ||||||||
static constexpr bool DEFAULT_LOGLEVELALWAYS = false; | ||||||||
extern const char * const DEFAULT_DEBUGLOGFILE; | ||||||||
|
||||||||
extern bool fLogIPs; | ||||||||
|
@@ -77,7 +78,6 @@ namespace BCLog { | |||||||
Info, // Default | ||||||||
Warning, | ||||||||
Error, | ||||||||
None, // Internal use only | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor thing, but there's still a lingering reference in |
||||||||
}; | ||||||||
constexpr auto DEFAULT_LOG_LEVEL{Level::Debug}; | ||||||||
|
||||||||
|
@@ -120,10 +120,13 @@ namespace BCLog { | |||||||
bool m_log_time_micros = DEFAULT_LOGTIMEMICROS; | ||||||||
bool m_log_threadnames = DEFAULT_LOGTHREADNAMES; | ||||||||
bool m_log_sourcelocations = DEFAULT_LOGSOURCELOCATIONS; | ||||||||
bool m_always_print_category_level = DEFAULT_LOGLEVELALWAYS; | ||||||||
|
||||||||
fs::path m_file_path; | ||||||||
std::atomic<bool> m_reopen_file{false}; | ||||||||
|
||||||||
std::string GetLogPrefix(LogFlags category, Level level) const; | ||||||||
|
||||||||
/** Send a string to the log output */ | ||||||||
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); | ||||||||
|
||||||||
|
@@ -194,7 +197,7 @@ namespace BCLog { | |||||||
std::string LogLevelsString() const; | ||||||||
|
||||||||
//! Returns the string representation of a log level. | ||||||||
std::string LogLevelToStr(BCLog::Level level) const; | ||||||||
static std::string LogLevelToStr(BCLog::Level level); | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: would it make sense to just move this to git diff on e60fc7ddiff --git a/src/init/common.cpp b/src/init/common.cpp
index 6560258ef5..0832845921 100644
--- a/src/init/common.cpp
+++ b/src/init/common.cpp
@@ -31,7 +31,7 @@ void AddLoggingArgs(ArgsManager& argsman)
ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
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);
argsman.AddArg("-logips", strprintf("Include IP addresses in debug output (default: %u)", DEFAULT_LOGIPS), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
- 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);
+ 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(), BCLog::LogLevelToStr(BCLog::DEFAULT_LOG_LEVEL), LogInstance().LogCategoriesString()), ArgsManager::DISALLOW_NEGATION | ArgsManager::DISALLOW_ELISION | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-logtimestamps", strprintf("Prepend debug output with timestamp (default: %u)", DEFAULT_LOGTIMESTAMPS), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
#ifdef HAVE_THREAD_LOCAL
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);
diff --git a/src/logging.cpp b/src/logging.cpp
index 42f100ded6..b1f0efbbd8 100644
--- a/src/logging.cpp
+++ b/src/logging.cpp
@@ -202,7 +202,7 @@ bool GetLogCategory(BCLog::LogFlags& flag, const std::string& str)
return false;
}
-std::string BCLog::Logger::LogLevelToStr(BCLog::Level level)
+std::string BCLog::LogLevelToStr(BCLog::Level level)
{
switch (level) {
case BCLog::Level::Trace:
@@ -407,7 +407,7 @@ std::string BCLog::Logger::GetLogPrefix(BCLog::LogFlags category, BCLog::Level l
// Only add separator if we have a category
if (has_category) s += ":";
- s += Logger::LogLevelToStr(level);
+ s += LogLevelToStr(level);
}
s += "] ";
diff --git a/src/logging.h b/src/logging.h
index 525e0aec6d..a2228ac7ba 100644
--- a/src/logging.h
+++ b/src/logging.h
@@ -81,6 +81,9 @@ namespace BCLog {
};
constexpr auto DEFAULT_LOG_LEVEL{Level::Debug};
+ //! Returns the string representation of a log level.
+ std::string LogLevelToStr(BCLog::Level level);
+
class Logger
{
private:
@@ -196,9 +199,6 @@ namespace BCLog {
//! Returns a string with all user-selectable log levels.
std::string LogLevelsString() const;
- //! Returns the string representation of a log level.
- static std::string LogLevelToStr(BCLog::Level level);
-
bool DefaultShrinkDebugFile() const;
};
|
||||||||
|
||||||||
bool DefaultShrinkDebugFile() const; | ||||||||
}; | ||||||||
|
@@ -234,22 +237,17 @@ static inline void LogPrintf_(const std::string& logging_function, const std::st | |||||||
#define LogPrintLevel_(category, level, ...) LogPrintf_(__func__, __FILE__, __LINE__, category, level, __VA_ARGS__) | ||||||||
|
||||||||
// Log unconditionally. | ||||||||
#define LogPrintf(...) LogPrintLevel_(BCLog::LogFlags::NONE, BCLog::Level::None, __VA_ARGS__) | ||||||||
#define LogInfo(...) LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Info, __VA_ARGS__) | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: it seems like we can easily avoid using a macro here, wouldn't that be better?
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Until we can use std::source_location (see #28999 (comment)) we need to use macros in order to pass There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gha forgout about that. Macro it is, thanks. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is changed, the format string could also be consteval'd into a compile-time checked object to do the newline check (and maybe others), which is currently done by clang-tidy-plugin. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could also check if source_location fixes the annoyance on Windows to print the full path when logging:
|
||||||||
#define LogWarning(...) LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Warning, __VA_ARGS__) | ||||||||
#define LogError(...) LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Error, __VA_ARGS__) | ||||||||
|
||||||||
// Log unconditionally, prefixing the output with the passed category name. | ||||||||
#define LogPrintfCategory(category, ...) LogPrintLevel_(category, BCLog::Level::None, __VA_ARGS__) | ||||||||
// Deprecated unconditional logging. | ||||||||
#define LogPrintf(...) LogInfo(__VA_ARGS__) | ||||||||
#define LogPrintfCategory(category, ...) LogPrintLevel_(category, BCLog::Level::Info, __VA_ARGS__) | ||||||||
|
||||||||
// Use a macro instead of a function for conditional logging to prevent | ||||||||
// evaluating arguments when logging for the category is not enabled. | ||||||||
|
||||||||
// Log conditionally, prefixing the output with the passed category name. | ||||||||
#define LogPrint(category, ...) \ | ||||||||
do { \ | ||||||||
if (LogAcceptCategory((category), BCLog::Level::Debug)) { \ | ||||||||
LogPrintLevel_(category, BCLog::Level::None, __VA_ARGS__); \ | ||||||||
} \ | ||||||||
} while (0) | ||||||||
|
||||||||
// Log conditionally, prefixing the output with the passed category name and severity level. | ||||||||
#define LogPrintLevel(category, level, ...) \ | ||||||||
do { \ | ||||||||
|
@@ -258,6 +256,13 @@ static inline void LogPrintf_(const std::string& logging_function, const std::st | |||||||
} \ | ||||||||
} while (0) | ||||||||
|
||||||||
// Log conditionally, prefixing the output with the passed category name. | ||||||||
#define LogDebug(category, ...) LogPrintLevel(category, BCLog::Level::Debug, __VA_ARGS__) | ||||||||
#define LogTrace(category, ...) LogPrintLevel(category, BCLog::Level::Trace, __VA_ARGS__) | ||||||||
|
||||||||
// Deprecated conditional logging | ||||||||
#define LogPrint(category, ...) LogDebug(category, __VA_ARGS__) | ||||||||
|
||||||||
template <typename... Args> | ||||||||
bool error(const char* fmt, const Args&... args) | ||||||||
{ | ||||||||
|
Uh oh!
There was an error while loading. Please reload this page.