Files
bitcoin/src/logging.h
Ryan Ofsky 796f18e559 Merge bitcoin/bitcoin#29415: Broadcast own transactions only via short-lived Tor or I2P connections
8937221304 doc: add release notes for 29415 (Vasil Dimov)
582016fa5f test: add unit test for the private broadcast storage (Vasil Dimov)
e74d54e048 test: add functional test for private broadcast (Vasil Dimov)
818b780a05 rpc: use private broadcast from sendrawtransaction RPC if -privatebroadcast is ON (Vasil Dimov)
eab595f9cf net_processing: retry private broadcast (Vasil Dimov)
37b79f9c39 net_processing: stop private broadcast of a transaction after round-trip (Vasil Dimov)
2de53eee74 net_processing: handle ConnectionType::PRIVATE_BROADCAST connections (Vasil Dimov)
30a9853ad3 net_processing: move a debug check in VERACK processing earlier (Vasil Dimov)
d1092e5d48 net_processing: modernize PushNodeVersion() (Vasil Dimov)
9937a12a2f net_processing: move the debug log about receiving VERSION earlier (Vasil Dimov)
a098f37b9e net_processing: reorder the code that handles the VERSION message (Vasil Dimov)
679ce3a0b8 net_processing: store transactions for private broadcast in PeerManager (Vasil Dimov)
a3faa6f944 node: extend node::TxBroadcast with a 3rd option (Vasil Dimov)
95c051e210 net_processing: rename RelayTransaction() to better describe what it does (Vasil Dimov)
bb49d26032 net: implement opening PRIVATE_BROADCAST connections (Vasil Dimov)
01dad4efe2 net: introduce a new connection type for private broadcast (Vasil Dimov)
94aaa5d31b init: introduce a new option to enable/disable private broadcast (Vasil Dimov)
d6ee490e0a log: introduce a new category for private broadcast (Vasil Dimov)

Pull request description:

  _Parts of this PR are isolated in independent smaller PRs to ease review:_

  * [x] _https://github.com/bitcoin/bitcoin/pull/29420_
  * [x] _https://github.com/bitcoin/bitcoin/pull/33454_
  * [x] _https://github.com/bitcoin/bitcoin/pull/33567_
  * [x] _https://github.com/bitcoin/bitcoin/pull/33793_

  ---

  To improve privacy, broadcast locally submitted transactions (from the `sendrawtransaction` RPC) to the P2P network only via Tor or I2P short-lived connections, or to IPv4/IPv6 peers but through the Tor network.

  * Introduce a new connection type for private broadcast of transactions with the following properties:
    * started whenever there are local transactions to be sent
    * opened to Tor or I2P peers or IPv4/IPv6 via the Tor proxy
    * opened regardless of max connections limits
    * after handshake is completed one local transaction is pushed to the peer, `PING` is sent and after receiving `PONG` the connection is closed
    * ignore all incoming messages after handshake is completed (except `PONG`)

  * Broadcast transactions submitted via `sendrawtransaction` using this new mechanism, to a few peers. Keep doing this until we receive back this transaction from one of our ordinary peers (this takes about 1 second on mainnet).

  * The transaction is stored in peerman and does not enter the mempool.

  * Once we get an `INV` from one of our ordinary peers, then the normal flow executes: we request the transaction with `GETDATA`, receive it with a `TX` message, put it in our mempool and broadcast it to all our existent connections (as if we see it for the first time).

  * After we receive the full transaction as a `TX` message, in reply to our `GETDATA` request, only then consider the transaction has propagated through the network and remove it from the storage in peerman, ending the private broadcast attempts.

  The messages exchange should look like this:

  ```
  tx-sender >--- connect -------> tx-recipient
  tx-sender >--- VERSION -------> tx-recipient (dummy VERSION with no revealing data)
  tx-sender <--- VERSION -------< tx-recipient
  tx-sender <--- WTXIDRELAY ----< tx-recipient (maybe)
  tx-sender <--- SENDADDRV2 ----< tx-recipient (maybe)
  tx-sender <--- SENDTXRCNCL ---< tx-recipient (maybe)
  tx-sender <--- VERACK --------< tx-recipient
  tx-sender >--- VERACK --------> tx-recipient
  tx-sender >--- INV/TX --------> tx-recipient
  tx-sender <--- GETDATA/TX ----< tx-recipient
  tx-sender >--- TX ------------> tx-recipient
  tx-sender >--- PING ----------> tx-recipient
  tx-sender <--- PONG ----------< tx-recipient
  tx-sender disconnects
  ```

  Whenever a new transaction is received from `sendrawtransaction` RPC, the node will send it to a few (`NUM_PRIVATE_BROADCAST_PER_TX`) recipients right away. If after some time we still have not heard anything about the transaction from the network, then it will be sent to 1 more peer (see `PeerManagerImpl::ReattemptPrivateBroadcast()`).

  A few considerations:
  * The short-lived private broadcast connections are very cheap and fast wrt network traffic. It is expected that some of those peers could blackhole the transaction. Just one honest/proper peer is enough for successful propagation.
  * The peers that receive the transaction could deduce that this is initial transaction broadcast from the transaction originator. This is ok, they can't identify the sender.

  ---

  <details>
  <summary>How to test this?</summary>

  Thank you, @stratospher and @andrewtoth!

  Start `bitcoind` with `-privatebroadcast=1 -debug=privatebroadcast`.

  Create a wallet and get a new address, go to the Signet faucet and request some coins to that address:
  ```bash
  build/bin/bitcoin-cli -chain="signet" createwallet test
  build/bin/bitcoin-cli -chain="signet" getnewaddress
  ```

  Get a new address for the test transaction recipient:
  ```bash
  build/bin/bitcoin-cli -chain="signet" loadwallet test
  new_address=$(build/bin/bitcoin-cli -chain="signet" getnewaddress)
  ```

  Create the transaction:
  ```bash
  # Option 1: `createrawtransaction` and `signrawtransactionwithwallet`:

  txid=$(build/bin/bitcoin-cli -chain="signet" listunspent | jq -r '.[0] | .txid')
  vout=$(build/bin/bitcoin-cli -chain="signet" listunspent | jq -r '.[0] | .vout')
  echo "txid: $txid"
  echo "vout: $vout"

  tx=$(build/bin/bitcoin-cli -chain="signet" createrawtransaction "[{\"txid\": \"$txid\", \"vout\": $vout}]" "[{\"$new_address\": 0.00001000}]" 0 false)
  echo "tx: $tx"

  signed_tx=$(build/bin/bitcoin-cli -chain="signet" signrawtransactionwithwallet "$tx" | jq -r '.hex')
  echo "signed_tx: $signed_tx"

  # OR Option 2: `walletcreatefundedpsbt` and `walletprocesspsbt`:
  # This makes it not have to worry about inputs and also automatically sends back change to the wallet.
  # Start `bitcoind` with `-fallbackfee=0.00003000` for instance for 3 sat/vbyte fee.

  psbt=$(build/bin/bitcoin-cli -chain="signet" walletcreatefundedpsbt "[]" "[{\"$new_address\": 0.00001000}]" | jq -r '.psbt')
  echo "psbt: $psbt"

  signed_tx=$(build/bin/bitcoin-cli -chain="signet" walletprocesspsbt "$psbt" | jq -r '.hex')
  echo "signed_tx: $signed_tx"
  ```

  Finally, send the transaction:
  ```bash
  raw_tx=$(build/bin/bitcoin-cli -chain="signet" sendrawtransaction "$signed_tx")
  echo "raw_tx: $raw_tx"
  ```

  </details>

  ---

  <details>
  <summary>High-level explanation of the commits</summary>

  * New logging category and config option to enable private broadcast
    * `log: introduce a new category for private broadcast`
    * `init: introduce a new option to enable/disable private broadcast`

  * Implement the private broadcast connection handling on the `CConnman` side:
    * `net: introduce a new connection type for private broadcast`
    * `net: implement opening PRIVATE_BROADCAST connections`

  * Prepare `BroadcastTransaction()` for private broadcast requests:
    * `net_processing: rename RelayTransaction to better describe what it does`
    * `node: extend node::TxBroadcast with a 3rd option`
    * `net_processing: store transactions for private broadcast in PeerManager`

  * Implement the private broadcast connection handling on the `PeerManager` side:
    * `net_processing: reorder the code that handles the VERSION message`
    * `net_processing: move the debug log about receiving VERSION earlier`
    * `net_processing: modernize PushNodeVersion()`
    * `net_processing: move a debug check in VERACK processing earlier`
    * `net_processing: handle ConnectionType::PRIVATE_BROADCAST connections`
    * `net_processing: stop private broadcast of a transaction after round-trip`
    * `net_processing: retry private broadcast`

  * Engage the new functionality from `sendrawtransaction`:
    * `rpc: use private broadcast from sendrawtransaction RPC if -privatebroadcast is ON`

  * New tests:
    * `test: add functional test for private broadcast`
    * `test: add unit test for the private broadcast storage`

  </details>

  ---

  **This PR would resolve the following issues:**
  https://github.com/bitcoin/bitcoin/issues/3828 Clients leak IPs if they are recipients of a transaction
  https://github.com/bitcoin/bitcoin/issues/14692 Can't configure bitocoind to only send tx via Tor but receive clearnet transactions
  https://github.com/bitcoin/bitcoin/issues/19042 Tor-only transaction broadcast onlynet=onion alternative
  https://github.com/bitcoin/bitcoin/issues/24557 Option for receive events with all networks, but send transactions and/or blocks only with anonymous network[s]?
  https://github.com/bitcoin/bitcoin/issues/25450 Ability to broadcast wallet transactions only via dedicated oneshot Tor connections
  https://github.com/bitcoin/bitcoin/issues/32235 Tor: TX circuit isolation

  **Issues that are related, but (maybe?) not to be resolved by this PR:**
  https://github.com/bitcoin/bitcoin/issues/21876 Broadcast a transaction to specific nodes
  https://github.com/bitcoin/bitcoin/issues/28636 new RPC: sendrawtransactiontopeer

  ---

  Further extensions:
  * Have the wallet do the private broadcast as well, https://github.com/bitcoin/bitcoin/issues/11887 would have to be resolved.
  * Have the `submitpackage` RPC do the private broadcast as well, [draft diff in the comment below](https://github.com/bitcoin/bitcoin/pull/29415#pullrequestreview-2972293733), thanks ismaelsadeeq!
  * Add some stats via RPC, so that the user can better monitor what is going on during and after the broadcast. Currently this can be done via the debug log, but that is not convenient.
  * Make the private broadcast storage, currently in peerman, persistent over node restarts.
  * Add (optional) random delay before starting to broadcast the transaction in order to avoid correlating unrelated transactions based on the time when they were broadcast. Suggested independently of this PR [here](https://github.com/bitcoin/bitcoin/issues/30471).
  * Consider periodically sending transactions that did not originate from the node as decoy, discussed [here](https://github.com/bitcoin/bitcoin/pull/29415#discussion_r2035414972).
  * Consider waiting for peer's FEEFILTER message and if the transaction that was sent to the peer is below that threshold, then assume the peer is going to drop it. Then use this knowledge to retry more aggressively with another peer, instead of the current 10 min. See [comment below](https://github.com/bitcoin/bitcoin/pull/29415#issuecomment-3258611648).
  * It may make sense to be able to override the default policy -- eg so submitrawtransaction can go straight to the mempool and relay, even if txs are normally privately relayed. See [comment below](https://github.com/bitcoin/bitcoin/pull/29415#issuecomment-3427086681).
  * As a side effect we have a new metric available - the time it takes for a transaction to reach a random node in the network (from the point of view of the private broadcast recipient the tx originator is a random node somewhere in the network). This can be useful for monitoring, unrelated to privacy characteristics of this feature.

  ---

  _A previous incarnation of this can be found at https://github.com/bitcoin/bitcoin/pull/27509. It puts the transaction in the mempool and (tries to) hide it from the outside observers. This turned out to be too error prone or maybe even impossible._

ACKs for top commit:
  l0rinc:
    code review diff ACK 8937221304
  andrewtoth:
    ACK 8937221304
  pinheadmz:
    ACK 8937221304
  w0xlt:
    ACK 8937221304 with nit https://github.com/bitcoin/bitcoin/pull/29415#discussion_r2654849875
  mzumsande:
    re-ACK 8937221304

Tree-SHA512: d51dadc865c2eb080c903cbe2f669e69a967e5f9fc64e9a20a68f39a67bf0db6ac2ad682af7fa24ef9f0942a41c89959341a16ba7b616475e1c5ab8e563b9b96
2026-01-12 15:02:14 -05:00

416 lines
18 KiB
C++

// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-present The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_LOGGING_H
#define BITCOIN_LOGGING_H
#include <crypto/siphash.h>
#include <threadsafety.h>
#include <tinyformat.h>
#include <util/check.h>
#include <util/fs.h>
#include <util/string.h>
#include <util/time.h>
#include <atomic>
#include <cstdint>
#include <cstring>
#include <functional>
#include <list>
#include <memory>
#include <mutex>
#include <source_location>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
static const bool DEFAULT_LOGTIMEMICROS = false;
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;
/// Like std::source_location, but allowing to override the function name.
class SourceLocation
{
public:
/// The func argument must be constructed from the C++11 __func__ macro.
/// Ref: https://en.cppreference.com/w/cpp/language/function.html#func
/// Non-static string literals are not supported.
SourceLocation(const char* func,
std::source_location loc = std::source_location::current())
: m_func{func}, m_loc{loc} {}
std::string_view file_name() const { return m_loc.file_name(); }
std::uint_least32_t line() const { return m_loc.line(); }
std::string_view function_name_short() const { return m_func; }
private:
std::string_view m_func;
std::source_location m_loc;
};
struct SourceLocationEqual {
bool operator()(const SourceLocation& lhs, const SourceLocation& rhs) const noexcept
{
return lhs.line() == rhs.line() && std::string_view(lhs.file_name()) == std::string_view(rhs.file_name());
}
};
struct SourceLocationHasher {
size_t operator()(const SourceLocation& s) const noexcept
{
// Use CSipHasher(0, 0) as a simple way to get uniform distribution.
return size_t(CSipHasher(0, 0)
.Write(s.line())
.Write(MakeUCharSpan(std::string_view{s.file_name()}))
.Finalize());
}
};
struct LogCategory {
std::string category;
bool active;
};
namespace BCLog {
using CategoryMask = uint64_t;
enum LogFlags : CategoryMask {
NONE = CategoryMask{0},
NET = (CategoryMask{1} << 0),
TOR = (CategoryMask{1} << 1),
MEMPOOL = (CategoryMask{1} << 2),
HTTP = (CategoryMask{1} << 3),
BENCH = (CategoryMask{1} << 4),
ZMQ = (CategoryMask{1} << 5),
WALLETDB = (CategoryMask{1} << 6),
RPC = (CategoryMask{1} << 7),
ESTIMATEFEE = (CategoryMask{1} << 8),
ADDRMAN = (CategoryMask{1} << 9),
SELECTCOINS = (CategoryMask{1} << 10),
REINDEX = (CategoryMask{1} << 11),
CMPCTBLOCK = (CategoryMask{1} << 12),
RAND = (CategoryMask{1} << 13),
PRUNE = (CategoryMask{1} << 14),
PROXY = (CategoryMask{1} << 15),
MEMPOOLREJ = (CategoryMask{1} << 16),
LIBEVENT = (CategoryMask{1} << 17),
COINDB = (CategoryMask{1} << 18),
QT = (CategoryMask{1} << 19),
LEVELDB = (CategoryMask{1} << 20),
VALIDATION = (CategoryMask{1} << 21),
I2P = (CategoryMask{1} << 22),
IPC = (CategoryMask{1} << 23),
#ifdef DEBUG_LOCKCONTENTION
LOCK = (CategoryMask{1} << 24),
#endif
BLOCKSTORAGE = (CategoryMask{1} << 25),
TXRECONCILIATION = (CategoryMask{1} << 26),
SCAN = (CategoryMask{1} << 27),
TXPACKAGES = (CategoryMask{1} << 28),
KERNEL = (CategoryMask{1} << 29),
PRIVBROADCAST = (CategoryMask{1} << 30),
ALL = ~NONE,
};
enum class Level {
Trace = 0, // High-volume or detailed logging for development/debugging
Debug, // Reasonably noisy logging, but still usable in production
Info, // Default
Warning,
Error,
};
constexpr auto DEFAULT_LOG_LEVEL{Level::Debug};
constexpr size_t DEFAULT_MAX_LOG_BUFFER{1'000'000}; // buffer up to 1MB of log data prior to StartLogging
constexpr uint64_t RATELIMIT_MAX_BYTES{1024 * 1024}; // maximum number of bytes per source location that can be logged within the RATELIMIT_WINDOW
constexpr auto RATELIMIT_WINDOW{1h}; // time window after which log ratelimit stats are reset
constexpr bool DEFAULT_LOGRATELIMIT{true};
//! Fixed window rate limiter for logging.
class LogRateLimiter
{
public:
//! Keeps track of an individual source location and how many available bytes are left for logging from it.
struct Stats {
//! Remaining bytes
uint64_t m_available_bytes;
//! Number of bytes that were consumed but didn't fit in the available bytes.
uint64_t m_dropped_bytes{0};
Stats(uint64_t max_bytes) : m_available_bytes{max_bytes} {}
//! Updates internal accounting and returns true if enough available_bytes were remaining
bool Consume(uint64_t bytes);
};
private:
mutable StdMutex m_mutex;
//! Stats for each source location that has attempted to log something.
std::unordered_map<SourceLocation, Stats, SourceLocationHasher, SourceLocationEqual> m_source_locations GUARDED_BY(m_mutex);
//! Whether any log locations are suppressed. Cached view on m_source_locations for performance reasons.
std::atomic<bool> m_suppression_active{false};
LogRateLimiter(uint64_t max_bytes, std::chrono::seconds reset_window);
public:
using SchedulerFunction = std::function<void(std::function<void()>, std::chrono::milliseconds)>;
/**
* @param scheduler_func Callable object used to schedule resetting the window. The first
* parameter is the function to be executed, and the second is the
* reset_window interval.
* @param max_bytes Maximum number of bytes that can be logged for each source
* location.
* @param reset_window Time window after which the stats are reset.
*/
static std::shared_ptr<LogRateLimiter> Create(
SchedulerFunction&& scheduler_func,
uint64_t max_bytes,
std::chrono::seconds reset_window);
//! Maximum number of bytes logged per location per window.
const uint64_t m_max_bytes;
//! Interval after which the window is reset.
const std::chrono::seconds m_reset_window;
//! Suppression status of a source log location.
enum class Status {
UNSUPPRESSED, // string fits within the limit
NEWLY_SUPPRESSED, // suppression has started since this string
STILL_SUPPRESSED, // suppression is still ongoing
};
//! Consumes `source_loc`'s available bytes corresponding to the size of the (formatted)
//! `str` and returns its status.
[[nodiscard]] Status Consume(
const SourceLocation& source_loc,
const std::string& str) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
//! Resets all usage to zero. Called periodically by the scheduler.
void Reset() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
//! Returns true if any log locations are currently being suppressed.
bool SuppressionsActive() const { return m_suppression_active; }
};
class Logger
{
public:
struct BufferedLog {
SystemClock::time_point now;
std::chrono::seconds mocktime;
std::string str, threadname;
SourceLocation source_loc;
LogFlags category;
Level level;
};
private:
mutable StdMutex m_cs; // Can not use Mutex from sync.h because in debug mode it would cause a deadlock when a potential deadlock was detected
FILE* m_fileout GUARDED_BY(m_cs) = nullptr;
std::list<BufferedLog> m_msgs_before_open GUARDED_BY(m_cs);
bool m_buffering GUARDED_BY(m_cs) = true; //!< Buffer messages before logging can be started.
size_t m_max_buffer_memusage GUARDED_BY(m_cs){DEFAULT_MAX_LOG_BUFFER};
size_t m_cur_buffer_memusage GUARDED_BY(m_cs){0};
size_t m_buffer_lines_discarded GUARDED_BY(m_cs){0};
//! Manages the rate limiting of each log location.
std::shared_ptr<LogRateLimiter> m_limiter GUARDED_BY(m_cs);
//! Category-specific log level. Overrides `m_log_level`.
std::unordered_map<LogFlags, Level> m_category_log_levels GUARDED_BY(m_cs);
//! If there is no category-specific log level, all logs with a severity
//! level lower than `m_log_level` will be ignored.
std::atomic<Level> m_log_level{DEFAULT_LOG_LEVEL};
/** Log categories bitfield. */
std::atomic<CategoryMask> m_categories{BCLog::NONE};
void FormatLogStrInPlace(std::string& str, LogFlags category, Level level, const SourceLocation& source_loc, std::string_view threadname, SystemClock::time_point now, std::chrono::seconds mocktime) const;
std::string LogTimestampStr(SystemClock::time_point now, std::chrono::seconds mocktime) const;
/** Slots that connect to the print signal */
std::list<std::function<void(const std::string&)>> m_print_callbacks GUARDED_BY(m_cs) {};
/** Send a string to the log output (internal) */
void LogPrintStr_(std::string_view str, SourceLocation&& source_loc, BCLog::LogFlags category, BCLog::Level level, bool should_ratelimit)
EXCLUSIVE_LOCKS_REQUIRED(m_cs);
std::string GetLogPrefix(LogFlags category, Level level) const;
public:
bool m_print_to_console = false;
bool m_print_to_file = false;
bool m_log_timestamps = DEFAULT_LOGTIMESTAMPS;
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};
/** Send a string to the log output */
void LogPrintStr(std::string_view str, SourceLocation&& source_loc, BCLog::LogFlags category, BCLog::Level level, bool should_ratelimit)
EXCLUSIVE_LOCKS_REQUIRED(!m_cs);
/** Returns whether logs will be written to any output */
bool Enabled() const EXCLUSIVE_LOCKS_REQUIRED(!m_cs)
{
StdLockGuard scoped_lock(m_cs);
return m_buffering || m_print_to_console || m_print_to_file || !m_print_callbacks.empty();
}
/** Connect a slot to the print signal and return the connection */
std::list<std::function<void(const std::string&)>>::iterator PushBackCallback(std::function<void(const std::string&)> fun) EXCLUSIVE_LOCKS_REQUIRED(!m_cs)
{
StdLockGuard scoped_lock(m_cs);
m_print_callbacks.push_back(std::move(fun));
return --m_print_callbacks.end();
}
/** Delete a connection */
void DeleteCallback(std::list<std::function<void(const std::string&)>>::iterator it) EXCLUSIVE_LOCKS_REQUIRED(!m_cs)
{
StdLockGuard scoped_lock(m_cs);
m_print_callbacks.erase(it);
}
size_t NumConnections()
{
StdLockGuard scoped_lock(m_cs);
return m_print_callbacks.size();
}
/** Start logging (and flush all buffered messages) */
bool StartLogging() EXCLUSIVE_LOCKS_REQUIRED(!m_cs);
/** Only for testing */
void DisconnectTestLogger() EXCLUSIVE_LOCKS_REQUIRED(!m_cs);
void SetRateLimiting(std::shared_ptr<LogRateLimiter> limiter) EXCLUSIVE_LOCKS_REQUIRED(!m_cs)
{
StdLockGuard scoped_lock(m_cs);
m_limiter = std::move(limiter);
}
/** Disable logging
* This offers a slight speedup and slightly smaller memory usage
* compared to leaving the logging system in its default state.
* Mostly intended for libbitcoin-kernel apps that don't want any logging.
* Should be used instead of StartLogging().
*/
void DisableLogging() EXCLUSIVE_LOCKS_REQUIRED(!m_cs);
void ShrinkDebugFile();
std::unordered_map<LogFlags, Level> CategoryLevels() const EXCLUSIVE_LOCKS_REQUIRED(!m_cs)
{
StdLockGuard scoped_lock(m_cs);
return m_category_log_levels;
}
void SetCategoryLogLevel(const std::unordered_map<LogFlags, Level>& levels) EXCLUSIVE_LOCKS_REQUIRED(!m_cs)
{
StdLockGuard scoped_lock(m_cs);
m_category_log_levels = levels;
}
void AddCategoryLogLevel(LogFlags category, Level level)
{
StdLockGuard scoped_lock(m_cs);
m_category_log_levels[category] = level;
}
bool SetCategoryLogLevel(std::string_view category_str, std::string_view level_str) EXCLUSIVE_LOCKS_REQUIRED(!m_cs);
Level LogLevel() const { return m_log_level.load(); }
void SetLogLevel(Level level) { m_log_level = level; }
bool SetLogLevel(std::string_view level);
CategoryMask GetCategoryMask() const { return m_categories.load(); }
void EnableCategory(LogFlags flag);
bool EnableCategory(std::string_view str);
void DisableCategory(LogFlags flag);
bool DisableCategory(std::string_view str);
bool WillLogCategory(LogFlags category) const;
bool WillLogCategoryLevel(LogFlags category, Level level) const EXCLUSIVE_LOCKS_REQUIRED(!m_cs);
/** Returns a vector of the log categories in alphabetical order. */
std::vector<LogCategory> LogCategoriesList() const;
/** Returns a string with the log categories in alphabetical order. */
std::string LogCategoriesString() const
{
return util::Join(LogCategoriesList(), ", ", [&](const LogCategory& i) { return i.category; });
};
//! 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;
};
} // namespace BCLog
BCLog::Logger& LogInstance();
/** Return true if log accepts specified category, at the specified level. */
static inline bool LogAcceptCategory(BCLog::LogFlags category, BCLog::Level level)
{
return LogInstance().WillLogCategoryLevel(category, level);
}
/** Return true if str parses as a log category and set the flag */
bool GetLogCategory(BCLog::LogFlags& flag, std::string_view str);
template <typename... Args>
inline void LogPrintFormatInternal(SourceLocation&& source_loc, BCLog::LogFlags flag, BCLog::Level level, bool should_ratelimit, 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, std::move(source_loc), flag, level, should_ratelimit);
}
}
// Allow __func__ to be used in any context without warnings:
// NOLINTNEXTLINE(bugprone-lambda-function-name)
#define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(SourceLocation{__func__}, category, level, should_ratelimit, __VA_ARGS__)
// Log unconditionally. Uses basic rate limiting to mitigate disk filling attacks.
// Be conservative when using functions that unconditionally log to debug.log!
// It should not be the case that an inbound peer can fill up a user's storage
// with debug.log entries.
#define LogInfo(...) LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Info, /*should_ratelimit=*/true, __VA_ARGS__)
#define LogWarning(...) LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Warning, /*should_ratelimit=*/true, __VA_ARGS__)
#define LogError(...) LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Error, /*should_ratelimit=*/true, __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 by prefixing the output with the passed category name and severity level. This logs conditionally if
// the category is allowed. No rate limiting is applied, because users specifying -debug are assumed to be
// developers or power users who are aware that -debug may cause excessive disk usage due to logging.
#define detail_LogIfCategoryAndLevelEnabled(category, level, ...) \
do { \
if (LogAcceptCategory((category), (level))) { \
bool rate_limit{level >= BCLog::Level::Info}; \
Assume(!rate_limit);/*Only called with the levels below*/ \
LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \
} \
} while (0)
// Log conditionally, prefixing the output with the passed category name.
#define LogDebug(category, ...) detail_LogIfCategoryAndLevelEnabled(category, BCLog::Level::Debug, __VA_ARGS__)
#define LogTrace(category, ...) detail_LogIfCategoryAndLevelEnabled(category, BCLog::Level::Trace, __VA_ARGS__)
#endif // BITCOIN_LOGGING_H