#ifndef BITCOIN_LOGGING_H
#define BITCOIN_LOGGING_H
#include <crypto/siphash.h>
#include <logging/categories.h>
#include <span.h>
#include <util/byte_units.h>
#include <util/fs.h>
#include <util/log.h>
#include <util/stdmutex.h>
#include <util/string.h>
#include <util/time.h>
#include <atomic>
#include <cstdint>
#include <cstring>
#include <functional>
#include <list>
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#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;
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
{
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 {
constexpr auto DEFAULT_LOG_LEVEL{Level::Debug};
constexpr size_t DEFAULT_MAX_LOG_BUFFER{1'000'000}; constexpr uint64_t RATELIMIT_MAX_BYTES{1_MiB}; constexpr auto RATELIMIT_WINDOW{1h}; constexpr bool DEFAULT_LOGRATELIMIT{true};
class LogRateLimiter
{
public:
struct Stats {
uint64_t m_available_bytes;
uint64_t m_dropped_bytes{0};
Stats(uint64_t max_bytes) : m_available_bytes{max_bytes} {}
bool Consume(uint64_t bytes);
};
private:
mutable StdMutex m_mutex;
std::unordered_map<SourceLocation, Stats, SourceLocationHasher, SourceLocationEqual> m_source_locations GUARDED_BY(m_mutex);
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)>;
static std::shared_ptr<LogRateLimiter> Create(
SchedulerFunction&& scheduler_func,
uint64_t max_bytes,
std::chrono::seconds reset_window);
const uint64_t m_max_bytes;
const std::chrono::seconds m_reset_window;
enum class Status {
UNSUPPRESSED, NEWLY_SUPPRESSED, STILL_SUPPRESSED, };
[[nodiscard]] Status Consume(
const SourceLocation& source_loc,
const std::string& str) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
void Reset() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
bool SuppressionsActive() const { return m_suppression_active; }
};
class Logger
{
private:
mutable StdMutex m_cs;
FILE* m_fileout GUARDED_BY(m_cs) = nullptr;
std::list<util::log::Entry> m_msgs_before_open GUARDED_BY(m_cs);
bool m_buffering GUARDED_BY(m_cs) = true; 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};
std::shared_ptr<LogRateLimiter> m_limiter GUARDED_BY(m_cs);
std::unordered_map<LogFlags, Level> m_category_log_levels GUARDED_BY(m_cs);
std::atomic<Level> m_log_level{DEFAULT_LOG_LEVEL};
std::atomic<CategoryMask> m_categories{BCLog::NONE};
std::string Format(const util::log::Entry& entry) const;
std::string LogTimestampStr(SystemClock::time_point now, std::chrono::seconds mocktime) const;
std::list<std::function<void(const std::string&)>> m_print_callbacks GUARDED_BY(m_cs){};
void LogPrint_(util::log::Entry log_entry) 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};
void LogPrint(util::log::Entry log_entry) EXCLUSIVE_LOCKS_REQUIRED(!m_cs);
bool Enabled() const EXCLUSIVE_LOCKS_REQUIRED(!m_cs)
{
STDLOCK(m_cs);
return m_buffering || m_print_to_console || m_print_to_file || !m_print_callbacks.empty();
}
std::list<std::function<void(const std::string&)>>::iterator PushBackCallback(std::function<void(const std::string&)> fun) EXCLUSIVE_LOCKS_REQUIRED(!m_cs)
{
STDLOCK(m_cs);
m_print_callbacks.push_back(std::move(fun));
return --m_print_callbacks.end();
}
void DeleteCallback(std::list<std::function<void(const std::string&)>>::iterator it) EXCLUSIVE_LOCKS_REQUIRED(!m_cs)
{
STDLOCK(m_cs);
m_print_callbacks.erase(it);
}
size_t NumConnections() EXCLUSIVE_LOCKS_REQUIRED(!m_cs)
{
STDLOCK(m_cs);
return m_print_callbacks.size();
}
bool StartLogging() EXCLUSIVE_LOCKS_REQUIRED(!m_cs);
void DisconnectTestLogger() EXCLUSIVE_LOCKS_REQUIRED(!m_cs);
void SetRateLimiting(std::shared_ptr<LogRateLimiter> limiter) EXCLUSIVE_LOCKS_REQUIRED(!m_cs)
{
STDLOCK(m_cs);
m_limiter = std::move(limiter);
}
void DisableLogging() EXCLUSIVE_LOCKS_REQUIRED(!m_cs);
void ShrinkDebugFile();
std::unordered_map<LogFlags, Level> CategoryLevels() const EXCLUSIVE_LOCKS_REQUIRED(!m_cs)
{
STDLOCK(m_cs);
return m_category_log_levels;
}
void SetCategoryLogLevel(const std::unordered_map<LogFlags, Level>& levels) EXCLUSIVE_LOCKS_REQUIRED(!m_cs)
{
STDLOCK(m_cs);
m_category_log_levels = levels;
}
void AddCategoryLogLevel(LogFlags category, Level level) EXCLUSIVE_LOCKS_REQUIRED(!m_cs)
{
STDLOCK(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);
std::vector<LogCategory> LogCategoriesList() const;
std::string LogCategoriesString() const
{
return util::Join(LogCategoriesList(), ", ", [&](const LogCategory& i) { return i.category; });
};
std::string LogLevelsString() const;
static std::string LogLevelToStr(BCLog::Level level);
bool DefaultShrinkDebugFile() const;
};
}
BCLog::Logger& LogInstance();
static inline bool LogAcceptCategory(BCLog::LogFlags category, BCLog::Level level)
{
return LogInstance().WillLogCategoryLevel(category, level);
}
std::optional<BCLog::LogFlags> GetLogCategory(std::string_view str);
#endif