mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 23:09:44 -04:00
Merge 093d58e055
into c5e44a0435
This commit is contained in:
commit
7bbd5a27ca
2 changed files with 248 additions and 38 deletions
179
src/logging.h
179
src/logging.h
|
@ -6,6 +6,7 @@
|
||||||
#ifndef BITCOIN_LOGGING_H
|
#ifndef BITCOIN_LOGGING_H
|
||||||
#define BITCOIN_LOGGING_H
|
#define BITCOIN_LOGGING_H
|
||||||
|
|
||||||
|
#include <attributes.h>
|
||||||
#include <threadsafety.h>
|
#include <threadsafety.h>
|
||||||
#include <tinyformat.h>
|
#include <tinyformat.h>
|
||||||
#include <util/fs.h>
|
#include <util/fs.h>
|
||||||
|
@ -21,6 +22,12 @@
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
namespace BCLog {
|
||||||
|
class Logger;
|
||||||
|
} // namespace BCLog
|
||||||
|
|
||||||
|
BCLog::Logger& LogInstance();
|
||||||
|
|
||||||
static const bool DEFAULT_LOGTIMEMICROS = false;
|
static const bool DEFAULT_LOGTIMEMICROS = false;
|
||||||
static const bool DEFAULT_LOGIPS = false;
|
static const bool DEFAULT_LOGIPS = false;
|
||||||
static const bool DEFAULT_LOGTIMESTAMPS = true;
|
static const bool DEFAULT_LOGTIMESTAMPS = true;
|
||||||
|
@ -73,6 +80,11 @@ namespace BCLog {
|
||||||
TXPACKAGES = (CategoryMask{1} << 28),
|
TXPACKAGES = (CategoryMask{1} << 28),
|
||||||
ALL = ~NONE,
|
ALL = ~NONE,
|
||||||
};
|
};
|
||||||
|
//! Log level constants. Most code will not need to use these directly and
|
||||||
|
//! can use LogTrace, LogDebug, LogInfo, LogWarning, and LogError macros
|
||||||
|
//! defined below for less verbosity and more efficiency (by not evaluating
|
||||||
|
//! unused format arguments). See macro definitions below or "Logging"
|
||||||
|
//! section in developer-notes.md for more detailed information.
|
||||||
enum class Level {
|
enum class Level {
|
||||||
Trace = 0, // High-volume or detailed logging for development/debugging
|
Trace = 0, // High-volume or detailed logging for development/debugging
|
||||||
Debug, // Reasonably noisy logging, but still usable in production
|
Debug, // Reasonably noisy logging, but still usable in production
|
||||||
|
@ -225,9 +237,81 @@ namespace BCLog {
|
||||||
bool DefaultShrinkDebugFile() const;
|
bool DefaultShrinkDebugFile() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//! Object representing a particular source of log messages. Holds a logging
|
||||||
|
//! category, a reference to the logger object to output to, and a
|
||||||
|
//! formatting hook.
|
||||||
|
struct Context {
|
||||||
|
Logger& logger;
|
||||||
|
LogFlags category;
|
||||||
|
|
||||||
|
explicit Context(Logger& logger, LogFlags category = LogFlags::ALL) : logger{logger}, category{category} {}
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
std::string Format(util::ConstevalFormatString<sizeof...(Args)> fmt, const Args&... args) const
|
||||||
|
{
|
||||||
|
std::string log_msg;
|
||||||
|
try {
|
||||||
|
log_msg = tfm::format(fmt, args...);
|
||||||
|
} catch (tinyformat::format_error& fmterr) {
|
||||||
|
log_msg = "Error \"" + std::string{fmterr.what()} + "\" while formatting log message: " + fmt.fmt;
|
||||||
|
}
|
||||||
|
return log_msg;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
//! Internal helper to get log context object from the first macro argument.
|
||||||
|
template <bool take_category>
|
||||||
|
static const Context& GetContext(const Context& ctx LIFETIMEBOUND) { return ctx; }
|
||||||
|
|
||||||
|
template <bool take_category>
|
||||||
|
static Context GetContext(LogFlags category)
|
||||||
|
{
|
||||||
|
//! Trigger a compile error if a caller tries to pass a category constant as
|
||||||
|
//! a source argument to a logging macro that specifies take_category ==
|
||||||
|
//! false. There is no technical reason why all logging macros cannot accept
|
||||||
|
//! category arguments, but for various reasons, such as (1) not wanting to
|
||||||
|
//! allow users filter by category at high priority levels, and (2) wanting
|
||||||
|
//! to incentivize developers to use lower log levels to avoid log spam,
|
||||||
|
//! passing category constants at higher levels is forbidden.
|
||||||
|
static_assert(take_category, "Cannot pass BCLog::LogFlags category argument to high level Info/Warning/Error logging macros. Please use lower level Debug/Trace macro, or drop the category argument!");
|
||||||
|
return Context{LogInstance(), category};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <bool take_category>
|
||||||
|
static Context GetContext(std::string_view fmt)
|
||||||
|
{
|
||||||
|
//! Trigger a compile error if a caller forgets to pass a category constant
|
||||||
|
//! as a source argument to a logging macro that specifies take_category ==
|
||||||
|
//! true. There is no technical reason why all logging macros could not
|
||||||
|
//! allow the category argument to be optional, but low priority level
|
||||||
|
//! macros should specify category in order to only be visible when the
|
||||||
|
//! selected category is enabled. This helps reduce log spam.
|
||||||
|
static_assert(!take_category, "Must pass category to Debug/Trace logging macros.");
|
||||||
|
return Context{LogInstance()};
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Internal helper to format log arguments and call a logging function.
|
||||||
|
template <typename LogFn, typename Context, typename ContextArg, typename... Args>
|
||||||
|
requires (!std::is_convertible_v<ContextArg, std::string_view>)
|
||||||
|
void Format(LogFn&& log, Context&& ctx, ContextArg&&, util::ConstevalFormatString<sizeof...(Args)> fmt, const Args&... args)
|
||||||
|
{
|
||||||
|
log(ctx.Format(fmt, args...));
|
||||||
|
}
|
||||||
|
template <typename LogFn, typename Context, typename... Args>
|
||||||
|
void Format(LogFn&& log, Context&& ctx, util::ConstevalFormatString<sizeof...(Args)> fmt, const Args&... args)
|
||||||
|
{
|
||||||
|
log(ctx.Format(fmt, args...));
|
||||||
|
}
|
||||||
|
} // namespace detail
|
||||||
} // namespace BCLog
|
} // namespace BCLog
|
||||||
|
|
||||||
BCLog::Logger& LogInstance();
|
//! Determine whether logging is enabled in a context at the specified level.
|
||||||
|
template <typename Context>
|
||||||
|
static inline bool LogEnabled(const Context& ctx, BCLog::Level level)
|
||||||
|
{
|
||||||
|
return ctx.logger.WillLogCategoryLevel(ctx.category, level) && ctx.logger.Enabled();
|
||||||
|
}
|
||||||
|
|
||||||
/** Return true if log accepts specified category, at the specified level. */
|
/** Return true if log accepts specified category, at the specified level. */
|
||||||
static inline bool LogAcceptCategory(BCLog::LogFlags category, BCLog::Level level)
|
static inline bool LogAcceptCategory(BCLog::LogFlags category, BCLog::Level level)
|
||||||
|
@ -238,46 +322,65 @@ static inline bool LogAcceptCategory(BCLog::LogFlags category, BCLog::Level leve
|
||||||
/** Return true if str parses as a log category and set the flag */
|
/** Return true if str parses as a log category and set the flag */
|
||||||
bool GetLogCategory(BCLog::LogFlags& flag, std::string_view str);
|
bool GetLogCategory(BCLog::LogFlags& flag, std::string_view str);
|
||||||
|
|
||||||
template <typename... Args>
|
|
||||||
inline void LogPrintFormatInternal(std::string_view logging_function, std::string_view source_file, const int source_line, const BCLog::LogFlags flag, const BCLog::Level level, util::ConstevalFormatString<sizeof...(Args)> fmt, const Args&... args)
|
|
||||||
{
|
|
||||||
if (LogInstance().Enabled()) {
|
|
||||||
std::string log_msg;
|
|
||||||
try {
|
|
||||||
log_msg = tfm::format(fmt, args...);
|
|
||||||
} catch (tinyformat::format_error& fmterr) {
|
|
||||||
log_msg = "Error \"" + std::string{fmterr.what()} + "\" while formatting log message: " + fmt.fmt;
|
|
||||||
}
|
|
||||||
LogInstance().LogPrintStr(log_msg, logging_function, source_file, source_line, flag, level);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#define LogPrintLevel_(category, level, ...) LogPrintFormatInternal(__func__, __FILE__, __LINE__, category, level, __VA_ARGS__)
|
//! Internal helper to return first arg in a __VA_ARGS__ pack. This could be
|
||||||
|
//! simplified to `#define FirstArg_(arg, ...) arg` if not for a preprocessor
|
||||||
|
//! bug in Visual C++.
|
||||||
|
#define FirstArg_Impl(arg, ...) arg
|
||||||
|
#define FirstArg_(args) FirstArg_Impl args
|
||||||
|
|
||||||
// Log unconditionally.
|
//! Internal helper to conditionally log. Only evaluates arguments when needed.
|
||||||
// Be conservative when using functions that unconditionally log to debug.log!
|
#define LogPrint_(level, take_category, ...) \
|
||||||
// It should not be the case that an inbound peer can fill up a user's storage
|
do { \
|
||||||
// with debug.log entries.
|
const auto& ctx{BCLog::detail::GetContext<take_category>(FirstArg_((__VA_ARGS__)))}; \
|
||||||
#define LogInfo(...) LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Info, __VA_ARGS__)
|
if (LogEnabled(ctx, (level))) { \
|
||||||
#define LogWarning(...) LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Warning, __VA_ARGS__)
|
const auto& func = __func__; \
|
||||||
#define LogError(...) LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Error, __VA_ARGS__)
|
BCLog::detail::Format([&](auto&& message) { \
|
||||||
|
ctx.logger.LogPrintStr(message, func, __FILE__, __LINE__, \
|
||||||
// Deprecated unconditional logging.
|
ctx.category, (level)); \
|
||||||
#define LogPrintf(...) LogInfo(__VA_ARGS__)
|
}, ctx, __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 and severity level.
|
|
||||||
#define LogPrintLevel(category, level, ...) \
|
|
||||||
do { \
|
|
||||||
if (LogAcceptCategory((category), (level))) { \
|
|
||||||
LogPrintLevel_(category, level, __VA_ARGS__); \
|
|
||||||
} \
|
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
// Log conditionally, prefixing the output with the passed category name.
|
//! Logging macros which output log messages at the specified levels, and avoid
|
||||||
#define LogDebug(category, ...) LogPrintLevel(category, BCLog::Level::Debug, __VA_ARGS__)
|
//! evaluating their arguments if logging is not enabled for the level. The
|
||||||
#define LogTrace(category, ...) LogPrintLevel(category, BCLog::Level::Trace, __VA_ARGS__)
|
//! macros accept an optional log context parameter followed by a printf-style
|
||||||
|
//! format string and arguments.
|
||||||
|
//!
|
||||||
|
//! - LogError(), LogWarning(), and LogInfo() are all enabled by default, so
|
||||||
|
//! they should be called infrequently, in cases where they will not spam the
|
||||||
|
//! log and take up disk space.
|
||||||
|
//!
|
||||||
|
//! - LogDebug() is enabled when debug logging is enabled, and should be used to
|
||||||
|
//! show messages that can help users troubleshoot issues.
|
||||||
|
//!
|
||||||
|
//! - LogTrace() is enabled when both debug logging AND tracing are enabled, and
|
||||||
|
//! should be used for fine-grained traces that will be helpful to developers.
|
||||||
|
//!
|
||||||
|
//! For more information about log levels, see the -debug and -loglevel
|
||||||
|
//! documentation, or the "Logging" section of developer notes.
|
||||||
|
//!
|
||||||
|
//! `LogDebug` and `LogTrace` macros take an initial category argument, so
|
||||||
|
//! messages can be filtered by category, but categories should be omitted at
|
||||||
|
//! higher levels:
|
||||||
|
//
|
||||||
|
//! LogDebug(BCLog::TXRECONCILIATION, "Forget txreconciliation state of peer=%d\n", peer_id);
|
||||||
|
//! LogInfo("Important information, no category.\n");
|
||||||
|
//!
|
||||||
|
//! Context arguments can also be passed to control log output (see class definition).
|
||||||
|
//!
|
||||||
|
//! const BCLog::Context m_log{BCLog::TXRECONCILIATION};
|
||||||
|
//! ...
|
||||||
|
//! LogDebug(m_log, "Forget txreconciliation state of peer=%d\n", peer_id);
|
||||||
|
//!
|
||||||
|
#define LogError(...) LogPrint_(BCLog::Level::Error, false, __VA_ARGS__)
|
||||||
|
#define LogWarning(...) LogPrint_(BCLog::Level::Warning, false, __VA_ARGS__)
|
||||||
|
#define LogInfo(...) LogPrint_(BCLog::Level::Info, false, __VA_ARGS__)
|
||||||
|
#define LogDebug(...) LogPrint_(BCLog::Level::Debug, true, __VA_ARGS__)
|
||||||
|
#define LogTrace(...) LogPrint_(BCLog::Level::Trace, true, __VA_ARGS__)
|
||||||
|
#define LogPrintLevel(ctx, level, ...) LogPrint_(level, true, ctx, __VA_ARGS__)
|
||||||
|
|
||||||
|
//! Deprecated macros.
|
||||||
|
#define LogPrintf(...) LogInfo(__VA_ARGS__)
|
||||||
|
|
||||||
#endif // BITCOIN_LOGGING_H
|
#endif // BITCOIN_LOGGING_H
|
||||||
|
|
|
@ -76,6 +76,113 @@ struct LogSetup : public BasicTestingSetup {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//! Test logging to local logger.
|
||||||
|
BOOST_AUTO_TEST_CASE(logging_local_logger)
|
||||||
|
{
|
||||||
|
BCLog::Logger logger;
|
||||||
|
logger.m_log_timestamps = false;
|
||||||
|
logger.EnableCategory(BCLog::LogFlags::ALL);
|
||||||
|
logger.SetLogLevel(BCLog::Level::Trace);
|
||||||
|
logger.StartLogging();
|
||||||
|
|
||||||
|
std::vector<std::string> messages;
|
||||||
|
logger.PushBackCallback([&](const std::string& s) { messages.push_back(s); });
|
||||||
|
|
||||||
|
BCLog::Context log{logger, BCLog::NET};
|
||||||
|
LogError(log, "error %s\n", "arg");
|
||||||
|
LogWarning(log, "warning %s\n", "arg");
|
||||||
|
LogInfo(log, "info %s\n", "arg");
|
||||||
|
LogDebug(log, "debug %s\n", "arg");
|
||||||
|
LogTrace(log, "trace %s\n", "arg");
|
||||||
|
|
||||||
|
constexpr auto expected{std::to_array({
|
||||||
|
"[net:error] error arg\n",
|
||||||
|
"[net:warning] warning arg\n",
|
||||||
|
"[net:info] info arg\n",
|
||||||
|
"[net] debug arg\n",
|
||||||
|
"[net:trace] trace arg\n",
|
||||||
|
})};
|
||||||
|
BOOST_CHECK_EQUAL_COLLECTIONS(messages.begin(), messages.end(), expected.begin(), expected.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Test logging to global logger with different types of context arguments.
|
||||||
|
BOOST_FIXTURE_TEST_CASE(logging_context_args, LogSetup)
|
||||||
|
{
|
||||||
|
LogInstance().EnableCategory(BCLog::LogFlags::ALL);
|
||||||
|
LogInstance().SetLogLevel(BCLog::Level::Trace);
|
||||||
|
|
||||||
|
// Test logging with no context arguments.
|
||||||
|
LogError("error\n");
|
||||||
|
LogWarning("warning\n");
|
||||||
|
LogInfo("info\n");
|
||||||
|
// LogDebug("debug\n"); // Not allowed because category is required!
|
||||||
|
// LogTrace("trace\n"); // Not allowed because category is required!
|
||||||
|
LogError("error %s\n", "arg");
|
||||||
|
LogWarning("warning %s\n", "arg");
|
||||||
|
LogInfo("info %s\n", "arg");
|
||||||
|
// LogDebug("debug %s\n", "arg"); // Not allowed because category is required!
|
||||||
|
// LogTrace("trace %s\n", "arg"); // Not allowed because category is required!
|
||||||
|
|
||||||
|
// Test logging with category constant arguments.
|
||||||
|
// LogError(BCLog::NET, "error\n"); // Not allowed because category is forbidden!
|
||||||
|
// LogWarning(BCLog::NET, "warning\n"); // Not allowed because category is forbidden!
|
||||||
|
// LogInfo(BCLog::NET, "info\n"); // Not allowed because category is forbidden!
|
||||||
|
LogDebug(BCLog::NET, "debug\n");
|
||||||
|
LogTrace(BCLog::NET, "trace\n");
|
||||||
|
// LogError(BCLog::NET, "error %s\n", "arg"); // Not allowed because category is forbidden!
|
||||||
|
// LogWarning(BCLog::NET, "warning %s\n", "arg"); // Not allowed because category is forbidden!
|
||||||
|
// LogInfo(BCLog::NET, "info %s\n", "arg"); // Not allowed because category is forbidden!
|
||||||
|
LogDebug(BCLog::NET, "debug %s\n", "arg");
|
||||||
|
LogTrace(BCLog::NET, "trace %s\n", "arg");
|
||||||
|
|
||||||
|
// Test logging with context object.
|
||||||
|
BCLog::Context log{LogInstance(), BCLog::TOR};
|
||||||
|
LogError(log, "error\n");
|
||||||
|
LogWarning(log, "warning\n");
|
||||||
|
LogInfo(log, "info\n");
|
||||||
|
LogDebug(log, "debug\n");
|
||||||
|
LogTrace(log, "trace\n");
|
||||||
|
LogError(log, "error %s\n", "arg");
|
||||||
|
LogWarning(log, "warning %s\n", "arg");
|
||||||
|
LogInfo(log, "info %s\n", "arg");
|
||||||
|
LogDebug(log, "debug %s\n", "arg");
|
||||||
|
LogTrace(log, "trace %s\n", "arg");
|
||||||
|
|
||||||
|
constexpr auto expected{std::to_array({
|
||||||
|
"[error] error",
|
||||||
|
"[warning] warning",
|
||||||
|
"info",
|
||||||
|
|
||||||
|
"[error] error arg",
|
||||||
|
"[warning] warning arg",
|
||||||
|
"info arg",
|
||||||
|
|
||||||
|
"[net] debug",
|
||||||
|
"[net:trace] trace",
|
||||||
|
|
||||||
|
"[net] debug arg",
|
||||||
|
"[net:trace] trace arg",
|
||||||
|
|
||||||
|
"[tor:error] error",
|
||||||
|
"[tor:warning] warning",
|
||||||
|
"[tor:info] info",
|
||||||
|
"[tor] debug",
|
||||||
|
"[tor:trace] trace",
|
||||||
|
|
||||||
|
"[tor:error] error arg",
|
||||||
|
"[tor:warning] warning arg",
|
||||||
|
"[tor:info] info arg",
|
||||||
|
"[tor] debug arg",
|
||||||
|
"[tor:trace] trace arg",
|
||||||
|
})};
|
||||||
|
std::ifstream file{tmp_log_path};
|
||||||
|
std::vector<std::string> log_lines;
|
||||||
|
for (std::string log; std::getline(file, log);) {
|
||||||
|
log_lines.push_back(log);
|
||||||
|
}
|
||||||
|
BOOST_CHECK_EQUAL_COLLECTIONS(log_lines.begin(), log_lines.end(), expected.begin(), expected.end());
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(logging_timer)
|
BOOST_AUTO_TEST_CASE(logging_timer)
|
||||||
{
|
{
|
||||||
auto micro_timer = BCLog::Timer<std::chrono::microseconds>("tests", "end_msg");
|
auto micro_timer = BCLog::Timer<std::chrono::microseconds>("tests", "end_msg");
|
||||||
|
|
Loading…
Add table
Reference in a new issue