Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 43 additions & 2 deletions doc/developer-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: for consistency to avoid confusion

Suggested change
Note that `LogPrint(BCLog::CATEGORY, fmt, params...)` is a deprecated
alias for `LogDebug`.
Note that `LogPrint(BCLog::CATEGORY, fmt, params...)` is a deprecated
alias for `LogDebug(fmt, params...)`.


- `LogInfo(fmt, params...)` should only be used rarely, eg for startup
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

f7ce5ac

It's somewhat wonky that e.g. LogInfo() doesn't take a category, but e.g. LogDebug does, and in later commits we actually remove category information from info-equivalent log statements, which seems backwards to me. Ideally, LogInfo would optionally take a category. But maybe this can be done later?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, LogInfo would optionally take a category.

I had that intuition at first too, but then changed my mind after reading ajtowns's comment stating:

My reasoning here is that the usefulness of the "category" isn't in telling us what bit of code triggered the log message (we have -logsourcelocations for that which is much more precise, and you can generally just grep for the message anyway), but in categorising log messages the user can disable. It's useful for the user to see [net] because it tells them they can use -debug=net to enable it; it's useful for the user to see [net:trace] because it tells them they also need to enable tracing to see that. It's useful to see [warning] or [error] because those are things they probably want to take action on, and having a consistent tag means they can grep for them more easily. It's not useful to see [net:info] because that doesn't tell them anything they can action, it's just noise that makes it harder to read the logs.

So I contend that approach is consistent, it's just consistent over a different (in my opinion more valuable) attribute; namely that categories are solely for conditional logging that the user can disable on a per-subsystem basis.

Given that LogInfo is unconditional, what would be your use case for adding a log category?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that LogInfo is unconditional, what would be your use case for adding a log category?

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, net or validation-related INFOs.

Copy link
Contributor

Choose a reason for hiding this comment

The 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:

I don't think that's a sensible concern: unconditional logging should be rare enough to not need disabling (eg, connecting to new outbounds, new headers, and new tips for about 400 entries a day), and at that point it's better not to disable them as that gives the user a pretty clear indication their node is still working right. Even for users that only care about errors/warnings, having the UpdateTip context at least is likely useful for giving some clue what state the node was in when it encountered the problem.

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.

Copy link
Contributor

@jamesob jamesob Jan 11, 2024

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

The 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 Log* interfaces and allow the optional specification of a category. That way, when migrating existing logging statements over, we're not throwing away category information just because something is INFO. And we support users who want to see WARN only most of the time, but care about, say, net INFO messages. That doesn't seem like a crazy thing to ask for.

Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The 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 args, have only a few log statements, so creating a category for each of them may be verbose. Though, that is probably less of a problem.

Again, no opinion on this. I think either solution is fine. PRs welcome, I guess?

Copy link
Contributor

@ryanofsky ryanofsky Jan 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, no opinion on this. I think either solution is fine. PRs welcome, I guess?

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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will see how this looks and probably open a PR

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
-------

Expand Down Expand Up @@ -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.

Expand All @@ -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.

Expand Down
5 changes: 2 additions & 3 deletions src/httpserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ bool InitHTTPServer()

LogPrint(BCLog::HTTP, "Initialized HTTP server\n");
int workQueueDepth = std::max((long)gArgs.GetIntArg("-rpcworkqueue", DEFAULT_HTTP_WORKQUEUE), 1L);
LogPrintfCategory(BCLog::HTTP, "creating work queue of depth %d\n", workQueueDepth);
LogDebug(BCLog::HTTP, "creating work queue of depth %d\n", workQueueDepth);

g_work_queue = std::make_unique<WorkQueue<HTTPClosure>>(workQueueDepth);
// transfer ownership to eventBase/HTTP via .release()
Expand All @@ -434,9 +434,8 @@ static std::vector<std::thread> g_thread_http_workers;

void StartHTTPServer()
{
LogPrint(BCLog::HTTP, "Starting HTTP server\n");
int rpcThreads = std::max((long)gArgs.GetIntArg("-rpcthreads", DEFAULT_HTTP_THREADS), 1L);
LogPrintfCategory(BCLog::HTTP, "starting %d worker threads\n", rpcThreads);
LogInfo("Starting HTTP server with %d worker threads\n", rpcThreads);
g_thread_http = std::thread(ThreadHTTP, eventBase);

for (int i = 0; i < rpcThreads; i++) {
Expand Down
2 changes: 1 addition & 1 deletion src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1503,7 +1503,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
if (status == node::ChainstateLoadStatus::SUCCESS) {
uiInterface.InitMessage(_("Verifying blocks…").translated);
if (chainman.m_blockman.m_have_pruned && options.check_blocks > MIN_BLOCKS_TO_KEEP) {
LogPrintfCategory(BCLog::PRUNE, "pruned datadir may not have more than %d blocks; only checking available blocks\n",
LogWarning("pruned datadir may not have more than %d blocks; only checking available blocks\n",
MIN_BLOCKS_TO_KEEP);
}
std::tie(status, error) = catch_exceptions([&]{ return VerifyLoadedChainstate(chainman, options);});
Expand Down
2 changes: 2 additions & 0 deletions src/init/common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ void AddLoggingArgs(ArgsManager& argsman)
#endif
argsman.AddArg("-logsourcelocations", strprintf("Prepend debug output with name of the originating source location (source file, line number and function name) (default: %u)", DEFAULT_LOGSOURCELOCATIONS), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-logtimemicros", strprintf("Add microsecond precision to debug timestamps (default: %u)", DEFAULT_LOGTIMEMICROS), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-loglevelalways", strprintf("Always prepend a category and level (default: %u)", DEFAULT_LOGLEVELALWAYS), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -daemon. To disable logging to file, set -nodebuglogfile)", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-shrinkdebugfile", "Shrink debug.log file on client startup (default: 1 when no -debug)", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
}
Expand All @@ -55,6 +56,7 @@ void SetLoggingOptions(const ArgsManager& args)
LogInstance().m_log_threadnames = args.GetBoolArg("-logthreadnames", DEFAULT_LOGTHREADNAMES);
#endif
LogInstance().m_log_sourcelocations = args.GetBoolArg("-logsourcelocations", DEFAULT_LOGSOURCELOCATIONS);
LogInstance().m_always_print_category_level = args.GetBoolArg("-loglevelalways", DEFAULT_LOGLEVELALWAYS);

fLogIPs = args.GetBoolArg("-logips", DEFAULT_LOGIPS);
}
Expand Down
56 changes: 31 additions & 25 deletions src/logging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Contributor

Choose a reason for hiding this comment

The 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 BCLog::Level::Info in httpserver from non-default to default because InitHTTPServer() just disables libevent logging callbacks entirely unless BCLog::LIBEVENT isn't enabled.

Copy link
Member

Choose a reason for hiding this comment

The 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 118c756 (#25203) from the Severity-based logging parent PR). Fixed in #29230.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly relevant comment: 118c756 (#25203)


if (!WillLogCategory(category)) return false;

Expand Down Expand Up @@ -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:
Expand All @@ -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);
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down
31 changes: 18 additions & 13 deletions src/logging.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -77,7 +78,6 @@ namespace BCLog {
Info, // Default
Warning,
Error,
None, // Internal use only
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor thing, but there's still a lingering reference in contrib/devtools/bitcoin-tidy/example_logprintf.cpp. Not sure if that needs to be updated or not.

};
constexpr auto DEFAULT_LOG_LEVEL{Level::Debug};

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: would it make sense to just move this to BCLog, out of ::Logger?

git diff on e60fc7d
diff --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;
};
Expand Down Expand Up @@ -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__)
Copy link
Contributor

Choose a reason for hiding this comment

The 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
#define LogInfo(...) LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Info, __VA_ARGS__)
template <typename... Args>
static inline void LogInfo(Args&&... args) { LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Info, std::forward<Args>(args)...); }

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 __func__, __FILE__ and __LINE__ information through.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gha forgout about that. Macro it is, thanks.

Copy link
Member

@maflcko maflcko Jan 9, 2024

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The 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:

2024-04-15T13:27:36.5198997Z  node0 2024-04-15T13:27:36.001614Z [D:\a\bitcoin\bitcoin\src\validation.cpp:2549] [ConnectBlock] [bench]     - Verify 0 txins: 0.06ms (0.000ms/txin) [0.00s (0.03ms/blk)] 

#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 { \
Expand All @@ -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)
{
Expand Down
2 changes: 1 addition & 1 deletion src/net_processing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4326,7 +4326,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
}

if (received_new_header) {
LogPrintfCategory(BCLog::NET, "Saw new cmpctblock header hash=%s peer=%d\n",
LogInfo("Saw new cmpctblock header hash=%s peer=%d\n",
blockhash.ToString(), pfrom.GetId());
}

Expand Down
42 changes: 34 additions & 8 deletions src/test/logging_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,27 +84,32 @@ BOOST_FIXTURE_TEST_CASE(logging_LogPrintf_, LogSetup)
{
LogInstance().m_log_sourcelocations = true;
LogPrintf_("fn1", "src1", 1, BCLog::LogFlags::NET, BCLog::Level::Debug, "foo1: %s\n", "bar1");
LogPrintf_("fn2", "src2", 2, BCLog::LogFlags::NET, BCLog::Level::None, "foo2: %s\n", "bar2");
LogPrintf_("fn3", "src3", 3, BCLog::LogFlags::NONE, BCLog::Level::Debug, "foo3: %s\n", "bar3");
LogPrintf_("fn4", "src4", 4, BCLog::LogFlags::NONE, BCLog::Level::None, "foo4: %s\n", "bar4");
LogPrintf_("fn2", "src2", 2, BCLog::LogFlags::NET, BCLog::Level::Info, "foo2: %s\n", "bar2");
LogPrintf_("fn3", "src3", 3, BCLog::LogFlags::ALL, BCLog::Level::Debug, "foo3: %s\n", "bar3");
LogPrintf_("fn4", "src4", 4, BCLog::LogFlags::ALL, BCLog::Level::Info, "foo4: %s\n", "bar4");
LogPrintf_("fn5", "src5", 5, BCLog::LogFlags::NONE, BCLog::Level::Debug, "foo5: %s\n", "bar5");
LogPrintf_("fn6", "src6", 6, BCLog::LogFlags::NONE, BCLog::Level::Info, "foo6: %s\n", "bar6");
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 = {
"[src1:1] [fn1] [net:debug] foo1: bar1",
"[src2:2] [fn2] [net] foo2: bar2",
"[src1:1] [fn1] [net] foo1: bar1",
"[src2:2] [fn2] [net:info] foo2: bar2",
"[src3:3] [fn3] [debug] foo3: bar3",
"[src4:4] [fn4] foo4: bar4",
"[src5:5] [fn5] [debug] foo5: bar5",
"[src6:6] [fn6] foo6: bar6",
};
BOOST_CHECK_EQUAL_COLLECTIONS(log_lines.begin(), log_lines.end(), expected.begin(), expected.end());
}

BOOST_FIXTURE_TEST_CASE(logging_LogPrintMacros, LogSetup)
BOOST_FIXTURE_TEST_CASE(logging_LogPrintMacrosDeprecated, LogSetup)
{
LogPrintf("foo5: %s\n", "bar5");
LogPrint(BCLog::NET, "foo6: %s\n", "bar6");
LogPrintLevel(BCLog::NET, BCLog::Level::Trace, "foo4: %s\n", "bar4"); // not logged
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");
Expand All @@ -118,11 +123,32 @@ BOOST_FIXTURE_TEST_CASE(logging_LogPrintMacros, LogSetup)
std::vector<std::string> expected = {
"foo5: bar5",
"[net] foo6: bar6",
"[net:debug] foo7: bar7",
"[net] foo7: bar7",
"[net:info] foo8: bar8",
"[net:warning] foo9: bar9",
"[net:error] foo10: bar10",
"[validation] foo11: bar11",
"[validation:info] foo11: bar11",
};
BOOST_CHECK_EQUAL_COLLECTIONS(log_lines.begin(), log_lines.end(), expected.begin(), expected.end());
}

BOOST_FIXTURE_TEST_CASE(logging_LogPrintMacros, LogSetup)
{
LogTrace(BCLog::NET, "foo6: %s\n", "bar6"); // not logged
LogDebug(BCLog::NET, "foo7: %s\n", "bar7");
LogInfo("foo8: %s\n", "bar8");
LogWarning("foo9: %s\n", "bar9");
LogError("foo10: %s\n", "bar10");
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());
}
Expand Down
2 changes: 1 addition & 1 deletion src/torcontrol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ void TorController::add_onion_cb(TorControlConnection& _conn, const TorControlRe
return;
}
service = LookupNumeric(std::string(service_id+".onion"), Params().GetDefaultPort());
LogPrintfCategory(BCLog::TOR, "Got service ID %s, advertising service %s\n", service_id, service.ToStringAddrPort());
LogInfo("Got tor service ID %s, advertising service %s\n", service_id, service.ToStringAddrPort());
if (WriteBinaryFile(GetPrivateKeyFile(), private_key)) {
LogPrint(BCLog::TOR, "Cached service private key to %s\n", fs::PathToString(GetPrivateKeyFile()));
} else {
Expand Down
Loading