19#include <unordered_map>
46 return fwrite(str.data(), 1, str.size(), fp);
53 assert(m_fileout ==
nullptr);
63 setbuf(m_fileout,
nullptr);
72 if (m_buffer_lines_discarded > 0) {
75 "Early logging buffer overflowed, %d log lines discarded.\n",
76 m_buffer_lines_discarded),
80 while (!m_msgs_before_open.empty()) {
81 const auto &buflog = m_msgs_before_open.front();
82 std::string s{buflog.str};
84 buflog.threadname, buflog.now, buflog.mocktime);
85 m_msgs_before_open.pop_front();
91 fwrite(s.data(), 1, s.size(), stdout);
93 for (
const auto &cb : m_print_callbacks) {
97 m_cur_buffer_memusage = 0;
108 if (m_fileout !=
nullptr) {
112 m_print_callbacks.clear();
114 m_cur_buffer_memusage = 0;
115 m_buffer_lines_discarded = 0;
116 m_msgs_before_open.clear();
123 assert(m_print_callbacks.empty());
125 m_print_to_file =
false;
126 m_print_to_console =
false;
130static const std::map<std::string, BCLog::LogFlags, std::less<>>
159#ifdef DEBUG_LOCKCONTENTION
169static const std::unordered_map<BCLog::LogFlags, std::string>
173 std::unordered_map<BCLog::LogFlags, std::string> out;
174 for (
const auto &[k, v] : in) {
224static std::optional<BCLog::Level>
GetLogLevel(std::string_view level_str) {
225 if (level_str ==
"trace") {
227 }
else if (level_str ==
"debug") {
229 }
else if (level_str ==
"info") {
231 }
else if (level_str ==
"warning") {
233 }
else if (level_str ==
"error") {
241 std::vector<LogCategory> ret;
245 .active = WillLogCategory(flag)});
264 return Join(std::vector<BCLog::Level>{levels.begin(), levels.end()},
", ",
265 [](
BCLog::Level level) {
return LogLevelToStr(level); });
270 std::chrono::seconds mocktime)
const {
271 std::string strStamped;
273 if (!m_log_timestamps) {
277 const auto now_seconds{
278 std::chrono::time_point_cast<std::chrono::seconds>(now)};
280 TicksSinceEpoch<std::chrono::seconds>(now_seconds));
281 if (m_log_time_micros && !strStamped.empty()) {
282 strStamped.pop_back();
284 ".%06dZ", Ticks<std::chrono::microseconds>(now - now_seconds));
306 for (
char ch_in : str) {
307 uint8_t ch = (uint8_t)ch_in;
308 if ((ch >= 32 || ch ==
'\n') && ch !=
'\x7f') {
321 category = LogFlags::ALL;
324 const bool has_category{m_always_print_category_level ||
325 category != LogFlags::ALL};
337 if (m_always_print_category_level || !has_category ||
360 std::chrono::seconds reset_window)
361 : m_max_bytes{max_bytes}, m_reset_window{reset_window} {}
363std::shared_ptr<BCLog::LogRateLimiter>
366 std::chrono::seconds reset_window) {
367 auto limiter{std::shared_ptr<LogRateLimiter>(
369 std::weak_ptr<LogRateLimiter> weak_limiter{limiter};
370 auto reset = [weak_limiter] {
371 if (
auto shared_limiter{weak_limiter.lock()}) {
372 shared_limiter->Reset();
376 scheduler_func(reset, limiter->m_reset_window);
382 const std::string &str) {
385 m_source_locations.try_emplace(source_loc, m_max_bytes).first->second};
386 Status status{stats.m_dropped_bytes > 0 ? Status::STILL_SUPPRESSED
387 : Status::UNSUPPRESSED};
389 if (!stats.Consume(str.size()) && status == Status::UNSUPPRESSED) {
390 status = Status::NEWLY_SUPPRESSED;
391 m_suppression_active =
true;
399 const std::source_location &source_loc, std::string_view threadname,
400 SystemClock::time_point now, std::chrono::seconds mocktime)
const {
401 str.insert(0, GetLogPrefix(category, level));
403 if (m_log_sourcelocations) {
406 source_loc.line(), source_loc.function_name()));
409 if (m_log_threadnames) {
411 (threadname.empty() ?
"unknown" : threadname)));
414 str.insert(0, LogTimestampStr(now, mocktime));
418 std::source_location &&source_loc,
420 bool should_ratelimit) {
422 return LogPrintStr_(str, std::move(source_loc), category, level,
427 std::source_location &&source_loc,
429 bool should_ratelimit) {
432 const bool starts_new_line = m_started_new_line;
433 m_started_new_line = !str.empty() && str[str.size() - 1] ==
'\n';
436 if (!starts_new_line) {
437 if (!m_msgs_before_open.empty()) {
438 m_msgs_before_open.back().str += str_prefixed;
439 m_cur_buffer_memusage += str_prefixed.size();
443 str_prefixed.insert(0,
"[...] ");
449 .
now = SystemClock::now(),
453 .source_loc = std::move(source_loc),
454 .category = category,
457 m_cur_buffer_memusage +=
MemUsage(buf);
458 m_msgs_before_open.push_back(std::move(buf));
461 while (m_cur_buffer_memusage > m_max_buffer_memusage) {
462 if (m_msgs_before_open.empty()) {
463 m_cur_buffer_memusage = 0;
466 m_cur_buffer_memusage -=
MemUsage(m_msgs_before_open.front());
467 m_msgs_before_open.pop_front();
468 ++m_buffer_lines_discarded;
474 FormatLogStrInPlace(str_prefixed, category, level, source_loc,
477 bool ratelimit{
false};
478 if (should_ratelimit && m_limiter) {
479 auto status{m_limiter->Consume(source_loc, str_prefixed)};
486 "Excessive logging detected from %s:%d (%s): >%d bytes "
488 "the last time window of %is. Suppressing logging to disk "
490 "source location until time window resets. Console logging "
491 "unaffected. Last log entry.\n",
492 source_loc.file_name(), source_loc.line(),
493 source_loc.function_name(), m_limiter->m_max_bytes,
494 Ticks<std::chrono::seconds>(m_limiter->m_reset_window)),
505 if (m_limiter && m_limiter->SuppressionsActive()) {
506 str_prefixed.insert(0,
"[*] ");
509 if (m_print_to_console) {
511 fwrite(str_prefixed.data(), 1, str_prefixed.size(), stdout);
514 for (
const auto &cb : m_print_callbacks) {
517 if (m_print_to_file && !ratelimit) {
518 assert(m_fileout !=
nullptr);
522 m_reopen_file =
false;
526 setbuf(m_fileout,
nullptr);
528 m_fileout = new_fileout;
537 constexpr size_t RECENT_DEBUG_HISTORY_SIZE = 10 * 1000000;
539 assert(!m_file_path.empty());
547 log_size = fs::file_size(m_file_path);
548 }
catch (
const fs::filesystem_error &) {
553 if (file && log_size > 11 * (RECENT_DEBUG_HISTORY_SIZE / 10)) {
555 std::vector<char> vch(RECENT_DEBUG_HISTORY_SIZE, 0);
556 if (fseek(file, -((
long)vch.size()), SEEK_END)) {
557 LogPrintf(
"Failed to shrink debug log file: fseek(...) failed\n");
561 int nBytes = fread(vch.data(), 1, vch.size(), file);
566 fwrite(vch.data(), 1, nBytes, file);
569 }
else if (file !=
nullptr) {
575 m_categories |= category;
583 EnableCategory(flag);
588 m_categories &= ~category;
596 DisableCategory(flag);
604 LogPrintf(
"Error trying to log using a category mask instead of an "
605 "explicit category.\n");
609 return (m_categories.load(std::memory_order_relaxed) & category) != 0;
620 if (!WillLogCategory(category)) {
625 const auto it{m_category_log_levels.find(category)};
627 (it == m_category_log_levels.end() ? LogLevel() : it->second);
635 decltype(m_source_locations) source_locations;
638 source_locations.swap(m_source_locations);
639 m_suppression_active =
false;
641 for (
const auto &[source_loc, stats] : source_locations) {
642 if (stats.m_dropped_bytes == 0) {
647 "Restarting logging from %s:%d (%s): %d bytes were "
648 "dropped during the last %ss.\n",
649 source_loc.file_name(), source_loc.line(),
650 source_loc.function_name(), stats.m_dropped_bytes,
651 Ticks<std::chrono::seconds>(m_reset_window));
656 if (bytes > m_available_bytes) {
657 m_dropped_bytes += bytes;
658 m_available_bytes = 0;
662 m_available_bytes -= bytes;
671 m_log_level = level.value();
676 std::string_view level_str) {
688 m_category_log_levels[flag] = level.value();
Fixed window rate limiter for logging.
static std::shared_ptr< LogRateLimiter > Create(SchedulerFunction &&scheduler_func, uint64_t max_bytes, std::chrono::seconds reset_window)
std::function< void(std::function< bool()>, std::chrono::milliseconds)> SchedulerFunction
LogRateLimiter(uint64_t max_bytes, std::chrono::seconds reset_window)
Status Consume(const std::source_location &source_loc, const std::string &str) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Consumes source_loc's available bytes corresponding to the size of the (formatted) str and returns it...
Status
Suppression status of a source log location.
void Reset() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Resets all usage to zero. Called periodically by the scheduler.
static std::string LogLevelToStr(BCLog::Level level)
Returns the string representation of a log level.
void LogPrintStr(std::string_view str, std::source_location &&source_loc, BCLog::LogFlags category, BCLog::Level level, bool should_ratelimit)
Send a string to the log output.
bool WillLogCategory(LogFlags category) const
Return true if log accepts specified category.
std::string LogTimestampStr(SystemClock::time_point now, std::chrono::seconds mocktime) const
bool DefaultShrinkDebugFile() const
Default for whether ShrinkDebugFile should be run.
void SetCategoryLogLevel(const std::unordered_map< LogFlags, Level > &levels) EXCLUSIVE_LOCKS_REQUIRED(!m_cs)
void SetLogLevel(Level level)
bool WillLogCategoryLevel(LogFlags category, Level level) const EXCLUSIVE_LOCKS_REQUIRED(!m_cs)
void DisableLogging() EXCLUSIVE_LOCKS_REQUIRED(!m_cs)
This offers a slight speedup and slightly smaller memory usage compared to leaving the logging system...
std::vector< LogCategory > LogCategoriesList() const
Returns a vector of the log categories in alphabetical order.
bool StartLogging() EXCLUSIVE_LOCKS_REQUIRED(!m_cs)
Start logging (and flush all buffered messages)
void DisableCategory(LogFlags category)
void EnableCategory(LogFlags category)
std::string GetLogPrefix(LogFlags category, Level level) const
void LogPrintStr_(std::string_view str, std::source_location &&source_loc, BCLog::LogFlags category, BCLog::Level level, bool should_ratelimit) EXCLUSIVE_LOCKS_REQUIRED(m_cs)
Send a string to the log output (internal)
void DisconnectTestLogger() EXCLUSIVE_LOCKS_REQUIRED(!m_cs)
Only for testing.
std::string LogLevelsString() const
Returns a string with all user-selectable log levels.
void FormatLogStrInPlace(std::string &str, LogFlags category, Level level, const std::source_location &source_loc, std::string_view threadname, SystemClock::time_point now, std::chrono::seconds mocktime) const
static constexpr std::array< BCLog::Level, 3 > LogLevelsList()
Log severity levels that can be selected by the user.
static int FileWriteStr(std::string_view str, FILE *fp)
bool GetLogCategory(BCLog::LogFlags &flag, std::string_view str)
Return true if str parses as a log category and set the flag.
static size_t MemUsage(const BCLog::Logger::BufferedLog &buflog)
std::string LogCategoryToStr(BCLog::LogFlags category)
static const std::map< std::string, BCLog::LogFlags, std::less<> > LOG_CATEGORIES_BY_STR
BCLog::Logger & LogInstance()
static const std::unordered_map< BCLog::LogFlags, std::string > LOG_CATEGORIES_BY_FLAG
const char *const DEFAULT_DEBUGLOGFILE
static std::optional< BCLog::Level > GetLogLevel(std::string_view level_str)
constexpr auto MAX_USER_SETABLE_SEVERITY_LEVEL
#define LogPrintLevel_(category, level, should_ratelimit,...)
static const bool DEFAULT_LOGIPS
std::string LogEscapeMessage(std::string_view str)
Belts and suspenders: make sure outgoing log messages don't contain potentially suspicious characters...
constexpr size_t DEFAULT_MAX_LOG_BUFFER
FILE * fopen(const fs::path &p, const char *mode)
bool StartLogging(const ArgsManager &args)
static size_t MallocUsage(size_t alloc)
Compute the total memory used by allocating alloc bytes.
const std::string & ThreadGetInternalName()
Get the thread's internal (in-memory) name; used e.g.
auto Join(const std::vector< T > &list, const BaseType &separator, UnaryOp unary_op) -> decltype(unary_op(list.at(0)))
Join a list of items.
std::string_view RemovePrefixView(std::string_view str, std::string_view prefix)
bool Consume(uint64_t bytes)
Updates internal accounting and returns true if enough available_bytes were remaining.
SystemClock::time_point now
std::chrono::seconds GetMockTime()
For testing.
std::string FormatISO8601DateTime(int64_t nTime)
ISO 8601 formatting is preferred.
constexpr int64_t count_seconds(std::chrono::seconds t)